<commit_before>import { parseAd } from './ad_parser';
import { EventEmitter } from 'events';
import { parserUtils } from './parser_utils';
import { urlHandler } from '../url_handler';
import { util } from '../util/util';
import { VASTResponse } from '../vast_response';

const DEFAULT_MAX_WRAPPER_DEPTH = 10;
const DEFAULT_EVENT_DATA = {
  ERRORCODE: 900,
  extensions: []
};

/**
 * This class provides methods to fetch and parse a VAST document.
 * @export
 * @class VASTParser
 * @extends EventEmitter
 */
export class VASTParser extends EventEmitter {
  /**
   * Creates an instance of VASTParser.
   * @constructor
   */
  constructor() {
    super();

    this.remainingAds = [];
    this.parentURLs = [];
    this.errorURLTemplates = [];
    this.rootErrorURLTemplates = [];
    this.maxWrapperDepth = null;
    this.URLTemplateFilters = [];
    this.fetchingOptions = {};
  }

  /**
   * Adds a filter function to the array of filters which are called before fetching a VAST document.
   * @param  {function} filter - The filter function to be added at the end of the array.
   * @return {void}
   */
  addURLTemplateFilter(filter) {
    if (typeof filter === 'function') {
      this.URLTemplateFilters.push(filter);
    }
  }

  /**
   * Removes the last element of the url templates filters array.
   * @return {void}
   */
  removeURLTemplateFilter() {
    this.URLTemplateFilters.pop();
  }

  /**
   * Returns the number of filters of the url templates filters array.
   * @return {Number}
   */
  countURLTemplateFilters() {
    return this.URLTemplateFilters.length;
  }

  /**
   * Removes all the filter functions from the url templates filters array.
   * @return {void}
   */
  clearURLTemplateFilters() {
    this.URLTemplateFilters = [];
  }

  /**
   * Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error.
   * @param  {Array} urlTemplates - An Array of url templates to use to make the tracking call.
   * @param  {Object} errorCode - An Object containing the error data.
   * @param  {Object} data - One (or more) Object containing additional data.
   * @emits  VASTParser#VAST-error
   * @return {void}
   */
  trackVastError(urlTemplates, errorCode, ...data) {
    this.emit(
      'VAST-error',
      Object.assign(DEFAULT_EVENT_DATA, errorCode, ...data)
    );
    util.track(urlTemplates, errorCode);
  }

  /**
   * Returns an array of errorURLTemplates for the VAST being parsed.
   * @return {Array}
   */
  getErrorURLTemplates() {
    return this.rootErrorURLTemplates.concat(this.errorURLTemplates);
  }

  /**
   * Fetches a VAST document for the given url.
   * Returns a Promise which resolves,rejects according to the result of the request.
   * @param  {String} url - The url to request the VAST document.
   * @param {Number} wrapperDepth - how many times the current url has been wrapped
   * @param {String} originalUrl - url of original wrapper
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  fetchVAST(url, wrapperDepth, originalUrl) {
    return new Promise((resolve, reject) => {
      // Process url with defined filter
      this.URLTemplateFilters.forEach(filter => {
        url = filter(url);
      });

      this.parentURLs.push(url);
      this.emit('VAST-resolving', { url, wrapperDepth, originalUrl });

      this.urlHandler.get(url, this.fetchingOptions, (err, xml) => {
        this.emit('VAST-resolved', { url, error: err });

        if (err) {
          reject(err);
        } else {
          resolve(xml);
        }
      });
    });
  }

  /**
   * Inits the parsing properties of the class with the custom values provided as options.
   * @param {Object} options - The options to initialize a parsing sequence
   */
  initParsingStatus(options = {}) {
    this.rootURL = '';
    this.remainingAds = [];
    this.parentURLs = [];
    this.errorURLTemplates = [];
    this.rootErrorURLTemplates = [];
    this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH;
    this.fetchingOptions = {
      timeout: options.timeout,
      withCredentials: options.withCredentials
    };

    this.urlHandler = options.urlhandler || urlHandler;
  }

  /**
   * Resolves the next group of ads. If all is true resolves all the remaining ads.
   * @param  {Boolean} all - If true all the remaining ads are resolved
   * @return {Promise}
   */
  getRemainingAds(all) {
    if (this.remainingAds.length === 0) {
      return Promise.reject(
        new Error('No more ads are available for the given VAST')
      );
    }

    const ads = all
      ? util.flatten(this.remainingAds)
      : this.remainingAds.shift();
    this.errorURLTemplates = [];
    this.parentURLs = [];

    return this.resolveAds(ads, {
      wrapperDepth: 0,
      originalUrl: this.rootURL
    }).then(resolvedAds => {
      return this.buildVASTResponse(resolvedAds);
    });
  }

  /**
   * Fetches and parses a VAST for the given url.
   * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error.
   * @param  {String} url - The url to request the VAST document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  getAndParseVAST(url, options = {}) {
    this.initParsingStatus(options);
    this.rootURL = url;

    return this.fetchVAST(url).then(xml => {
      options.originalUrl = url;
      options.isRootVAST = true;

      return this.parse(xml, options).then(ads => {
        return this.buildVASTResponse(ads);
      });
    });
  }

  /**
   * Parses the given xml Object into a VASTResponse.
   * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error.
   * @param  {Object} vastXml - An object representing a vast xml document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  parseVAST(vastXml, options = {}) {
    this.initParsingStatus(options);

    options.isRootVAST = true;

    return this.parse(vastXml, options).then(ads => {
      return this.buildVASTResponse(ads);
    });
  }

  /**
   * Builds a VASTResponse which can be returned.
   * @param  {Array} ads - An Array of unwrapped ads
   * @return {VASTResponse}
   */
  buildVASTResponse(ads) {
    const response = new VASTResponse();
    response.ads = ads;
    response.errorURLTemplates = this.getErrorURLTemplates();
    this.completeWrapperResolving(response);

    return response;
  }

  /**
   * Parses the given xml Object into an array of ads
   * Returns a Promise which resolves with the array or rejects with an error according to the result of the parsing.
   * @param  {Object} vastXml - An object representing an xml document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  parse(
    vastXml,
    {
      resolveAll = true,
      wrapperSequence = null,
      originalUrl = null,
      wrapperDepth = 0,
      isRootVAST = false
    }
  ) {
    // check if is a valid VAST document
    if (
      !vastXml ||
      !vastXml.documentElement ||
      vastXml.documentElement.nodeName !== 'VAST'
    ) {
      return Promise.reject(new Error('Invalid VAST XMLDocument'));
    }

    let ads = [];
    const childNodes = vastXml.documentElement.childNodes;

    // Fill the VASTResponse object with ads and errorURLTemplates
    for (const nodeKey in childNodes) {
      const node = childNodes[nodeKey];

      if (node.nodeName === 'Error') {
        const errorURLTemplate = parserUtils.parseNodeText(node);

        // Distinguish root VAST url templates from ad specific ones
        isRootVAST
          ? this.rootErrorURLTemplates.push(errorURLTemplate)
          : this.errorURLTemplates.push(errorURLTemplate);
      }

      if (node.nodeName === 'Ad') {
        const ad = parseAd(node);

        if (ad) {
          ads.push(ad);
        } else {
          // VAST version of response not supported.
          this.trackVastError(this.getErrorURLTemplates(), {
            ERRORCODE: 101
          });
        }
      }
    }

    const adsCount = ads.length;
    const lastAddedAd = ads[adsCount - 1];
    // if in child nodes we have only one ads
    // and wrapperSequence is defined
    // and this ads doesn't already have sequence
    if (
      adsCount === 1 &&
      wrapperSequence !== undefined &&
      wrapperSequence !== null &&
      lastAddedAd &&
      !lastAddedAd.sequence
    ) {
      lastAddedAd.sequence = wrapperSequence;
    }

    // Split the VAST in case we don't want to resolve everything at the first time
    if (resolveAll === false) {
      this.remainingAds = parserUtils.splitVAST(ads);
      // Remove the first element from the remaining ads array, since we're going to resolve that element
      ads = this.remainingAds.shift();
    }

    return this.resolveAds(ads, { wrapperDepth, originalUrl });
  }

  /**
   * Resolves an Array of ads, recursively calling itself with the remaining ads if a no ad
   * response is returned for the given array.
   * @param {Array} ads - An array of ads to resolve
   * @param {Object} options - An options Object containing resolving parameters
   * @return {Promise}
   */
  resolveAds(ads = [], { wrapperDepth, originalUrl }) {
    const resolveWrappersPromises = [];

    ads.forEach(ad => {
      const resolveWrappersPromise = this.resolveWrappers(
        ad,
        wrapperDepth,
        originalUrl
      );

      resolveWrappersPromises.push(resolveWrappersPromise);
    });

    return Promise.all(resolveWrappersPromises).then(unwrappedAds => {
      const resolvedAds = util.flatten(unwrappedAds);

      if (!resolvedAds && this.remainingAds.length > 0) {
        const remainingAdsToResolve = this.remainingAds.shift();

        return this.resolveAds(remainingAdsToResolve, {
          wrapperDepth,
          originalUrl
        });
      }

      return resolvedAds;
    });
  }

