<commit_before>const spawn = require('cross-spawn-promise');
const { hasCommand, warn } = require('../util');

const stdio = 'ignore';

exports.install = function(cwd, isYarn) {
	let cmd = isYarn ? 'yarn' : 'npm';
	return spawn(cmd, ['install'], { cwd, stdio });
};

exports.addScripts = async function(obj, cwd, isYarn) {
	let cmd = isYarn ? 'yarn' : 'npm';
	let args = isYarn ? ['add', '--dev'] : ['install', '--save-dev'];

	// Install `if-env`
	await spawn(cmd, [...args, 'if-env'], { cwd, stdio });

	return {
		build: 'preact build',
		serve: 'preact build && preact serve',
		start: `if-env NODE_ENV=production && ${cmd} run -s serve || ${cmd} run -s watch`,
		watch: 'preact watch',
	};
};

// Initializes the folder using `git init` and a proper `.gitignore` file
// if `git` is present in the $PATH.
exports.initGit = async function(target) {
	let git = hasCommand('git');

	if (git) {
		const cwd = target;

		await spawn('git', ['init'], { cwd });
		await spawn('git', ['add', '-A'], { cwd });

		let gitUser, gitEmail;
		const defaultGitUser = 'Preact CLI';
		const defaultGitEmail = 'preact-cli@users.noreply.github.com';

		try {
			gitEmail = (await spawn('git', ['config', 'user.email'])).toString();
		} catch (e) {
			gitEmail = defaultGitEmail;
		}

		try {
			gitUser = (await spawn('git', ['config', 'user.name'])).toString();
		} catch (e) {
			gitUser = defaultGitUser;
		}

		await spawn('git', ['commit', '-m', 'initial commit from Preact CLI'], {
			cwd,
			env: {
				GIT_COMMITTER_NAME: gitUser,
				GIT_COMMITTER_EMAIL: gitEmail,
				GIT_AUTHOR_NAME: defaultGitUser,
				GIT_AUTHOR_EMAIL: defaultGitEmail,
			},
		});
	} else {
		warn('Could not locate `git` binary in `$PATH`. Skipping!');
	}
};

// Formulate Questions if `create` args are missing
exports.isMissing = function(argv) {
	let out = [];

	const ask = (name, message, val) => {
		let type = val === void 0 ? 'input' : 'confirm';
		out.push({ name, message, type, initial: val });
	};

	// Required data
	!argv.template && ask('template', 'Remote template to clone (user/repo#tag)');
	!argv.dest && ask('dest', 'Directory to create the app');
	// Extra data / flags
	!argv.name && ask('name', "The application's name");
	!argv.force &&
		ask('force', 'Enforce `dest` directory; will overwrite!', false);
	ask('install', 'Install dependencies', true); // defaults `true`, ask anyway
	!argv.yarn && ask('yarn', 'Install with `yarn` instead of `npm`', false);
	!argv.git && ask('git', 'Initialize a `git` repository', false);

	return out;
};
<commit_msg>Make 'Installing Dependencies' stage of app creation more verbose<commit_after>const spawn = require('cross-spawn-promise');
const { hasCommand, warn } = require('../util');

const stdio = 'ignore';

exports.install = function(cwd, isYarn) {
	let cmd = isYarn ? 'yarn' : 'npm';
	return spawn(cmd, ['install'], { cwd, stdio: 'inherit' });
};

exports.addScripts = async function(obj, cwd, isYarn) {
	let cmd = isYarn ? 'yarn' : 'npm';
	let args = isYarn ? ['add', '--dev'] : ['install', '--save-dev'];

	// Install `if-env`
	await spawn(cmd, [...args, 'if-env'], { cwd, stdio });

	return {
		build: 'preact build',
		serve: 'preact build && preact serve',
		start: `if-env NODE_ENV=production && ${cmd} run -s serve || ${cmd} run -s watch`,
		watch: 'preact watch',
	};
};

// Initializes the folder using `git init` and a proper `.gitignore` file
// if `git` is present in the $PATH.
exports.initGit = async function(target) {
	let git = hasCommand('git');

	if (git) {
		const cwd = target;

		await spawn('git', ['init'], { cwd });
		await spawn('git', ['add', '-A'], { cwd });

		let gitUser, gitEmail;
		const defaultGitUser = 'Preact CLI';
		const defaultGitEmail = 'preact-cli@users.noreply.github.com';

		try {
			gitEmail = (await spawn('git', ['config', 'user.email'])).toString();
		} catch (e) {
			gitEmail = defaultGitEmail;
		}

		try {
			gitUser = (await spawn('git', ['config', 'user.name'])).toString();
		} catch (e) {
			gitUser = defaultGitUser;
		}

		await spawn('git', ['commit', '-m', 'initial commit from Preact CLI'], {
			cwd,
			env: {
				GIT_COMMITTER_NAME: gitUser,
				GIT_COMMITTER_EMAIL: gitEmail,
				GIT_AUTHOR_NAME: defaultGitUser,
				GIT_AUTHOR_EMAIL: defaultGitEmail,
			},
		});
	} else {
		warn('Could not locate `git` binary in `$PATH`. Skipping!');
	}
};

// Formulate Questions if `create` args are missing
exports.isMissing = function(argv) {
	let out = [];

	const ask = (name, message, val) => {
		let type = val === void 0 ? 'input' : 'confirm';
		out.push({ name, message, type, initial: val });
	};

	// Required data
	!argv.template && ask('template', 'Remote template to clone (user/repo#tag)');
	!argv.dest && ask('dest', 'Directory to create the app');
	// Extra data / flags
	!argv.name && ask('name', "The application's name");
	!argv.force &&
		ask('force', 'Enforce `dest` directory; will overwrite!', false);
	ask('install', 'Install dependencies', true); // defaults `true`, ask anyway
	!argv.yarn && ask('yarn', 'Install with `yarn` instead of `npm`', false);
	!argv.git && ask('git', 'Initialize a `git` repository', false);

	return out;
};
<|endoftext|>


<commit_before>const { dirname, join, relative } = require('path')
const Core = require('./core')
const { debounce } = require('./util/debounce')
const { debug3 } = require('./util/debug')

const globPattern = '**/*'

const sourceFilesFromConfig = ({ source: { configFile, components, data, entities, pages, templates }, adapters, debug, ui }) => {
  const componentsGlob = components ? join(components, globPattern) : null
  const templatesGlob = templates ? join(templates, globPattern) : null
  const pagesGlob = pages ? join(pages, globPattern) : null
  const dataGlob = data ? join(data, globPattern) : null
  const entitiesGlob = entities ? join(entities, globPattern) : null
  const sourceFiles = [configFile, componentsGlob, dataGlob, entitiesGlob, pagesGlob, templatesGlob].filter(a => a)

  if (debug) {
    const uiLibGlob = join(dirname(require.resolve('@uiengine/ui')), globPattern)
    const uiDistGlob = uiLibGlob.replace('ui/lib/', 'ui/dist/')
    sourceFiles.push(uiLibGlob, uiDistGlob)
  }

  return sourceFiles
}

// see https://github.com/paulmillr/chokidar#api
const watchOptions = {
  ignoreInitial: true,
  awaitWriteFinish: true
}

