<commit_before>const React = require('react');
const clone = require('clone');
const render = require('../render');

class Index extends React.Component {
  render() {
    return
Index
;
  }
}
// eslint-disable-next-line react/no-multi-comp
class EntryWrapper extends React.Component {
  render() {
    return (

EntryWrapper

    );
  }
}
// eslint-disable-next-line react/no-multi-comp
class BodyWrapper extends React.Component {
  render() {
    return (

BodyWrapper

    );
  }
}

describe('renderer/render', () => {
  const context = {};
  const request = { headers: { 'user-agent': 'Moznota Browser 1.0' } };
  const store = {
    getState: jest.fn(() => {}),
  };
  const getRoutes = () => [
    { path: 'hola' },
  ];
  const renderProps = {
    routes: getRoutes(),
  };
  const currentRoute = {};
  const httpClient = {};
  const entryWrapperConfig = {};
  const envVariables = [];
  const assets = {};
  const cacheManager = { setCacheIfProd: jest.fn() };

  describe('without a custom render method', () => {
    describe('when the route is an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithEmail = clone(renderProps);
        currentRouteWithEmail.email = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithEmail },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          {},
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).toBeNull();
        expect(results.rootElement.props.body.props.html).toBeDefined();
        expect(results.rootElement.props.body.props.html).not.toContain('data-reactid');
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });

    describe('when the route is not an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          {},
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).not.toBeNull();
        expect(results.rootElement.props.body.props.html).toContain('data-reactid');
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });
  });

  describe('with a custom render method', () => {
    const renderMethod = (component, styleTags) => {
      expect(component).toBeDefined();
      expect(styleTags).toBeDefined();
      return {
        head: (<meta name="hi" />),
        body: '
That body!
',
      };
    };

    describe('when the route is an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithEmail = clone(renderProps);
        currentRouteWithEmail.email = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithEmail },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          { renderMethod },
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).toBeNull();
        expect(results.rootElement.props.body.props.html).toBeDefined();
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });

    describe('when the route is not an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          { renderMethod },
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).not.toBeNull();
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });
  });
});
<commit_msg>fix test for routes wit cache<commit_after>const React = require('react');
const clone = require('clone');
const render = require('../render');

class Index extends React.Component {
  render() {
    return
Index
;
  }
}
// eslint-disable-next-line react/no-multi-comp
class EntryWrapper extends React.Component {
  render() {
    return (

EntryWrapper

    );
  }
}
// eslint-disable-next-line react/no-multi-comp
class BodyWrapper extends React.Component {
  render() {
    return (

BodyWrapper

    );
  }
}

describe('renderer/render', () => {
  const context = {};
  const request = { headers: { 'user-agent': 'Moznota Browser 1.0' } };
  const store = {
    getState: jest.fn(() => {}),
  };
  const getRoutes = () => [
    { path: 'hola' },
  ];
  const renderProps = {
    routes: getRoutes(),
  };
  const currentRoute = {};
  const httpClient = {};
  const entryWrapperConfig = {};
  const envVariables = [];
  const assets = {};
  const cacheManager = { setCacheIfProd: jest.fn() };

  describe('without a custom render method', () => {
    describe('when the route is an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithEmail = clone(renderProps);
        currentRouteWithEmail.email = true;
        currentRouteWithEmail.cache = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithEmail },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          {},
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).toBeNull();
        expect(results.rootElement.props.body.props.html).toBeDefined();
        expect(results.rootElement.props.body.props.html).not.toContain('data-reactid');
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });

    describe('when the route is not an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithCache = clone(renderProps);
        currentRouteWithCache.cache = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithCache },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          {},
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).not.toBeNull();
        expect(results.rootElement.props.body.props.html).toContain('data-reactid');
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });
  });

  describe('with a custom render method', () => {
    const renderMethod = (component, styleTags) => {
      expect(component).toBeDefined();
      expect(styleTags).toBeDefined();
      return {
        head: (<meta name="hi" />),
        body: '
That body!
',
      };
    };

    describe('when the route is an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithEmail = clone(renderProps);
        currentRouteWithEmail.email = true;
        currentRouteWithEmail.cache = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithEmail },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          { renderMethod },
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).toBeNull();
        expect(results.rootElement.props.body.props.html).toBeDefined();
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });

    describe('when the route is not an email route', () => {
      it('should render output', async () => {
        cacheManager.setCacheIfProd.mockReset();
        const currentRouteWithCache = clone(renderProps);
        currentRouteWithCache.cache = true;
        const results = await render(
          context,
          request,
          { EntryPoint: Index, entryName: 'main', store, routes: getRoutes, httpClient },
          { renderProps, currentRoute: currentRouteWithCache },
          { EntryWrapper, BodyWrapper, entryWrapperConfig, envVariables },
          { assets, cacheManager },
          { renderMethod },
        );
        expect(results.responseString).toBeDefined();
        expect(results.rootElement.props.head).not.toBeNull();
        expect(cacheManager.setCacheIfProd.mock.calls.length).toBe(1);
      });
    });
  });
});
<|endoftext|>


<commit_before>module.exports.safe = true;
module.exports.manual_interpolation = ["actions"];

/*
 Purpose:
 Executes the specified actions for each value in the specified array(s), assigning the array value to the specified key(s).
 If parallel=true, then the actions will be processed concurrently with each iteration receiving a separate snapshot of the session.

 Example:
- foreach:
    key: [ item, ad ]
    in: [ items, ads ]
    parallel: true
    actions:
      - goto: token_exchange

 */

var _ = require("lodash");
var verror = require("verror");
var Promise = require("bluebird");

module.exports.normalize = function (action, normalize_action) {
	if (_.has(action, "foreach")) {
		// Normalize nested actions
		action.foreach.key = _.castArray(action.foreach.key);
		action.foreach.in = _.castArray(action.foreach.in);
		action.foreach.actions = normalize_action(action.foreach.actions, "foreach.actions");
		return action;
	}
};

module.exports.process = function (context) {
	var foreachPath = this.path + ".foreach";
	var matrix = prepareMatrix(this.foreach.in, this.foreach.key, context, foreachPath);

	// Select serial or parallel mapper
	var mappingFunction = getMappingFunctionForConcurrency(this.foreach.parallel, foreachPath + ".parallel");
	return mappingFunction(matrix, tuple => {
		applySharedProperties(tuple, this.foreach.shared, context, foreachPath + ".shared");
		exports.logger.debug("Forking with " + JSON.stringify(tuple));
		var new_context = context.clone(tuple);
		return new_context.processAction(this.foreach.actions)
			.catch(context.ProcessingDirective, new_context.handleDirective);
	});
};

module.exports.tuplify = tuplify;
module.exports.transpose = transpose;
module.exports.interpolateAndValidate = interpolateAndValidate;
module.exports.prepareMatrix = prepareMatrix;
module.exports.getMappingFunctionForConcurrency = getMappingFunctionForConcurrency;
module.exports.applySharedProperties = applySharedProperties;

function applySharedProperties(tuple, shared, context, path) {
	if (_.isArray(shared)) {
		shared.forEach((item, index) => {
			if (!_.isString(item)) {
				throw new verror.VError({
					name: "InvalidSharedPropertiesValue",
					info: {
						path: `${path}.${index}`,
						value: item
					}
				}, "If .foreach.shared is an array, it must be an array of string property keys");
			}
		});
	}
	if (_.isString(shared) || _.isArray(shared)) {
		_.castArray(shared).forEach(item => {
			_.set(tuple, item, context.getSessionValue(item));
		});
	}
	else {
		_.assign(tuple, shared);
	}
}

function getMappingFunctionForConcurrency(parallel_option, path) {
	if (parallel_option === false || _.isNil(parallel_option)) {
		return Promise.mapSeries;
	}
	else if (parallel_option === true) {
		return _.partialRight(Promise.map, { concurrency: Infinity });
	}
	else {
		if (!_.isNumber(parallel_option)) {
			throw new verror.VError({
				name: "InvalidParallelOption",
				info: {
					path
				}
			}, "If specified, foreach.parallel must be either a boolean value or an integer.");
		}
		else {
			return _.partialRight(Promise.map, { concurrency: parallel_option });
		}
	}
}

function prepareMatrix(arrays, keys, context, path) {
	return tuplify(transpose(interpolateAndValidate(arrays, context, path)), keys);
}

function interpolateAndValidate(arrays, context, path) {
	// Each of the array targets, is either an array, or a reference to an array
	// So normalize and interpolate any key references, then validate
	return arrays.map((vector, index) => {
		if (_.isString(vector)) {
			vector = context.getSessionValue(vector, path + ".in");
		}
		if (!_.isArray(vector)) {
			throw new verror.VError({
				name: "InvalidForeachTarget",
				info: {
					path: `${path}.in[${index}]`
				}
			}, "foreach .in elements must be an array or reference to an array");
		}
		return vector;
	});
}