  /**
   * Resolves the wrappers for the given ad in a recursive way.
   * Returns a Promise which resolves with the unwrapped ad or rejects with an error.
   * @param  {Ad} ad - An ad to be unwrapped.
   * @param  {Number} wrapperDepth - The reached depth in the wrapper resolving chain.
   * @param  {String} originalUrl - The original vast url.
   * @return {Promise}
   */
  resolveWrappers(ad, wrapperDepth, originalUrl) {
    return new Promise(resolve => {
      // Going one level deeper in the wrapper chain
      wrapperDepth++;
      // We already have a resolved VAST ad, no need to resolve wrapper
      if (!ad.nextWrapperURL) {
        delete ad.nextWrapperURL;
        return resolve(ad);
      }

      if (
        wrapperDepth >= this.maxWrapperDepth ||
        this.parentURLs.indexOf(ad.nextWrapperURL) !== -1
      ) {
        // Wrapper limit reached, as defined by the video player.
        // Too many Wrapper responses have been received with no InLine response.
        ad.errorCode = 302;
        delete ad.nextWrapperURL;
        return resolve(ad);
      }

      // Get full URL
      ad.nextWrapperURL = parserUtils.resolveVastAdTagURI(
        ad.nextWrapperURL,
        originalUrl
      );

      // sequence doesn't carry over in wrapper element
      const wrapperSequence = ad.sequence;
      originalUrl = ad.nextWrapperURL;

      this.fetchVAST(ad.nextWrapperURL, wrapperDepth, originalUrl)
        .then(xml => {
          return this.parse(xml, {
            originalUrl,
            wrapperSequence,
            wrapperDepth
          }).then(unwrappedAds => {
            delete ad.nextWrapperURL;
            if (unwrappedAds.length === 0) {
              // No ads returned by the wrappedResponse, discard current <Ad><Wrapper> creatives
              ad.creatives = [];
              return resolve(ad);
            }

            unwrappedAds.forEach(unwrappedAd => {
              if (unwrappedAd) {
                parserUtils.mergeWrapperAdData(unwrappedAd, ad);
              }
            });

            resolve(unwrappedAds);
          });
        })
        .catch(err => {
          // Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element.
          // (URI was either unavailable or reached a timeout as defined by the video player.)
          ad.errorCode = 301;
          ad.errorMessage = err.message;

          resolve(ad);
        });
    });
  }

  /**
   * Takes care of handling errors when the wrappers are resolved.
   * @param {VASTResponse} vastResponse - A resolved VASTResponse.
   */
  completeWrapperResolving(vastResponse) {
    // We've to wait for all <Ad> elements to be parsed before handling error so we can:
    // - Send computed extensions data
    // - Ping all <Error> URIs defined across VAST files

    // No Ad case - The parser never bump into an <Ad> element
    if (vastResponse.ads.length === 0) {
      this.trackVastError(vastResponse.errorURLTemplates, { ERRORCODE: 303 });
    } else {
      for (let index = vastResponse.ads.length - 1; index >= 0; index--) {
        // - Error encountred while parsing
        // - No Creative case - The parser has dealt with soma <Ad><Wrapper> or/and an <Ad><Inline> elements
        // but no creative was found
        const ad = vastResponse.ads[index];
        if (ad.errorCode || ad.creatives.length === 0) {
          this.trackVastError(
            ad.errorURLTemplates.concat(vastResponse.errorURLTemplates),
            { ERRORCODE: ad.errorCode || 303 },
            { ERRORMESSAGE: ad.errorMessage || '' },
            { extensions: ad.extensions },
            { system: ad.system }
          );
          vastResponse.ads.splice(index, 1);
        }
      }
    }
  }
}
<commit_msg>[util] Add option.urlHandler as a valid custom URL handler location and prefer it to option.urlhandler<commit_after>import { parseAd } from './ad_parser';
import { EventEmitter } from 'events';
import { parserUtils } from './parser_utils';
import { urlHandler } from '../url_handler';
import { util } from '../util/util';
import { VASTResponse } from '../vast_response';

const DEFAULT_MAX_WRAPPER_DEPTH = 10;
const DEFAULT_EVENT_DATA = {
  ERRORCODE: 900,
  extensions: []
};

/**
 * This class provides methods to fetch and parse a VAST document.
 * @export
 * @class VASTParser
 * @extends EventEmitter
 */
export class VASTParser extends EventEmitter {
  /**
   * Creates an instance of VASTParser.
   * @constructor
   */
  constructor() {
    super();

    this.remainingAds = [];
    this.parentURLs = [];
    this.errorURLTemplates = [];
    this.rootErrorURLTemplates = [];
    this.maxWrapperDepth = null;
    this.URLTemplateFilters = [];
    this.fetchingOptions = {};
  }

  /**
   * Adds a filter function to the array of filters which are called before fetching a VAST document.
   * @param  {function} filter - The filter function to be added at the end of the array.
   * @return {void}
   */
  addURLTemplateFilter(filter) {
    if (typeof filter === 'function') {
      this.URLTemplateFilters.push(filter);
    }
  }

  /**
   * Removes the last element of the url templates filters array.
   * @return {void}
   */
  removeURLTemplateFilter() {
    this.URLTemplateFilters.pop();
  }

  /**
   * Returns the number of filters of the url templates filters array.
   * @return {Number}
   */
  countURLTemplateFilters() {
    return this.URLTemplateFilters.length;
  }

  /**
   * Removes all the filter functions from the url templates filters array.
   * @return {void}
   */
  clearURLTemplateFilters() {
    this.URLTemplateFilters = [];
  }

  /**
   * Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error.
   * @param  {Array} urlTemplates - An Array of url templates to use to make the tracking call.
   * @param  {Object} errorCode - An Object containing the error data.
   * @param  {Object} data - One (or more) Object containing additional data.
   * @emits  VASTParser#VAST-error
   * @return {void}
   */
  trackVastError(urlTemplates, errorCode, ...data) {
    this.emit(
      'VAST-error',
      Object.assign(DEFAULT_EVENT_DATA, errorCode, ...data)
    );
    util.track(urlTemplates, errorCode);
  }

  /**
   * Returns an array of errorURLTemplates for the VAST being parsed.
   * @return {Array}
   */
  getErrorURLTemplates() {
    return this.rootErrorURLTemplates.concat(this.errorURLTemplates);
  }

  /**
   * Fetches a VAST document for the given url.
   * Returns a Promise which resolves,rejects according to the result of the request.
   * @param  {String} url - The url to request the VAST document.
   * @param {Number} wrapperDepth - how many times the current url has been wrapped
   * @param {String} originalUrl - url of original wrapper
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  fetchVAST(url, wrapperDepth, originalUrl) {
    return new Promise((resolve, reject) => {
      // Process url with defined filter
      this.URLTemplateFilters.forEach(filter => {
        url = filter(url);
      });

      this.parentURLs.push(url);
      this.emit('VAST-resolving', { url, wrapperDepth, originalUrl });

      this.urlHandler.get(url, this.fetchingOptions, (err, xml) => {
        this.emit('VAST-resolved', { url, error: err });

        if (err) {
          reject(err);
        } else {
          resolve(xml);
        }
      });
    });
  }

  /**
   * Inits the parsing properties of the class with the custom values provided as options.
   * @param {Object} options - The options to initialize a parsing sequence
   */
  initParsingStatus(options = {}) {
    this.rootURL = '';
    this.remainingAds = [];
    this.parentURLs = [];
    this.errorURLTemplates = [];
    this.rootErrorURLTemplates = [];
    this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH;
    this.fetchingOptions = {
      timeout: options.timeout,
      withCredentials: options.withCredentials
    };

    this.urlHandler = options.urlHandler || options.urlhandler || urlHandler;
  }

  /**
   * Resolves the next group of ads. If all is true resolves all the remaining ads.
   * @param  {Boolean} all - If true all the remaining ads are resolved
   * @return {Promise}
   */
  getRemainingAds(all) {
    if (this.remainingAds.length === 0) {
      return Promise.reject(
        new Error('No more ads are available for the given VAST')
      );
    }

    const ads = all
      ? util.flatten(this.remainingAds)
      : this.remainingAds.shift();
    this.errorURLTemplates = [];
    this.parentURLs = [];

    return this.resolveAds(ads, {
      wrapperDepth: 0,
      originalUrl: this.rootURL
    }).then(resolvedAds => {
      return this.buildVASTResponse(resolvedAds);
    });
  }

  /**
   * Fetches and parses a VAST for the given url.
   * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error.
   * @param  {String} url - The url to request the VAST document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  getAndParseVAST(url, options = {}) {
    this.initParsingStatus(options);
    this.rootURL = url;

    return this.fetchVAST(url).then(xml => {
      options.originalUrl = url;
      options.isRootVAST = true;

      return this.parse(xml, options).then(ads => {
        return this.buildVASTResponse(ads);
      });
    });
  }

  /**
   * Parses the given xml Object into a VASTResponse.
   * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error.
   * @param  {Object} vastXml - An object representing a vast xml document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  parseVAST(vastXml, options = {}) {
    this.initParsingStatus(options);

    options.isRootVAST = true;

    return this.parse(vastXml, options).then(ads => {
      return this.buildVASTResponse(ads);
    });
  }

  /**
   * Builds a VASTResponse which can be returned.
   * @param  {Array} ads - An Array of unwrapped ads
   * @return {VASTResponse}
   */
  buildVASTResponse(ads) {
    const response = new VASTResponse();
    response.ads = ads;
    response.errorURLTemplates = this.getErrorURLTemplates();
    this.completeWrapperResolving(response);

    return response;
  }