// see https://www.browsersync.io/docs/options/
const browserSyncOptions = {
  notify: false,
  single: true,
  watchOptions
}

const optionWithDefault = (def, value) =>
  typeof value !== 'undefined' ? value : def

const requireOptional = (module, option) => {
  try {
    return require(module)
  } catch (err) {
    console.error(`The optional dependency ${module} failed to install and is required for --${option}.`, 'It is likely not supported on your platform.')

    throw err
  }
}

const startWatcher = (state, opts, server) => {
  const { config } = state
  const { watch, info } = opts
  const chokidar = requireOptional('chokidar', 'watch')
  const sourceFiles = sourceFilesFromConfig(config)

  let additionalFiles = []
  if (typeof watch === 'string') {
    additionalFiles = [watch]
  } else if (Array.isArray(watch)) {
    additionalFiles = watch
  }

  const handleFileChange = (filePath, type) => {
    return debounce('handleFileChange', async () => {
      if (Core.isGenerating()) return

      try {
        const { state, change } = await Core.generateIncrementForFileChange(filePath, type)

        if (info) console.info(`✨  Rebuilt ${change.type} ${change.item}`, `(${change.action} ${change.file})`)
        if (server) server.sockets.emit('uiengine:state:update', state)
      } catch (err) {
        console.error(`🚨  Rebuild for changed file ${relative(process.cwd(), filePath)} failed:`, err)
      }
    })
  }

  const watchFiles = sourceFiles.concat(additionalFiles)
  const watcher = chokidar.watch(watchFiles, watchOptions)
    .on('add', filePath => handleFileChange(filePath, 'added'))
    .on('addDir', filePath => handleFileChange(filePath, 'added'))
    .on('change', filePath => handleFileChange(filePath, 'changed'))
    .on('unlink', filePath => handleFileChange(filePath, 'deleted'))
    .on('unlinkDir', filePath => handleFileChange(filePath, 'deleted'))

  debug3(state, 'Watching files:', JSON.stringify(watchFiles, null, 2))

  return watcher
}

const startServer = (state, opts) => {
  const { browserSync, target, ui } = state.config
  const { watch, info } = opts
  const server = requireOptional('browser-sync', 'serve').create('UIengine')
  const history = requireOptional('connect-history-api-fallback')
  const pagesPattern = '_pages/**/*'
  const variantsPattern = '_variants/**/*'
  const defaults = {
    server: {
      baseDir: target
    },
    files: [
      {
        match: [globPattern],
        options: {
          cwd: target,
          ignored: [
            // exclude files with state as they change on every rebuild.
            // changes are injected via websockets (see handleFileChange)
            'index.html',
            '_sketch.html',
            '_state.json',
            // exclude pages and variants as the iframes are reloaded
            // separately (see server.init callback)
            pagesPattern,
            variantsPattern
          ]
        }
      }
    ],
    middleware: []
  }
  const options = optionWithDefault(defaults, browserSync)

  options.server = optionWithDefault(defaults.server, options.server)
  options.server.baseDir = optionWithDefault(defaults.server.baseDir, options.server.baseDir)
  options.middleware = optionWithDefault(defaults.middleware, options.middleware)
  options.logLevel = optionWithDefault((info ? 'info' : 'silent'), options.logLevel)

  const basePath = (optionWithDefault('/', ui.base)).replace(/\/$/, '')
  options.middleware.push({
    route: basePath,
    handle: history()
  })

  if (watch) {
    options.files = optionWithDefault(defaults.files, options.files)
    options.watchOptions = optionWithDefault(browserSyncOptions.watchOptions, options.watchOptions)
    options.notify = optionWithDefault(browserSyncOptions.notify, options.notify)
  }

  debug3(state, 'BrowserSync options:', JSON.stringify(options, null, 2))

  server.init(options, (err, instance) => {
    if (err) console.error('Initializing server failed: ', err)

    const _paths = filePath => join(target, filePath)
    // trigger iframe reloads, see
    // https://github.com/BrowserSync/browser-sync/issues/662#issuecomment-110478137
    server.watch([
      _paths(pagesPattern),
      _paths(variantsPattern)
    ]).on('change', filePath => {
      const file = relative(target, filePath)

      instance.io.sockets.emit('uiengine:file:change', file)
    })
  })

  return server
}

export const theo = (theo, options = {}) =>
  require('./integrations/theo')(theo, options)

export async function build (options = {}) {
  options.info = optionWithDefault(true, options.info)
  options.serve = optionWithDefault(false, options.serve)
  options.watch = optionWithDefault(false, options.watch)

  if (options.info) console.info('🚧  Building …')

  try {
    const state = await Core.generate(options)

    if (options.info) console.info('✅  Build done!')

    let server, watcher
    if (options.serve) server = startServer(state, options)
    if (options.watch) watcher = startWatcher(state, options, server)

    return { state, server, watcher }
  } catch (err) {
    console.error(['🚨  Build failed!', err.stack].join('\n\n'))

    throw err
  }
}
<commit_msg>⏱ reduced 2s file watching delay for faster refreshes<commit_after>const { dirname, join, relative } = require('path')
const Core = require('./core')
const { debounce } = require('./util/debounce')
const { debug3 } = require('./util/debug')

const globPattern = '**/*'

const sourceFilesFromConfig = ({ source: { configFile, components, data, entities, pages, templates }, adapters, debug, ui }) => {
  const componentsGlob = components ? join(components, globPattern) : null
  const templatesGlob = templates ? join(templates, globPattern) : null
  const pagesGlob = pages ? join(pages, globPattern) : null
  const dataGlob = data ? join(data, globPattern) : null
  const entitiesGlob = entities ? join(entities, globPattern) : null
  const sourceFiles = [configFile, componentsGlob, dataGlob, entitiesGlob, pagesGlob, templatesGlob].filter(a => a)

  if (debug) {
    const uiLibGlob = join(dirname(require.resolve('@uiengine/ui')), globPattern)
    const uiDistGlob = uiLibGlob.replace('ui/lib/', 'ui/dist/')
    sourceFiles.push(uiLibGlob, uiDistGlob)
  }

  return sourceFiles
}

// see https://github.com/paulmillr/chokidar#api
const watchOptions = {
  ignoreInitial: true,
  awaitWriteFinish: {
    stabilityThreshold: 200,
    pollInterval: 50
  }
}

// see https://www.browsersync.io/docs/options/
const browserSyncOptions = {
  notify: false,
  single: true,
  watchOptions
}

const optionWithDefault = (def, value) =>
  typeof value !== 'undefined' ? value : def

const requireOptional = (module, option) => {
  try {
    return require(module)
  } catch (err) {
    console.error(`The optional dependency ${module} failed to install and is required for --${option}.`, 'It is likely not supported on your platform.')

    throw err
  }
}