function transpose(matrix) {
	/* Transpose switches a multi-dimensional array AxB -> BxA.

	   [ 'a', 'b', 'c' ]         [ 'a', 1 ]
	   [  1 ,  2 ,  3  ]   =>    [ 'b', 2 ]
								 [ 'c', 3 ]
	*/
	return _.zip.apply(null, matrix);
}

function tuplify(matrix, keys) {
	return matrix.map(_.partial(_.zipObject, keys));
}
<commit_msg>foreach allows "isolated: false" to not fork the context for memory savings on large iteration sets<commit_after>module.exports.safe = true;
module.exports.manual_interpolation = ["actions"];

/*
 Purpose:
 Executes the specified actions for each value in the specified array(s), assigning the array value to the specified key(s).
 If parallel=true, then the actions will be processed concurrently with each iteration receiving a separate snapshot of the session.

 Example:
- foreach:
    key: [ item, ad ]
    in: [ items, ads ]
    parallel: true
    actions:
      - goto: token_exchange

 */

var _ = require("lodash");
var verror = require("verror");
var Promise = require("bluebird");

module.exports.normalize = function (action, normalize_action) {
	if (_.has(action, "foreach")) {
		// Normalize nested actions
		action.foreach.key = _.castArray(action.foreach.key);
		action.foreach.in = _.castArray(action.foreach.in);
		action.foreach.actions = normalize_action(action.foreach.actions, "foreach.actions");
		return action;
	}
};

module.exports.process = function (context) {
	var foreachPath = this.path + ".foreach";
	var matrix = prepareMatrix(this.foreach.in, this.foreach.key, context, foreachPath);

	// Select serial or parallel mapper
	var mappingFunction = getMappingFunctionForConcurrency(this.foreach.parallel, foreachPath + ".parallel");
	return mappingFunction(matrix, tuple => {
		applySharedProperties(tuple, this.foreach.shared, context, foreachPath + ".shared");
		exports.logger.debug("Forking with " + JSON.stringify(tuple));
		var new_context;
		if(this.foreach.isolated == false) {
			new_context = context;
			_.assign(new_context.session, tuple);
		}
		else {
			new_context = context.clone(tuple);
		}
		return new_context.processAction(this.foreach.actions)
			.catch(context.ProcessingDirective, new_context.handleDirective);
	});
};

module.exports.tuplify = tuplify;
module.exports.transpose = transpose;
module.exports.interpolateAndValidate = interpolateAndValidate;
module.exports.prepareMatrix = prepareMatrix;
module.exports.getMappingFunctionForConcurrency = getMappingFunctionForConcurrency;
module.exports.applySharedProperties = applySharedProperties;

function applySharedProperties(tuple, shared, context, path) {
	if (_.isArray(shared)) {
		shared.forEach((item, index) => {
			if (!_.isString(item)) {
				throw new verror.VError({
					name: "InvalidSharedPropertiesValue",
					info: {
						path: `${path}.${index}`,
						value: item
					}
				}, "If .foreach.shared is an array, it must be an array of string property keys");
			}
		});
	}
	if (_.isString(shared) || _.isArray(shared)) {
		_.castArray(shared).forEach(item => {
			_.set(tuple, item, context.getSessionValue(item));
		});
	}
	else {
		_.assign(tuple, shared);
	}
}

function getMappingFunctionForConcurrency(parallel_option, path) {
	if (parallel_option === false || _.isNil(parallel_option)) {
		return Promise.mapSeries;
	}
	else if (parallel_option === true) {
		return _.partialRight(Promise.map, { concurrency: Infinity });
	}
	else {
		if (!_.isNumber(parallel_option)) {
			throw new verror.VError({
				name: "InvalidParallelOption",
				info: {
					path
				}
			}, "If specified, foreach.parallel must be either a boolean value or an integer.");
		}
		else {
			return _.partialRight(Promise.map, { concurrency: parallel_option });
		}
	}
}

function prepareMatrix(arrays, keys, context, path) {
	return tuplify(transpose(interpolateAndValidate(arrays, context, path)), keys);
}

function interpolateAndValidate(arrays, context, path) {
	// Each of the array targets, is either an array, or a reference to an array
	// So normalize and interpolate any key references, then validate
	return arrays.map((vector, index) => {
		if (_.isString(vector)) {
			vector = context.getSessionValue(vector, path + ".in");
		}
		if (!_.isArray(vector)) {
			throw new verror.VError({
				name: "InvalidForeachTarget",
				info: {
					path: `${path}.in[${index}]`
				}
			}, "foreach .in elements must be an array or reference to an array");
		}
		return vector;
	});
}

function transpose(matrix) {
	/* Transpose switches a multi-dimensional array AxB -> BxA.

	   [ 'a', 'b', 'c' ]         [ 'a', 1 ]
	   [  1 ,  2 ,  3  ]   =>    [ 'b', 2 ]
								 [ 'c', 3 ]
	*/
	return _.zip.apply(null, matrix);
}

function tuplify(matrix, keys) {
	return matrix.map(_.partial(_.zipObject, keys));
}
<|endoftext|>


<commit_before>const { pipe, prop, sortBy, propSatisfies, filter } = require('ramda')
const { startsWith, dotPath } = require('ramdasauce')
const Shell = require('shelljs')
const exitCodes = require('../../../lib/exitCodes')

/**
 * The current executing ignite plugin path.
 */
let pluginPath = null

/**
 * Set the current executing ignite plugin path.
 */
function setIgnitePluginPath (path) { pluginPath = path }

/**
 * Gets the path to the current running ignite plugin.
 */
function ignitePluginPath () { return pluginPath }

/**
 * Adds ignite goodies
 *
 * @return {Function} A function to attach to the context.
 */