  /**
   * Parses the given xml Object into an array of ads
   * Returns a Promise which resolves with the array or rejects with an error according to the result of the parsing.
   * @param  {Object} vastXml - An object representing an xml document.
   * @param  {Object} options - An optional Object of parameters to be used in the parsing process.
   * @emits  VASTParser#VAST-resolving
   * @emits  VASTParser#VAST-resolved
   * @return {Promise}
   */
  parse(
    vastXml,
    {
      resolveAll = true,
      wrapperSequence = null,
      originalUrl = null,
      wrapperDepth = 0,
      isRootVAST = false
    }
  ) {
    // check if is a valid VAST document
    if (
      !vastXml ||
      !vastXml.documentElement ||
      vastXml.documentElement.nodeName !== 'VAST'
    ) {
      return Promise.reject(new Error('Invalid VAST XMLDocument'));
    }

    let ads = [];
    const childNodes = vastXml.documentElement.childNodes;

    // Fill the VASTResponse object with ads and errorURLTemplates
    for (const nodeKey in childNodes) {
      const node = childNodes[nodeKey];

      if (node.nodeName === 'Error') {
        const errorURLTemplate = parserUtils.parseNodeText(node);

        // Distinguish root VAST url templates from ad specific ones
        isRootVAST
          ? this.rootErrorURLTemplates.push(errorURLTemplate)
          : this.errorURLTemplates.push(errorURLTemplate);
      }

      if (node.nodeName === 'Ad') {
        const ad = parseAd(node);

        if (ad) {
          ads.push(ad);
        } else {
          // VAST version of response not supported.
          this.trackVastError(this.getErrorURLTemplates(), {
            ERRORCODE: 101
          });
        }
      }
    }

    const adsCount = ads.length;
    const lastAddedAd = ads[adsCount - 1];
    // if in child nodes we have only one ads
    // and wrapperSequence is defined
    // and this ads doesn't already have sequence
    if (
      adsCount === 1 &&
      wrapperSequence !== undefined &&
      wrapperSequence !== null &&
      lastAddedAd &&
      !lastAddedAd.sequence
    ) {
      lastAddedAd.sequence = wrapperSequence;
    }

    // Split the VAST in case we don't want to resolve everything at the first time
    if (resolveAll === false) {
      this.remainingAds = parserUtils.splitVAST(ads);
      // Remove the first element from the remaining ads array, since we're going to resolve that element
      ads = this.remainingAds.shift();
    }

    return this.resolveAds(ads, { wrapperDepth, originalUrl });
  }

  /**
   * Resolves an Array of ads, recursively calling itself with the remaining ads if a no ad
   * response is returned for the given array.
   * @param {Array} ads - An array of ads to resolve
   * @param {Object} options - An options Object containing resolving parameters
   * @return {Promise}
   */
  resolveAds(ads = [], { wrapperDepth, originalUrl }) {
    const resolveWrappersPromises = [];

    ads.forEach(ad => {
      const resolveWrappersPromise = this.resolveWrappers(
        ad,
        wrapperDepth,
        originalUrl
      );

      resolveWrappersPromises.push(resolveWrappersPromise);
    });

    return Promise.all(resolveWrappersPromises).then(unwrappedAds => {
      const resolvedAds = util.flatten(unwrappedAds);

      if (!resolvedAds && this.remainingAds.length > 0) {
        const remainingAdsToResolve = this.remainingAds.shift();

        return this.resolveAds(remainingAdsToResolve, {
          wrapperDepth,
          originalUrl
        });
      }

      return resolvedAds;
    });
  }

  /**
   * Resolves the wrappers for the given ad in a recursive way.
   * Returns a Promise which resolves with the unwrapped ad or rejects with an error.
   * @param  {Ad} ad - An ad to be unwrapped.
   * @param  {Number} wrapperDepth - The reached depth in the wrapper resolving chain.
   * @param  {String} originalUrl - The original vast url.
   * @return {Promise}
   */
  resolveWrappers(ad, wrapperDepth, originalUrl) {
    return new Promise(resolve => {
      // Going one level deeper in the wrapper chain
      wrapperDepth++;
      // We already have a resolved VAST ad, no need to resolve wrapper
      if (!ad.nextWrapperURL) {
        delete ad.nextWrapperURL;
        return resolve(ad);
      }

      if (
        wrapperDepth >= this.maxWrapperDepth ||
        this.parentURLs.indexOf(ad.nextWrapperURL) !== -1
      ) {
        // Wrapper limit reached, as defined by the video player.
        // Too many Wrapper responses have been received with no InLine response.
        ad.errorCode = 302;
        delete ad.nextWrapperURL;
        return resolve(ad);
      }

      // Get full URL
      ad.nextWrapperURL = parserUtils.resolveVastAdTagURI(
        ad.nextWrapperURL,
        originalUrl
      );

      // sequence doesn't carry over in wrapper element
      const wrapperSequence = ad.sequence;
      originalUrl = ad.nextWrapperURL;

      this.fetchVAST(ad.nextWrapperURL, wrapperDepth, originalUrl)
        .then(xml => {
          return this.parse(xml, {
            originalUrl,
            wrapperSequence,
            wrapperDepth
          }).then(unwrappedAds => {
            delete ad.nextWrapperURL;
            if (unwrappedAds.length === 0) {
              // No ads returned by the wrappedResponse, discard current <Ad><Wrapper> creatives
              ad.creatives = [];
              return resolve(ad);
            }

            unwrappedAds.forEach(unwrappedAd => {
              if (unwrappedAd) {
                parserUtils.mergeWrapperAdData(unwrappedAd, ad);
              }
            });

            resolve(unwrappedAds);
          });
        })
        .catch(err => {
          // Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element.
          // (URI was either unavailable or reached a timeout as defined by the video player.)
          ad.errorCode = 301;
          ad.errorMessage = err.message;

          resolve(ad);
        });
    });
  }