const startWatcher = (state, opts, server) => {
  const { config } = state
  const { watch, info } = opts
  const chokidar = requireOptional('chokidar', 'watch')
  const sourceFiles = sourceFilesFromConfig(config)

  let additionalFiles = []
  if (typeof watch === 'string') {
    additionalFiles = [watch]
  } else if (Array.isArray(watch)) {
    additionalFiles = watch
  }

  const handleFileChange = (filePath, type) => {
    return debounce('handleFileChange', async () => {
      if (Core.isGenerating()) return

      try {
        const { state, change } = await Core.generateIncrementForFileChange(filePath, type)

        if (info) console.info(`✨  Rebuilt ${change.type} ${change.item}`, `(${change.action} ${change.file})`)
        if (server) server.sockets.emit('uiengine:state:update', state)
      } catch (err) {
        console.error(`🚨  Rebuild for changed file ${relative(process.cwd(), filePath)} failed:`, err)
      }
    })
  }

  const watchFiles = sourceFiles.concat(additionalFiles)
  const watcher = chokidar.watch(watchFiles, watchOptions)
    .on('add', filePath => handleFileChange(filePath, 'added'))
    .on('addDir', filePath => handleFileChange(filePath, 'added'))
    .on('change', filePath => handleFileChange(filePath, 'changed'))
    .on('unlink', filePath => handleFileChange(filePath, 'deleted'))
    .on('unlinkDir', filePath => handleFileChange(filePath, 'deleted'))

  debug3(state, 'Watching files:', JSON.stringify(watchFiles, null, 2))

  return watcher
}

const startServer = (state, opts) => {
  const { browserSync, target, ui } = state.config
  const { watch, info } = opts
  const server = requireOptional('browser-sync', 'serve').create('UIengine')
  const history = requireOptional('connect-history-api-fallback')
  const pagesPattern = '_pages/**/*'
  const variantsPattern = '_variants/**/*'
  const defaults = {
    server: {
      baseDir: target
    },
    files: [
      {
        match: [globPattern],
        options: {
          cwd: target,
          ignored: [
            // exclude files with state as they change on every rebuild.
            // changes are injected via websockets (see handleFileChange)
            'index.html',
            '_sketch.html',
            '_state.json',
            // exclude pages and variants as the iframes are reloaded
            // separately (see server.init callback)
            pagesPattern,
            variantsPattern
          ]
        }
      }
    ],
    middleware: []
  }
  const options = optionWithDefault(defaults, browserSync)

  options.server = optionWithDefault(defaults.server, options.server)
  options.server.baseDir = optionWithDefault(defaults.server.baseDir, options.server.baseDir)
  options.middleware = optionWithDefault(defaults.middleware, options.middleware)
  options.logLevel = optionWithDefault((info ? 'info' : 'silent'), options.logLevel)

  const basePath = (optionWithDefault('/', ui.base)).replace(/\/$/, '')
  options.middleware.push({
    route: basePath,
    handle: history()
  })

  if (watch) {
    options.files = optionWithDefault(defaults.files, options.files)
    options.watchOptions = optionWithDefault(browserSyncOptions.watchOptions, options.watchOptions)
    options.notify = optionWithDefault(browserSyncOptions.notify, options.notify)
  }

  debug3(state, 'BrowserSync options:', JSON.stringify(options, null, 2))

  server.init(options, (err, instance) => {
    if (err) console.error('Initializing server failed: ', err)

    const _paths = filePath => join(target, filePath)
    // trigger iframe reloads, see
    // https://github.com/BrowserSync/browser-sync/issues/662#issuecomment-110478137
    server.watch([
      _paths(pagesPattern),
      _paths(variantsPattern)
    ]).on('change', filePath => {
      const file = relative(target, filePath)

      instance.io.sockets.emit('uiengine:file:change', file)
    })
  })

  return server
}

export const theo = (theo, options = {}) =>
  require('./integrations/theo')(theo, options)

export async function build (options = {}) {
  options.info = optionWithDefault(true, options.info)
  options.serve = optionWithDefault(false, options.serve)
  options.watch = optionWithDefault(false, options.watch)

  if (options.info) console.info('🚧  Building …')

  try {
    const state = await Core.generate(options)

    if (options.info) console.info('✅  Build done!')

    let server, watcher
    if (options.serve) server = startServer(state, options)
    if (options.watch) watcher = startWatcher(state, options, server)

    return { state, server, watcher }
  } catch (err) {
    console.error(['🚨  Build failed!', err.stack].join('\n\n'))

    throw err
  }
}
<|endoftext|>


<commit_before><commit_msg>Update Testing3.js<commit_after><|endoftext|>


<commit_before>import React from 'react';
import * as Objects from './objects/_index';

export default class extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      rowResult: {
        checks: [
          {
            object: 'Lamp',
            value: 'aan',
            valid: true
          },
          {
            object: 'Lamp',
            value: 'uit',
            valid: false
          }
        ],
        assignments: [
          {
            object: 'Deur',
            value: 'aan',
            valid: false
          },
          {
            object: 'Deur',
            value: 'open',
            valid: true
          }
        ]
      }
    }
  }

  render() {
    const checks = this.state.rowResult.checks.map((check, i) => {
      const ObjectRef = Objects[check.object];
      const classNames = 'check' + (check.valid ? ' valid' : '');
      return
<ObjectRef/> = {check.value}

    });

    const assignments = this.state.rowResult.assignments.map((assignment, i) => {
      const ObjectRef = Objects[assignment.object];
      const classNames = 'assignment' + (assignment.valid ? ' valid' : '');
      return
<ObjectRef/> = {assignment.value}

    });

    return(

          {checks}

          // This is our PC component //

          {assignments}

    );
  }
};
<commit_msg>Rework render method to use array of optional items.<commit_after>import React from 'react';
import * as Objects from './objects/_index';

export default class extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      rowResult: {
        checks: [
          {
            object: 'Lamp',
            value: 'aan',
            valid: true
          },
          {
            object: 'Lamp',
            value: 'uit',
            valid: false
          }
        ],
        assignments: [
          {
            object: 'Deur',
            value: 'aan',
            valid: false
          },
          {
            object: 'Deur',
            value: 'open',
            valid: true
          }
        ]
      }
    }
  }

  render() {
    const checks = this.state.rowResult.checks.map((check, i) => {
      const ObjectRef = Objects[check.object];
      const classNames = 'check' + (check.valid ? ' valid' : '');
      return
<ObjectRef/> = {check.value}

    });

    const assignments = this.state.rowResult.assignments.map((assignment, i) => {
      const ObjectRef = Objects[assignment.object];
      const classNames = 'assignment' + (assignment.valid ? ' valid' : '');
      return
<ObjectRef/> = {assignment.value}

    });

    let output = [];

    if(checks.length > 0) {
      output.push(

          { checks }

      );

      if(assignments.length > 0) {
        output.push(

            // Hier staat een computer //

        );
      }
    }

    if(assignments.length > 0) {
      output.push(

          { assignments }

      );
    }

    return(

        { output }

    );
  }
};
<|endoftext|>


<commit_before>// npm dependencies
//
import React from 'react';
import update from 'react-addons-update';
import uniqueId from 'lodash/uniqueId';
import isArray from 'lodash/isArray';
import isEqual from 'lodash/isEqual';
import classnames from 'classnames';

/**
 * The CheckboxGroup component
 */