function attach (plugin, command, context) {
  const { template, config, runtime, system, parameters, print, filesystem } = context
  const { error, warning } = print

  if (command.name === 'new' || (command.name === 'add' && parameters.rawCommand.includes('ignite-basic-structure'))) {
    if (filesystem.exists(`${process.cwd()}/ignite`) === 'dir') {
      error(`This is already an Ignite project root directory.`)
      process.exit(exitCodes.GENERIC)
    }
  } else {
    if (filesystem.exists(`${process.cwd()}/ignite`) !== 'dir') {
      error(`💩 This is not an Ignite project root directory!`)
      process.exit(exitCodes.GENERIC)
    }
  }

  // determine which package manager to use
  const forceNpm = parameters.options.npm

  // you know what?  just turn off yarn for now.
  const useYarn = false && !forceNpm && Shell.which('yarn')

  /**
   * Finds the gluegun plugins that are also ignite plugins.
   *
   * @returns {Plugin[]} - an array of ignite plugins
   */
  function findIgnitePlugins () {
    return pipe(
      filter(propSatisfies(startsWith('ignite-'), 'name')),
      sortBy(prop('name'))
    )(runtime.plugins)
  }

  function getToml () {
    const oldToml = `${process.cwd()}/ignite.toml`
    const globalToml = `${process.cwd()}/ignite/ignite.toml`

    if (filesystem.exists(globalToml)) {
      return globalToml
    } else if (filesystem.exists(oldToml)) {
      return oldToml
    } else {
      error('💩 No `ignite.toml` file found are you sure it is an Ignite project?')
      process.exit(exitCodes.GENERIC)
    }
  }

  /**
   * Adds a npm-based module to the project.
   *
   * @param {string}  moduleName - The module name as found on npm.
   * @param {Object}  options - Various installing flags.
   * @param {boolean} options.link - Should we run `react-native link`?
   * @param {boolean} options.dev - Should we install as a dev-dependency?
   */
  async function addModule (moduleName, options = {}) {
    const depType = options.dev ? 'as dev dependency' : ''
    print.info(` ⌙⚙  installing ${print.colors.cyan(moduleName)} ${depType}`)

    // install the module
    if (useYarn) {
      const addSwitch = options.dev ? '--dev' : ''
      await system.run(`yarn add ${moduleName} ${addSwitch}`)
    } else {
      const installSwitch = options.dev ? '--save-dev' : '--save'
      await system.run(`npm i ${moduleName} ${installSwitch}`)
    }

    // should we react-native link?
    if (options.link) {
      try {
        print.info(` ⌙⚙  linking`)

        await system.run(`react-native link ${moduleName}`)
      } catch (err) {
        throw new Error(`Error running: react-native link ${moduleName}.\n${err.stderr}`)
      }
    }
  }

  /**
   * Removes a npm-based module from the project.
   *
   * @param {string}  moduleName - The module name to remove.
   * @param {Object}  options - Various uninstalling flags.
   * @param {boolean} options.unlink - Should we unlink?
   * @param {boolean} options.dev - is this a dev dependency?
   */
  async function removeModule (moduleName, options = {}) {
    print.info(` ⌙⚙  uninstalling ${moduleName}`)

    // unlink
    if (options.unlink) {
      print.info(` ⌙⚙  unlinking`)
      await system.run(`react-native unlink ${moduleName}`)
    }

    print.info(` ⌙⚙  removing`)
    // uninstall
    if (useYarn) {
      const addSwitch = options.dev ? '--dev' : ''
      await system.run(`yarn remove ${moduleName} ${addSwitch}`)
    } else {
      const uninstallSwitch = options.dev ? '--save-dev' : '--save'
      await system.run(`npm rm ${moduleName} ${uninstallSwitch}`)
    }
  }

  async function copyBatch (context, jobs, props) {
    // grab some features
    const { config, template, prompt, filesystem, print } = context
    const { generate } = template
    const { confirm } = prompt

    // read some configuration
    const { askToOverwrite } = config.ignite || false

    // If the file exists
    const shouldGenerate = async (target) => {
      if (!askToOverwrite) return true
      if (!filesystem.exists(target)) return true
      return await confirm(`overwrite ${target}`)
    }

    // old school loop because of async stuff
    for (let index = 0; index < jobs.length; index++) {
      // grab the current job
      const job = jobs[index]
      // safety check
      if (!job) continue

      // generate the React component
      if (await shouldGenerate(job.target)) {
        const currentPluginPath = ignitePluginPath()
        await generate({
          directory: currentPluginPath && `${currentPluginPath}/templates`,
          template: job.template,
          target: job.target,
          props
        })
        print.info(`    ${print.checkmark}  ${job.target}`)
      }
    }
  }

  /**
   * Generates an example for use with the dev screens.
   *
   * @param {string} fileName - The js file to create. (.ejs will be appended to pick up the template.)
   * @param {Object} props - The properties to use for template expansion.
   */
  async function addComponentExample (fileName, props = {}) {
    const { filesystem, patching } = context

    // do we want to use examples in the classic format?
    if (dotPath('ignite.examples', config) === 'classic') {
      print.info(` ⌙⚙  adding component example`)

      // generate the file
      const templatePath = ignitePluginPath() ? `${ignitePluginPath()}/templates` : `templates`
      template.generate({
        directory: templatePath,
        template: `${fileName}.ejs`,
        target: `ignite/Examples/Components/${fileName}`,
        props
      })

      // adds reference to usage example screen (if it exists)
      const destinationPath = `${process.cwd()}/ignite/DevScreens/PluginExamplesScreen.js`
      if (filesystem.exists(destinationPath)) {
        patching.insertInFile(destinationPath, 'import ExamplesRegistry', `import '../../Examples/Components/${fileName}`)
      }
    }
  }

  /**
   * Removes the component example.
   */
  function removeComponentExample (fileName) {
    const { filesystem, patching, print } = context
    print.info(` ⌙⚙  removing component example`)
    // remove file from Components/Examples folder
    filesystem.remove(`${process.cwd()}/ignite/Examples/Components/${fileName}`)
    // remove reference in usage example screen (if it exists)
    const destinationPath = `${process.cwd()}/ignite/DevScreens/PluginExamplesScreen.js`
    if (filesystem.exists(destinationPath)) {
      patching.replaceInFile(destinationPath, `import '../../Examples/Components/${fileName}`, '')
    }
  }

  /**
   * Sets Global Config setting
   *
   * @param {string}  key             Key of setting to be defined
   * @param {string}  value           Value to be set
   * @param {bool}    isVariableName  Optional flag to set value as variable name instead of string
   */
  function setGlobalConfig (key, value, isVariableName = false) {
    const { patching } = context
    const globalToml = getToml()

    if (patching.isInFile(globalToml, key)) {
      if (isVariableName) {
        patching.replaceInFile(globalToml, key, `${key} = ${value}`)
      } else {
        patching.replaceInFile(globalToml, key, `${key} = '${value}'`)
      }
    } else {
      // Toml destroyed [ignite] again :rage5:
      if (!patching.isInFile(globalToml, '\\[ignite\\]')) {
        patching.prependToFile(globalToml, '[ignite]\n\n')
      }

      if (isVariableName) {
        patching.insertInFile(globalToml, '\\[ignite\\]', `${key} = ${value}`)
      } else {
        patching.insertInFile(globalToml, '\\[ignite\\]', `${key} = '${value}'`)
      }
    }
  }

  /**
   * Remove Global Config setting
   *
   * @param {string}  key Key of setting to be removed
   */
  function removeGlobalConfig (key) {
    const { patching } = context
    const globalToml = getToml()

    if (patching.isInFile(globalToml, key)) {
      patching.replaceInFile(globalToml, key, '')
    } else {
      warning(`Global Config '${key}' not found.`)
    }
  }

  /**
   * Sets Debug Config setting
   *
   * @param {string}  key             Key of setting to be defined
   * @param {string}  value           Value to be set
   * @param {bool}    isVariableName  Optional flag to set value as variable name instead of string
   */
  function setDebugConfig (key, value, isVariableName = false) {
    const { patching } = context
    const debugConfig = `${process.cwd()}/App/Config/DebugConfig.js`

    if (!filesystem.exists(debugConfig)) {
      error('No `App/Config/DebugConfig.js` file found in this folder, are you sure it is an Ignite project?')
      process.exit(exitCodes.GENERIC)
    }

    if (patching.isInFile(debugConfig, key)) {
      if (isVariableName) {
        patching.replaceInFile(debugConfig, key, `  ${key}: ${value},`)
      } else {
        patching.replaceInFile(debugConfig, key, `  ${key}: '${value},'`)
      }
    } else {
      if (isVariableName) {
        patching.insertInFile(debugConfig, 'export default {', `  ${key}: ${value},`)
      } else {
        patching.insertInFile(debugConfig, 'export default {', `  ${key}: '${value}',`)
      }
    }
  }

  /**
   * Remove Debug Config setting
   *
   * @param {string}  key Key of setting to be removed
   */
  function removeDebugConfig (key) {
    const { patching } = context
    const debugConfig = `${process.cwd()}/App/Config/DebugConfig.js`

    if (!filesystem.exists(debugConfig)) {
      error('💩 No `App/Config/DebugConfig.js` file found in this folder, are you sure it is an ignite project?')
      process.exit(exitCodes.generic)
    }

    if (patching.isInFile(debugConfig, key)) {
      patching.replaceInFile(debugConfig, key, '')
    } else {
      warning(`Debug Setting ${key} not found.`)
    }
  }

  /**
   * Conditionally inserts a string into a file before or after another string.
   * TODO: Move to infinitered/gluegun eventually? Plugin or core?
   *
   * @param {string}  file            File to be patched
   * @param {Object}  opts            Options
   * @param {string}  opts.before     Insert before this string
   * @param {string}  opts.after      Insert after this string
   * @param {string}  opts.insert     String to be inserted
   * @param {string}  opts.match      Skip if this string exists already
   *
   * @example
   *   patchInFile('thing.js', { before: 'bar', insert: 'foo' })
   *
   */
  function patchInFile (file, opts) {
    const { patching } = context
    if (!patching.isInFile(file, opts.match || opts.insert)) {
      patching.insertInFile(file, opts.before || opts.after, opts.insert, !!opts.after)
    }
  }

  // send back the extension
  return {
    ignitePluginPath,
    setIgnitePluginPath,
    useYarn,
    findIgnitePlugins,
    addModule,
    removeModule,
    copyBatch,
    addComponentExample,
    removeComponentExample,
    setGlobalConfig,
    removeGlobalConfig,
    setDebugConfig,
    removeDebugConfig,
    patchInFile
  }
}

module.exports = attach
<commit_msg>fix no-return hang<commit_after>const { pipe, prop, sortBy, propSatisfies, filter } = require('ramda')
const { startsWith, dotPath } = require('ramdasauce')
const Shell = require('shelljs')
const exitCodes = require('../../../lib/exitCodes')

/**
 * The current executing ignite plugin path.
 */
let pluginPath = null

/**
 * Set the current executing ignite plugin path.
 */
function setIgnitePluginPath (path) { pluginPath = path }

/**
 * Gets the path to the current running ignite plugin.
 */
function ignitePluginPath () { return pluginPath }

/**
 * Adds ignite goodies
 *
 * @return {Function} A function to attach to the context.
 */