  /**
   * Takes care of handling errors when the wrappers are resolved.
   * @param {VASTResponse} vastResponse - A resolved VASTResponse.
   */
  completeWrapperResolving(vastResponse) {
    // We've to wait for all <Ad> elements to be parsed before handling error so we can:
    // - Send computed extensions data
    // - Ping all <Error> URIs defined across VAST files

    // No Ad case - The parser never bump into an <Ad> element
    if (vastResponse.ads.length === 0) {
      this.trackVastError(vastResponse.errorURLTemplates, { ERRORCODE: 303 });
    } else {
      for (let index = vastResponse.ads.length - 1; index >= 0; index--) {
        // - Error encountred while parsing
        // - No Creative case - The parser has dealt with soma <Ad><Wrapper> or/and an <Ad><Inline> elements
        // but no creative was found
        const ad = vastResponse.ads[index];
        if (ad.errorCode || ad.creatives.length === 0) {
          this.trackVastError(
            ad.errorURLTemplates.concat(vastResponse.errorURLTemplates),
            { ERRORCODE: ad.errorCode || 303 },
            { ERRORMESSAGE: ad.errorMessage || '' },
            { extensions: ad.extensions },
            { system: ad.system }
          );
          vastResponse.ads.splice(index, 1);
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>/* UMD.define */ (function (root, factory) {
    if (typeof customLoader === 'function'){ customLoader(factory, 'router'); }
    else if (typeof define === 'function' && define.amd){ define([], factory); }
    else if(typeof exports === 'object'){ module.exports = factory(); }
    else{ root.returnExports = factory();
        window.router = factory(); }
}(this, function () {

    var
        router,
        currentState = {},
        callbacks = {},
        wildbacks = {},
        initTimer,
        initialize = function () {
            clearTimeout(initTimer);
            initTimer = setTimeout(function () {
                window.addEventListener('hashchange', function (event) {
                    //console.log('event', event);
                    router.emit(parse(event.newURL, event.oldURL));
                });
                router.emit(parse(location.href));
                initialize = function () {};
            },100);
        };

    // TODO:
    // more than one on('/route')
    // more than one REST param
    // nested routes? like for tabs?
    // wildcards? any? So route can be inspected

    router = {

        on: function (route, callback) {
            initialize();
            if(route.indexOf(':') > -1){
                // /worksheet/employees/:id
                // /worksheet/employees/Mike123456

                var id = route.split('/:')[0];
                wildbacks[id] = {
                    callback: callback,
                    prop: route.split('/:')[1]
                };
                return {
                    remove: function () {
                        delete wildbacks[id];
                    }
                };
            }

            callbacks[route] = callback;
            return {
                remove: function () {
                    delete callbacks[route];
                }
            };
        },

        emit: function (eventDetail) {

            var
                hash = eventDetail.hash,
                wildCardUrl,
                lastPath = getLastPath(hash);

            if(callbacks[hash]){
                callbacks[hash](eventDetail);
                return;
            }

            wildCardUrl = removeLastPath(hash);
            if(wildbacks[wildCardUrl]){
                eventDetail[wildbacks[wildCardUrl].prop] = lastPath;
                wildbacks[wildCardUrl].callback(eventDetail);
                return;
            }

            console.log('no route found (404)', hash);
        }
    };

    function parse(newUrl, oldUrl){

        currentState = urlToObject(newUrl);
        currentState.previous = urlToObject(oldUrl);
        currentState.oldUrl = currentState.previous.url;

        return currentState;
    }

    function paramsToObject (params) {
        if(!params){ return null; }
        params = params.split('&');
        var o = {};
        params.forEach(function (param) {
            o[param.split('=')[0]] = normalize(param.split('=')[1]);
        });
        return o;
    }

    function urlToObject (url) {
        if(!url){
            return {};
        }
        var
            params = paramsToObject(url.split('?')[1]),
            hash = url.split('?')[0].split('#')[1] || '/',
            paths = hash ? hash.split('/') : [],
            map = {};
        paths.forEach(function (path) {
            map[path] = true;
        });

        if(hash.indexOf('/') !== 0){
            // should start with slash
            hash = '/';
        }
        console.log('hash', hash);
        return {
            url: url,
            hash: hash,
            paths: paths,
            map: map,
            params: params
        };
    }

    function getLastPath (url) {
        var paths = url.split('/');
        return paths[paths.length - 1];
    }

    function removeLastPath (url) {
        var paths = url.split('/');
        paths.pop();
        return paths.join('/');
    }

    function normalize(val){
        if(val === 'false'){
            return false;
        }else if(val === 'true'){
            return true;
        }
        if(!isNaN(parseFloat(val))){
            return parseFloat(val);
        }
        return val;
    }

    return router;
}));<commit_msg>IE Fixes<commit_after>/* UMD.define */ (function (root, factory) {
    if (typeof customLoader === 'function'){ customLoader(factory, 'router'); }
    else if (typeof define === 'function' && define.amd){ define([], factory); }
    else if(typeof exports === 'object'){ module.exports = factory(); }
    else{ root.returnExports = factory();
        window.router = factory(); }
}(this, function () {

    var
        router,
        currentState = {},
        callbacks = {},
        wildbacks = {},
        initTimer,
        initialize = function () {
            clearTimeout(initTimer);
            initTimer = setTimeout(function () {
                window.addEventListener('hashchange', function (event) {
                    //console.log('hashchange', event);
                    // IE does not have URLs in the event
                    router.emit(parse(event.newURL || location.href, event.oldURL));
                });
                router.emit(parse(location.href));
                initialize = function () {};
            },100);
        };

    // TODO:
    // more than one on('/route')
    // more than one REST param
    // nested routes? like for tabs?
    // wildcards? any? So route can be inspected

    router = {

        on: function (route, callback) {
            initialize();
            if(route.indexOf(':') > -1){
                // /worksheet/employees/:id
                // /worksheet/employees/Mike123456

                var id = route.split('/:')[0];
                wildbacks[id] = {
                    callback: callback,
                    prop: route.split('/:')[1]
                };
                return {
                    remove: function () {
                        delete wildbacks[id];
                    }
                };
            }

            callbacks[route] = callback;
            return {
                remove: function () {
                    delete callbacks[route];
                }
            };
        },

        emit: function (eventDetail) {

            var
                hash = eventDetail.hash,
                wildCardUrl,
                lastPath = getLastPath(hash);

            if(callbacks[hash]){
                callbacks[hash](eventDetail);
                return;
            }

            wildCardUrl = removeLastPath(hash);
            if(wildbacks[wildCardUrl]){
                eventDetail[wildbacks[wildCardUrl].prop] = lastPath;
                wildbacks[wildCardUrl].callback(eventDetail);
                return;
            }

            console.log('no route found (404)', hash);
        }
    };

    function parse(newUrl, oldUrl){

        currentState = urlToObject(newUrl);
        // FIXME: either remove previous or stub it for IE
        currentState.previous = urlToObject(oldUrl);
        currentState.oldUrl = currentState.previous.url;

        return currentState;
    }

    function paramsToObject (params) {
        if(!params){ return null; }
        params = params.split('&');
        var o = {};
        params.forEach(function (param) {
            o[param.split('=')[0]] = normalize(param.split('=')[1]);
        });
        return o;
    }

    function urlToObject (url) {
        if(!url){
            return {};
        }
        var
            params = paramsToObject(url.split('?')[1]),
            hash = url.split('?')[0].split('#')[1] || '/',
            paths = hash ? hash.split('/') : [],
            map = {};
        paths.forEach(function (path) {
            map[path] = true;
        });

        if(hash.indexOf('/') !== 0){
            // should start with slash
            hash = '/';
        }
        console.log('hash', hash);
        return {
            url: url,
            hash: hash,
            paths: paths,
            map: map,
            params: params
        };
    }

    function getLastPath (url) {
        if(!url) { return ''; }
        var paths = url.split('/');
        return paths[paths.length - 1];
    }

    function removeLastPath (url) {
        if(!url) { return ''; }
        var paths = url.split('/');
        paths.pop();
        return paths.join('/');
    }

    function normalize(val){
        if(val === 'false'){
            return false;
        }else if(val === 'true'){
            return true;
        }
        if(!isNaN(parseFloat(val))){
            return parseFloat(val);
        }
        return val;
    }

    return router;
}));<|endoftext|>


<commit_before>(function($) {

Echo.Tests.module("Echo.UserSession", {
	"meta": {
		"className": "Echo.UserSession",
		"functions": [
			// public interface
			"set",
			"get",
			"is",
			"has",
			"any",
			"logout",

			// private methods
			"_maybeDelegate",
			"_onInit",
			"_isLogged",
			"_getActiveIdentities",
			"_getAvatar",
			"_getName",
			"_hasIdentity",
			"_anyMarker",
			"_anyRole"
		]
	},
	"setup": function() {
		resetUserSession();
	}
});

Echo.Tests.asyncTest("logged in checks", function() {
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			var user = this;
			var identity = "http://somedomain.com/users/fake_user";

			QUnit.ok(user.is("logged"),
				"Check that user is logged in via user.is(\"logged\") function");
			QUnit.equal(user.is("logged"), user._isLogged(),
				"Check \"is\" function delegation using \"logged\" property");

			QUnit.equal(user.get("name"), "john.doe",
				"Checking user.get() method, requesting user name");

			QUnit.equal(user.get("avatar"),
				"http://example.com/avatar.png",
				"Checking functions delegation: user.get() method, requesting user avatar");
			QUnit.equal(user.get("avatar"), user._getAvatar(),
				"Checking functions delegation: user.get() method should return the same value as user._getAvatar() one");
			QUnit.equal(user.get("activeIdentities").length, user._getActiveIdentities().length,
				"Checking active identities calculation via user.get(\"activeIdentities\") and via private user._getActiveIdentities() function");

			QUnit.ok(user.has("identity", identity),
				"Checking user.has(\"identity\", \"...\") function if condition is true");
			QUnit.ok(!user.has("identity", identity + "/other"),
				"Checking user.has(\"identity\", \"...\") function if condition is false");

			QUnit.ok(!!user.get("emails").length,
				"Checking email list extraction");
			QUnit.equal(user.get("email"), "john.doe@test.com",
				"Checking primary email extraction from user object");

			checkBasicOperations(user);

			user.logout(function() {
				QUnit.ok(!user.is("logged"), "Check that user is not logged after logout");
				QUnit.start();
			});
		}
	});
}, {
	"user": {"status": "logged"},
	"timeout": 6000
});

Echo.Tests.asyncTest("anonymous checks", function() {
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			var user = this;
			QUnit.ok(!user.is("logged"),
				"Check if the user is not logged in using user.is(\"logged\") function");
			QUnit.equal(user.is("logged"), user._isLogged(),
				"Check \"is\" function delegation using \"logged\" property");

			var avatar = user.get("avatar");
			var defaultAvatar = "http://example.com/default-avatar.png";
			QUnit.equal(user.get("avatar", defaultAvatar), defaultAvatar,
				"Checking get operation with existing attribute and default value via function call delegation (avatar field)");

			QUnit.ok(!user.get("emails"),
				"Checking email list extraction for anonymous user");
			QUnit.ok(!user.get("email"),
				"Checking primary email extraction for anonymous user");

			checkBasicOperations(user);
			QUnit.start();
		}
	});
});

// FIXME: test is disabled because it doesn't really test anything at the moment
Echo.Tests._asyncTest("error handling", function() {
	Echo.UserSession({
		"ready": function() {
			var user = this;
			QUnit.ok(true, "The \"ready\" callback is executed even when the appkey is missing");
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized with no appkey is considered as anonymous user"
			);
		}
	});

	resetUserSession();
	Echo.UserSession({
		"appkey": "invalid.appkey.sdk.test",
		"ready": function() {
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized using invalid appkey is considered as anonymous user"
			);
		}
	});

	Backplane.initialized = false;
	resetUserSession();
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized with Backplane inactive is considered as anonymous user"
			);
			Backplane.initialized = true;
			QUnit.start();
		}
	});
});

function resetUserSession() {
	// force Echo.UserSession to re-initialize itself completely
	// during the next Echo.UserSession object initialization
	Echo.UserSession.state = "init";
}