class CheckboxGroup extends React.Component {
    /**
     * Construct the component instance
     * @param  {Object} props The component properties
     */
    constructor(props) {
        super(props);

        // declare a variable for the component value
        //
        let value = [];

        // do we have a value prop? if so, make sure it's either an array, or
        // convert it to an array
        //
        if (this.props.value) {
            value = isArray(this.props.value) ? this.props.value : [this.props.value];
        }

        // calculate a unique ID for the component if one is not provided
        //
        this.id = this.props.id || uniqueId('form-checkboxgroup-');

        // intialize the validation message for the component
        //
        this.validationMessage = this.props.validationMessage
            || `At least one item in ${this.props.description} must be selected`;

        // initialize the component state
        //
        this.state = {
            value,
            hasBeenClicked: false,
        };

        // bind `this` to the onClick handler
        //
        this.onClick = this.onClick.bind(this);
    }

    /**
     * Handle the "component is mounting" event
     */
    componentWillMount() {
        // determine if it's valid
        //
        const isValid = !this.props.required || this.state.value.length;

        // call the `onChildValidationEvent` handler once with
        // `hasValidated`=false, just to ensure that the parent knows about this
        // child
        //
        if (this.context.onChildValidationEvent) {
            this.context.onChildValidationEvent(
                this.id,
                false,
                isValid ? null : this.validationMessage
            );
        }
    }

    /**
     * Handle the receipt of new properties
     * @param  {Object} newProps The new properties for the component
     */
    componentWillReceiveProps(newProps) {
        // save the initial value, so we can check for changes later
        //
        const initialValue = this.state.value;

        // remove any items from the value that do not appear in the new options
        //
        this.setState((state) => update(state, {
            value: {
                $set: state.value.filter((val) =>
                    newProps.options.findIndex((opt) => opt.value === `${val}`) !== -1
                )
            }
        }), () => {
            // then check to see if the value has changed and, if so, call the
            // `onChange` handler with the new values
            //
            if (!isEqual(this.state.value, initialValue) && this.props.onChange) {
                this.props.onChange(this.state.value);
            }
        });
    }

    /**
     * Handle a click or change event for one of the input elements
     * @param  {String} value The value for the input element that was clicked
     */
    onClick(value) {
        // determine if the item clicked is currently in the list of checked
        // items
        //
        const idx = this.state.value.findIndex((val) => val === value);

        // declare a variable to hold the update details
        //
        let delta = null;

        // do we have this item in the list of checked items already?
        //
        if (idx === -1) {
            // no. add the item to the array
            //
            delta = { hasBeenClicked: { $set: true }, value: { $push: [value] } };
        } else {
            // yes. remove the item from the array
            //
            delta = { hasBeenClicked: { $set: true }, value: { $splice: [[idx, 1]] } };
        }

        // update the state...
        //
        this.setState((state) => update(state, delta), () => {
            // then call the `onChange` handler (if any)
            //
            if (this.props.onChange) {
                this.props.onChange(this.state.value);
            }

            // determine if the value is valid
            //
            const isValid = !this.props.required || this.state.value.length;

            // call the "child has validated" handler
            //
            if (this.context.onChildValidationEvent) {
                this.context.onChildValidationEvent(
                    this.id,
                    true,
                    isValid ? null : this.validationMessage
                );
            }
        });
    }

    /**
     * Render the component
     * @return {React.Element} The React element describing the component
     */
    render() {
        // build the list of classes for the outermost div
        //
        const classes = classnames('form-group', {
            'has-error': this.props.required
                && this.state.hasBeenClicked
                && !this.state.value.length,
        });

        // render a label for the component, if a label has been provided
        //
        const label = this.props.label
            ? (
            <label
                className={classnames('control-label', 'pull-left', {
                    [`col-xs-${this.context.labelColumns}`]: this.context.labelColumns,
                })}
            >
                {this.props.label}
                {this.props.required
                    ?
                    : ''
                }

            )
            : null;

        // render the help block if the component has failed validation
        //
        const helpBlock =
            this.props.required && this.state.hasBeenClicked && !this.state.value.length
            ? {this.validationMessage}
            : '';

        // render the input elements for the component
        //
        const inputs = (
            <div
                className={classnames('form-checkboxgroup-inputs', {
                    [`col-xs-${12 - this.context.labelColumns}`]: this.context.labelColumns,
                })}
            >
                {this.props.options.map((opt) =>

                            <input
                                type="checkbox"
                                value={opt.value}
                                checked={
                                    this.state.value.findIndex((val) =>
                                        val === opt.value
                                    ) !== -1
                                }
                                onChange={() => this.onClick(opt.value)}
                            />
                            {opt.name}

                )}

                {helpBlock}

        );

        // render the whole component and return it
        //
        return (

                {label}

                {inputs}

        );
    }
}

/**
 * The property types for the component
 * @type {Object}
 */
CheckboxGroup.propTypes = {
    /**
     * A flag to indicated whether this component is required (ie. at least one
     * checkbox must be checked)
     */
    required:          React.PropTypes.bool,
    /** The ID for the component */
    id:                React.PropTypes.string,
    /** The label for the checkbox group */
    label:             React.PropTypes.string,
    /**
     * The description of the component for use in validation error messages
     * (ie., "at least one item in <description> must be checked")
     */
    description:       React.PropTypes.string,
    /** A custom validation error message for the component */
    validationMessage: React.PropTypes.string,
    /** The `onChange` handler for the component */
    onChange:          React.PropTypes.func,
    /** The value of the component */
    value:             React.PropTypes.oneOfType([
        React.PropTypes.arrayOf(React.PropTypes.string),
        React.PropTypes.arrayOf(React.PropTypes.number),
        React.PropTypes.string,
        React.PropTypes.number,
    ]),
    /** The options for the checkbox group */
    options:           React.PropTypes.arrayOf(React.PropTypes.object),
};

/**
 * The context types for the component
 * @type {Object}
 */
CheckboxGroup.contextTypes = {
    onChildValidationEvent: React.PropTypes.func,
    labelColumns:           React.PropTypes.number,
};

// export the component
//
export default CheckboxGroup;
<commit_msg>implemented prop-types package<commit_after>// npm dependencies
//
import React from 'react';
import PropTypes from 'prop-types';
import update from 'react-addons-update';
import uniqueId from 'lodash/uniqueId';
import isArray from 'lodash/isArray';
import isEqual from 'lodash/isEqual';
import classnames from 'classnames';

/**
 * The CheckboxGroup component
 */
class CheckboxGroup extends React.Component {
    /**
     * Construct the component instance
     * @param  {Object} props The component properties
     */
    constructor(props) {
        super(props);

        // declare a variable for the component value
        //
        let value = [];

        // do we have a value prop? if so, make sure it's either an array, or
        // convert it to an array
        //
        if (this.props.value) {
            value = isArray(this.props.value) ? this.props.value : [this.props.value];
        }

        // calculate a unique ID for the component if one is not provided
        //
        this.id = this.props.id || uniqueId('form-checkboxgroup-');

        // intialize the validation message for the component
        //
        this.validationMessage = this.props.validationMessage
            || `At least one item in ${this.props.description} must be selected`;

        // initialize the component state
        //
        this.state = {
            value,
            hasBeenClicked: false,
        };

        // bind `this` to the onClick handler
        //
        this.onClick = this.onClick.bind(this);
    }