function attach (plugin, command, context) {
  const { template, config, runtime, system, parameters, print, filesystem } = context
  const { error, warning } = print

  if (command.name === 'new' || (command.name === 'add' && parameters.rawCommand.includes('ignite-basic-structure'))) {
    if (filesystem.exists(`${process.cwd()}/ignite`) === 'dir') {
      error(`This is already an Ignite project root directory.`)
      process.exit(exitCodes.GENERIC)
    }
  } else {
    if (filesystem.exists(`${process.cwd()}/ignite`) !== 'dir') {
      error(`💩 This is not an Ignite project root directory!`)
      process.exit(exitCodes.GENERIC)
    }
  }

  // determine which package manager to use
  const forceNpm = parameters.options.npm

  // you know what?  just turn off yarn for now.
  const useYarn = false && !forceNpm && Shell.which('yarn')

  /**
   * Finds the gluegun plugins that are also ignite plugins.
   *
   * @returns {Plugin[]} - an array of ignite plugins
   */
  function findIgnitePlugins () {
    return pipe(
      filter(propSatisfies(startsWith('ignite-'), 'name')),
      sortBy(prop('name'))
    )(runtime.plugins)
  }

  function getToml () {
    const oldToml = `${process.cwd()}/ignite.toml`
    const globalToml = `${process.cwd()}/ignite/ignite.toml`

    if (filesystem.exists(globalToml)) {
      return globalToml
    } else if (filesystem.exists(oldToml)) {
      return oldToml
    } else {
      error('💩 No `ignite.toml` file found are you sure it is an Ignite project?')
      process.exit(exitCodes.GENERIC)
    }
  }

  /**
   * Adds a npm-based module to the project.
   *
   * @param {string}  moduleName - The module name as found on npm.
   * @param {Object}  options - Various installing flags.
   * @param {boolean} options.link - Should we run `react-native link`?
   * @param {boolean} options.dev - Should we install as a dev-dependency?
   */
  async function addModule (moduleName, options = {}) {
    const depType = options.dev ? 'as dev dependency' : ''
    print.info(` ⌙⚙  installing ${print.colors.cyan(moduleName)} ${depType}`)

    // install the module
    if (useYarn) {
      const addSwitch = options.dev ? '--dev' : ''
      await system.run(`yarn add ${moduleName} ${addSwitch}`)
    } else {
      const installSwitch = options.dev ? '--save-dev' : '--save'
      await system.run(`npm i ${moduleName} ${installSwitch}`)
    }

    // should we react-native link?
    if (options.link) {
      try {
        print.info(` ⌙⚙  linking`)

        system.run(`react-native link ${moduleName} &`)
      } catch (err) {
        throw new Error(`Error running: react-native link ${moduleName}.\n${err.stderr}`)
      }
    }
  }

  /**
   * Removes a npm-based module from the project.
   *
   * @param {string}  moduleName - The module name to remove.
   * @param {Object}  options - Various uninstalling flags.
   * @param {boolean} options.unlink - Should we unlink?
   * @param {boolean} options.dev - is this a dev dependency?
   */
  async function removeModule (moduleName, options = {}) {
    print.info(` ⌙⚙  uninstalling ${moduleName}`)

    // unlink
    if (options.unlink) {
      print.info(` ⌙⚙  unlinking`)
      await system.run(`react-native unlink ${moduleName}`)
    }

    print.info(` ⌙⚙  removing`)
    // uninstall
    if (useYarn) {
      const addSwitch = options.dev ? '--dev' : ''
      await system.run(`yarn remove ${moduleName} ${addSwitch}`)
    } else {
      const uninstallSwitch = options.dev ? '--save-dev' : '--save'
      await system.run(`npm rm ${moduleName} ${uninstallSwitch}`)
    }
  }

  async function copyBatch (context, jobs, props) {
    // grab some features
    const { config, template, prompt, filesystem, print } = context
    const { generate } = template
    const { confirm } = prompt

    // read some configuration
    const { askToOverwrite } = config.ignite || false

    // If the file exists
    const shouldGenerate = async (target) => {
      if (!askToOverwrite) return true
      if (!filesystem.exists(target)) return true
      return await confirm(`overwrite ${target}`)
    }

    // old school loop because of async stuff
    for (let index = 0; index < jobs.length; index++) {
      // grab the current job
      const job = jobs[index]
      // safety check
      if (!job) continue

      // generate the React component
      if (await shouldGenerate(job.target)) {
        const currentPluginPath = ignitePluginPath()
        await generate({
          directory: currentPluginPath && `${currentPluginPath}/templates`,
          template: job.template,
          target: job.target,
          props
        })
        print.info(`    ${print.checkmark}  ${job.target}`)
      }
    }
  }

  /**
   * Generates an example for use with the dev screens.
   *
   * @param {string} fileName - The js file to create. (.ejs will be appended to pick up the template.)
   * @param {Object} props - The properties to use for template expansion.
   */
  async function addComponentExample (fileName, props = {}) {
    const { filesystem, patching } = context

    // do we want to use examples in the classic format?
    if (dotPath('ignite.examples', config) === 'classic') {
      print.info(` ⌙⚙  adding component example`)

      // generate the file
      const templatePath = ignitePluginPath() ? `${ignitePluginPath()}/templates` : `templates`
      template.generate({
        directory: templatePath,
        template: `${fileName}.ejs`,
        target: `ignite/Examples/Components/${fileName}`,
        props
      })

      // adds reference to usage example screen (if it exists)
      const destinationPath = `${process.cwd()}/ignite/DevScreens/PluginExamplesScreen.js`
      if (filesystem.exists(destinationPath)) {
        patching.insertInFile(destinationPath, 'import ExamplesRegistry', `import '../../Examples/Components/${fileName}`)
      }
    }
  }

  /**
   * Removes the component example.
   */
  function removeComponentExample (fileName) {
    const { filesystem, patching, print } = context
    print.info(` ⌙⚙  removing component example`)
    // remove file from Components/Examples folder
    filesystem.remove(`${process.cwd()}/ignite/Examples/Components/${fileName}`)
    // remove reference in usage example screen (if it exists)
    const destinationPath = `${process.cwd()}/ignite/DevScreens/PluginExamplesScreen.js`
    if (filesystem.exists(destinationPath)) {
      patching.replaceInFile(destinationPath, `import '../../Examples/Components/${fileName}`, '')
    }
  }

  /**
   * Sets Global Config setting
   *
   * @param {string}  key             Key of setting to be defined
   * @param {string}  value           Value to be set
   * @param {bool}    isVariableName  Optional flag to set value as variable name instead of string
   */
  function setGlobalConfig (key, value, isVariableName = false) {
    const { patching } = context
    const globalToml = getToml()

    if (patching.isInFile(globalToml, key)) {
      if (isVariableName) {
        patching.replaceInFile(globalToml, key, `${key} = ${value}`)
      } else {
        patching.replaceInFile(globalToml, key, `${key} = '${value}'`)
      }
    } else {
      // Toml destroyed [ignite] again :rage5:
      if (!patching.isInFile(globalToml, '\\[ignite\\]')) {
        patching.prependToFile(globalToml, '[ignite]\n\n')
      }

      if (isVariableName) {
        patching.insertInFile(globalToml, '\\[ignite\\]', `${key} = ${value}`)
      } else {
        patching.insertInFile(globalToml, '\\[ignite\\]', `${key} = '${value}'`)
      }
    }
  }

  /**
   * Remove Global Config setting
   *
   * @param {string}  key Key of setting to be removed
   */
  function removeGlobalConfig (key) {
    const { patching } = context
    const globalToml = getToml()

    if (patching.isInFile(globalToml, key)) {
      patching.replaceInFile(globalToml, key, '')
    } else {
      warning(`Global Config '${key}' not found.`)
    }
  }

  /**
   * Sets Debug Config setting
   *
   * @param {string}  key             Key of setting to be defined
   * @param {string}  value           Value to be set
   * @param {bool}    isVariableName  Optional flag to set value as variable name instead of string
   */
  function setDebugConfig (key, value, isVariableName = false) {
    const { patching } = context
    const debugConfig = `${process.cwd()}/App/Config/DebugConfig.js`

    if (!filesystem.exists(debugConfig)) {
      error('No `App/Config/DebugConfig.js` file found in this folder, are you sure it is an Ignite project?')
      process.exit(exitCodes.GENERIC)
    }

    if (patching.isInFile(debugConfig, key)) {
      if (isVariableName) {
        patching.replaceInFile(debugConfig, key, `  ${key}: ${value},`)
      } else {
        patching.replaceInFile(debugConfig, key, `  ${key}: '${value},'`)
      }
    } else {
      if (isVariableName) {
        patching.insertInFile(debugConfig, 'export default {', `  ${key}: ${value},`)
      } else {
        patching.insertInFile(debugConfig, 'export default {', `  ${key}: '${value}',`)
      }
    }
  }

  /**
   * Remove Debug Config setting
   *
   * @param {string}  key Key of setting to be removed
   */
  function removeDebugConfig (key) {
    const { patching } = context
    const debugConfig = `${process.cwd()}/App/Config/DebugConfig.js`

    if (!filesystem.exists(debugConfig)) {
      error('💩 No `App/Config/DebugConfig.js` file found in this folder, are you sure it is an ignite project?')
      process.exit(exitCodes.generic)
    }

    if (patching.isInFile(debugConfig, key)) {
      patching.replaceInFile(debugConfig, key, '')
    } else {
      warning(`Debug Setting ${key} not found.`)
    }
  }

  /**
   * Conditionally inserts a string into a file before or after another string.
   * TODO: Move to infinitered/gluegun eventually? Plugin or core?
   *
   * @param {string}  file            File to be patched
   * @param {Object}  opts            Options
   * @param {string}  opts.before     Insert before this string
   * @param {string}  opts.after      Insert after this string
   * @param {string}  opts.insert     String to be inserted
   * @param {string}  opts.match      Skip if this string exists already
   *
   * @example
   *   patchInFile('thing.js', { before: 'bar', insert: 'foo' })
   *
   */
  function patchInFile (file, opts) {
    const { patching } = context
    if (!patching.isInFile(file, opts.match || opts.insert)) {
      patching.insertInFile(file, opts.before || opts.after, opts.insert, !!opts.after)
    }
  }

  // send back the extension
  return {
    ignitePluginPath,
    setIgnitePluginPath,
    useYarn,
    findIgnitePlugins,
    addModule,
    removeModule,
    copyBatch,
    addComponentExample,
    removeComponentExample,
    setGlobalConfig,
    removeGlobalConfig,
    setDebugConfig,
    removeDebugConfig,
    patchInFile
  }
}