function checkBasicOperations(user) {
	var identity = "http://somedomain.com/users/fake_user";

	QUnit.equal(user.is("some-random-value"), undefined,
		"Check if we received expected result when calling 'is' function with unexpected argument");

	user.set("state", "ModeratorBanned");
	QUnit.equal(user.get("state"), "ModeratorBanned",
		"Checking basic get/set operations with simple values (user state: string)");

	user.set("name", "sean.doe");
	QUnit.equal(user.get("name"), "sean.doe",
		"Checking basic get/set operations with simple values (user name: string)");

	user.set("avatar", "http://example.com/my-avatar.png");
	QUnit.equal(user.get("avatar"), "http://example.com/my-avatar.png",
		"Checking basic get/set operations with simple values (user avatar: string)");

	user.set("avatar", false);
	QUnit.strictEqual(user.get("avatar"), false,
		"Checking basic get/set operations with casting to false values");

	user.set("some_attr", undefined);
	QUnit.equal(user.get("some_attr", "default value"), "default value",
		"Checking get operation with undefined attribute and default value");
	QUnit.equal(user.get("fake_attr", "default value"), "default value",
		"Checking get operation with fake attribute and default value");

	var roles = ["role1", "role2", "role3"];
	user.set("roles", roles);
	QUnit.equal(user.get("roles").length, roles.length,
		"Checking get/set operations with arrays (user roles)");
	QUnit.ok(user.any("roles", ["role2"]),
		"Checking user.any() function with arrays (user roles)");
	QUnit.ok(!user.any("roles", ["non-existing-role"]),
		"Checking user.any() function with arrays (user roles) with empty intersection");
	QUnit.ok(user.has("role", "role3"),
		"Checking user.has(\"role\", \"...\") function with delegation (user roles)");
	QUnit.ok(!user.has("role", "non-existing-role"),
		"Checking user.has() function for non-existing role");

	var markers = ["marker1", "marker2", "marker3"];
	user.set("markers", markers);
	QUnit.equal(user.get("markers").length, markers.length,
		"Checking get/set operations with arrays (user markers)");
	QUnit.ok(user.any("markers", ["marker2"]),
		"Checking user.any() function with arrays (user markers)");
	QUnit.ok(!user.any("markers", ["non-existing-marker"]),
		"Checking user.any() function with arrays (user markers) with empty intersection");
	QUnit.ok(user.has("marker", "marker3"),
		"Checking user.has() function with arrays (user marker)");
	QUnit.ok(!user.has("marker", "non-existing-marker"),
		"Checking user.has() function for non-existing marker");

	QUnit.equal(user.has("identity", identity), user._hasIdentity(identity),
		"Checking delegation using user.has(\"identity\", \"...\") function");
}

})(Echo.jQuery);
<commit_msg>Slightly increase test timeout because it requires more time with real requests<commit_after>(function($) {

Echo.Tests.module("Echo.UserSession", {
	"meta": {
		"className": "Echo.UserSession",
		"functions": [
			// public interface
			"set",
			"get",
			"is",
			"has",
			"any",
			"logout",

			// private methods
			"_maybeDelegate",
			"_onInit",
			"_isLogged",
			"_getActiveIdentities",
			"_getAvatar",
			"_getName",
			"_hasIdentity",
			"_anyMarker",
			"_anyRole"
		]
	},
	"setup": function() {
		resetUserSession();
	}
});

Echo.Tests.asyncTest("logged in checks", function() {
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			var user = this;
			var identity = "http://somedomain.com/users/fake_user";

			QUnit.ok(user.is("logged"),
				"Check that user is logged in via user.is(\"logged\") function");
			QUnit.equal(user.is("logged"), user._isLogged(),
				"Check \"is\" function delegation using \"logged\" property");

			QUnit.equal(user.get("name"), "john.doe",
				"Checking user.get() method, requesting user name");

			QUnit.equal(user.get("avatar"),
				"http://example.com/avatar.png",
				"Checking functions delegation: user.get() method, requesting user avatar");
			QUnit.equal(user.get("avatar"), user._getAvatar(),
				"Checking functions delegation: user.get() method should return the same value as user._getAvatar() one");
			QUnit.equal(user.get("activeIdentities").length, user._getActiveIdentities().length,
				"Checking active identities calculation via user.get(\"activeIdentities\") and via private user._getActiveIdentities() function");

			QUnit.ok(user.has("identity", identity),
				"Checking user.has(\"identity\", \"...\") function if condition is true");
			QUnit.ok(!user.has("identity", identity + "/other"),
				"Checking user.has(\"identity\", \"...\") function if condition is false");

			QUnit.ok(!!user.get("emails").length,
				"Checking email list extraction");
			QUnit.equal(user.get("email"), "john.doe@test.com",
				"Checking primary email extraction from user object");

			checkBasicOperations(user);

			user.logout(function() {
				QUnit.ok(!user.is("logged"), "Check that user is not logged after logout");
				QUnit.start();
			});
		}
	});
}, {
	"user": {"status": "logged"},
	"timeout": 8000
});

Echo.Tests.asyncTest("anonymous checks", function() {
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			var user = this;
			QUnit.ok(!user.is("logged"),
				"Check if the user is not logged in using user.is(\"logged\") function");
			QUnit.equal(user.is("logged"), user._isLogged(),
				"Check \"is\" function delegation using \"logged\" property");

			var avatar = user.get("avatar");
			var defaultAvatar = "http://example.com/default-avatar.png";
			QUnit.equal(user.get("avatar", defaultAvatar), defaultAvatar,
				"Checking get operation with existing attribute and default value via function call delegation (avatar field)");

			QUnit.ok(!user.get("emails"),
				"Checking email list extraction for anonymous user");
			QUnit.ok(!user.get("email"),
				"Checking primary email extraction for anonymous user");

			checkBasicOperations(user);
			QUnit.start();
		}
	});
});

// FIXME: test is disabled because it doesn't really test anything at the moment
Echo.Tests._asyncTest("error handling", function() {
	Echo.UserSession({
		"ready": function() {
			var user = this;
			QUnit.ok(true, "The \"ready\" callback is executed even when the appkey is missing");
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized with no appkey is considered as anonymous user"
			);
		}
	});

	resetUserSession();
	Echo.UserSession({
		"appkey": "invalid.appkey.sdk.test",
		"ready": function() {
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized using invalid appkey is considered as anonymous user"
			);
		}
	});

	Backplane.initialized = false;
	resetUserSession();
	Echo.UserSession({
		"appkey": "echo.jssdk.tests.aboutecho.com",
		"ready": function() {
			QUnit.ok(
				!this.is("logged"),
				"UserSession object initialized with Backplane inactive is considered as anonymous user"
			);
			Backplane.initialized = true;
			QUnit.start();
		}
	});
});

function resetUserSession() {
	// force Echo.UserSession to re-initialize itself completely
	// during the next Echo.UserSession object initialization
	Echo.UserSession.state = "init";
}

function checkBasicOperations(user) {
	var identity = "http://somedomain.com/users/fake_user";

	QUnit.equal(user.is("some-random-value"), undefined,
		"Check if we received expected result when calling 'is' function with unexpected argument");

	user.set("state", "ModeratorBanned");
	QUnit.equal(user.get("state"), "ModeratorBanned",
		"Checking basic get/set operations with simple values (user state: string)");

	user.set("name", "sean.doe");
	QUnit.equal(user.get("name"), "sean.doe",
		"Checking basic get/set operations with simple values (user name: string)");

	user.set("avatar", "http://example.com/my-avatar.png");
	QUnit.equal(user.get("avatar"), "http://example.com/my-avatar.png",
		"Checking basic get/set operations with simple values (user avatar: string)");

	user.set("avatar", false);
	QUnit.strictEqual(user.get("avatar"), false,
		"Checking basic get/set operations with casting to false values");

	user.set("some_attr", undefined);
	QUnit.equal(user.get("some_attr", "default value"), "default value",
		"Checking get operation with undefined attribute and default value");
	QUnit.equal(user.get("fake_attr", "default value"), "default value",
		"Checking get operation with fake attribute and default value");

	var roles = ["role1", "role2", "role3"];
	user.set("roles", roles);
	QUnit.equal(user.get("roles").length, roles.length,
		"Checking get/set operations with arrays (user roles)");
	QUnit.ok(user.any("roles", ["role2"]),
		"Checking user.any() function with arrays (user roles)");
	QUnit.ok(!user.any("roles", ["non-existing-role"]),
		"Checking user.any() function with arrays (user roles) with empty intersection");
	QUnit.ok(user.has("role", "role3"),
		"Checking user.has(\"role\", \"...\") function with delegation (user roles)");
	QUnit.ok(!user.has("role", "non-existing-role"),
		"Checking user.has() function for non-existing role");

	var markers = ["marker1", "marker2", "marker3"];
	user.set("markers", markers);
	QUnit.equal(user.get("markers").length, markers.length,
		"Checking get/set operations with arrays (user markers)");
	QUnit.ok(user.any("markers", ["marker2"]),
		"Checking user.any() function with arrays (user markers)");
	QUnit.ok(!user.any("markers", ["non-existing-marker"]),
		"Checking user.any() function with arrays (user markers) with empty intersection");
	QUnit.ok(user.has("marker", "marker3"),
		"Checking user.has() function with arrays (user marker)");
	QUnit.ok(!user.has("marker", "non-existing-marker"),
		"Checking user.has() function for non-existing marker");

	QUnit.equal(user.has("identity", identity), user._hasIdentity(identity),
		"Checking delegation using user.has(\"identity\", \"...\") function");
}

})(Echo.jQuery);
<|endoftext|>