    /**
     * Handle the "component is mounting" event
     */
    componentWillMount() {
        // determine if it's valid
        //
        const isValid = !this.props.required || this.state.value.length;

        // call the `onChildValidationEvent` handler once with
        // `hasValidated`=false, just to ensure that the parent knows about this
        // child
        //
        if (this.context.onChildValidationEvent) {
            this.context.onChildValidationEvent(
                this.id,
                false,
                isValid ? null : this.validationMessage
            );
        }
    }

    /**
     * Handle the receipt of new properties
     * @param  {Object} newProps The new properties for the component
     */
    componentWillReceiveProps(newProps) {
        // save the initial value, so we can check for changes later
        //
        const initialValue = this.state.value;

        // remove any items from the value that do not appear in the new options
        //
        this.setState((state) => update(state, {
            value: {
                $set: state.value.filter((val) =>
                    newProps.options.findIndex((opt) => opt.value === `${val}`) !== -1
                )
            }
        }), () => {
            // then check to see if the value has changed and, if so, call the
            // `onChange` handler with the new values
            //
            if (!isEqual(this.state.value, initialValue) && this.props.onChange) {
                this.props.onChange(this.state.value);
            }
        });
    }

    /**
     * Handle a click or change event for one of the input elements
     * @param  {String} value The value for the input element that was clicked
     */
    onClick(value) {
        // determine if the item clicked is currently in the list of checked
        // items
        //
        const idx = this.state.value.findIndex((val) => val === value);

        // declare a variable to hold the update details
        //
        let delta = null;

        // do we have this item in the list of checked items already?
        //
        if (idx === -1) {
            // no. add the item to the array
            //
            delta = { hasBeenClicked: { $set: true }, value: { $push: [value] } };
        } else {
            // yes. remove the item from the array
            //
            delta = { hasBeenClicked: { $set: true }, value: { $splice: [[idx, 1]] } };
        }

        // update the state...
        //
        this.setState((state) => update(state, delta), () => {
            // then call the `onChange` handler (if any)
            //
            if (this.props.onChange) {
                this.props.onChange(this.state.value);
            }

            // determine if the value is valid
            //
            const isValid = !this.props.required || this.state.value.length;

            // call the "child has validated" handler
            //
            if (this.context.onChildValidationEvent) {
                this.context.onChildValidationEvent(
                    this.id,
                    true,
                    isValid ? null : this.validationMessage
                );
            }
        });
    }

    /**
     * Render the component
     * @return {React.Element} The React element describing the component
     */
    render() {
        // build the list of classes for the outermost div
        //
        const classes = classnames('form-group', {
            'has-error': this.props.required
                && this.state.hasBeenClicked
                && !this.state.value.length,
        });

        // render a label for the component, if a label has been provided
        //
        const label = this.props.label
            ? (
            <label
                className={classnames('control-label', 'pull-left', {
                    [`col-xs-${this.context.labelColumns}`]: this.context.labelColumns,
                })}
            >
                {this.props.label}
                {this.props.required
                    ?
                    : ''
                }

            )
            : null;

        // render the help block if the component has failed validation
        //
        const helpBlock =
            this.props.required && this.state.hasBeenClicked && !this.state.value.length
            ? {this.validationMessage}
            : '';

        // render the input elements for the component
        //
        const inputs = (
            <div
                className={classnames('form-checkboxgroup-inputs', {
                    [`col-xs-${12 - this.context.labelColumns}`]: this.context.labelColumns,
                })}
            >
                {this.props.options.map((opt) =>

                            <input
                                type="checkbox"
                                value={opt.value}
                                checked={
                                    this.state.value.findIndex((val) =>
                                        val === opt.value
                                    ) !== -1
                                }
                                onChange={() => this.onClick(opt.value)}
                            />
                            {opt.name}

                )}

                {helpBlock}

        );

        // render the whole component and return it
        //
        return (

                {label}

                {inputs}

        );
    }
}

/**
 * The property types for the component
 * @type {Object}
 */
CheckboxGroup.propTypes = {
    /**
     * A flag to indicated whether this component is required (ie. at least one
     * checkbox must be checked)
     */
    required:          PropTypes.bool,
    /** The ID for the component */
    id:                PropTypes.string,
    /** The label for the checkbox group */
    label:             PropTypes.string,
    /**
     * The description of the component for use in validation error messages
     * (ie., "at least one item in <description> must be checked")
     */
    description:       PropTypes.string,
    /** A custom validation error message for the component */
    validationMessage: PropTypes.string,
    /** The `onChange` handler for the component */
    onChange:          PropTypes.func,
    /** The value of the component */
    value:             PropTypes.oneOfType([
        PropTypes.arrayOf(PropTypes.string),
        PropTypes.arrayOf(PropTypes.number),
        PropTypes.string,
        PropTypes.number,
    ]),
    /** The options for the checkbox group */
    options:           PropTypes.arrayOf(PropTypes.object),
};

/**
 * The context types for the component
 * @type {Object}
 */
CheckboxGroup.contextTypes = {
    onChildValidationEvent: PropTypes.func,
    labelColumns:           PropTypes.number,
};

// export the component
//
export default CheckboxGroup;
<|endoftext|>


<commit_before>'use strict';

//--- String

function hasLf(s) {
  if (s.slice(-1) !== '\n') return 0;
  if (s.slice(-2) !== '\r\n') return 1;
  return 2;
}

String.prototype.chomp = function (tail) {
  if (tail === undefined) {
    return this.substr(0, this.length - hasLf(this)); // NB: we cut 0 if no LF
  }
  var pos = this.length - tail.length;
  if (this.substr(pos) === tail) {
    return this.substr(0, pos);
  }
  return this.toString(); // unchanged string is returned
};

String.prototype.chop = function (count) {
  if (count === undefined) {
    // special behavior: chop ending \n or \r\n
    count = hasLf(this) || 1; // we cut at least 1
  } else if(typeof count !== 'number') {
    throw new Error('Invalid parameter type for String.count: ' + typeof count);
  }
  return this.substr(0, this.length - count);
};

String.prototype.startWith = function (head) {
  return this.substr(0, head.length) === head;
};

String.prototype.endWith = function (tail) {
  return this.substr(this.length - tail.length) === tail;
};

String.prototype.tail = function (count) {
  if (count <= 0) return '';
  return this.slice(-count);
};

/** Inclusive range, like s[n..m] - exclusive one is done by using "slice" */
String.prototype.range = function (begin, end) {
  if (end === -1 || end === undefined) {
    return this.slice(begin);
  }
  return this.slice(begin, end + 1);
};

String.prototype.format = function (num) {
  if (this[0] !== '%') throw new Error('Invalid format: ' + this.toString());
  var fmt = this.slice(1,-1), res, pos = 0;
  var code = this.slice(-1);
  switch (code) {
  case 'd': case 'x': //'%2d'
    var padChar = ' ';
    if (fmt[pos] === '0') { pos++; padChar = '0'; }
    var len = parseInt(fmt.substr(pos));
    res = num.toString(code === 'x' ? 16 : 10);
    for (var i = len - res.length; i > 0; i--) { res = padChar + res; }
    return res;
  case 'f': //'%.02f'
    if (fmt[0] !== '.') break;
    var prec = parseInt(fmt.substr(1));
    return num.toFixed(prec);
  }
  throw new Error('Unknown format: ' + this.toString());
};