module.exports = attach
<|endoftext|>


<commit_before>const path = require('path');
const fs = require('fs');
const HTMLHint = require('htmlhint').HTMLHint;

// add base rules
function getConfig() {
  const configPath = path.join(__dirname, '..', '.htmlhintrc.js');
  if (fs.existsSync(configPath)) {
    const config = require(configPath);
    return config;
  }
}
const ruleset = getConfig();

// add custom rules
require('../lib/htmlhint_custom');

//rule is currently disabled
/*
describe('--attr-value-single-quotes', function() {
  describe('input is valid', function() {
    it('should have no errors', function (done) {
      const input = '';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
      done();
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --attr-value-single-quotes', function (done) {
      const input = '';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1 &&
      expectRuleName(output, '--attr-value-single-quotes');
      done();
    });
  });
});
*/

function expectRuleName(output, ruleName) {
  expect(output[0].rule.id).toEqual(ruleName);
}

describe('--no-self-close-common-html5-tags', function() {
  describe('input is valid', function() {
    it('should have no errors', function() {
      const input = '

';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --no-self-close-common-html5-tags', function() {
      const input = '
';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--no-self-close-common-html5-tags');
    });
  });
});

describe('--vue-component-conventions', function() {
  describe('input is valid', function() {
    it('should have no errors', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
    });
  });
  describe('input is invalid', function() {
    it('should have one error', function() {
      const input =
        "<template>\n\n  html\n\n</template>\n\n\n\n\n\n";
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\nhtml\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '\n<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is valid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('defer unpaired tags', function() {
    it('should not check for, or fail on, unpaired tags', function() {
      const input =
        '<template>\n\n
\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, 'tag-pair');
    });
  });
});
<commit_msg>Remove obsolete self-closing elements specs<commit_after>const path = require('path');
const fs = require('fs');
const HTMLHint = require('htmlhint').HTMLHint;

// add base rules
function getConfig() {
  const configPath = path.join(__dirname, '..', '.htmlhintrc.js');
  if (fs.existsSync(configPath)) {
    const config = require(configPath);
    return config;
  }
}
const ruleset = getConfig();

// add custom rules
require('../lib/htmlhint_custom');

//rule is currently disabled
/*
describe('--attr-value-single-quotes', function() {
  describe('input is valid', function() {
    it('should have no errors', function (done) {
      const input = '';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
      done();
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --attr-value-single-quotes', function (done) {
      const input = '';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1 &&
      expectRuleName(output, '--attr-value-single-quotes');
      done();
    });
  });
});
*/

function expectRuleName(output, ruleName) {
  expect(output[0].rule.id).toEqual(ruleName);
}

describe('--vue-component-conventions', function() {
  describe('input is valid', function() {
    it('should have no errors', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
    });
  });
  describe('input is invalid', function() {
    it('should have one error', function() {
      const input =
        "<template>\n\n  html\n\n</template>\n\n\n\n\n\n";
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\nhtml\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '\n<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is valid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(0);
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n  html\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('input is invalid', function() {
    it('should have one error with rule id: --vue-component-conventions', function() {
      const input =
        '<template>\n\n\n  html\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, '--vue-component-conventions');
    });
  });
  describe('defer unpaired tags', function() {
    it('should not check for, or fail on, unpaired tags', function() {
      const input =
        '<template>\n\n
\n\n</template>\n\n\n\n\n\n';
      const output = HTMLHint.verify(input, ruleset);
      expect(output).toHaveLength(1);
      expectRuleName(output, 'tag-pair');
    });
  });
});
<|endoftext|>


<commit_before>import moment from 'moment';

RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
	// skips this callback if the message was edited
	if (message.editedAt) {
		return message;
	}

	if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) {
		return message;
	}

	const getMessageLink = (room, sub) => {
		const roomPath = RocketChat.roomTypes.getRouteLink(room.t, sub);
		const path = Meteor.absoluteUrl(roomPath ? roomPath.replace(/^\//, '') : '');
		const style = [
			'color: #fff;',
			'padding: 9px 12px;',
			'border-radius: 4px;',
			'background-color: #04436a;',
			'text-decoration: none;'
		].join(' ');
		const message = TAPi18n.__('Offline_Link_Message');
		return `<p style="text-align:center;margin-bottom:8px;"><a style="${ style }" href="${ path }">${ message }</a>`;
	};

	const divisorMessage = '
';
	let messageHTML = s.escapeHTML(message.msg);

	message = RocketChat.callbacks.run('renderMessage', message);
	if (message.tokens && message.tokens.length > 0) {
		message.tokens.forEach((token) => {
			token.text = token.text.replace(/([^\$])(\$[^\$])/gm, '$1$$$2');
			messageHTML = messageHTML.replace(token.token, token.text);
		});
	}

	const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
	let footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
	messageHTML = messageHTML.replace(/\n/gm, '
');

	const usersToSendEmail = {};
	if (room.t === 'd') {
		usersToSendEmail[message.rid.replace(message.u._id, '')] = 'direct';
	} else {
		RocketChat.models.Subscriptions.findWithSendEmailByRoomId(room._id).forEach((sub) => {
			if (sub.disableNotifications) {
				return delete usersToSendEmail[sub.u._id];
			}

			const emailNotifications = sub.emailNotifications;

			if (emailNotifications !== 'nothing') {
				const mentionedUser = message.mentions.find((mention) => {
					return mention._id === sub.u._id;
				});

				if (emailNotifications === 'mentions' || mentionedUser) {
					return usersToSendEmail[sub.u._id] = 'mention';
				}

				if (emailNotifications === 'all') {
					return usersToSendEmail[sub.u._id] = 'all';
				}
			}
			delete usersToSendEmail[sub.u._id];
		});
	}
	const userIdsToSendEmail = Object.keys(usersToSendEmail);

	let defaultLink;

	const linkByUser = {};
	if (RocketChat.roomTypes.hasCustomLink(room.t)) {
		RocketChat.models.Subscriptions.findByRoomIdAndUserIds(room._id, userIdsToSendEmail).forEach((sub) => {
			linkByUser[sub.u._id] = getMessageLink(room, sub);
		});
	} else {
		defaultLink = getMessageLink(room, {
			name: room.name
		});
	}

	if (userIdsToSendEmail.length > 0) {
		const usersOfMention = RocketChat.models.Users.getUsersToSendOfflineEmail(userIdsToSendEmail).fetch();

		if (usersOfMention && usersOfMention.length > 0) {
			usersOfMention.forEach((user) => {
				if (user.settings && user.settings.preferences && user.settings.preferences.emailNotificationMode && user.settings.preferences.emailNotificationMode === 'disabled' && usersToSendEmail[user._id] !== 'force') {
					return;
				}

				// Checks if user is in the room he/she is mentioned (unless it's public channel)
				if (room.t !== 'c' && room.usernames.indexOf(user.username) === -1) {
					return;
				}

				// Footer in case direct reply is enabled.
				if (RocketChat.settings.get('Direct_Reply_Enable')) {
					footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || '');
				}

				let emailSubject;
				switch (usersToSendEmail[user._id]) {
					case 'all':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_All_Email'), {
							user: message.u.username,
							room: room.name || room.label
						});
						break;
					case 'direct':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_DM_Email'), {
							user: message.u.username,
							room: room.name
						});
						break;
					case 'mention':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_Email'), {
							user: message.u.username,
							room: room.name
						});
						break;
				}
				user.emails.some((email) => {
					if (email.verified) {
						email = {
							to: email.address,
							from: RocketChat.settings.get('From_Email'),
							subject: emailSubject,
							html: header + messageHTML + divisorMessage + (linkByUser[user._id] || defaultLink) + footer
						};
						// If direct reply enabled, email content with headers
						if (RocketChat.settings.get('Direct_Reply_Enable')) {
							email.headers = {
								// Reply-To header with format "username+messageId@domain"
								'Reply-To': `${ RocketChat.settings.get('Direct_Reply_Username').split('@')[0].split(RocketChat.settings.get('Direct_Reply_Separator'))[0] }${ RocketChat.settings.get('Direct_Reply_Separator') }${ message._id }@${ RocketChat.settings.get('Direct_Reply_Username').split('@')[1] }`
							};
						}

						Meteor.defer(() => {
							Email.send(email);
						});

						return true;
					}
				});
			});
		}
	}

	return message;

}, RocketChat.callbacks.priority.LOW, 'sendEmailOnMessage');
<commit_msg>Use real names for user and room in emails<commit_after>import moment from 'moment';

RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
	// skips this callback if the message was edited
	if (message.editedAt) {
		return message;
	}

	if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) {
		return message;
	}

	const getMessageLink = (room, sub) => {
		const roomPath = RocketChat.roomTypes.getRouteLink(room.t, sub);
		const path = Meteor.absoluteUrl(roomPath ? roomPath.replace(/^\//, '') : '');
		const style = [
			'color: #fff;',
			'padding: 9px 12px;',
			'border-radius: 4px;',
			'background-color: #04436a;',
			'text-decoration: none;'
		].join(' ');
		const message = TAPi18n.__('Offline_Link_Message');
		return `<p style="text-align:center;margin-bottom:8px;"><a style="${ style }" href="${ path }">${ message }</a>`;
	};

	const divisorMessage = '
';
	let messageHTML = s.escapeHTML(message.msg);

	message = RocketChat.callbacks.run('renderMessage', message);
	if (message.tokens && message.tokens.length > 0) {
		message.tokens.forEach((token) => {
			token.text = token.text.replace(/([^\$])(\$[^\$])/gm, '$1$$$2');
			messageHTML = messageHTML.replace(token.token, token.text);
		});
	}

	const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
	let footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
	messageHTML = messageHTML.replace(/\n/gm, '
');

	const usersToSendEmail = {};
	if (room.t === 'd') {
		usersToSendEmail[message.rid.replace(message.u._id, '')] = 'direct';
	} else {
		RocketChat.models.Subscriptions.findWithSendEmailByRoomId(room._id).forEach((sub) => {
			if (sub.disableNotifications) {
				return delete usersToSendEmail[sub.u._id];
			}

			const emailNotifications = sub.emailNotifications;

			if (emailNotifications !== 'nothing') {
				const mentionedUser = message.mentions.find((mention) => {
					return mention._id === sub.u._id;
				});

				if (emailNotifications === 'mentions' || mentionedUser) {
					return usersToSendEmail[sub.u._id] = 'mention';
				}

				if (emailNotifications === 'all') {
					return usersToSendEmail[sub.u._id] = 'all';
				}
			}
			delete usersToSendEmail[sub.u._id];
		});
	}
	const userIdsToSendEmail = Object.keys(usersToSendEmail);

	let defaultLink;

	const linkByUser = {};
	if (RocketChat.roomTypes.hasCustomLink(room.t)) {
		RocketChat.models.Subscriptions.findByRoomIdAndUserIds(room._id, userIdsToSendEmail).forEach((sub) => {
			linkByUser[sub.u._id] = getMessageLink(room, sub);
		});
	} else {
		defaultLink = getMessageLink(room, {
			name: room.name
		});
	}

	if (userIdsToSendEmail.length > 0) {
		const usersOfMention = RocketChat.models.Users.getUsersToSendOfflineEmail(userIdsToSendEmail).fetch();

		if (usersOfMention && usersOfMention.length > 0) {
			usersOfMention.forEach((user) => {
				if (user.settings && user.settings.preferences && user.settings.preferences.emailNotificationMode && user.settings.preferences.emailNotificationMode === 'disabled' && usersToSendEmail[user._id] !== 'force') {
					return;
				}

				// Checks if user is in the room he/she is mentioned (unless it's public channel)
				if (room.t !== 'c' && room.usernames.indexOf(user.username) === -1) {
					return;
				}

				// Footer in case direct reply is enabled.
				if (RocketChat.settings.get('Direct_Reply_Enable')) {
					footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || '');
				}

				let emailSubject;
				const username = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name : message.u.username;
				const roomName = RocketChat.settings.get('UI_Allow_room_names_with_special_chars') ? room.fname : room.name;
				switch (usersToSendEmail[user._id]) {
					case 'all':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_All_Email'), {
							user: username,
							room: roomName || room.label
						});
						break;
					case 'direct':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_DM_Email'), {
							user: username,
							room: roomName
						});
						break;
					case 'mention':
						emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_Email'), {
							user: username,
							room: roomName
						});
						break;
				}
				user.emails.some((email) => {
					if (email.verified) {
						email = {
							to: email.address,
							from: RocketChat.settings.get('From_Email'),
							subject: emailSubject,
							html: header + messageHTML + divisorMessage + (linkByUser[user._id] || defaultLink) + footer
						};
						// If direct reply enabled, email content with headers
						if (RocketChat.settings.get('Direct_Reply_Enable')) {
							email.headers = {
								// Reply-To header with format "username+messageId@domain"
								'Reply-To': `${ RocketChat.settings.get('Direct_Reply_Username').split('@')[0].split(RocketChat.settings.get('Direct_Reply_Separator'))[0] }${ RocketChat.settings.get('Direct_Reply_Separator') }${ message._id }@${ RocketChat.settings.get('Direct_Reply_Username').split('@')[1] }`
							};
						}

						Meteor.defer(() => {
							Email.send(email);
						});

						return true;
					}
				});
			});
		}
	}

	return message;

}, RocketChat.callbacks.priority.LOW, 'sendEmailOnMessage');
<|endoftext|>


<commit_before>import Categories from "../collection.js";
import { Utils, newMutation } from 'meteor/vulcan:core';

// Load categories from settings, if there are any

if (Meteor.settings && Meteor.settings.categories) {
  Meteor.settings.categories.forEach(category => {

    // get slug (or slugified name)
    const slug = category.slug || Utils.slugify(category.name);

    // look for existing category with same slug
    let existingCategory = Categories.findOne({slug: slug});

    if (existingCategory) {
      // if category exists, update it with settings data except slug
      delete category.slug;
      Categories.update(existingCategory._id, {$set: category});
    } else {
      // if not, create it
      newMutation({
        collection: Categories,
        document: category,
        validate: false,
      });

      // Categories.insert(category);
      console.log(`// Creating category “${category.name}”`); // eslint-disable-line
    }
  });
}
<commit_msg>support predefining nested categories in settings.json<commit_after>import Categories from "../collection.js";
import { Utils, newMutation } from 'meteor/vulcan:core';

// Load categories from settings, if there are any

if (Meteor.settings && Meteor.settings.categories) {
  Meteor.settings.categories.forEach(category => {

    // get slug (or slugified name)
    const slug = category.slug || Utils.slugify(category.name);

    // look for existing category with same slug
    let existingCategory = Categories.findOne({slug: slug});

    // look for parent category
    if (category.parent) {
      const parentCategory = Categories.findOne({slug: category.parent});
      if (parentCategory) {
        category.parentId = parentCategory._id;
        delete category.parent;
      }
    }

    if (existingCategory) {
      // if category exists, update it with settings data except slug
      delete category.slug;
      Categories.update(existingCategory._id, {$set: category});
    } else {
      // if not, create it
      newMutation({
        collection: Categories,
        document: category,
        validate: false,
      });

      // Categories.insert(category);
      console.log(`// Creating category “${category.name}”`); // eslint-disable-line
    }
  });
}
<|endoftext|>


<commit_before>/* Internal helpers */
function AtomicCanvas() {
	// modeled after QUnit's "Keeping Tests Atomic" cookbook guidance:
	//    http://qunitjs.com/cookbook/#keeping_tests_atomic
	this.fixture = $("#qunit-fixture")
	this.fixture.append("<canvas id='atomic-canvas' width='250' height='250'></canvas>")
	this.canvas = document.getElementById('atomic-canvas')
}

/* jCanvas tests */
module("jCanvas Arcs")

test("drawArc, simplest possible", function() {
	var ac = new AtomicCanvas()
	drawArc_simplest("#atomic-canvas")
	QUnit.pixelEqual(ac.canvas, 50, 50, 0, 255, 0, 255)
})

module("jCanvas Layers")

test("addLayer, method drawRect", function () {
	var ac = new AtomicCanvas()
	addLayer_drawRect("#atomic-canvas")
	QUnit.pixelEqual(ac.canvas, 55, 55, 0, 255, 0, 255)
})