<commit_before>'use strict';

var utils           = require('../utils');

/**
 * Parses the given document and creates tokens that can be used later when
 * compiling the file.
 * @method function
 * @param  {String} _source  The origional source
 * @param  {Object} _options The options object
 * @return {Object}          The tokens object
 */
module.exports = function( _tokens, _options ) {

    var options = _options;

    var i = 0;
    var e = 0;
    // the tokens we are to update
    var tokens = [];
    var stack = [];

    /*!
    * Loop over the source, split via the tag/var/comment regular expression splitter.
    * Send each chunk to the appropriate parser.
    */
    utils.each( _tokens.split_source, function ( _chunk ) {

        // check if the chunk has a class attr to parse
        // if not return
        var hasClass = _chunk.includes('class="');

        if (!_chunk || !hasClass) {
            e++;
            return;
        }

        // create the token for the chunk containing a class
        tokens.push( utils.createToken( _chunk, options, e ) );
        e++;
    });

    return tokens;
};
<commit_msg>Removed redundant vars<commit_after>'use strict';

var utils           = require('../utils');

/**
 * Parses the given document and creates tokens that can be used later when
 * compiling the file.
 * @method function
 * @param  {String} _source  The origional source
 * @param  {Object} _options The options object
 * @return {Object}          The tokens object
 */
module.exports = function( _tokens, _options ) {

    var options = _options;
    var i       = 0;
    var tokens  = [];

    /*!
    * Loop over the source, split via the tag/var/comment regular expression splitter.
    * Send each chunk to the appropriate parser.
    */
    utils.each( _tokens.split_source, function ( _chunk ) {

        // check if the chunk has a class attr to parse
        // if not return
        var hasClass = _chunk.includes('class="');

        if (!_chunk || !hasClass) {
            i++;
            return;
        }

        // create the token for the chunk containing a class
        tokens.push( utils.createToken( _chunk, options, i ) );
        i++;
    });

    return tokens;
};
<|endoftext|>


<commit_before>import db from 'baqend'
import Login from './components/Login'
import VueRouter from 'vue-router'

function dbReady (to, from, next) {
  db.ready(() => next())
}

/**
 * Asynchronously load components (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the component to load.
 */
function view (name) {
  return function (resolve) {
    require(['./components/' + name + '.vue'], resolve)
  }
}

const routes = [
  { path: '/', component: Login, name: 'login' },
  { path: '/dashboard', name: 'dashboard', component: view('Dashboard'), beforeEnter: dbReady },
  { path: '/settings', name: 'settings', component: view('Settings'), beforeEnter: dbReady },
  { path: '/swop-dialog', name: 'swop-dialog', component: view('SwopDialog'), beforeEnter: dbReady },
  { path: '/spielregeln', name: 'spielregeln', component: view('Spielregeln'), beforeEnter: dbReady },
  { path: '/navigation', name: 'navigation', component: view('Navigation'), beforeEnter: dbReady },
  { path: '/modal-sample', name: 'modal-sample', component: view('ModalSample'), beforeEnter: dbReady },
  // { path: '/login', name: 'login', component: view('Login'), beforeEnter: dbReady },
  // { path: '/chats', name: 'chats', component: view('Chats'), beforeEnter: dbReady },
  // { path: '/chat/:id', name: 'chat', component: view('Chat'), beforeEnter: dbReady },
  { path: '/signup', name: 'signup', component: view('Signup'), beforeEnter: dbReady },
  { path: '/me', name: 'me', component: view('Me'), beforeEnter: dbReady },
  { path: '/passwort-vergessen', name: 'passwort-vergessen', component: view('ForgotPassword'), beforeEnter: dbReady },
  { path: '/passwort-reset', name: 'passwort-reset', component: view('ResetPassword'), beforeEnter: dbReady }
]

const router = new VueRouter({
  routes
})

export default router
<commit_msg>Neue Route für testzwecke<commit_after>import db from 'baqend'
import Login from './components/Login'
import VueRouter from 'vue-router'

function dbReady (to, from, next) {
  db.ready(() => next())
}

/**
 * Asynchronously load components (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the component to load.
 */
function view (name) {
  return function (resolve) {
    require(['./components/' + name + '.vue'], resolve)
  }
}

const routes = [
  { path: '/', component: Login, name: 'login' },
  { path: '/dashboard', name: 'dashboard', component: view('Dashboard'), beforeEnter: dbReady },
  { path: '/settings', name: 'settings', component: view('Settings'), beforeEnter: dbReady },
  { path: '/swop-dialog', name: 'swop-dialog', component: view('SwopDialog'), beforeEnter: dbReady },
  { path: '/spielregeln', name: 'spielregeln', component: view('Spielregeln'), beforeEnter: dbReady },
  { path: '/hello', name: 'hello', component: view('Hello'), beforeEnter: dbReady },
  { path: '/modal-sample', name: 'modal-sample', component: view('ModalSample'), beforeEnter: dbReady },
  // { path: '/login', name: 'login', component: view('Login'), beforeEnter: dbReady },
  // { path: '/chats', name: 'chats', component: view('Chats'), beforeEnter: dbReady },
  // { path: '/chat/:id', name: 'chat', component: view('Chat'), beforeEnter: dbReady },
  { path: '/signup', name: 'signup', component: view('Signup'), beforeEnter: dbReady },
  { path: '/me', name: 'me', component: view('Me'), beforeEnter: dbReady },
  { path: '/passwort-vergessen', name: 'passwort-vergessen', component: view('ForgotPassword'), beforeEnter: dbReady },
  { path: '/passwort-reset', name: 'passwort-reset', component: view('ResetPassword'), beforeEnter: dbReady }
]

const router = new VueRouter({
  routes
})

export default router
<|endoftext|>


<commit_before>/*
* Copyright (c) 2013, Yahoo! Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/

/*jslint node:true, nomen:true*/

"use strict";

var YUITest = require('yuitest'),
    A = YUITest.Assert,
    OA = YUITest.ObjectAssert,
    suite,
    server = require('../../lib/server.js');

suite = new YUITest.TestSuite("expres-yui server suite");

suite.add(new YUITest.TestCase({
    name: "server-test",

    _should: {
        error: {
            "test ready without locator": true
        }
    },

    setUp: function () {
        server._clientModules = {};
        server._serverModules = {};
        server.YUI = {};
        server._app = {
            _mockAttrs: {},
            // express app
            set: function (name, value) {
                this._mockAttrs[name] = value;
            },
            get: function (name) {
                return this._mockAttrs[name];
            }
        };
    },

    tearDown: function () {
        // unregister mocks
        delete server.config;
        delete server.registerGroup;
        delete server._app;
    },

    "test constructor": function () {
        A.isNotNull(server, "server require failed");
    },

    "test empty registerBundle": function () {
        var bundle = { name: 'foo' };
        // attaching for the first time
        var result = server.registerBundle(bundle);
        A.areEqual(server, result, 'registerBundle should be chainable');
    },

    "test registerBundle with client stuff": function () {
        var bundle = {
            name: 'bar',
            buildDirectory: __dirname,
            yui: {
                client: {
                    foo: {
                        group: 'abc'
                    }
                },
                metaModuleName: 'loader-foo'
            }
        };
        server._clientModules = {
            foo: {
                group: 'efg'
            }
        };
        YUITest.Mock.expect(server, {
            method: 'registerGroup',
            args: ['bar', __dirname, 'loader-foo']
        });
        server.registerBundle(bundle);
        A.areSame('abc', server._clientModules.foo.group, 'internal registry with _clientModules was not successfully updated');
    },

    "test registerBundle with server stuff": function () {
        var bundle = {
            name: 'bar',
            buildDirectory: __dirname,
            yui: {
                server: {
                    bar: {
                        group: 'abc'
                    }
                }
            }
        };
        server._serverModules = {
            bar: {
                group: 'efg'
            }
        };
        YUITest.Mock.expect(server.YUI, {
            method: 'applyConfig',
            args: [YUITest.Mock.Value.Object]
        });
        server.registerBundle(bundle);
        A.areSame('abc', server._serverModules.bar.group, 'internal registry with _serverModules was not successfully updated');
    },

    "test use": function () {
        var Y = YUITest.Mock(),
            result,
            attachedModules = {};

        server._app.set('locator', {
            getRootBundle: function () {}
        });
        YUITest.Mock.expect(server, {
            method: 'config',
            args: [],
            callCount: 3,
            run: function () {
                return {
                    groups: {
                        'foo': {
                            more: 1
                        }
                    }
                };
            }
        });
        Y.use = function () {
            Array.prototype.slice(arguments).forEach(function (name) {
                attachedModules[name] = true;
            });
            return Y;
        };
        Y.Env = { _loader: { getRequires: function () {} } };
        Y.Template = { get: function () {} };
        Y.Intl = { get: function () {} };
        YUITest.Mock.expect(server, {
            method: 'YUI',
            callCount: 1,
            args: [YUITest.Mock.Value.Object],
            run: function (c) {
                A.isTrue(c.useSync, 'useSync is required when running on the server');
                return Y;
            }
        });
        server.YUI.applyConfig = function () {};

        // first pass
        result = server.use();
        A.areSame(Y, result);

        // second pass
        result = server.use('foo', 'baz', 'bar');
        A.areSame(Y, result);

        // third pass
        result = server.use('foo', 'baz', 'bar');
        A.areSame(Y, result);
        A.areSame(Y, server._Y, 'private _Y used by other internal methods was not exposed');

        YUITest.Mock.verify(server);
        YUITest.Mock.verify(Y);
    },

    "test ready without locator": function () {
        server.ready(function () {});
        // this test is meant to throw because server._app.get('locator') is not set.
    },

    "test ready fulfilled": function () {
        server._app.set('locator', {
            getBundle: function (name) {
                return {
                    foo: {},
                    bar: {}
                }[name];
            },
            listBundleNames: function () {
                return ['foo', 'bar'];
            },
            ready: {
                then: function (fulfilled, rejected) {
                    fulfilled();
                }
            }
        });
        server.ready(function (err) {
            A.isUndefined(err, 'the ready promise should be fulfilled');
        });
    },

    "test ready rejected": function () {
        var rejection = new Error('this should be propagated');
        server._app.set('locator', {
            ready: {
                then: function (fulfilled, rejected) {
                    rejected(rejection);
                }
            }
        });
        server.ready(function (err) {
            A.isObject(err, 'the ready promise should be rejected');
            A.areSame(rejection, err, 'the promise rejection error should be propagated');
        });
    }

}));