String.prototype.between = function (low, high) {
  return this >= low && this <= high;
};

function escapeRegexp(str) {
  return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}

String.prototype.replaceAll = function (pattern, replaceBy) {
  if (pattern instanceof RegExp) {
    if (pattern.global) return this.replace(pattern, replaceBy); // just in case...
    var mode = pattern.ignoreCase ? 'gi' : 'g';
    return this.replace(new RegExp(pattern.source, mode), replaceBy);
  }
  // NB: this would be fast and simpler on V8: this.split(pattern).join('')
  return this.replace(new RegExp(escapeRegexp(pattern), 'g'), replaceBy);
};

//--- Array

/** Mimics the Ruby Array constructor. Same limitation as in Ruby: do not use an object unless
 *  you really want all items to "point" to this object (i.e. modifying the objet will affect all items)
 *  @param {int} size
 *  @param {value|func} init - if a value is given, it is assigned to all items in the array.
 *         If a func is given, array[i] = func(i) will be performed for each item.
 *  @return {Array} - the new array
 */
Array.new = function (size, init) {
  if (size === undefined) return [];
  if (init === undefined) return new Array(size);

  var i, a = [];
  if (typeof init === 'function') {
    for (i = 0; i < size; i++) { a[i] = init(i); }
  } else {
    for (i = 0; i < size; i++) { a[i] = init; }
  }
  return a;
};

Array.prototype.contains = function (e) {
  return this.indexOf(e) !== -1;
};

Array.prototype.size = function () {
  return this.length;
};

Array.prototype.clear = function () {
  this.length = 0;
};

Array.prototype.select = Array.prototype.filter;

Array.prototype.count = function (what) {
  switch (typeof what) {
  case 'undefined': return this.length;
  case 'function':
    var count = 0;
    for (var i = this.length - 1; i >= 0; i--) {
      if (what(this[i])) count++;
    }
    return count;
  default:
    count = 0;
    for (i = this.length - 1; i >= 0; i--) {
      if (this[i] === what) count++;
    }
    return count;
  }
};

/** Inclusive range, like a[n..m] - exclusive one is done by using "slice" */
Array.prototype.range = function (begin, end) {
  if (end === -1 || end === undefined) {
    return this.slice(begin);
  }
  return this.slice(begin, end + 1);
};
<commit_msg>Handle explicit sign in String#format for floats<commit_after>'use strict';

//--- String

function hasLf(s) {
  if (s.slice(-1) !== '\n') return 0;
  if (s.slice(-2) !== '\r\n') return 1;
  return 2;
}

String.prototype.chomp = function (tail) {
  if (tail === undefined) {
    return this.substr(0, this.length - hasLf(this)); // NB: we cut 0 if no LF
  }
  var pos = this.length - tail.length;
  if (this.substr(pos) === tail) {
    return this.substr(0, pos);
  }
  return this.toString(); // unchanged string is returned
};

String.prototype.chop = function (count) {
  if (count === undefined) {
    // special behavior: chop ending \n or \r\n
    count = hasLf(this) || 1; // we cut at least 1
  } else if(typeof count !== 'number') {
    throw new Error('Invalid parameter type for String.count: ' + typeof count);
  }
  return this.substr(0, this.length - count);
};

String.prototype.startWith = function (head) {
  return this.substr(0, head.length) === head;
};

String.prototype.endWith = function (tail) {
  return this.substr(this.length - tail.length) === tail;
};

String.prototype.tail = function (count) {
  if (count <= 0) return '';
  return this.slice(-count);
};

/** Inclusive range, like s[n..m] - exclusive one is done by using "slice" */
String.prototype.range = function (begin, end) {
  if (end === -1 || end === undefined) {
    return this.slice(begin);
  }
  return this.slice(begin, end + 1);
};

String.prototype.format = function (num) {
  if (this[0] !== '%') throw new Error('Invalid format: ' + this.toString());
  var fmt = this.slice(1,-1), res, pos = 0;
  var code = this.slice(-1);
  switch (code) {
  case 'd': case 'x': //'%2d'
    var padChar = ' ';
    if (fmt[pos] === '0') { pos++; padChar = '0'; }
    var len = parseInt(fmt.substr(pos));
    res = num.toString(code === 'x' ? 16 : 10);
    for (var i = len - res.length; i > 0; i--) { res = padChar + res; }
    return res;
  case 'f': //'%.02f'
    var sign = '';
    if (fmt[pos] === '+') { pos++; if (num > 0) sign = '+'; }
    if (fmt[pos++] !== '.') break;
    var prec = parseInt(fmt.substr(pos));
    return sign + num.toFixed(prec);
  }
  throw new Error('Unknown format: ' + this.toString());
};

String.prototype.between = function (low, high) {
  return this >= low && this <= high;
};

function escapeRegexp(str) {
  return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}

String.prototype.replaceAll = function (pattern, replaceBy) {
  if (pattern instanceof RegExp) {
    if (pattern.global) return this.replace(pattern, replaceBy); // just in case...
    var mode = pattern.ignoreCase ? 'gi' : 'g';
    return this.replace(new RegExp(pattern.source, mode), replaceBy);
  }
  // NB: this would be fast and simpler on V8: this.split(pattern).join('')
  return this.replace(new RegExp(escapeRegexp(pattern), 'g'), replaceBy);
};

//--- Array

/** Mimics the Ruby Array constructor. Same limitation as in Ruby: do not use an object unless
 *  you really want all items to "point" to this object (i.e. modifying the objet will affect all items)
 *  @param {int} size
 *  @param {value|func} init - if a value is given, it is assigned to all items in the array.
 *         If a func is given, array[i] = func(i) will be performed for each item.
 *  @return {Array} - the new array
 */
Array.new = function (size, init) {
  if (size === undefined) return [];
  if (init === undefined) return new Array(size);

  var i, a = [];
  if (typeof init === 'function') {
    for (i = 0; i < size; i++) { a[i] = init(i); }
  } else {
    for (i = 0; i < size; i++) { a[i] = init; }
  }
  return a;
};

Array.prototype.contains = function (e) {
  return this.indexOf(e) !== -1;
};

Array.prototype.size = function () {
  return this.length;
};

Array.prototype.clear = function () {
  this.length = 0;
};

Array.prototype.select = Array.prototype.filter;

Array.prototype.count = function (what) {
  switch (typeof what) {
  case 'undefined': return this.length;
  case 'function':
    var count = 0;
    for (var i = this.length - 1; i >= 0; i--) {
      if (what(this[i])) count++;
    }
    return count;
  default:
    count = 0;
    for (i = this.length - 1; i >= 0; i--) {
      if (this[i] === what) count++;
    }
    return count;
  }
};

/** Inclusive range, like a[n..m] - exclusive one is done by using "slice" */
Array.prototype.range = function (begin, end) {
  if (end === -1 || end === undefined) {
    return this.slice(begin);
  }
  return this.slice(begin, end + 1);
};
<|endoftext|>