asyncTest("animateLayer, method drawLine", 1, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawLine("#atomic-canvas", 0,
		function () {
			QUnit.pixelEqual(ac.canvas, 200, 50, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, four opacity and position animations", 4, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawLinesInstantShiftVariedOpacity("#atomic-canvas",
		function () {
			QUnit.pixelEqual(ac.canvas, 200, 20, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 50, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 80, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 110, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, method drawRect", 1, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawRect("#atomic-canvas", 0,
		function () {
			QUnit.pixelEqual(ac.canvas, 115, 65, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, two addLayers & two animateLayers", 2, function() {
	var ac = new AtomicCanvas()
	animateLayer_twoAddsTwoAnimates("#atomic-canvas", 0,
		function () {
			QUnit.pixelEqual(ac.canvas,  70, 100, 0, 255, 0, 255)  // rectangle
			QUnit.pixelEqual(ac.canvas, 125, 100, 0, 255, 0, 255)  // circle
			QUnit.start()
		})
})

// DKS (2012-11-24) - not sure test is well formed yet, not working on Chromium or FF
//asyncTest("animateLayerGroup, methods drawRect, drawArc", 1, function() {
//	var ac = new AtomicCanvas()
//	animateLayerGroup_circleRect("#atomic-canvas", 0,
//		function () {
//			QUnit.pixelEqual(ac.canvas,  70, 100, 0, 255, 0, 255)  // rectangle
//			QUnit.start()
//		})
//})

module("jCanvas Transformation")

test("transformation, rotateCanvas", function () {
	var ac = new AtomicCanvas()
	transformation_rotateCanvas("#atomic-canvas", 100, 0)
	// unspecified x, y in above function, so rotation occurs around x:0, y:0
	//    clockwise
	// center of box should be at:
	//    x = cos(30)*100 ~= 87
	//    y = sin(30)*100 ~= 50
	QUnit.pixelEqual(ac.canvas, 87, 50, 0, 255, 0, 255)
})

test("transformation, scaleCanvas", function () {
	var ac = new AtomicCanvas()
	transformation_scaleCanvas("#atomic-canvas", 100, 20)
	// scaling should both shift and balloon the shape
	QUnit.pixelEqual(ac.canvas, 193, 72, 0, 255, 0, 255)
})
<commit_msg>DKS - added a 1ms duration to the animateLayer_twoAddsTwoAnimates test.  Not sure why that's required (other tests succeed at 0ms), but not worried about it right now.<commit_after>/* Internal helpers */
function AtomicCanvas() {
	// modeled after QUnit's "Keeping Tests Atomic" cookbook guidance:
	//    http://qunitjs.com/cookbook/#keeping_tests_atomic
	this.fixture = $("#qunit-fixture")
	this.fixture.append("<canvas id='atomic-canvas' width='250' height='250'></canvas>")
	this.canvas = document.getElementById('atomic-canvas')
}

/* jCanvas tests */
module("jCanvas Arcs")

test("drawArc, simplest possible", function() {
	var ac = new AtomicCanvas()
	drawArc_simplest("#atomic-canvas")
	QUnit.pixelEqual(ac.canvas, 50, 50, 0, 255, 0, 255)
})

module("jCanvas Layers")

test("addLayer, method drawRect", function () {
	var ac = new AtomicCanvas()
	addLayer_drawRect("#atomic-canvas")
	QUnit.pixelEqual(ac.canvas, 55, 55, 0, 255, 0, 255)
})

asyncTest("animateLayer, method drawLine", 1, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawLine("#atomic-canvas", 0,
		function () {
			QUnit.pixelEqual(ac.canvas, 200, 50, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, four opacity and position animations", 4, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawLinesInstantShiftVariedOpacity("#atomic-canvas",
		function () {
			QUnit.pixelEqual(ac.canvas, 200, 20, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 50, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 80, 0, 255, 0, 255)
			QUnit.pixelEqual(ac.canvas, 200, 110, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, method drawRect", 1, function () {
	var ac = new AtomicCanvas()
	animateLayer_drawRect("#atomic-canvas", 0,
		function () {
			QUnit.pixelEqual(ac.canvas, 115, 65, 0, 255, 0, 255)
			QUnit.start()
		})
})

asyncTest("animateLayer, two addLayers & two animateLayers", 2, function() {
	var ac = new AtomicCanvas()
	// 2013-01-10: this test fails with an animation duration of 0ms, but passes
	//    with a duration of 1ms.  Not sure why, and not worried about it right
	//    now.
	animateLayer_twoAddsTwoAnimates("#atomic-canvas", 1,
		function () {
			QUnit.pixelEqual(ac.canvas,  70, 100, 0, 255, 0, 255)  // rectangle
			QUnit.pixelEqual(ac.canvas, 125, 100, 0, 255, 0, 255)  // circle
			QUnit.start()
		})
})

// DKS (2012-11-24) - not sure test is well formed yet, not working on Chromium or FF
//asyncTest("animateLayerGroup, methods drawRect, drawArc", 1, function() {
//	var ac = new AtomicCanvas()
//	animateLayerGroup_circleRect("#atomic-canvas", 0,
//		function () {
//			QUnit.pixelEqual(ac.canvas,  70, 100, 0, 255, 0, 255)  // rectangle
//			QUnit.start()
//		})
//})

module("jCanvas Transformation")

test("transformation, rotateCanvas", function () {
	var ac = new AtomicCanvas()
	transformation_rotateCanvas("#atomic-canvas", 100, 0)
	// unspecified x, y in above function, so rotation occurs around x:0, y:0
	//    clockwise
	// center of box should be at:
	//    x = cos(30)*100 ~= 87
	//    y = sin(30)*100 ~= 50
	QUnit.pixelEqual(ac.canvas, 87, 50, 0, 255, 0, 255)
})

test("transformation, scaleCanvas", function () {
	var ac = new AtomicCanvas()
	transformation_scaleCanvas("#atomic-canvas", 100, 20)
	// scaling should both shift and balloon the shape
	QUnit.pixelEqual(ac.canvas, 193, 72, 0, 255, 0, 255)
})
<|endoftext|>


<commit_before>const NULL = undefined; // never use "null"
const BehaviorContainer = require('./behavior-container')
const removeByIndex = require('./helper/remove-by-index')

function BehaviorSystem () {
  this._children = []
}

BehaviorSystem.prototype = {

  constructor: BehaviorSystem,

  enable (entity) {
    if (entity.behaviors === NULL) {
      entity.behaviors = new BehaviorContainer(entity, this)
      entity.behaviors.ID = this._children.push(entity.behaviors) - 1
      return true
    }
    return false
  },

  disable (entity) {
    if (entity.behaviors !== NULL && entity.behaviors.system === this) {
      entity.behaviors.removeAll()
      removeByIndex(this._children, entity.behaviors.ID)
      entity.behaviors = NULL
      return true
    }
    return false
  },

  globalProcessAll (methodName, ...args) {
    const list = this._children
    const len = list.length

    if (len === 0) return

    for (let i = 0; i < len; i++) {
      list[i].processAll(methodName, ...args)
    }
  }
}

module.exports = BehaviorSystem
<commit_msg>Update behavior-system.js<commit_after>const BehaviorContainer = require('./behavior-container')
const removeByIndex = require('./helper/remove-by-index')

function BehaviorSystem () {
  this._children = []
}

BehaviorSystem.prototype = {

  constructor: BehaviorSystem,

  enable (entity) {
    if (typeof entity.behaviors !== 'object') {
      entity.behaviors = new BehaviorContainer(entity, this)
      entity.behaviors.ID = this._children.push(entity.behaviors) - 1
      return true
    }
    return false
  },

  disable (entity) {
    if (entity.behaviors && entity.behaviors.system === this) {
      entity.behaviors.removeAll()
      removeByIndex(this._children, entity.behaviors.ID)
      delete entity.behaviors
      return true
    }
    return false
  },

  globalProcessAll (methodName, ...args) {
    const list = this._children
    const len = list.length

    if (len === 0) return

    for (let i = 0; i < len; i++) {
      list[i].processAll(methodName, ...args)
    }
  }
}

module.exports = BehaviorSystem
<|endoftext|>


<commit_before>events = {};

var diacriticsMap = [{
  'base': 'a',
  'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g
}, {
  'base': 'aa',
  'letters': /[\uA733]/g
}, {
  'base': 'ae',
  'letters': /[\u00E6\u01FD\u01E3]/g
}, {
  'base': 'ao',
  'letters': /[\uA735]/g
}, {
  'base': 'au',
  'letters': /[\uA737]/g
}, {
  'base': 'av',
  'letters': /[\uA739\uA73B]/g
}, {
  'base': 'ay',
  'letters': /[\uA73D]/g
}, {
  'base': 'b',
  'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g
}, {
  'base': 'c',
  'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g
}, {
  'base': 'd',
  'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g
}, {
  'base': 'dz',
  'letters': /[\u01F3\u01C6]/g
}, {
  'base': 'e',
  'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g
}, {
  'base': 'f',
  'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g
}, {
  'base': 'g',
  'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g
}, {
  'base': 'h',
  'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g
}, {
  'base': 'hv',
  'letters': /[\u0195]/g
}, {
  'base': 'i',
  'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g
}, {
  'base': 'j',
  'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g
}, {
  'base': 'k',
  'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g
}, {
  'base': 'l',
  'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g
}, {
  'base': 'lj',
  'letters': /[\u01C9]/g
}, {
  'base': 'm',
  'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g
}, {
  'base': 'n',
  'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g
}, {
  'base': 'nj',
  'letters': /[\u01CC]/g
}, {
  'base': 'o',
  'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g
}, {
  'base': 'oi',
  'letters': /[\u01A3]/g
}, {
  'base': 'ou',
  'letters': /[\u0223]/g
}, {
  'base': 'oo',
  'letters': /[\uA74F]/g
}, {
  'base': 'p',
  'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g
}, {
  'base': 'q',
  'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g
}, {
  'base': 'r',
  'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g
}, {
  'base': 's',
  'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g
}, {
  'base': 't',
  'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g
}, {
  'base': 'tz',
  'letters': /[\uA729]/g
}, {
  'base': 'u',
  'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g
}, {
  'base': 'v',
  'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g
}, {
  'base': 'vy',
  'letters': /[\uA761]/g
}, {
  'base': 'w',
  'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g
}, {
  'base': 'x',
  'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g
}, {
  'base': 'y',
  'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g
}, {
  'base': 'z',
  'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g
}];

var removeDiacritics = function(value) {
  _.each(diacriticsMap, function(diacriticMap) {
    value = value.replace(diacriticMap.letters, diacriticMap.base);
  });

  return value;
};

var generateSlug = function(value, separator) {
  // Make text lower case and...
  return removeDiacritics(value.toLowerCase()) // Remove diacritics characters.
    .replace(/[^\w\s-]+/g, '') // Remove unsupported characters.
    .replace(/^\s+|\s+$/g, '') // Trim.
    .replace(/\s+/g, separator); // Replace white characters with separator.
};

events.beforeSave = function() {
  var doc = this;
  var Class = doc.constructor;

  // Find a class on which the behavior had been set.
  var behaviorData = Astro.utils.behaviors.findBehavior(Class, 'slug');

  // Check if a field from which we want to create a slug has been
  // modified.
  var modified = doc.getModified();
  if (!_.has(modified, behaviorData.fieldName)) {
    return;
  }

  // Get a value of the field to make a slug from.
  var value = doc.get(behaviorData.fieldName);

  // If a value is null then we can not create a slug.
  if (_.isNull(value)) {
    return;
  }

  // Take the current value of the slug field.
  var currentSlug = doc.get(behaviorData.slugFieldName);

  // If the current slug is not empty and we can not update, then we have to
  // stop here.
  if (currentSlug && !behaviorData.canUpdate) {
    return;
  }

  // Generate a slug and use a defined separator.
  var slug = generateSlug(value, behaviorData.separator);

  // If the "unique" option was set, then check whether there are any
  // duplicates. If there is any document with the same slug, then we have to
  // add number at the end of the slug.
  if (behaviorData.unique) {
    var selector = {};
    selector[behaviorData.slugFieldName] = slug;
    var Collection = Class.getCollection();
    var count = Collection.find(selector).count();

    if (count > 0) {
      // Prepare the selector with a regular expression querying all the
      // documents that contains our slug and ends with number.
      var prefix = slug + behaviorData.separator;
      var re = new RegExp('^' + prefix + '\\d+$');
      selector[behaviorData.slugFieldName] = re;

      // Limit the amount of fields being fetched to only the slug field.
      var fields = {};
      fields[behaviorData.slugFieldName] = 1;

      // Set the first number that will be added at the end of the slug to 2.
      var index = 2;

      // Loop through all the documents with the same slug.
      Collection.find(selector, fields).forEach(function(d) {
        var dSlug = d.get(behaviorData.slugFieldName);
        var dIndex = parseInt(dSlug.replace(prefix, ''), 10);
        if (dIndex >= index) {
          index = dIndex + 1;
        }
      });

      slug = prefix + index;
    }
  }

  // Set the slug.
  doc.set(behaviorData.slugFieldName, slug);
};
<commit_msg>Don't use utils<commit_after>events = {};

var diacriticsMap = [{
  'base': 'a',
  'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g
}, {
  'base': 'aa',
  'letters': /[\uA733]/g
}, {
  'base': 'ae',
  'letters': /[\u00E6\u01FD\u01E3]/g
}, {
  'base': 'ao',
  'letters': /[\uA735]/g
}, {
  'base': 'au',
  'letters': /[\uA737]/g
}, {
  'base': 'av',
  'letters': /[\uA739\uA73B]/g
}, {
  'base': 'ay',
  'letters': /[\uA73D]/g
}, {
  'base': 'b',
  'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g
}, {
  'base': 'c',
  'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g
}, {
  'base': 'd',
  'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g
}, {
  'base': 'dz',
  'letters': /[\u01F3\u01C6]/g
}, {
  'base': 'e',
  'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g
}, {
  'base': 'f',
  'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g
}, {
  'base': 'g',
  'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g
}, {
  'base': 'h',
  'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g
}, {
  'base': 'hv',
  'letters': /[\u0195]/g
}, {
  'base': 'i',
  'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g
}, {
  'base': 'j',
  'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g
}, {
  'base': 'k',
  'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g
}, {
  'base': 'l',
  'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g
}, {
  'base': 'lj',
  'letters': /[\u01C9]/g
}, {
  'base': 'm',
  'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g
}, {
  'base': 'n',
  'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g
}, {
  'base': 'nj',
  'letters': /[\u01CC]/g
}, {
  'base': 'o',
  'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g
}, {
  'base': 'oi',
  'letters': /[\u01A3]/g
}, {
  'base': 'ou',
  'letters': /[\u0223]/g
}, {
  'base': 'oo',
  'letters': /[\uA74F]/g
}, {
  'base': 'p',
  'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g
}, {
  'base': 'q',
  'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g
}, {
  'base': 'r',
  'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g
}, {
  'base': 's',
  'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g
}, {
  'base': 't',
  'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g
}, {
  'base': 'tz',
  'letters': /[\uA729]/g
}, {
  'base': 'u',
  'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g
}, {
  'base': 'v',
  'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g
}, {
  'base': 'vy',
  'letters': /[\uA761]/g
}, {
  'base': 'w',
  'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g
}, {
  'base': 'x',
  'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g
}, {
  'base': 'y',
  'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g
}, {
  'base': 'z',
  'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g
}];

var removeDiacritics = function(value) {
  _.each(diacriticsMap, function(diacriticMap) {
    value = value.replace(diacriticMap.letters, diacriticMap.base);
  });

  return value;
};

var generateSlug = function(value, separator) {
  // Make text lower case and...
  return removeDiacritics(value.toLowerCase()) // Remove diacritics characters.
    .replace(/[^\w\s-]+/g, '') // Remove unsupported characters.
    .replace(/^\s+|\s+$/g, '') // Trim.
    .replace(/\s+/g, separator); // Replace white characters with separator.
};

events.beforeSave = function() {
  var doc = this;
  var Class = doc.constructor;

  // Find a class on which the behavior had been set.
  var behaviorData = Class.getBehavior('slug');

  // Check if a field from which we want to create a slug has been
  // modified.
  var modified = doc.getModified();
  if (!_.has(modified, behaviorData.fieldName)) {
    return;
  }

  // Get a value of the field to make a slug from.
  var value = doc.get(behaviorData.fieldName);

  // If a value is null then we can not create a slug.
  if (_.isNull(value)) {
    return;
  }

  // Take the current value of the slug field.
  var currentSlug = doc.get(behaviorData.slugFieldName);

  // If the current slug is not empty and we can not update, then we have to
  // stop here.
  if (currentSlug && !behaviorData.canUpdate) {
    return;
  }

  // Generate a slug and use a defined separator.
  var slug = generateSlug(value, behaviorData.separator);

  // If the "unique" option was set, then check whether there are any
  // duplicates. If there is any document with the same slug, then we have to
  // add number at the end of the slug.
  if (behaviorData.unique) {
    var selector = {};
    selector[behaviorData.slugFieldName] = slug;
    var Collection = Class.getCollection();
    var count = Collection.find(selector).count();

    if (count > 0) {
      // Prepare the selector with a regular expression querying all the
      // documents that contains our slug and ends with number.
      var prefix = slug + behaviorData.separator;
      var re = new RegExp('^' + prefix + '\\d+$');
      selector[behaviorData.slugFieldName] = re;

      // Limit the amount of fields being fetched to only the slug field.
      var fields = {};
      fields[behaviorData.slugFieldName] = 1;

      // Set the first number that will be added at the end of the slug to 2.
      var index = 2;

      // Loop through all the documents with the same slug.
      Collection.find(selector, fields).forEach(function(d) {
        var dSlug = d.get(behaviorData.slugFieldName);
        var dIndex = parseInt(dSlug.replace(prefix, ''), 10);
        if (dIndex >= index) {
          index = dIndex + 1;
        }
      });

      slug = prefix + index;
    }
  }

  // Set the slug.
  doc.set(behaviorData.slugFieldName, slug);
};
<|endoftext|>