YUITest.TestRunner.add(suite);
<commit_msg>add test to check that patching happens before the use<commit_after>/*
* Copyright (c) 2013, Yahoo! Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/

/*jslint node:true, nomen:true*/

"use strict";

var YUITest = require('yuitest'),
    mockery = require('mockery'),
    A = YUITest.Assert,
    OA = YUITest.ObjectAssert,
    suite,
    server;

suite = new YUITest.TestSuite("expres-yui server suite");

suite.add(new YUITest.TestCase({
    name: "server-test",

    _should: {
        error: {
            "test ready without locator": true
        }
    },

    setUp: function () {
        mockery.enable({
            useCleanCache: true,
            warnOnUnregistered: false
        });

        server = require('../../lib/server.js');
        server._clientModules = {};
        server._serverModules = {};
        server.YUI = {};
        server._app = {
            _mockAttrs: {},
            // express app
            set: function (name, value) {
                this._mockAttrs[name] = value;
            },
            get: function (name) {
                return this._mockAttrs[name];
            }
        };
    },

    tearDown: function () {
        server = null;
        mockery.disable();
    },

    "test constructor": function () {
        A.isNotNull(server, "server require failed");
    },

    "test empty registerBundle": function () {
        var bundle = { name: 'foo' };
        // attaching for the first time
        var result = server.registerBundle(bundle);
        A.areEqual(server, result, 'registerBundle should be chainable');
    },

    "test registerBundle with client stuff": function () {
        var bundle = {
            name: 'bar',
            buildDirectory: __dirname,
            yui: {
                client: {
                    foo: {
                        group: 'abc'
                    }
                },
                metaModuleName: 'loader-foo'
            }
        };
        server._clientModules = {
            foo: {
                group: 'efg'
            }
        };
        YUITest.Mock.expect(server, {
            method: 'registerGroup',
            args: ['bar', __dirname, 'loader-foo']
        });
        server.registerBundle(bundle);
        A.areSame('abc', server._clientModules.foo.group, 'internal registry with _clientModules was not successfully updated');
    },

    "test registerBundle with server stuff": function () {
        var bundle = {
            name: 'bar',
            buildDirectory: __dirname,
            yui: {
                server: {
                    bar: {
                        group: 'abc'
                    }
                }
            }
        };
        server._serverModules = {
            bar: {
                group: 'efg'
            }
        };
        YUITest.Mock.expect(server.YUI, {
            method: 'applyConfig',
            args: [YUITest.Mock.Value.Object]
        });
        server.registerBundle(bundle);
        A.areSame('abc', server._serverModules.bar.group, 'internal registry with _serverModules was not successfully updated');
    },

    "test use": function () {
        var Y = YUITest.Mock(),
            result,
            attachedModules = {};

        server._app.set('locator', {
            getRootBundle: function () {}
        });
        YUITest.Mock.expect(server, {
            method: 'config',
            args: [],
            callCount: 3,
            run: function () {
                return {
                    groups: {
                        'foo': {
                            more: 1
                        }
                    }
                };
            }
        });
        Y.use = function () {
            Array.prototype.slice(arguments).forEach(function (name) {
                attachedModules[name] = true;
            });
            return Y;
        };
        Y.Env = { _loader: { getRequires: function () {} } };
        Y.Template = { get: function () {} };
        Y.Intl = { get: function () {} };
        YUITest.Mock.expect(server, {
            method: 'YUI',
            callCount: 1,
            args: [YUITest.Mock.Value.Object],
            run: function (c) {
                A.isTrue(c.useSync, 'useSync is required when running on the server');
                return Y;
            }
        });
        server.YUI.applyConfig = function () {};

        // first pass
        result = server.use();
        A.areSame(Y, result);

        // second pass
        result = server.use('foo', 'baz', 'bar');
        A.areSame(Y, result);

        // third pass
        result = server.use('foo', 'baz', 'bar');
        A.areSame(Y, result);
        A.areSame(Y, server._Y, 'private _Y used by other internal methods was not exposed');

        YUITest.Mock.verify(server);
        YUITest.Mock.verify(Y);
    },

    "test hook for patching loader": function () {
        var Y = YUITest.Mock(),
            newUse = function () {
                used = 'useCalled';
            },
            used = 'useNotCalled',
            result;

        YUITest.Mock.expect(server, {
            method: 'config',
            args: [],
            callCount: 1,
            run: function () {
                return {
                    groups: {
                        'foo': {
                            more: 1
                        }
                    }
                };
            }
        });

        YUITest.Mock.expect(server, {
            method: 'YUI',
            callCount: 1,
            args: [YUITest.Mock.Value.Object],
            run: function (c) {
                A.isTrue(c.useSync, 'useSync is required when running on the server');
                return Y;
            }
        });

        server._app.set('locator', {
            getRootBundle: function () {}
        });

        server.YUI.applyConfig = function () {};

        server._patches = [
            function (Y) {
                Y.use = newUse;
                // This `use` should be invoked after patching occurs
                A.areSame('useNotCalled', used);
            }
        ];

        result = server.use('foo', function (Y) {
            // The patch should have been applied
            A.areEqual(newUse, Y.use);
            // The `use` statement should have been invoked
            A.areSame('useCalled', used);
        });

        YUITest.Mock.verify(server);
        YUITest.Mock.verify(Y);
    },

    "test ready without locator": function () {
        server.ready(function () {});
        // this test is meant to throw because server._app.get('locator') is not set.
    },

    "test ready fulfilled": function () {
        server._app.set('locator', {
            getBundle: function (name) {
                return {
                    foo: {},
                    bar: {}
                }[name];
            },
            listBundleNames: function () {
                return ['foo', 'bar'];
            },
            ready: {
                then: function (fulfilled, rejected) {
                    fulfilled();
                }
            }
        });
        server.ready(function (err) {
            A.isUndefined(err, 'the ready promise should be fulfilled');
        });
    },

    "test ready rejected": function () {
        var rejection = new Error('this should be propagated');
        server._app.set('locator', {
            ready: {
                then: function (fulfilled, rejected) {
                    rejected(rejection);
                }
            }
        });
        server.ready(function (err) {
            A.isObject(err, 'the ready promise should be rejected');
            A.areSame(rejection, err, 'the promise rejection error should be propagated');
        });
    }

}));

YUITest.TestRunner.add(suite);
<|endoftext|>


<commit_before>/* Task Description */
/*
 * Create a module for a Telerik Academy course
 * The course has a title and presentations
 * Each presentation also has a title
 * There is a homework for each presentation
 * There is a set of students listed for the course
 * Each student has firstname, lastname and an ID
 * IDs must be unique integer numbers which are at least 1
 * Each student can submit a homework for each presentation in the course
 * Create method init
 * Accepts a string - course title
 * Accepts an array of strings - presentation titles
 * Throws if there is an invalid title
 * Titles do not start or end with spaces
 * Titles do not have consecutive spaces
 * Titles have at least one character
 * Throws if there are no presentations
 * Create method addStudent which lists a student for the course
 * Accepts a string in the format 'Firstname Lastname'
 * Throws if any of the names are not valid
 * Names start with an upper case letter
 * All other symbols in the name (if any) are lowercase letters
 * Generates a unique student ID and returns it
 * Create method getAllStudents that returns an array of students in the format:
 * {firstname: 'string', lastname: 'string', id: StudentID}
 * Create method submitHomework
 * Accepts studentID and homeworkID
 * homeworkID 1 is for the first presentation
 * homeworkID 2 is for the second one
 * ...
 * Throws if any of the IDs are invalid
 * Create method pushExamResults
 * Accepts an array of items in the format {StudentID: ..., Score: ...}
 * StudentIDs which are not listed get 0 points
 * Throw if there is an invalid StudentID
 * Throw if same StudentID is given more than once ( he tried to cheat (: )
 * Throw if Score is not a number
 * Create method getTopStudents which returns an array of the top 10 performing students
 * Array must be sorted from best to worst
 * If there are less than 10, return them all
 * The final score that is used to calculate the top performing students is done as follows:
 * 75% of the exam result
 * 25% the submitted homework (count of submitted homeworks / count of all homeworks) for the course
 */