<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a Dropdown
    should include a <Form.Dropdown> as a child
    should be a select.form-control
    should have the options specified in the `options` prop
    should have an empty set of options if options=[]
    should not include a label if none was specified
    should include a label if one was specified
    should select a specific value when value is set
    should select the first item when value is not set
    should select the first item when value is set to an invalid value
    should have a label.col-xs-3 when form is horizontal and labelColumns=3
    should have a div.col-xs-9 when form is horizontal and labelColumns=3
    should have the appropriate optgroups when options is an object
    should be disabled when disabled flag set
    should not be disabled when disabled flag no set
    should not have a name if the name prop is not set
    should have the appropriate name if the name prop is set

    should have no options when the options is an empty array
    should have no options when the options is an empty object
*/
describe('a Form component containing a Dropdown', () => {

    it('should include a <Form.Dropdown> as a child', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = shallow(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find(Form.Dropdown)).to.have.length(1);
    });

    it('should be a select.form-control', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select.form-control')).to.have.length(1);
    });

    it('should have the options specified in the `options` prop', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');

        options.forEach((opt) => {
            expect(component.find(`option[value="${opt.value}"]`))
                .to.have.length(1, `value=${opt.value}`);
            expect(component.find(`option[value="${opt.value}"]`).text())
                .to.equal(opt.name, `name=${opt.name}`);

        });
    });

    it('should have an empty set of options if options=[]', () => {

        const options = [];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');
    });

    it('should not include a label if none was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('label')).to.have.length(0);
    });

    it('should include a label if one was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const label = 'some label';

        const component = render(
            <Form>
                <Form.Dropdown options={options} label={label} />
            </Form>
        );

        expect(component.find('label')).to.have.length(1);
        expect(component.find('label').text()).to.equal(label);
    });

    it('should select a specific value when value is set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '2';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(value);
    });

    it('should select the first item when value is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should select the first item when value is set to an invalid value', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should have a label.col-xs-3 when form is horizontal and labelColumns=3', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('label.control-label').props().className)
            .to.contain('col-xs-3', 'col-xs-3');
    });

    it('should have a div.col-xs-9 when form is horizontal and labelColumns=3', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('div.form-dropdown-columns').props().className)
            .to.contain('col-xs-9', 'col-xs-9');
    });

    it('should have the appropriate optgroups when options is an object', () => {

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        const label = 'peter piper';
        const value = 2;

        const component = mount(
            <Form>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select optgroup')).to.have.length(2, 'optgroup');
        expect(component.find('select optgroup').at(0).props().label)
            .to.equal('Group 1', 'optgroup 1 text');
        expect(component.find('select optgroup').at(1).props().label)
            .to.equal('Group 2', 'optgroup 2 text');
        expect(component.find('select optgroup').at(0).find('option'))
            .to.have.length(2, 'optgroup 1 options');
        expect(component.find('select optgroup').at(1).find('option'))
            .to.have.length(1, 'optgroup 1 options');
    });

    it('should be disabled when disabled flag set', () => {

        const disabled = true;

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown disabled={disabled} label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().disabled).to.equal(true);
    });

    it('should not be disabled when disabled flag not set', () => {

        const disabled = false;

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown disabled={disabled} label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().disabled).to.equal(false);
    });

    it('should not have a name if the name prop is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().name).to.equal(undefined);
    });

    it('should have the appropriate name if the name prop is set', () => {

        const name = 'blah';

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown name={name} options={options} />
            </Form>
        );

        expect(component.find('select').props().name).to.equal(name);
    });

    it('should have no options when the options is an empty array', () => {

        const onChange = (arg) => arg;

        const options = [];

        const component = render(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');
    });

    it('should have no options when the options is an empty object', () => {

        const onChange = (arg) => arg;

        const options = {};

        const component = render(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(component.find('option')).to.have.length(0);
    });

});

/* *****************************************************************************
the Dropdown element
    should call onChange with the correct value on value change to a valid value
    should call onChange with the first option on value change to a invalid value
    should call onChange with the correct value on value change to a valid value
        from an optgroup
    should call onChange ON INITIALIZATION with the first option (options=array)
        when value is undefined
    should call onChange ON INITIALIZATION with the first option
        (options=object) when value is undefined
*/
describe('the Dropdown element', () => {

    it('should call onChange with the correct value on value change to a valid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue));
    });

    it('should call onChange with the first option on value change to a invalid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options[0].value);
    });

    it('should call onChange with the correct value on value change to a ' +
        'valid value from an optgroup', () => {

        const onChange = sinon.spy();

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options['Group 2'][0].value);
    });

    it('should call onChange ON INITIALIZATION with the first option ' +
        '(options=array) when value is undefined', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        mount(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options[0].value);
    });

    it('should call onChange ON INITIALIZATION with the first option ' +
        '(options=object) when value is undefined', () => {

        const onChange = sinon.spy();

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        mount(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options['Group 1'][0].value);
    });

});
<commit_msg>added specs for dropdown id value<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a Dropdown
    should include a <Form.Dropdown> as a child
    should be a select.form-control
    should have the options specified in the `options` prop
    should have an empty set of options if options=[]
    should not include a label if none was specified
    should include a label if one was specified
    should select a specific value when value is set
    should select the first item when value is not set
    should select the first item when value is set to an invalid value
    should have a label.col-xs-3 when form is horizontal and labelColumns=3
    should have a div.col-xs-9 when form is horizontal and labelColumns=3
    should have the appropriate optgroups when options is an object
    should be disabled when disabled flag set
    should not be disabled when disabled flag no set
    should not have a name if the name prop is not set
    should have the appropriate name if the name prop is set

    should have no options when the options is an empty array
    should have no options when the options is an empty object
*/
describe('a Form component containing a Dropdown', () => {

    it('should include a <Form.Dropdown> as a child', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = shallow(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find(Form.Dropdown)).to.have.length(1);
    });

    it('should be a select.form-control', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select.form-control')).to.have.length(1);
    });

    it('should have the options specified in the `options` prop', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');

        options.forEach((opt) => {
            expect(component.find(`option[value="${opt.value}"]`))
                .to.have.length(1, `value=${opt.value}`);
            expect(component.find(`option[value="${opt.value}"]`).text())
                .to.equal(opt.name, `name=${opt.name}`);

        });
    });

    it('should have an empty set of options if options=[]', () => {

        const options = [];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');
    });

    it('should not include a label if none was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('label')).to.have.length(0);
    });

    it('should include a label if one was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const label = 'some label';

        const component = render(
            <Form>
                <Form.Dropdown options={options} label={label} />
            </Form>
        );

        expect(component.find('label')).to.have.length(1);
        expect(component.find('label').text()).to.equal(label);
    });

    it('should select a specific value when value is set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '2';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(value);
    });

    it('should select the first item when value is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should select the first item when value is set to an invalid value', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should have a label.col-xs-3 when form is horizontal and labelColumns=3', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('label.control-label').props().className)
            .to.contain('col-xs-3', 'col-xs-3');
    });

    it('should have a div.col-xs-9 when form is horizontal and labelColumns=3', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('div.form-dropdown-columns').props().className)
            .to.contain('col-xs-9', 'col-xs-9');
    });

    it('should have the appropriate optgroups when options is an object', () => {

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        const label = 'peter piper';
        const value = 2;

        const component = mount(
            <Form>
                <Form.Dropdown label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select optgroup')).to.have.length(2, 'optgroup');
        expect(component.find('select optgroup').at(0).props().label)
            .to.equal('Group 1', 'optgroup 1 text');
        expect(component.find('select optgroup').at(1).props().label)
            .to.equal('Group 2', 'optgroup 2 text');
        expect(component.find('select optgroup').at(0).find('option'))
            .to.have.length(2, 'optgroup 1 options');
        expect(component.find('select optgroup').at(1).find('option'))
            .to.have.length(1, 'optgroup 1 options');
    });

    it('should be disabled when disabled flag set', () => {

        const disabled = true;

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown disabled={disabled} label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().disabled).to.equal(true);
    });

    it('should not be disabled when disabled flag not set', () => {

        const disabled = false;

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.Dropdown disabled={disabled} label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().disabled).to.equal(false);
    });

    it('should not have a name if the name prop is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().name).to.equal(undefined);
    });

    it('should have the appropriate name if the name prop is set', () => {

        const name = 'blah';

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown name={name} options={options} />
            </Form>
        );

        expect(component.find('select').props().name).to.equal(name);
    });

    it('should have no options when the options is an empty array', () => {

        const onChange = (arg) => arg;

        const options = [];

        const component = render(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');
    });

    it('should have no options when the options is an empty object', () => {

        const onChange = (arg) => arg;

        const options = {};

        const component = render(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(component.find('option')).to.have.length(0);
    });

    it('should have an ID value if provided', () => {

        const id = 'something';

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown id={id} options={options} />
            </Form>
        );

        expect(component.find('select').props().id).to.equal(id);
    });

    it('should have a random ID value if not provided', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().id).to.match(/^form-dropdown-[0-9]+$/);
    });
});