function solve() {

	var Student = (function () {

		var Student = {
			init: function (firstname, lastname, id) {
				this.firstname = firstname;
				this.lastname = lastname;
				this.id = id;
			}
		};

		Object.defineProperties(Student, {
			firstname: {
				get: function () {

					return this._firstname;
				},
				set: function (value) {
					validateStudentName(value);
					this._firstname = value;
					return this;
				}
			},
			lastname: {
				get: function () {

					return this._lastname;
				},
				set: function (value) {
					validateStudentName(value);
					this._lastname = value;
					return this;
				}
			}
		});

		function validateStudentName(value) {

			var namePattern = /^[A-Z]{1}[a-z]*/;

			if (!namePattern.test(value)) {
				throw {message: 'You have entered an invalid name'};
			}
		}

		return Student;
	}());

	var id = 0;

	var Course = {
		init: function (title, presentations) {
			id = 0;
			this.title = title;
			this.presentations = presentations;
			this.students = [];
			return this;
		},
		addStudent: function (name) {

			validateIfStringIsPassed(name);

			var names = name.split(' ');

			if (names.length > 2) {
				throw {message: 'Students must have only two names'};
			}

			var firstName = names[0].trim(),
				lastName = names[1].trim(),
				objToPush = Object.create(Student);

			id += 1;

			objToPush.init(firstName, lastName, id);

			this.students.push(objToPush);
			return objToPush.id;
		},
		getAllStudents: function () {
			return this.students.slice();
		},
		submitHomework: function (studentID, homeworkID) {

			validateIDs(this, studentID, homeworkID);
			return this;
		},
		pushExamResults: function (results) {
			throw {message: 'Not implemented'};
		},
		getTopStudents: function () {
			throw {message: 'Not implemented'};
		}
	};

	Object.defineProperties(Course, {
		title: {
			get: function () {

				return this._title;
			},
			set: function (value) {

				validateIfStringIsPassed(value);
				validateWhiteSpace(value);
				this._title = value;
				return this;
			}
		},
		presentations: {
			get: function () {
				return this._presentations.slice();
			},
			set: function (value) {

				validatePresentations(value);
				this._presentations = value;
				return this;
			}
		}
	});

	function validateIfStringIsPassed(value) {

		if (!value) {
			throw {message: 'Title can not be an empty string'};
		}

		if (typeof value !== 'string') {
			throw {message: 'You need to pass a string as title'};
		}
	}

	function validateWhiteSpace(value) {

		var whiteSpacePattern = /^\s|[\s]{2,}|\s+$/;

		if (whiteSpacePattern.test(value)) {
			throw {message: 'The title can not start or end with white-space'};
		}
	}

	function validatePresentations(value) {

		if (!Array.isArray(value)) {
			throw {message: 'You must pass an Array as argument'};
		}

		if (value.length < 1) {
			throw {message: 'You can not pass an empty Array'};
		}

		value.forEach(function (element) {

			validateIfStringIsPassed(element);
			validateWhiteSpace(element);
		});
	}

	function validateIDs(object, studentID, homeworkID) {

		if (studentID > object.students.length || studentID < 1) {

			throw {message: 'Invalid Student ID'};
		}

		if (homeworkID > object.presentations.length || homeworkID < 1) {
			throw {message: 'Invalid Homework ID'};
		}
	}

	return Course;
}

module.exports = solve;
<commit_msg>small id update<commit_after>/* Task Description */
/*
 * Create a module for a Telerik Academy course
 * The course has a title and presentations
 * Each presentation also has a title
 * There is a homework for each presentation
 * There is a set of students listed for the course
 * Each student has firstname, lastname and an ID
 * IDs must be unique integer numbers which are at least 1
 * Each student can submit a homework for each presentation in the course
 * Create method init
 * Accepts a string - course title
 * Accepts an array of strings - presentation titles
 * Throws if there is an invalid title
 * Titles do not start or end with spaces
 * Titles do not have consecutive spaces
 * Titles have at least one character
 * Throws if there are no presentations
 * Create method addStudent which lists a student for the course
 * Accepts a string in the format 'Firstname Lastname'
 * Throws if any of the names are not valid
 * Names start with an upper case letter
 * All other symbols in the name (if any) are lowercase letters
 * Generates a unique student ID and returns it
 * Create method getAllStudents that returns an array of students in the format:
 * {firstname: 'string', lastname: 'string', id: StudentID}
 * Create method submitHomework
 * Accepts studentID and homeworkID
 * homeworkID 1 is for the first presentation
 * homeworkID 2 is for the second one
 * ...
 * Throws if any of the IDs are invalid
 * Create method pushExamResults
 * Accepts an array of items in the format {StudentID: ..., Score: ...}
 * StudentIDs which are not listed get 0 points
 * Throw if there is an invalid StudentID
 * Throw if same StudentID is given more than once ( he tried to cheat (: )
 * Throw if Score is not a number
 * Create method getTopStudents which returns an array of the top 10 performing students
 * Array must be sorted from best to worst
 * If there are less than 10, return them all
 * The final score that is used to calculate the top performing students is done as follows:
 * 75% of the exam result
 * 25% the submitted homework (count of submitted homeworks / count of all homeworks) for the course
 */

function solve() {

	var Student = (function () {

		var id = 0,
			Student = {
			init: function (firstname, lastname) {
				this.firstname = firstname;
				this.lastname = lastname;
				this.id = ++id;
			}
		};

		Object.defineProperties(Student, {
			firstname: {
				get: function () {

					return this._firstname;
				},
				set: function (value) {
					validateStudentName(value);
					this._firstname = value;
					return this;
				}
			},
			lastname: {
				get: function () {

					return this._lastname;
				},
				set: function (value) {
					validateStudentName(value);
					this._lastname = value;
					return this;
				}
			}
		});

		function validateStudentName(value) {

			var namePattern = /^[A-Z]{1}[a-z]*/;

			if (!namePattern.test(value)) {
				throw {message: 'You have entered an invalid name'};
			}
		}

		return Student;
	}());

	var Course = {
		init: function (title, presentations) {
			// this.id = 0;
			this.title = title;
			this.presentations = presentations;
			this.students = [];
			return this;
		},
		addStudent: function (name) {

			validateIfStringIsPassed(name);

			var names = name.split(' ');

			if (names.length > 2) {
				throw {message: 'Students must have only two names'};
			}

			var firstName = names[0].trim(),
				lastName = names[1].trim(),
				objToPush = Object.create(Student);

			// this.id += 1;

			objToPush.init(firstName, lastName);

			this.students.push(objToPush);
			return objToPush.id;
		},
		getAllStudents: function () {
			return this.students.slice();
		},
		submitHomework: function (studentID, homeworkID) {

			validateIDs(this, studentID, homeworkID);
			return this;
		},
		pushExamResults: function (results) {
			throw {message: 'Not implemented'};
		},
		getTopStudents: function () {
			throw {message: 'Not implemented'};
		}
	};

	Object.defineProperties(Course, {
		title: {
			get: function () {

				return this._title;
			},
			set: function (value) {

				validateIfStringIsPassed(value);
				validateWhiteSpace(value);
				this._title = value;
				return this;
			}
		},
		presentations: {
			get: function () {
				return this._presentations.slice();
			},
			set: function (value) {

				validatePresentations(value);
				this._presentations = value;
				return this;
			}
		}
	});

	function validateIfStringIsPassed(value) {

		if (!value) {
			throw {message: 'Title can not be an empty string'};
		}

		if (typeof value !== 'string') {
			throw {message: 'You need to pass a string as title'};
		}
	}

	function validateWhiteSpace(value) {

		var whiteSpacePattern = /^\s|[\s]{2,}|\s+$/;

		if (whiteSpacePattern.test(value)) {
			throw {message: 'The title can not start or end with white-space'};
		}
	}

	function validatePresentations(value) {

		if (!Array.isArray(value)) {
			throw {message: 'You must pass an Array as argument'};
		}

		if (value.length < 1) {
			throw {message: 'You can not pass an empty Array'};
		}

		value.forEach(function (element) {

			validateIfStringIsPassed(element);
			validateWhiteSpace(element);
		});
	}

	function validateIDs(object, studentID, homeworkID) {

		if (studentID > object.students.length || studentID < 1) {

			throw {message: 'Invalid Student ID'};
		}

		if (homeworkID > object.presentations.length || homeworkID < 1) {
			throw {message: 'Invalid Homework ID'};
		}
	}

	return Course;
}

module.exports = solve;
<|endoftext|>