/* *****************************************************************************
the Dropdown element
    should call onChange with the correct value on value change to a valid value
    should call onChange with the first option on value change to a invalid value
    should call onChange with the correct value on value change to a valid value
        from an optgroup
    should call onChange ON INITIALIZATION with the first option (options=array)
        when value is undefined
    should call onChange ON INITIALIZATION with the first option
        (options=object) when value is undefined
*/
describe('the Dropdown element', () => {

    it('should call onChange with the correct value on value change to a valid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue));
    });

    it('should call onChange with the first option on value change to a invalid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options[0].value);
    });

    it('should call onChange with the correct value on value change to a ' +
        'valid value from an optgroup', () => {

        const onChange = sinon.spy();

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options['Group 2'][0].value);
    });

    it('should call onChange ON INITIALIZATION with the first option ' +
        '(options=array) when value is undefined', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        mount(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options[0].value);
    });

    it('should call onChange ON INITIALIZATION with the first option ' +
        '(options=object) when value is undefined', () => {

        const onChange = sinon.spy();

        const options = {
            'Group 1': [
                { value: '1', name: 'one' },
                { value: '2', name: 'two' },
            ],
            'Group 2': [
                { value: '3', name: 'three' },
            ],
        };

        mount(
            <Form>
                <Form.Dropdown options={options} onChange={onChange} />
            </Form>
        );

        expect(onChange.callCount).to.equal(1);
        expect(onChange.args[0][0]).to.equal(options['Group 1'][0].value);
    });

});
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box, { pickBoxProps } from '../box';
import theme from './theme.css';

class IslandGroup extends PureComponent {
  render() {
    const { children, className, color, dark, direction, size, ...otherProps } = this.props;

    const boxProps = pickBoxProps(otherProps);

    const classNames = cx(theme[`direction-${direction}`], theme['island-group'], className);

    const hasMoreThanOneChild = children.length > 1;

    return (
      <Box {...boxProps} className={classNames}>
        {React.Children.map(children, (child, index) => {
          const isFirstChild = index === 0;
          const isLastChild = index === children.length - 1;

          return React.cloneElement(child, {
            ...(!isFirstChild &&
              !isLastChild && {
                borderRadius: 'none',
              }),
            ...(direction === 'horizontal' &&
              hasMoreThanOneChild &&
              isFirstChild && {
                borderBottomRightRadius: 'square',
                borderTopRightRadius: 'square',
              }),
            ...(direction === 'horizontal' &&
              hasMoreThanOneChild &&
              isLastChild && {
                borderBottomLeftRadius: 'square',
                borderTopLeftRadius: 'square',
              }),
            ...(direction === 'vertical' &&
              hasMoreThanOneChild &&
              isFirstChild && {
                borderBottomLeftRadius: 'square',
                borderBottomRightRadius: 'square',
              }),
            ...(direction === 'vertical' &&
              hasMoreThanOneChild &&
              isLastChild && {
                borderTopLeftRadius: 'square',
                borderTopRightRadius: 'square',
              }),
            ...child.props,
            color: color || child.props.color,
            dark: dark || child.props.dark,
            size: size || child.props.size,
          });
        })}
      </Box>
    );
  }
}

IslandGroup.propTypes = {
  children: PropTypes.any,
  className: PropTypes.string,
  color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua', 'white']),
  dark: PropTypes.bool,
  direction: PropTypes.oneOf(['horizontal', 'vertical']),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

IslandGroup.defaultProps = {
  direction: 'horizontal',
};

export default IslandGroup;
<commit_msg>IslandGroup: 'none' is not valid for 'borderRadius', use 'square' instead<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box, { pickBoxProps } from '../box';
import theme from './theme.css';

class IslandGroup extends PureComponent {
  render() {
    const { children, className, color, dark, direction, size, ...otherProps } = this.props;

    const boxProps = pickBoxProps(otherProps);

    const classNames = cx(theme[`direction-${direction}`], theme['island-group'], className);

    const hasMoreThanOneChild = children.length > 1;

    return (
      <Box {...boxProps} className={classNames}>
        {React.Children.map(children, (child, index) => {
          const isFirstChild = index === 0;
          const isLastChild = index === children.length - 1;

          return React.cloneElement(child, {
            ...(!isFirstChild &&
              !isLastChild && {
                borderRadius: 'square',
              }),
            ...(direction === 'horizontal' &&
              hasMoreThanOneChild &&
              isFirstChild && {
                borderBottomRightRadius: 'square',
                borderTopRightRadius: 'square',
              }),
            ...(direction === 'horizontal' &&
              hasMoreThanOneChild &&
              isLastChild && {
                borderBottomLeftRadius: 'square',
                borderTopLeftRadius: 'square',
              }),
            ...(direction === 'vertical' &&
              hasMoreThanOneChild &&
              isFirstChild && {
                borderBottomLeftRadius: 'square',
                borderBottomRightRadius: 'square',
              }),
            ...(direction === 'vertical' &&
              hasMoreThanOneChild &&
              isLastChild && {
                borderTopLeftRadius: 'square',
                borderTopRightRadius: 'square',
              }),
            ...child.props,
            color: color || child.props.color,
            dark: dark || child.props.dark,
            size: size || child.props.size,
          });
        })}
      </Box>
    );
  }
}

IslandGroup.propTypes = {
  children: PropTypes.any,
  className: PropTypes.string,
  color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua', 'white']),
  dark: PropTypes.bool,
  direction: PropTypes.oneOf(['horizontal', 'vertical']),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

IslandGroup.defaultProps = {
  direction: 'horizontal',
};

export default IslandGroup;
<|endoftext|>