<commit_before>import { useState } from 'react'
import { useRouter } from 'next/router'
import { mutate } from 'swr'

const Form = ({ formId, petForm, forNewPet = true }) => {
  const router = useRouter()
  const contentType = 'application/json'
  const [errors, setErrors] = useState({})
  const [message, setMessage] = useState('')

  const [form, setForm] = useState({
    name: petForm.name,
    owner_name: petForm.owner_name,
    species: petForm.species,
    age: petForm.age,
    poddy_trained: petForm.poddy_trained,
    diet: petForm.diet,
    image_url: petForm.image_url,
    likes: petForm.likes,
    dislikes: petForm.dislikes,
  })

  /* The PUT method edits an existing entry in the mongodb database. */
  const putData = async (form) => {
    const { id } = router.query

    try {
      const res = await fetch(`/api/pets/${id}`, {
        method: 'PUT',
        headers: {
          Accept: contentType,
          'Content-Type': contentType,
        },
        body: JSON.stringify(form),
      })
      const { data } = await res.json()

      mutate(`/api/pets/${id}`, data, false) // Update the local data without a revalidation
      router.push('/')
    } catch (error) {
      setMessage('Failed to update pet')
    }
  }

  /* The POST method adds a new entry in the mongodb database. */
  const postData = async (form) => {
    try {
      await fetch('/api/pets', {
        method: 'POST',
        headers: {
          Accept: contentType,
          'Content-Type': contentType,
        },
        body: JSON.stringify(form),
      })
      router.push('/')
    } catch (error) {
      setMessage('Failed to add pet')
    }
  }

  const handleChange = (e) => {
    const target = e.target
    const value =
      target.name === 'poddy_trained' ? target.checked : target.value
    const name = target.name

    setForm({
      ...form,
      [name]: value,
    })
  }

  const handleSubmit = (e) => {
    e.preventDefault()
    const errs = formValidate()
    if (Object.keys(errs).length === 0) {
      forNewPet ? postData(form) : putData(form)
    } else {
      setErrors({ errs })
    }
  }

  /* Makes sure pet info is filled for pet name, owner name, species, and image url*/
  const formValidate = () => {
    let err = {}
    if (!form.name) err.name = 'Name is required'
    if (!form.owner_name) err.owner_name = 'Owner is required'
    if (!form.species) err.species = 'Species is required'
    if (!form.image_url) err.image_url = 'Image URL is required'
    return err
  }

  return (
    <>
      <form id={formId} onSubmit={handleSubmit}>
        Name
        <input
          type="text"
          maxLength="20"
          name="name"
          value={form.name}
          onChange={handleChange}
          required
        />

        Owner
        <input
          type="text"
          maxLength="20"
          name="owner_name"
          value={form.owner_name}
          onChange={handleChange}
          required
        />

        Species
        <input
          type="text"
          maxLength="30"
          name="species"
          value={form.species}
          onChange={handleChange}
          required
        />

        Age
        <input
          type="number"
          name="age"
          value={form.age}
          onChange={handleChange}
        />

        Potty Trained
        <input
          type="checkbox"
          name="poddy_trained"
          checked={form.poddy_trained}
          onChange={handleChange}
        />

        Diet
        <textarea
          name="diet"
          maxLength="60"
          value={form.diet}
          onChange={handleChange}
        />

        Image URL
        <input
          type="url"
          name="image_url"
          value={form.image_url}
          onChange={handleChange}
          required
        />

        Likes
        <textarea
          name="likes"
          maxLength="60"
          value={form.likes}
          onChange={handleChange}
        />

        Dislikes
        <textarea
          name="dislikes"
          maxLength="60"
          value={form.dislikes}
          onChange={handleChange}
        />

        <button type="submit" className="btn">
          Submit
        </button>
      </form>

{message}

        {Object.keys(errors).map((err, index) => (

{err}

        ))}

    </>
  )
}

export default Form
<commit_msg>Fix: with-mongodb-mongoose example improper fetch error handling (#15856)<commit_after>import { useState } from 'react'
import { useRouter } from 'next/router'
import { mutate } from 'swr'

const Form = ({ formId, petForm, forNewPet = true }) => {
  const router = useRouter()
  const contentType = 'application/json'
  const [errors, setErrors] = useState({})
  const [message, setMessage] = useState('')

  const [form, setForm] = useState({
    name: petForm.name,
    owner_name: petForm.owner_name,
    species: petForm.species,
    age: petForm.age,
    poddy_trained: petForm.poddy_trained,
    diet: petForm.diet,
    image_url: petForm.image_url,
    likes: petForm.likes,
    dislikes: petForm.dislikes,
  })

  /* The PUT method edits an existing entry in the mongodb database. */
  const putData = async (form) => {
    const { id } = router.query

    try {
      const res = await fetch(`/api/pets/${id}`, {
        method: 'PUT',
        headers: {
          Accept: contentType,
          'Content-Type': contentType,
        },
        body: JSON.stringify(form),
      })

      // Throw error with status code in case Fetch API req failed
      if (!res.ok) {
        throw new Error(res.status)
      }

      const { data } = await res.json()

      mutate(`/api/pets/${id}`, data, false) // Update the local data without a revalidation
      router.push('/')
    } catch (error) {
      setMessage('Failed to update pet')
    }
  }

  /* The POST method adds a new entry in the mongodb database. */
  const postData = async (form) => {
    try {
      const res = await fetch('/api/pets', {
        method: 'POST',
        headers: {
          Accept: contentType,
          'Content-Type': contentType,
        },
        body: JSON.stringify(form),
      })

      // Throw error with status code in case Fetch API req failed
      if (!res.ok) {
        throw new Error(res.status)
      }

      router.push('/')
    } catch (error) {
      setMessage('Failed to add pet')
    }
  }

  const handleChange = (e) => {
    const target = e.target
    const value =
      target.name === 'poddy_trained' ? target.checked : target.value
    const name = target.name

    setForm({
      ...form,
      [name]: value,
    })
  }

  const handleSubmit = (e) => {
    e.preventDefault()
    const errs = formValidate()
    if (Object.keys(errs).length === 0) {
      forNewPet ? postData(form) : putData(form)
    } else {
      setErrors({ errs })
    }
  }

  /* Makes sure pet info is filled for pet name, owner name, species, and image url*/
  const formValidate = () => {
    let err = {}
    if (!form.name) err.name = 'Name is required'
    if (!form.owner_name) err.owner_name = 'Owner is required'
    if (!form.species) err.species = 'Species is required'
    if (!form.image_url) err.image_url = 'Image URL is required'
    return err
  }

  return (
    <>
      <form id={formId} onSubmit={handleSubmit}>
        Name
        <input
          type="text"
          maxLength="20"
          name="name"
          value={form.name}
          onChange={handleChange}
          required
        />

        Owner
        <input
          type="text"
          maxLength="20"
          name="owner_name"
          value={form.owner_name}
          onChange={handleChange}
          required
        />

        Species
        <input
          type="text"
          maxLength="30"
          name="species"
          value={form.species}
          onChange={handleChange}
          required
        />

        Age
        <input
          type="number"
          name="age"
          value={form.age}
          onChange={handleChange}
        />

        Potty Trained
        <input
          type="checkbox"
          name="poddy_trained"
          checked={form.poddy_trained}
          onChange={handleChange}
        />

        Diet
        <textarea
          name="diet"
          maxLength="60"
          value={form.diet}
          onChange={handleChange}
        />

        Image URL
        <input
          type="url"
          name="image_url"
          value={form.image_url}
          onChange={handleChange}
          required
        />

        Likes
        <textarea
          name="likes"
          maxLength="60"
          value={form.likes}
          onChange={handleChange}
        />

        Dislikes
        <textarea
          name="dislikes"
          maxLength="60"
          value={form.dislikes}
          onChange={handleChange}
        />

        <button type="submit" className="btn">
          Submit
        </button>
      </form>

{message}

        {Object.keys(errors).map((err, index) => (

{err}

        ))}

    </>
  )
}

export default Form
<|endoftext|>


<commit_before>/**
* Copyright (c) 2009 Chris Leonello
* This software is licensed under the GPL version 2.0 and MIT licenses.
*/
(function($) {
    // class: $.jqplot.AxisTickRenderer
    // A "tick" object showing the value of a tick/gridline on the plot.
    $.jqplot.AxisTickRenderer = function(options) {
        // Group: Properties
        $.jqplot.ElemContainer.call(this);
        // prop: mark
        // tick mark on the axis.  One of 'inside', 'outside', 'cross', '' or null.
        this.mark = 'outside';
        // prop: showMark
        // wether or not to show the mark on the axis.
        this.showMark = true;
        // prop: showGridline
        // wether or not to draw the gridline on the grid at this tick.
        this.showGridline = true;
        // prop: isMinorTick
        // if this is a minor tick.
        this.isMinorTick = false;
        // prop: size
        // Length of the tick beyond the grid in pixels.
        this.size = 4;
        // prop:  markSize
        // Length of the tick marks in pixels.  For 'cross' style, length
        // will be stoked above and below axis, so total length will be twice this.
        this.markSize = 4;
        // prop: show
        // wether or not to show the tick (mark and label).
        this.show = true;
        // prop: showLabel
        // wether or not to show the label.
        this.showLabel = true;
        this.label = '';
        this.value = null;
        this._styles = {};
        // prop: formatter
        // A class of a formatter for the tick text.  sprintf by default.
        this.formatter = $.jqplot.DefaultTickFormatter;
        // prop: formatString
        // string passed to the formatter.
        this.formatString = '';
        // prop: fontFamily
        // css spec for the font-family css attribute.
        this.fontFamily;
        // prop: fontSize
        // css spec for the font-size css attribute.
        this.fontSize;
        // prop: textColor
        // css spec for the color attribute.
        this.textColor;
        this._elem;

        $.extend(true, this, options);
    };

    $.jqplot.AxisTickRenderer.prototype.init = function(options) {
        $.extend(true, this, options);
    };

    $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
    $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;

    $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
        this.value = value;
        if (isMinor) {
        	this.isMinorTick = true;
        }
        return this;
    };

    $.jqplot.AxisTickRenderer.prototype.draw = function() {
        if (!this.label) {
        	this.label = this.formatter(this.formatString, this.value);
        }
        style='position:absolute;';
        if (Number(this.label)) {
            style+='style="white-space:nowrap;" ';
        }
        this._elem = $('
'+this.label+'
');
        for (var s in this._styles) {
            this._elem.css(s, this._styles[s]);
        }
        if (this.fontFamily) {
        	this._elem.css('font-family', this.fontFamily);
        }
        if (this.fontSize) {
        	this._elem.css('font-size', this.fontSize);
        }
        if (this.textColor) {
        	this._elem.css('color', this.textColor);
        }
        return this._elem;
    };

    $.jqplot.DefaultTickFormatter = function (format, val) {
        if (typeof val == 'number') {
            if (!format) {
            	format = '%.1f';
            }
            return $.jqplot.sprintf(format, val);
        }
        else {
            return String(val);
        }
    };

    $.jqplot.AxisTickRenderer.prototype.pack = function() {
    };
})(jQuery);<commit_msg>reverting axis tick renderer to pre 0.6.6.  Seems to have introduced more problems.<commit_after>/**
* Copyright (c) 2009 Chris Leonello
* This software is licensed under the GPL version 2.0 and MIT licenses.
*/
(function($) {
    // class: $.jqplot.AxisTickRenderer
    // A "tick" object showing the value of a tick/gridline on the plot.
    $.jqplot.AxisTickRenderer = function(options) {
        // Group: Properties
        $.jqplot.ElemContainer.call(this);
        // prop: mark
        // tick mark on the axis.  One of 'inside', 'outside', 'cross', '' or null.
        this.mark = 'outside';
        // prop: showMark
        // wether or not to show the mark on the axis.
        this.showMark = true;
        // prop: showGridline
        // wether or not to draw the gridline on the grid at this tick.
        this.showGridline = true;
        // prop: isMinorTick
        // if this is a minor tick.
        this.isMinorTick = false;
        // prop: size
        // Length of the tick beyond the grid in pixels.
        this.size = 4;
        // prop:  markSize
        // Length of the tick marks in pixels.  For 'cross' style, length
        // will be stoked above and below axis, so total length will be twice this.
        this.markSize = 4;
        // prop: show
        // wether or not to show the tick (mark and label).
        this.show = true;
        // prop: showLabel
        // wether or not to show the label.
        this.showLabel = true;
        this.label = '';
        this.value = null;
        this._styles = {};
        // prop: formatter
        // A class of a formatter for the tick text.  sprintf by default.
        this.formatter = $.jqplot.DefaultTickFormatter;
        // prop: formatString
        // string passed to the formatter.
        this.formatString = '';
        // prop: fontFamily
        // css spec for the font-family css attribute.
        this.fontFamily;
        // prop: fontSize
        // css spec for the font-size css attribute.
        this.fontSize;
        // prop: textColor
        // css spec for the color attribute.
        this.textColor;
        this._elem;

        $.extend(true, this, options);
    };

    $.jqplot.AxisTickRenderer.prototype.init = function(options) {
        $.extend(true, this, options);
    };

    $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
    $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;

    $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
        this.value = value;
        if (isMinor) {
        	this.isMinorTick = true;
        }
        return this;
    };

    $.jqplot.AxisTickRenderer.prototype.draw = function() {
        if (!this.label) {
        	this.label = this.formatter(this.formatString, this.value);
        }
        style='';
        if (Number(this.label)) {
            style='style="white-space:nowrap;" ';
        }
        this._elem = $('
'+this.label+'
');
        for (var s in this._styles) {
            this._elem.css(s, this._styles[s]);
        }
        if (this.fontFamily) {
        	this._elem.css('font-family', this.fontFamily);
        }
        if (this.fontSize) {
        	this._elem.css('font-size', this.fontSize);
        }
        if (this.textColor) {
        	this._elem.css('color', this.textColor);
        }
        return this._elem;
    };

    $.jqplot.DefaultTickFormatter = function (format, val) {
        if (typeof val == 'number') {
            if (!format) {
            	format = '%.1f';
            }
            return $.jqplot.sprintf(format, val);
        }
        else {
            return String(val);
        }
    };

    $.jqplot.AxisTickRenderer.prototype.pack = function() {
    };
})(jQuery);<|endoftext|>


<commit_before>var deepEqual = require('deep-equal');
var defined = require('defined');
var path = require('path');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;

module.exports = Test;

var nextTick = typeof setImmediate !== 'undefined'
    ? setImmediate
    : process.nextTick
;

inherits(Test, EventEmitter);

var getTestArgs = function (name_, opts_, cb_) {
    var name = '(anonymous)';
    var opts = {};
    var cb;

    for (var i = 0; i < arguments.length; i++) {
        var arg = arguments[i];
        var t = typeof arg;
        if (t === 'string') {
            name = arg;
        }
        else if (t === 'object') {
            opts = arg || opts;
        }
        else if (t === 'function') {
            cb = arg;
        }
    }
    return { name: name, opts: opts, cb: cb };
};

function Test (name_, opts_, cb_) {
    if (! (this instanceof Test)) {
        return new Test(name_, opts_, cb_);
    }

    var args = getTestArgs(name_, opts_, cb_);

    this.readable = true;
    this.name = args.name || '(anonymous)';
    this.assertCount = 0;
    this.pendingCount = 0;
    this._skip = args.opts.skip || false;
    this._plan = undefined;
    this._cb = args.cb;
    this._progeny = [];
    this._ok = true;

    if (args.opts.timeout !== undefined) {
        this.timeoutAfter(args.opts.timeout);
    }

    for (var prop in this) {
        this[prop] = (function bind(self, val) {
            if (typeof val === 'function') {
                return function bound() {
                    return val.apply(self, arguments);
                };
            }
            else return val;
        })(this, this[prop]);
    }
}

Test.prototype.run = function () {
    if (!this._cb || this._skip) {
        return this._end();
    }
    this.emit('prerun');
    this._cb(this);
    this.emit('run');
};

Test.prototype.test = function (name, opts, cb) {
    var self = this;
    var t = new Test(name, opts, cb);
    this._progeny.push(t);
    this.pendingCount++;
    this.emit('test', t);
    t.on('prerun', function () {
        self.assertCount++;
    })

    if (!self._pendingAsserts()) {
        nextTick(function () {
            self._end();
        });
    }

    nextTick(function() {
        if (!self._plan && self.pendingCount == self._progeny.length) {
            self._end();
        }
    });
};

Test.prototype.comment = function (msg) {
    this.emit('result', msg.trim().replace(/^#\s*/, ''));
};

Test.prototype.plan = function (n) {
    this._plan = n;
    this.emit('plan', n);
};

Test.prototype.timeoutAfter = function(ms) {
    if (!ms) throw new Error('timeoutAfter requires a timespan');
    var self = this;
    var timeout = setTimeout(function() {
        self.fail('test timed out after ' + ms + 'ms');
        self.end();
    }, ms);
    this.once('end', function() {
        clearTimeout(timeout);
    });
}

Test.prototype.end = function (err) {
    var self = this;
    if (arguments.length >= 1 && !!err) {
        this.ifError(err);
    }

    if (this.calledEnd) {
        this.fail('.end() called twice');
    }
    this.calledEnd = true;
    this._end();
};

Test.prototype._end = function (err) {
    var self = this;
    if (this._progeny.length) {
        var t = this._progeny.shift();
        t.on('end', function () { self._end() });
        t.run();
        return;
    }

    if (!this.ended) this.emit('end');
    var pendingAsserts = this._pendingAsserts();
    if (!this._planError && this._plan !== undefined && pendingAsserts) {
        this._planError = true;
        this.fail('plan != count', {
            expected : this._plan,
            actual : this.assertCount
        });
    }
    this.ended = true;
};

Test.prototype._exit = function () {
    if (this._plan !== undefined &&
        !this._planError && this.assertCount !== this._plan) {
        this._planError = true;
        this.fail('plan != count', {
            expected : this._plan,
            actual : this.assertCount,
            exiting : true
        });
    }
    else if (!this.ended) {
        this.fail('test exited without ending', {
            exiting: true
        });
    }
};

Test.prototype._pendingAsserts = function () {
    if (this._plan === undefined) {
        return 1;
    }
    else {
        return this._plan - (this._progeny.length + this.assertCount);
    }
};

Test.prototype._assert = function assert (ok, opts) {
    var self = this;
    var extra = opts.extra || {};

    var res = {
        id : self.assertCount ++,
        ok : Boolean(ok),
        skip : defined(extra.skip, opts.skip),
        name : defined(extra.message, opts.message, '(unnamed assert)'),
        operator : defined(extra.operator, opts.operator)
    };
    if (has(opts, 'actual') || has(extra, 'actual')) {
        res.actual = defined(extra.actual, opts.actual);
    }
    if (has(opts, 'expected') || has(extra, 'expected')) {
        res.expected = defined(extra.expected, opts.expected);
    }
    this._ok = Boolean(this._ok && ok);

    if (!ok) {
        res.error = defined(extra.error, opts.error, new Error(res.name));
    }

    if (!ok) {
        var e = new Error('exception');
        var err = (e.stack || '').split('\n');
        var dir = path.dirname(__dirname) + '/';

        for (var i = 0; i < err.length; i++) {
            var m = /^[^\s]*\s*\bat\s+(.+)/.exec(err[i]);
            if (!m) {
                continue;
            }

            var s = m[1].split(/\s+/);
            var filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[1]);
            if (!filem) {
                filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[2]);

                if (!filem) {
                    filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[3]);

                    if (!filem) {
                        continue;
                    }
                }
            }

            if (filem[1].slice(0, dir.length) === dir) {
                continue;
            }

            res.functionName = s[0];
            res.file = filem[1];
            res.line = Number(filem[2]);
            if (filem[3]) res.column = filem[3];

            res.at = m[1];
            break;
        }
    }

    self.emit('result', res);

    var pendingAsserts = self._pendingAsserts();
    if (!pendingAsserts) {
        if (extra.exiting) {
            self._end();
        } else {
            nextTick(function () {
                self._end();
            });
        }
    }

    if (!self._planError && pendingAsserts < 0) {
        self._planError = true;
        self.fail('plan != count', {
            expected : self._plan,
            actual : self._plan - pendingAsserts
        });
    }
};

Test.prototype.fail = function (msg, extra) {
    this._assert(false, {
        message : msg,
        operator : 'fail',
        extra : extra
    });
};

Test.prototype.pass = function (msg, extra) {
    this._assert(true, {
        message : msg,
        operator : 'pass',
        extra : extra
    });
};

Test.prototype.skip = function (msg, extra) {
    this._assert(true, {
        message : msg,
        operator : 'skip',
        skip : true,
        extra : extra
    });
};

Test.prototype.ok
= Test.prototype['true']
= Test.prototype.assert
= function (value, msg, extra) {
    this._assert(value, {
        message : msg,
        operator : 'ok',
        expected : true,
        actual : value,
        extra : extra
    });
};

Test.prototype.notOk
= Test.prototype['false']
= Test.prototype.notok
= function (value, msg, extra) {
    this._assert(!value, {
        message : msg,
        operator : 'notOk',
        expected : false,
        actual : value,
        extra : extra
    });
};

Test.prototype.error
= Test.prototype.ifError
= Test.prototype.ifErr
= Test.prototype.iferror
= function (err, msg, extra) {
    this._assert(!err, {
        message : defined(msg, String(err)),
        operator : 'error',
        actual : err,
        extra : extra
    });
};

Test.prototype.equal
= Test.prototype.equals
= Test.prototype.isEqual
= Test.prototype.is
= Test.prototype.strictEqual
= Test.prototype.strictEquals
= function (a, b, msg, extra) {
    this._assert(a === b, {
        message : defined(msg, 'should be equal'),
        operator : 'equal',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.notEqual
= Test.prototype.notEquals
= Test.prototype.notStrictEqual
= Test.prototype.notStrictEquals
= Test.prototype.isNotEqual
= Test.prototype.isNot
= Test.prototype.not
= Test.prototype.doesNotEqual
= Test.prototype.isInequal
= function (a, b, msg, extra) {
    this._assert(a !== b, {
        message : defined(msg, 'should not be equal'),
        operator : 'notEqual',
        actual : a,
        notExpected : b,
        extra : extra
    });
};

Test.prototype.deepEqual
= Test.prototype.deepEquals
= Test.prototype.isEquivalent
= Test.prototype.same
= function (a, b, msg, extra) {
    this._assert(deepEqual(a, b, { strict: true }), {
        message : defined(msg, 'should be equivalent'),
        operator : 'deepEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.deepLooseEqual
= Test.prototype.looseEqual
= Test.prototype.looseEquals
= function (a, b, msg, extra) {
    this._assert(deepEqual(a, b), {
        message : defined(msg, 'should be equivalent'),
        operator : 'deepLooseEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.notDeepEqual
= Test.prototype.notEquivalent
= Test.prototype.notDeeply
= Test.prototype.notSame
= Test.prototype.isNotDeepEqual
= Test.prototype.isNotDeeply
= Test.prototype.isNotEquivalent
= Test.prototype.isInequivalent
= function (a, b, msg, extra) {
    this._assert(!deepEqual(a, b, { strict: true }), {
        message : defined(msg, 'should not be equivalent'),
        operator : 'notDeepEqual',
        actual : a,
        notExpected : b,
        extra : extra
    });
};

Test.prototype.notDeepLooseEqual
= Test.prototype.notLooseEqual
= Test.prototype.notLooseEquals
= function (a, b, msg, extra) {
    this._assert(!deepEqual(a, b), {
        message : defined(msg, 'should be equivalent'),
        operator : 'notDeepLooseEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype['throws'] = function (fn, expected, msg, extra) {
    if (typeof expected === 'string') {
        msg = expected;
        expected = undefined;
    }

    var caught = undefined;

    try {
        fn();
    } catch (err) {
        caught = { error : err };
        var message = err.message;
        delete err.message;
        err.message = message;
    }

    var passed = caught;

    if (expected instanceof RegExp) {
        passed = expected.test(caught && caught.error);
        expected = String(expected);
    }

    if (typeof expected === 'function' && caught) {
        passed = caught.error instanceof expected;
        caught.error = caught.error.constructor;
    }

    this._assert(passed, {
        message : defined(msg, 'should throw'),
        operator : 'throws',
        actual : caught && caught.error,
        expected : expected,
        error: !passed && caught && caught.error,
        extra : extra
    });
};

Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
    if (typeof expected === 'string') {
        msg = expected;
        expected = undefined;
    }
    var caught = undefined;
    try {
        fn();
    }
    catch (err) {
        caught = { error : err };
    }
    this._assert(!caught, {
        message : defined(msg, 'should not throw'),
        operator : 'throws',
        actual : caught && caught.error,
        expected : expected,
        error : caught && caught.error,
        extra : extra
    });
};

function has (obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}

Test.skip = function (name_, _opts, _cb) {
    var args = getTestArgs.apply(null, arguments);
    args.opts.skip = true;
    return Test(args.name, args.opts, args.cb);
};

// vim: set softtabstop=4 shiftwidth=4:

<commit_msg>Cache `Object.prototype.hasOwnProperty` here also.<commit_after>var deepEqual = require('deep-equal');
var defined = require('defined');
var path = require('path');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;

module.exports = Test;

var nextTick = typeof setImmediate !== 'undefined'
    ? setImmediate
    : process.nextTick
;

inherits(Test, EventEmitter);

var getTestArgs = function (name_, opts_, cb_) {
    var name = '(anonymous)';
    var opts = {};
    var cb;

    for (var i = 0; i < arguments.length; i++) {
        var arg = arguments[i];
        var t = typeof arg;
        if (t === 'string') {
            name = arg;
        }
        else if (t === 'object') {
            opts = arg || opts;
        }
        else if (t === 'function') {
            cb = arg;
        }
    }
    return { name: name, opts: opts, cb: cb };
};

function Test (name_, opts_, cb_) {
    if (! (this instanceof Test)) {
        return new Test(name_, opts_, cb_);
    }

    var args = getTestArgs(name_, opts_, cb_);

    this.readable = true;
    this.name = args.name || '(anonymous)';
    this.assertCount = 0;
    this.pendingCount = 0;
    this._skip = args.opts.skip || false;
    this._plan = undefined;
    this._cb = args.cb;
    this._progeny = [];
    this._ok = true;

    if (args.opts.timeout !== undefined) {
        this.timeoutAfter(args.opts.timeout);
    }

    for (var prop in this) {
        this[prop] = (function bind(self, val) {
            if (typeof val === 'function') {
                return function bound() {
                    return val.apply(self, arguments);
                };
            }
            else return val;
        })(this, this[prop]);
    }
}

Test.prototype.run = function () {
    if (!this._cb || this._skip) {
        return this._end();
    }
    this.emit('prerun');
    this._cb(this);
    this.emit('run');
};

Test.prototype.test = function (name, opts, cb) {
    var self = this;
    var t = new Test(name, opts, cb);
    this._progeny.push(t);
    this.pendingCount++;
    this.emit('test', t);
    t.on('prerun', function () {
        self.assertCount++;
    })

    if (!self._pendingAsserts()) {
        nextTick(function () {
            self._end();
        });
    }

    nextTick(function() {
        if (!self._plan && self.pendingCount == self._progeny.length) {
            self._end();
        }
    });
};

Test.prototype.comment = function (msg) {
    this.emit('result', msg.trim().replace(/^#\s*/, ''));
};

Test.prototype.plan = function (n) {
    this._plan = n;
    this.emit('plan', n);
};

Test.prototype.timeoutAfter = function(ms) {
    if (!ms) throw new Error('timeoutAfter requires a timespan');
    var self = this;
    var timeout = setTimeout(function() {
        self.fail('test timed out after ' + ms + 'ms');
        self.end();
    }, ms);
    this.once('end', function() {
        clearTimeout(timeout);
    });
}

Test.prototype.end = function (err) {
    var self = this;
    if (arguments.length >= 1 && !!err) {
        this.ifError(err);
    }

    if (this.calledEnd) {
        this.fail('.end() called twice');
    }
    this.calledEnd = true;
    this._end();
};

Test.prototype._end = function (err) {
    var self = this;
    if (this._progeny.length) {
        var t = this._progeny.shift();
        t.on('end', function () { self._end() });
        t.run();
        return;
    }

    if (!this.ended) this.emit('end');
    var pendingAsserts = this._pendingAsserts();
    if (!this._planError && this._plan !== undefined && pendingAsserts) {
        this._planError = true;
        this.fail('plan != count', {
            expected : this._plan,
            actual : this.assertCount
        });
    }
    this.ended = true;
};

Test.prototype._exit = function () {
    if (this._plan !== undefined &&
        !this._planError && this.assertCount !== this._plan) {
        this._planError = true;
        this.fail('plan != count', {
            expected : this._plan,
            actual : this.assertCount,
            exiting : true
        });
    }
    else if (!this.ended) {
        this.fail('test exited without ending', {
            exiting: true
        });
    }
};

Test.prototype._pendingAsserts = function () {
    if (this._plan === undefined) {
        return 1;
    }
    else {
        return this._plan - (this._progeny.length + this.assertCount);
    }
};

Test.prototype._assert = function assert (ok, opts) {
    var self = this;
    var extra = opts.extra || {};

    var res = {
        id : self.assertCount ++,
        ok : Boolean(ok),
        skip : defined(extra.skip, opts.skip),
        name : defined(extra.message, opts.message, '(unnamed assert)'),
        operator : defined(extra.operator, opts.operator)
    };
    if (has(opts, 'actual') || has(extra, 'actual')) {
        res.actual = defined(extra.actual, opts.actual);
    }
    if (has(opts, 'expected') || has(extra, 'expected')) {
        res.expected = defined(extra.expected, opts.expected);
    }
    this._ok = Boolean(this._ok && ok);

    if (!ok) {
        res.error = defined(extra.error, opts.error, new Error(res.name));
    }

    if (!ok) {
        var e = new Error('exception');
        var err = (e.stack || '').split('\n');
        var dir = path.dirname(__dirname) + '/';

        for (var i = 0; i < err.length; i++) {
            var m = /^[^\s]*\s*\bat\s+(.+)/.exec(err[i]);
            if (!m) {
                continue;
            }

            var s = m[1].split(/\s+/);
            var filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[1]);
            if (!filem) {
                filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[2]);

                if (!filem) {
                    filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[3]);

                    if (!filem) {
                        continue;
                    }
                }
            }

            if (filem[1].slice(0, dir.length) === dir) {
                continue;
            }

            res.functionName = s[0];
            res.file = filem[1];
            res.line = Number(filem[2]);
            if (filem[3]) res.column = filem[3];

            res.at = m[1];
            break;
        }
    }

    self.emit('result', res);

    var pendingAsserts = self._pendingAsserts();
    if (!pendingAsserts) {
        if (extra.exiting) {
            self._end();
        } else {
            nextTick(function () {
                self._end();
            });
        }
    }

    if (!self._planError && pendingAsserts < 0) {
        self._planError = true;
        self.fail('plan != count', {
            expected : self._plan,
            actual : self._plan - pendingAsserts
        });
    }
};

Test.prototype.fail = function (msg, extra) {
    this._assert(false, {
        message : msg,
        operator : 'fail',
        extra : extra
    });
};

Test.prototype.pass = function (msg, extra) {
    this._assert(true, {
        message : msg,
        operator : 'pass',
        extra : extra
    });
};

Test.prototype.skip = function (msg, extra) {
    this._assert(true, {
        message : msg,
        operator : 'skip',
        skip : true,
        extra : extra
    });
};

Test.prototype.ok
= Test.prototype['true']
= Test.prototype.assert
= function (value, msg, extra) {
    this._assert(value, {
        message : msg,
        operator : 'ok',
        expected : true,
        actual : value,
        extra : extra
    });
};

Test.prototype.notOk
= Test.prototype['false']
= Test.prototype.notok
= function (value, msg, extra) {
    this._assert(!value, {
        message : msg,
        operator : 'notOk',
        expected : false,
        actual : value,
        extra : extra
    });
};

Test.prototype.error
= Test.prototype.ifError
= Test.prototype.ifErr
= Test.prototype.iferror
= function (err, msg, extra) {
    this._assert(!err, {
        message : defined(msg, String(err)),
        operator : 'error',
        actual : err,
        extra : extra
    });
};

Test.prototype.equal
= Test.prototype.equals
= Test.prototype.isEqual
= Test.prototype.is
= Test.prototype.strictEqual
= Test.prototype.strictEquals
= function (a, b, msg, extra) {
    this._assert(a === b, {
        message : defined(msg, 'should be equal'),
        operator : 'equal',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.notEqual
= Test.prototype.notEquals
= Test.prototype.notStrictEqual
= Test.prototype.notStrictEquals
= Test.prototype.isNotEqual
= Test.prototype.isNot
= Test.prototype.not
= Test.prototype.doesNotEqual
= Test.prototype.isInequal
= function (a, b, msg, extra) {
    this._assert(a !== b, {
        message : defined(msg, 'should not be equal'),
        operator : 'notEqual',
        actual : a,
        notExpected : b,
        extra : extra
    });
};

Test.prototype.deepEqual
= Test.prototype.deepEquals
= Test.prototype.isEquivalent
= Test.prototype.same
= function (a, b, msg, extra) {
    this._assert(deepEqual(a, b, { strict: true }), {
        message : defined(msg, 'should be equivalent'),
        operator : 'deepEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.deepLooseEqual
= Test.prototype.looseEqual
= Test.prototype.looseEquals
= function (a, b, msg, extra) {
    this._assert(deepEqual(a, b), {
        message : defined(msg, 'should be equivalent'),
        operator : 'deepLooseEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype.notDeepEqual
= Test.prototype.notEquivalent
= Test.prototype.notDeeply
= Test.prototype.notSame
= Test.prototype.isNotDeepEqual
= Test.prototype.isNotDeeply
= Test.prototype.isNotEquivalent
= Test.prototype.isInequivalent
= function (a, b, msg, extra) {
    this._assert(!deepEqual(a, b, { strict: true }), {
        message : defined(msg, 'should not be equivalent'),
        operator : 'notDeepEqual',
        actual : a,
        notExpected : b,
        extra : extra
    });
};

Test.prototype.notDeepLooseEqual
= Test.prototype.notLooseEqual
= Test.prototype.notLooseEquals
= function (a, b, msg, extra) {
    this._assert(!deepEqual(a, b), {
        message : defined(msg, 'should be equivalent'),
        operator : 'notDeepLooseEqual',
        actual : a,
        expected : b,
        extra : extra
    });
};

Test.prototype['throws'] = function (fn, expected, msg, extra) {
    if (typeof expected === 'string') {
        msg = expected;
        expected = undefined;
    }

    var caught = undefined;

    try {
        fn();
    } catch (err) {
        caught = { error : err };
        var message = err.message;
        delete err.message;
        err.message = message;
    }

    var passed = caught;

    if (expected instanceof RegExp) {
        passed = expected.test(caught && caught.error);
        expected = String(expected);
    }

    if (typeof expected === 'function' && caught) {
        passed = caught.error instanceof expected;
        caught.error = caught.error.constructor;
    }

    this._assert(passed, {
        message : defined(msg, 'should throw'),
        operator : 'throws',
        actual : caught && caught.error,
        expected : expected,
        error: !passed && caught && caught.error,
        extra : extra
    });
};

Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
    if (typeof expected === 'string') {
        msg = expected;
        expected = undefined;
    }
    var caught = undefined;
    try {
        fn();
    }
    catch (err) {
        caught = { error : err };
    }
    this._assert(!caught, {
        message : defined(msg, 'should not throw'),
        operator : 'throws',
        actual : caught && caught.error,
        expected : expected,
        error : caught && caught.error,
        extra : extra
    });
};

var hasOwn = Object.prototype.hasOwnProperty;
function has (obj, prop) {
    return hasOwn.call(obj, prop);
}

Test.skip = function (name_, _opts, _cb) {
    var args = getTestArgs.apply(null, arguments);
    args.opts.skip = true;
    return Test(args.name, args.opts, args.cb);
};

// vim: set softtabstop=4 shiftwidth=4:

<|endoftext|>


<commit_before>/*global require, module, rendr, __dirname */

/**
  Compile and combine handlebar templates into one js file.  Then stitch all javascript files
  (including templates) into one js file
*/
var _ = require('underscore');
var path = require('path');
var stitch  = require('stitch');
var fs      = require('fs');
var stylus = require('stylus');
var mkdirp = require('mkdirp');
var async = require('async');
var walk = require('walk');
var stylusHelpers = require('./stylusHelpers');
var jsPackage;
var exec = require('child_process').exec;

var baseDir = rendr.entryPath;
var rootDir = path.normalize(__dirname + '/../..');
var publicDir = baseDir + '/../public';
var assetsDir = baseDir + '/../assets';

var config = {
  stitchedJsFile: publicDir + '/mergedAssets.js',
  stylusEntryFile: assetsDir + '/stylesheets/index.styl',
  cssOutputFile: publicDir + '/styles.css',
  fingerprintedImagesDir: publicDir + '/fingerprinted',
  tempDir: rootDir + '/tmp',
  hbsTemplateSrcPath: baseDir + '/templates',
  minify: true
};

/**
  Init and start the template and js compiler.
  options:
  - jsSrcPaths: directory where javascript files are found
  - stitchedJsFile: path and filename of where compiled/combined javascript file will be stored
  - minify: true/false if templates.js should be minified
*/
module.exports.init = function(options, logger, callback) {
  if (options) _.extend(config, options);

  // normalize paths
  var toNormalize = ['stitchedJsFile', 'stylusEntryFile', 'cssOutputFile', 'fingerprintedImagesDir', 'tempDir'];
  for (var i in toNormalize) {
    var key = toNormalize[i];
    config[key] = path.normalize(config[key]);
  }

  // create tmp dir
  if (!fs.existsSync(config.tempDir)) {
    fs.mkdirSync(config.tempDir);
  }
  // this is where the merged template file will live
  // notify stitch to look in tempDir for the js file we will place there
  if (!config.jsSrcPaths) config.jsSrcPaths = [];

  // Before we can create the Stitch package, let's grab the filenames
  // of all non-CommonJS dependencies.
  var walker = walk.walk(assetsDir + '/vendor'),
      vendorFiles = [],
      dependencies,
      i,
      filename;

  // If certain ones need to go first, list them.
  dependencies = [
    'jquery-1.8.2.min.js',
    'node_modules/underscore/underscore.js',
    'node_modules/backbone/backbone.js',
    'node_modules/async/lib/async.js',
    'node_modules/node-polyglot/lib/polyglot.js'
  ];

  walker.on('names', function(root, files){
    for (i in files) {
      filename = root + '/' + files[i];
      // Ignore explicitly-ordered deps.
      if (dependencies.indexOf(files[i]) === -1) {
        // Ignore dirs.
        if (filename.slice(filename.length - 3) === '.js') {
          vendorFiles.push(filename);
        }
      }
    }
  });

  // Full path.
  for (i in dependencies) {
    var dep = dependencies[i];
    if (~dep.indexOf('/')) {
      dep = rootDir + '/' + dep;
    } else {
      dep = assetsDir + '/vendor/' + dep;
    }
    dependencies[i] = dep;
  }

  walker.on('end', function(){
    // Sort so we always get the same fingerprint on different systems.
    vendorFiles = vendorFiles.sort();

    // init Stitch
    jsPackage = stitch.createPackage({
      paths: config.jsSrcPaths,
      dependencies: dependencies.concat(vendorFiles)
    });
    callback(undefined, "OK");
  });
};

function compileStylus(stylusPath, callback) {
  fs.readFile(stylusPath, function(err, stylusFile) {
    stylus(stylusFile.toString())
      .include(assetsDir + '/stylesheets')
      .set('filename', config.cssOutputFile)
      .set('compress', config.minify)
      .define('asset-url', stylusHelpers.assetUrl())
      .define('image-url', stylusHelpers.assetUrl('images'))
      .render(function(err, css) {
        if (err) return callback(err);
        callback(null, css);
      });
  });
}

var compiledTemplatePrefix = [];
// compiledTemplatePrefix.push("Handlebars = this.window ? window.Handlebars : require('handlebars');");
compiledTemplatePrefix.push("module.exports = Handlebars.templates = {};");
compiledTemplatePrefix.push("\n");

function compileHbs(options, callback) {
  var srcPath = config.hbsTemplateSrcPath;
  var destFile = config.hbsTemplateSrcPath + "/compiledTemplates.js";

  // compile handlebar templates
  // reference handlebars locally vs globally
  var handlebarsCmd = rootDir + '/node_modules/.bin/handlebars';
  exec(handlebarsCmd + ' ' + srcPath + '/*.hbs', function(err, stdout, stderr) {
    if (err) return callback(err);
    // write compiled templates to file
    var data = compiledTemplatePrefix.join("\n") + stdout;
    fs.writeFile(destFile, data, 'utf8', function(err) {
      if (err) return callback(err);
      console.log(destFile + ' created');
      callback();
    });
  });
}

/**
  Compile templates into one js file, then stitch all js into one js file
*/
module.exports.compile = function(callback) {
  async.series([
    function(next){
      compileHbs(undefined, next);
    },
    function(next){
      mkdirp.sync(config.tempDir + '/assetCompiler/rendr');
      exec('cp -R '+rootDir+'/client '+rootDir+'/tmp/assetCompiler/rendr/; cp -R '+rootDir+'/shared '+rootDir+'/tmp/assetCompiler/rendr/', next);
    },
    function(next){
      jsPackage.compile(function (err, source) {
        if (err) return next(err);
        fs.writeFile(config.stitchedJsFile, source, function (err) {
          if (err) return next(err);
          console.log('Compiled ' + config.stitchedJsFile);
          next(null, {js: config.stitchedJsFile});
        });
      });
    },
    function(next){
      compileStylus(config.stylusEntryFile, function(err, css){
        if (err) return next(err);
        fs.writeFile(config.cssOutputFile, css, function (err) {
          if (err) return next(err);
          console.log('Compiled ' + config.cssOutputFile);
          next(null, {css: config.cssOutputFile});
        });
      });
    }
  ], function(err, results){
    if (err) return callback(err);
    var hash = {};
    results.forEach(function(result){
      _.extend(hash, result);
    });
    callback(null, hash);
  });
};

/**
  return path to compiled/combined javascript file
*/
module.exports.stitchedJsFile = function stitchedJsFile() {
  return config.stitchedJsFile;
};
<commit_msg>cleaning up assetCompiler<commit_after>/*jshint strict: false */
/*global require, module, rendr, __dirname */

/**
  Compile and combine handlebar templates into one js file.  Then stitch all javascript files
  (including templates) into one js file
*/
var _ = require('underscore');
var path = require('path');
var stitch  = require('stitch');
var fs      = require('fs');
var stylus = require('stylus');
var mkdirp = require('mkdirp');
var async = require('async');
var walk = require('walk');
var stylusHelpers = require('./stylusHelpers');
var jsPackage;
var exec = require('child_process').exec;

var baseDir = rendr.entryPath;
var rootDir = path.normalize(__dirname + '/../..');
var publicDir = baseDir + '/../public';
var assetsDir = baseDir + '/../assets';

var config = {
  stitchedJsFile: publicDir + '/mergedAssets.js',
  stylusEntryFile: assetsDir + '/stylesheets/index.styl',
  cssOutputFile: publicDir + '/styles.css',
  fingerprintedImagesDir: publicDir + '/fingerprinted',
  tempDir: rootDir + '/tmp',
  hbsTemplateSrcPath: baseDir + '/templates',
  minify: true
};

/**
  Init and start the template and js compiler.
  options:
  - jsSrcPaths: directory where javascript files are found
  - stitchedJsFile: path and filename of where compiled/combined javascript file will be stored
  - minify: true/false if templates.js should be minified
*/
module.exports.init = function(options, logger, callback) {
  if (options) _.extend(config, options);

  // normalize paths
  var toNormalize = ['stitchedJsFile', 'stylusEntryFile', 'cssOutputFile', 'fingerprintedImagesDir', 'tempDir'];
  for (var i in toNormalize) {
    var key = toNormalize[i];
    config[key] = path.normalize(config[key]);
  }

  // create tmp dir
  if (!fs.existsSync(config.tempDir)) {
    fs.mkdirSync(config.tempDir);
  }
  // this is where the merged template file will live
  // notify stitch to look in tempDir for the js file we will place there
  if (!config.jsSrcPaths) config.jsSrcPaths = [];

  // Before we can create the Stitch package, let's grab the filenames
  // of all non-CommonJS dependencies.
  var walker = walk.walk(assetsDir + '/vendor'),
      vendorFiles = [],
      dependencies,
      i,
      filename;

  // If certain ones need to go first, list them.
  dependencies = [
    'jquery-1.8.2.min.js',
    'node_modules/underscore/underscore.js',
    'node_modules/backbone/backbone.js',
    'node_modules/async/lib/async.js',
    'node_modules/node-polyglot/lib/polyglot.js'
  ];

  walker.on('names', function(root, files){
    for (i in files) {
      filename = root + '/' + files[i];
      // Ignore explicitly-ordered deps.
      if (dependencies.indexOf(files[i]) === -1) {
        // Ignore dirs.
        if (filename.slice(filename.length - 3) === '.js') {
          vendorFiles.push(filename);
        }
      }
    }
  });

  // Full path.
  for (i in dependencies) {
    var dep = dependencies[i];
    if (~dep.indexOf('/')) {
      dep = rootDir + '/' + dep;
    } else {
      dep = assetsDir + '/vendor/' + dep;
    }
    dependencies[i] = dep;
  }

  walker.on('end', function(){
    // Sort so we always get the same fingerprint on different systems.
    vendorFiles = vendorFiles.sort();

    // init Stitch
    jsPackage = stitch.createPackage({
      paths: config.jsSrcPaths,
      dependencies: dependencies.concat(vendorFiles)
    });
    callback(undefined, "OK");
  });
};

function compileStylus(stylusPath, callback) {
  fs.readFile(stylusPath, function(err, stylusFile) {
    stylus(stylusFile.toString())
      .include(assetsDir + '/stylesheets')
      .set('filename', config.cssOutputFile)
      .set('compress', config.minify)
      .define('asset-url', stylusHelpers.assetUrl())
      .define('image-url', stylusHelpers.assetUrl('images'))
      .render(function(err, css) {
        if (err) return callback(err);
        callback(null, css);
      });
  });
}

var compiledTemplatePrefix = [];
// compiledTemplatePrefix.push("Handlebars = this.window ? window.Handlebars : require('handlebars');");
compiledTemplatePrefix.push("module.exports = Handlebars.templates = {};");
compiledTemplatePrefix.push("\n");

function compileHbs(options, callback) {
  var srcPath = config.hbsTemplateSrcPath;
  var destFile = config.hbsTemplateSrcPath + "/compiledTemplates.js";

  // compile handlebar templates
  // reference handlebars locally vs globally
  var handlebarsCmd = rootDir + '/node_modules/.bin/handlebars';
  exec(handlebarsCmd + ' ' + srcPath + '/*.hbs', function(err, stdout, stderr) {
    if (err) return callback(err);
    // write compiled templates to file
    var data = compiledTemplatePrefix.join("\n") + stdout;
    fs.writeFile(destFile, data, 'utf8', function(err) {
      if (err) return callback(err);
      console.log(destFile + ' created');
      callback();
    });
  });
}

/**
  Compile templates into one js file, then stitch all js into one js file
*/
module.exports.compile = function(callback) {
  async.series([
    function(next){
      compileHbs(undefined, next);
    },
    function(next){
      mkdirp.sync(config.tempDir + '/assetCompiler/rendr');
      exec('cp -R '+rootDir+'/client '+rootDir+'/tmp/assetCompiler/rendr/; cp -R '+rootDir+'/shared '+rootDir+'/tmp/assetCompiler/rendr/', next);
    },
    function(next){
      jsPackage.compile(function (err, source) {
        if (err) return next(err);
        fs.writeFile(config.stitchedJsFile, source, function (err) {
          if (err) return next(err);
          console.log('Compiled ' + config.stitchedJsFile);
          next(null, {js: config.stitchedJsFile});
        });
      });
    },
    function(next){
      compileStylus(config.stylusEntryFile, function(err, css){
        if (err) return next(err);
        fs.writeFile(config.cssOutputFile, css, function (err) {
          if (err) return next(err);
          console.log('Compiled ' + config.cssOutputFile);
          next(null, {css: config.cssOutputFile});
        });
      });
    }
  ], function(err, results){
    if (err) return callback(err);
    var hash = {};
    results.forEach(function(result){
      _.extend(hash, result);
    });
    callback(null, hash);
  });
};
<|endoftext|>


<commit_before>const request = require('supertest')
const { last, isArray, isFunction, forIn } = require('lodash')
const { expect, test, afterAll, beforeEach } = global
const Promise = require('bluebird')
const context = {}

function getPathWithContext (paths, index, items) {
  index = parseInt(index)
  let path = ''
  for (var i = 0; i <= index; i++) {
    if (i === index) {
      path += `${paths[index]}`
    } else {
      path += `${paths[i]}/${items[i].id}`
    }
  }
  return path
}

function assertSuccess (status, attrs = null) {
  return (res, strict = true) => {
    expect(res.status).toBe(status)
    if (attrs && strict) {
      forIn(attrs, (v, k) => {
        expect(res.body[k]).toEqual(v)
      })
    }
  }
}

function assertError () {
  return e => { throw e }
}

class ResourceTester {
  constructor (server, paths, options) {
    options = Object.assign({
      teardown: true
    }, options)
    this.server = server
    this.middlewares = []
    this.paths = !isArray(paths) ? [paths] : paths
    if (options.teardown) {
      afterAll(async () => {
        await Promise.promisify(this.server.close).call(this.server)
      })
    }
  }
  get testPath () { return this.paths.join('/:id') }
  use (...middlewares) {
    this.middlewares = this.middlewares.concat(middlewares)
    return this
  }
  setup (setup) {
    beforeEach(async () => {
      const utils = {
        create: async (nestedDatas, options = {}) => {
          nestedDatas = isArray(nestedDatas) ? nestedDatas : [nestedDatas]
          const { use = req => req } = options
          const middlewares = [ use ]
          const items = []
          const responses = []
          for (const index in nestedDatas) {
            const data = nestedDatas[index]
            const req = middlewares.reduce(
              (req, middleware) => {
                middleware(req)
                return req
              },
              request(this.server).post(getPathWithContext(this.paths, index, items))
            )
            const response = data ? req.send(data) : req
            items.push((await response).body)
            responses.push(response)
          }
          return { items, responses }
        }
      }
      const contextSetup = isFunction(setup) ? setup : async ({create}) => {
        return await create(!isArray(setup) ? [setup] : setup)
      }
      Object.assign(context, await contextSetup(utils))
    })

    return this
  }
  applyMiddleware (middleware, req) {
    const middlewares = [ middleware ].concat(this.middlewares)
    return middlewares.reduce((req, middleware) => {
      middleware(req)
      return req
    }, req)
  }
  create (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const { assert = assertSuccess(201, data), assertCatch = assertError(), use = req => req } = options
    test(`POST ${this.testPath}`, async () => {
      const response = !data
        ? last(context.responses)
        : this.applyMiddleware(use, request(this.server).post(
          getPathWithContext(this.paths, this.paths.length - 1, context.items)
        )).send(data)
      await response.then(res => {
        assert(res, { success: assertSuccess(201, data) })
      }).catch(assertCatch)
    })
    return this
  }
  read (data = null, options = {}) {
    this.list(data, options)
    this.item(data, options)
    return this
  }
  list (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(200),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`LIST ${this.testPath}`, async () => {
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          if (isFunction(data)) {
            await data({req})
          } else {
            await req.send(data)
          }
        }
      }
      const response = this.applyMiddleware(use, request(this.server).get(
        getPathWithContext(this.paths, this.paths.length - 1, context.items)
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(200) })
      }).catch(assertCatch)
    })
    return this
  }
  item (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(200),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`GET ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }
      const response = this.applyMiddleware(use, request(this.server).get(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(200), item })
      }).catch(assertCatch)
    })
    return this
  }
  update (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(202),
      assertCatch = assertError(),
      use = req => req,
      patch,
      prepare = true
    } = options
    test(`PATCH ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }

      const response = this.applyMiddleware(use, request(this.server).patch(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      )).send(patch)
      await response.then(res => {
        assert(res, { success: assertSuccess(202, patch) })
      }).catch(assertCatch)
    })
    return this
  }
  destroy (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(204),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`PATCH ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }

      const response = this.applyMiddleware(use, request(this.server).del(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(204) })
      }).catch(assertCatch)
    })
    return this
  }
  crud (options) {
    return this.create().read().update(null, options).destroy()
  }
}

function restful (...args) {
  return new ResourceTester(...args)
}

module.exports = {
  restful,
  request
}
<commit_msg>setup use global middleware<commit_after>const request = require('supertest')
const { last, isArray, isFunction, forIn } = require('lodash')
const { expect, test, afterAll, beforeEach } = global
const Promise = require('bluebird')
const context = {}

function getPathWithContext (paths, index, items) {
  index = parseInt(index)
  let path = ''
  for (var i = 0; i <= index; i++) {
    if (i === index) {
      path += `${paths[index]}`
    } else {
      path += `${paths[i]}/${items[i].id}`
    }
  }
  return path
}

function assertSuccess (status, attrs = null) {
  return (res, strict = true) => {
    expect(res.status).toBe(status)
    if (attrs && strict) {
      forIn(attrs, (v, k) => {
        expect(res.body[k]).toEqual(v)
      })
    }
  }
}

function assertError () {
  return e => { throw e }
}

class ResourceTester {
  constructor (server, paths, options) {
    options = Object.assign({
      teardown: true
    }, options)
    this.server = server
    this.middlewares = []
    this.paths = !isArray(paths) ? [paths] : paths
    if (options.teardown) {
      afterAll(async () => {
        await Promise.promisify(this.server.close).call(this.server)
      })
    }
  }
  get testPath () { return this.paths.join('/:id') }
  use (...middlewares) {
    this.middlewares = this.middlewares.concat(middlewares)
    return this
  }
  setup (setup) {
    beforeEach(async () => {
      const utils = {
        create: async (nestedDatas, options = {}) => {
          nestedDatas = isArray(nestedDatas) ? nestedDatas : [nestedDatas]
          const { use = req => req } = options
          const items = []
          const responses = []
          for (const index in nestedDatas) {
            const data = nestedDatas[index]
            const req = this.applyMiddleware(use,
              request(this.server).post(getPathWithContext(this.paths, index, items))
            )
            const response = data ? req.send(data) : req
            items.push((await response).body)
            responses.push(response)
          }
          return { items, responses }
        }
      }
      const contextSetup = isFunction(setup) ? setup : async ({create}) => {
        return await create(!isArray(setup) ? [setup] : setup)
      }
      Object.assign(context, await contextSetup(utils))
    })

    return this
  }
  applyMiddleware (middleware, req) {
    const middlewares = [ middleware ].concat(this.middlewares)
    return middlewares.reduce((req, middleware) => {
      middleware(req)
      return req
    }, req)
  }
  create (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const { assert = assertSuccess(201, data), assertCatch = assertError(), use = req => req } = options
    test(`POST ${this.testPath}`, async () => {
      const response = !data
        ? last(context.responses)
        : this.applyMiddleware(use, request(this.server).post(
          getPathWithContext(this.paths, this.paths.length - 1, context.items)
        )).send(data)
      await response.then(res => {
        assert(res, { success: assertSuccess(201, data) })
      }).catch(assertCatch)
    })
    return this
  }
  read (data = null, options = {}) {
    this.list(data, options)
    this.item(data, options)
    return this
  }
  list (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(200),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`LIST ${this.testPath}`, async () => {
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          if (isFunction(data)) {
            await data({req})
          } else {
            await req.send(data)
          }
        }
      }
      const response = this.applyMiddleware(use, request(this.server).get(
        getPathWithContext(this.paths, this.paths.length - 1, context.items)
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(200) })
      }).catch(assertCatch)
    })
    return this
  }
  item (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(200),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`GET ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }
      const response = this.applyMiddleware(use, request(this.server).get(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(200), item })
      }).catch(assertCatch)
    })
    return this
  }
  update (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(202),
      assertCatch = assertError(),
      use = req => req,
      patch,
      prepare = true
    } = options
    test(`PATCH ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }

      const response = this.applyMiddleware(use, request(this.server).patch(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      )).send(patch)
      await response.then(res => {
        assert(res, { success: assertSuccess(202, patch) })
      }).catch(assertCatch)
    })
    return this
  }
  destroy (data = null, options = {}) {
    options = isFunction(options) ? {assert: options} : options
    const {
      assert = assertSuccess(204),
      assertCatch = assertError(),
      use = req => req,
      prepare = true
    } = options
    test(`PATCH ${this.testPath}/:id`, async () => {
      let item
      if (prepare) {
        if (data) {
          const req = request(this.server).post(
            getPathWithContext(this.paths, this.paths.length - 1, context.items)
          )
          let res
          if (isFunction(data)) {
            res = await data({req})
          } else {
            res = await req.send(data)
          }
          item = res.body
        } else {
          item = last(context.items)
        }
      } else {
        item = data
      }

      const response = this.applyMiddleware(use, request(this.server).del(
        `${getPathWithContext(this.paths, this.paths.length - 1, context.items)}/${item.id}`
      ))
      await response.then(res => {
        assert(res, { success: assertSuccess(204) })
      }).catch(assertCatch)
    })
    return this
  }
  crud (options) {
    return this.create().read().update(null, options).destroy()
  }
}

function restful (...args) {
  return new ResourceTester(...args)
}

module.exports = {
  restful,
  request
}
<|endoftext|>


<commit_before>(function($) {

    function RichAutocomplete(element, options) {
        this.element = element;
        this.options = options;

        //store the original set of items
        this.items = this.options.items.slice();

        //store all the items
        this.filteredItems = this.items.slice();

        //prepare element
        this.init();

        //bind the focus and blur events
        this.bindEvents();

        //render the list initially
        this.updateList();
    }

    RichAutocomplete.prototype.init = function() {
        //ensure that the type is a div
        if (this.element[0].nodeName !== 'INPUT')
            throw 'Rich Autocomplete - Expected <input> but instead got <' + this.element[0].nodeName.toLowerCase() + '>';

        //wrap element in div
        this.element.wrap('

');

        //create list control and add to the container
        this.list = $('

');

        this.list.css('max-height', this.options.maxHeight + 'px');
        this.list.hide();

        //get the container element
        this.container = this.element.parent();

        //add the list to the container
        this.container.append(this.list);
    };

    RichAutocomplete.prototype.bindEvents = function() {
        var context = this;

        this.element.focus(function(event) {
            context.showList.apply(context, [event]);
        });

        this.element.blur(function(event) {
            context.hideList.apply(context, [event]);
        });

        this.element.keyup(function(event) {
            if (event.keyCode !== 38 && event.keyCode !== 40)
                context.filterResults.apply(context, [event]);
        });

        this.element.keydown(function(event) {

            //up arrow pressed
            if (event.keyCode === 38) context.highlightUp.apply(context, [event]);

            //down arrow pressed
            if (event.keyCode === 40) context.highlightDown.apply(context, [event]);
        });
    };

    RichAutocomplete.prototype.showList = function() {
        this.list.show();
    };

    RichAutocomplete.prototype.hideList = function(event) {
        this.list.hide();

        //remove any highlighting
        this.list.find('.highlighted').removeClass('highlighted');

    };

    RichAutocomplete.prototype.filterResults = function(event) {
        var searchTerm = this.element.val();

        //filter items based on search terms
        this.filteredItems = this.options.filter(this.items, searchTerm);

        //update the list
        this.updateList();
    };

    RichAutocomplete.prototype.updateList = function() {
        var context = this;

        //empty the list of previous items
        this.list.empty();

        //if the list is empty render the empty list item instead
        if (this.filteredItems.length === 0) {
            var emptyItem = $('

');

            //insert rendered item
            emptyItem.append($(this.options.emptyRender()));

            this.list.append(emptyItem);
            return;
        }

        //select item function
        var selectItem = function(event) {
            var itemData = $(this).data('item-data');
            context.selectItem.apply(context, [itemData]);
        };

        //loop through each item and render
        for (var idx = 0; idx < this.filteredItems.length; idx++) {

            //create list item
            var listItem = $('

');

            //insert the rendered template
            listItem.append($(this.options.render(this.filteredItems[idx])));

            //store item data in element data
            listItem.data('item-data', this.filteredItems[idx]);

            //add click event listener
            listItem.mousedown(selectItem);

            //add list item to virtual list control
            this.list.append(listItem);
        }
    };

    RichAutocomplete.prototype.selectItem = function(item) {

        //extract the text from the selected data object
        var itemText = this.options.extractText(item);

        //set the text of the input control
        this.element.val(itemText);

        //inform the select option
        this.options.select(item);

        //update the list
        this.filterResults();
    };

    RichAutocomplete.prototype.highlightUp = function() {
        //find if any items are currently highlighted
        var highlighted = this.list.find('.highlighted');

        if (highlighted.length === 0) {
            // no item is currently highlighted so hide the list
            this.hideList();
        } else if (this.listVisible() && this.filteredItems.length > 0) {

            var listItems = this.list.find('.rich-autocomplete-list-item');

            //get highlighted element index
            var currentIndex = +highlighted.first().attr('index');
            var minIndex = +listItems.first().attr('index');

            if (currentIndex > minIndex) {
                var previousSibling = highlighted.first().prev('.rich-autocomplete-list-item');

                //ensure we have a sibling to move to
                if (previousSibling.length === 0) return;

                //dehighlight previously highlighted
                highlighted.removeClass('highlighted');

                //highlight the next item in the list
                previousSibling.addClass('highlighted');
            } else if (currentIndex === minIndex) {
                this.hideList();
            }
        }
    };

    RichAutocomplete.prototype.highlightDown = function() {
        //find if any items are currently highlighted
        var highlighted = this.list.find('.highlighted');

        if (highlighted.length === 0) {

            //if list is hidden show it
            if (!this.listVisible()) this.showList();

            // no item is currently highlighted so highlight first element
            this.list.find('.rich-autocomplete-list-item').first().addClass('highlighted');
        } else if (this.listVisible()) {

            var listItems = this.list.find('.rich-autocomplete-list-item');

            //get highlighted element index
            var currentIndex = +highlighted.first().attr('index');
            var maxIndex = +listItems.last().attr('index');

            //check to make sure we arent at the bottom
            if (currentIndex < maxIndex) {
                var nextSibling = highlighted.first().next('.rich-autocomplete-list-item');

                //ensure we have a sibling to move to
                if (nextSibling.length === 0) return;

                //dehighlight previously highlighted
                highlighted.removeClass('highlighted');

                //highlight the next item in the list
                nextSibling.addClass('highlighted');

                //we may need to scroll the newly highlighted option into view

                var listHeight = this.list.height();
                var scrollTop = this.list.scrollTop();
                var scrollBottom = scrollTop + listHeight;

                //get the position of the highlighted option
                var highlightTop = nextSibling.position().top + scrollTop;
                var highlightBottom = highlightTop + nextSibling.outerHeight();

                if (highlightBottom >= scrollBottom) {
                    return this.list.scrollTop((highlightBottom - listHeight) > 0 ? highlightBottom - listHeight : 0);
                }
            }
        }
    };

    RichAutocomplete.prototype.listVisible = function() {
        return this.list[0].style.display !== 'none';
    };

    $.fn.richAutocomplete = function(options) {

        var defaultOptions = {
            maxHeight: 200,
            items: [],
            extractText: function(item) {
                return item;
            },
            filter: function(items, searchTerm) {
                return items.filter(function(item) {
                    return item.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
                });
            },
            render: function(item) {
                return '
' + item + '
';
            },
            emptyRender: function() {
                return '
No Matches Found...
';
            },
            select: function(item) {}
        };

        options = $.extend(defaultOptions, options);

        $(this).data('rich-autocomplete', new RichAutocomplete(this, options));
    };

}(jQuery));
<commit_msg>Adding up arrow functionality<commit_after>(function($) {

    function RichAutocomplete(element, options) {
        this.element = element;
        this.options = options;

        //store the original set of items
        this.items = this.options.items.slice();

        //store all the items
        this.filteredItems = this.items.slice();

        //prepare element
        this.init();

        //bind the focus and blur events
        this.bindEvents();

        //render the list initially
        this.updateList();
    }

    RichAutocomplete.prototype.init = function() {
        //ensure that the type is a div
        if (this.element[0].nodeName !== 'INPUT')
            throw 'Rich Autocomplete - Expected <input> but instead got <' + this.element[0].nodeName.toLowerCase() + '>';

        //wrap element in div
        this.element.wrap('

');

        //create list control and add to the container
        this.list = $('

');

        this.list.css('max-height', this.options.maxHeight + 'px');
        this.list.hide();

        //get the container element
        this.container = this.element.parent();

        //add the list to the container
        this.container.append(this.list);
    };

    RichAutocomplete.prototype.bindEvents = function() {
        var context = this;

        this.element.focus(function(event) {
            context.showList.apply(context, [event]);
        });

        this.element.blur(function(event) {
            context.hideList.apply(context, [event]);
        });

        this.element.keyup(function(event) {
            if (event.keyCode !== 38 && event.keyCode !== 40)
                context.filterResults.apply(context, [event]);
        });

        this.element.keydown(function(event) {

            //up arrow pressed
            if (event.keyCode === 38) context.highlightUp.apply(context, [event]);

            //down arrow pressed
            if (event.keyCode === 40) context.highlightDown.apply(context, [event]);
        });
    };

    RichAutocomplete.prototype.showList = function() {
        this.list.show();
    };

    RichAutocomplete.prototype.hideList = function(event) {
        this.list.hide();

        //remove any highlighting
        this.list.find('.highlighted').removeClass('highlighted');

    };

    RichAutocomplete.prototype.filterResults = function(event) {
        var searchTerm = this.element.val();

        //filter items based on search terms
        this.filteredItems = this.options.filter(this.items, searchTerm);

        //update the list
        this.updateList();
    };

    RichAutocomplete.prototype.updateList = function() {
        var context = this;

        //empty the list of previous items
        this.list.empty();

        //if the list is empty render the empty list item instead
        if (this.filteredItems.length === 0) {
            var emptyItem = $('

');

            //insert rendered item
            emptyItem.append($(this.options.emptyRender()));

            this.list.append(emptyItem);
            return;
        }

        //select item function
        var selectItem = function(event) {
            var itemData = $(this).data('item-data');
            context.selectItem.apply(context, [itemData]);
        };

        //loop through each item and render
        for (var idx = 0; idx < this.filteredItems.length; idx++) {

            //create list item
            var listItem = $('

');

            //insert the rendered template
            listItem.append($(this.options.render(this.filteredItems[idx])));

            //store item data in element data
            listItem.data('item-data', this.filteredItems[idx]);

            //add click event listener
            listItem.mousedown(selectItem);

            //add list item to virtual list control
            this.list.append(listItem);
        }
    };

    RichAutocomplete.prototype.selectItem = function(item) {

        //extract the text from the selected data object
        var itemText = this.options.extractText(item);

        //set the text of the input control
        this.element.val(itemText);

        //inform the select option
        this.options.select(item);

        //update the list
        this.filterResults();
    };

    RichAutocomplete.prototype.highlightUp = function() {
        //find if any items are currently highlighted
        var highlighted = this.list.find('.highlighted');

        if (highlighted.length === 0) {
            // no item is currently highlighted so hide the list
            this.hideList();
        } else if (this.listVisible() && this.filteredItems.length > 0) {

            var listItems = this.list.find('.rich-autocomplete-list-item');

            //get highlighted element index
            var currentIndex = +highlighted.first().attr('index');
            var minIndex = +listItems.first().attr('index');

            if (currentIndex > minIndex) {
                var previousSibling = highlighted.first().prev('.rich-autocomplete-list-item');

                //ensure we have a sibling to move to
                if (previousSibling.length === 0) return;

                //dehighlight previously highlighted
                highlighted.removeClass('highlighted');

                //highlight the next item in the list
                previousSibling.addClass('highlighted');

                var listHeight = this.list.height();
                var scrollTop = this.list.scrollTop();

                //get the position of the highlighted option
                var highlightTop = previousSibling.position().top + scrollTop;

                if (highlightTop < scrollTop) {
                    return this.list.scrollTop(highlightTop);
                }

            } else if (currentIndex === minIndex) {
                this.hideList();
            }
        }
    };

    RichAutocomplete.prototype.highlightDown = function() {
        //find if any items are currently highlighted
        var highlighted = this.list.find('.highlighted');

        if (highlighted.length === 0) {

            //if list is hidden show it
            if (!this.listVisible()) this.showList();

            // no item is currently highlighted so highlight first element
            this.list.find('.rich-autocomplete-list-item').first().addClass('highlighted');
        } else if (this.listVisible()) {

            var listItems = this.list.find('.rich-autocomplete-list-item');

            //get highlighted element index
            var currentIndex = +highlighted.first().attr('index');
            var maxIndex = +listItems.last().attr('index');

            //check to make sure we arent at the bottom
            if (currentIndex < maxIndex) {
                var nextSibling = highlighted.first().next('.rich-autocomplete-list-item');

                //ensure we have a sibling to move to
                if (nextSibling.length === 0) return;

                //dehighlight previously highlighted
                highlighted.removeClass('highlighted');

                //highlight the next item in the list
                nextSibling.addClass('highlighted');

                //we may need to scroll the newly highlighted option into view

                var listHeight = this.list.height();
                var scrollTop = this.list.scrollTop();
                var scrollBottom = scrollTop + listHeight;

                //get the position of the highlighted option
                var highlightTop = nextSibling.position().top + scrollTop;
                var highlightBottom = highlightTop + nextSibling.outerHeight();

                if (highlightBottom >= scrollBottom) {
                    return this.list.scrollTop((highlightBottom - listHeight) > 0 ? highlightBottom - listHeight : 0);
                }
            }
        }
    };

    RichAutocomplete.prototype.listVisible = function() {
        return this.list[0].style.display !== 'none';
    };

    $.fn.richAutocomplete = function(options) {

        var defaultOptions = {
            maxHeight: 200,
            items: [],
            extractText: function(item) {
                return item;
            },
            filter: function(items, searchTerm) {
                return items.filter(function(item) {
                    return item.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
                });
            },
            render: function(item) {
                return '
' + item + '
';
            },
            emptyRender: function() {
                return '
No Matches Found...
';
            },
            select: function(item) {}
        };

        options = $.extend(defaultOptions, options);

        $(this).data('rich-autocomplete', new RichAutocomplete(this, options));
    };

}(jQuery));
<|endoftext|>


<commit_before>// bindinator.js Copyright ©2016-2017 Tonio Loewald
/* globals module, console */

(function(module){
'use strict';

var report_container;

function Test(fn) {
	if (!this || this === window) {
		return new Test(fn);
	} else if (typeof fn === 'function') {
		this.fn = fn;
		this.run();
	} else {
		console.error('Test expects a function, got', fn.toString());
	}
}

Test.onError = () => {};

Test.setReportContainer = (destination) => report_container = destination;

function trigger (event_type, target) {
	var targets;
	if (typeof target === 'string') {
		targets = document.querySelectorAll(target);
	} else if (!target.length) {
		targets = [target];
	}
	if (targets.length && !(targets instanceof Array)) {
		targets = [].slice.apply(targets);
	}
	targets.forEach(target => {
		target.dispatchEvent(new Event(event_type, {bubbles: true, view: window}));
	});
}

function report (message, _class_) {
	if (report_container) {
		var p = document.createElement('p');
		if (_class_) {
			p.classList.add(_class_);
		}
		p.textContent = message;
		report_container.appendChild(p);
	} else {
		console.error(message);
	}
}

function quote (x) {
	if (typeof x === 'string') {
		return '"' + x + '"';
	} else if (typeof x === 'number') {
		return x;
	} else if (x instanceof Array) {
		return '[...]';
	} else if (typeof x === 'object') {
		return '{...}';
	} else {
		return x;
	}
}

Test.trigger = trigger;
Test.report = report;

Test.prototype = {
	shouldBe: function(value) {
		this._expected = `expected ${value}`;
		this._test = x => x === value;
	},

	shouldNotBe: function(value) {
		this._expected = `did not expect ${value}`;
		this._test = x => x !== value;
	},

	run: function() {
		var result = this.fn();
		if (result instanceof Promise) {
			result.then(() => {
				if (!this._test(result)) {
					this.failure(this.fn.toString(), '==', quote(result), this._expected);
				} else {
					this.success(this.fn.toString(), '==', quote(result));
				}
			}, () => {
				this.failure('Promise failed');
			});
		} else {
			setTimeout(() => {
				if (!this._test(result)) {
					this.failure(this.fn.toString(), '==', quote(result), this._expected);
				} else {
					this.success(this.fn.toString(), '==', quote(result));
				}
			});
		}
	},

	success: function() {
		report([].slice.apply(arguments).join(' '), 'success');
	},

	failure: function() {
		Test.onError();
		report([].slice.apply(arguments).join(' '), 'failure');
	}
};

module.exports = Test;
}(module));<commit_msg>Added documentation and cleaned up some stuff; also laid groundwork for automatic background testing.<commit_after>/*
# Test
Copyright ©2016-2017 Tonio Loewald

A very simple test library with support for async tests.

To test something:

		Test(*function-expression-that-may-return-promise* [, *description*]).
		shouldBe(*exapectedValue*);

Or sometimes:

		Test(*function-expression-that-may-return-promise* [, *description*]).
		shouldNotBe(*exapectedValue*);

To setup visible reporting:

		Test.
		setReportContainer(*destination_element*);

To set an error callback:

		Test.onError = *function*;
*/

/* globals module, console */

(function(module){
'use strict';

var report_container;
const tests = [];

function Test(fn, description) {
	if (!this || this === window) {
		return new Test(fn, description);
	} else if (typeof fn === 'function') {
		this.fn = fn;
		this.description = description || fn.toString();
		this.status = 'incomplete';
		tests.push(this);
		run(this);
	} else {
		console.error('Test expects a function, got', fn);
	}
}

Test.onError = () => {};

Test.tests = () => tests;

Test.reset = () => tests.splice(0);

Test.setReportContainer = (destination) => report_container = destination;

function trigger (event_type, target) {
	var targets;
	if (typeof target === 'string') {
		targets = document.querySelectorAll(target);
	} else if (!target.length) {
		targets = [target];
	}
	if (targets.length && !(targets instanceof Array)) {
		targets = [].slice.apply(targets);
	}
	targets.forEach(target => {
		target.dispatchEvent(new Event(event_type, {bubbles: true, view: window}));
	});
}

function report (message, _class_) {
	if (report_container) {
		var p = document.createElement('p');
		if (_class_) {
			p.classList.add(_class_);
		}
		p.textContent = message;
		report_container.appendChild(p);
	} else {
		console.error(message);
	}
}

function quote (x) {
	if (typeof x === 'string') {
		return '"' + x + '"';
	} else if (typeof x === 'number') {
		return x;
	} else if (x instanceof Array) {
		return '[...]';
	} else if (typeof x === 'object') {
		return '{...}';
	} else {
		return x;
	}
}

Test.trigger = trigger;
Test.report = report;

function run(test) {
	var result = test.fn();
	if (result instanceof Promise) {
		result.then(() => {
			if (!test._test(result)) {
				failure(test, result);
			} else {
				success(test, result);
			}
		}, () => {
			test.failure('Promise failed');
		});
	} else {
		setTimeout(() => {
			if (!test._test(result)) {
				failure(test, result);
			} else {
				success(test, result);
			}
		});
	}
}

function success(test, result) {
	test.status = 'success';
	report(`${test.description} == ${quote(result)}`, 'success');
}

function failure(test, result) {
	Test.onError();
	test.status = 'failure';
	report(`${test.description} == ${quote(result)}`, 'success');
}

Test.prototype = {
	shouldBe: function(value) {
		if(this._test) {
			throw 'cannot set a different expectation';
		}
		this._expected = `expected ${value}`;
		this._test = x => x === value;
	},

	shouldNotBe: function(value) {
		if(this._test) {
			throw 'cannot set a different expectation';
		}
		this._expected = `did not expect ${value}`;
		this._test = x => x !== value;
	}
};

module.exports = Test;
}(module));<|endoftext|>


<commit_before>/*eslint no-unused-vars: 0 */
var Changelog = {
  changes: {
    "2.0.0": {
      title: "2.0.0 : Workflows have arrived!",
      message: "Chain rules together to form sequences of actions. Even survives page reload!",
      target: "#workflows"
    },
    "2.2.1": {
      title: "FoF updated: Save data between rules!",
      message: "Save data between rule executions. Ideal for workflows.",
      target: "#help-before-context"
    },
    "2.2.2": {
      title: "FoF updated: Unified export/import!",
      message: "Unified export/import for rules & workflows. See changelog!",
      target: "#changelog"
    },
    "2.3.0": {
      title: "FoF updated: Live tutorials!",
      message: "Learn the features of FoF with the live tutorials. See options.",
      target: "#tutorials"
    },
    "2.3.1": {
      title: "FoF updated: More tutorials!",
      message: "Added tutorials for workflows, context and more. Have a look!",
      target: "#tutorials"
    },
    "2.4.0": {
      title: "FoF updated: Screenshot + onlyEmpty",
      message: "Take screenshots! Fill only empty fields!",
      target: "#help-screenshot"
    },
    "2.5.0": {
      title: "FoF updated: setupContent + auto. rematch",
      message: "prepare function for content! automatic rematch for SPAs!",
      target: "#changelog"
    }
  },
  findForVersion: function(version) {
    if(typeof this.changes[version] !== "undefined") {
      return this.changes[version];
    }
    return null;
  }
};
<commit_msg>changelog notification for 2.6.0<commit_after>/*eslint no-unused-vars: 0 */
var Changelog = {
  changes: {
    "2.0.0": {
      title: "2.0.0 : Workflows have arrived!",
      message: "Chain rules together to form sequences of actions. Even survives page reload!",
      target: "#workflows"
    },
    "2.2.1": {
      title: "FoF updated: Save data between rules!",
      message: "Save data between rule executions. Ideal for workflows.",
      target: "#help-before-context"
    },
    "2.2.2": {
      title: "FoF updated: Unified export/import!",
      message: "Unified export/import for rules & workflows. See changelog!",
      target: "#changelog"
    },
    "2.3.0": {
      title: "FoF updated: Live tutorials!",
      message: "Learn the features of FoF with the live tutorials. See options.",
      target: "#tutorials"
    },
    "2.3.1": {
      title: "FoF updated: More tutorials!",
      message: "Added tutorials for workflows, context and more. Have a look!",
      target: "#tutorials"
    },
    "2.4.0": {
      title: "FoF updated: Screenshot + onlyEmpty",
      message: "Take screenshots! Fill only empty fields!",
      target: "#help-screenshot"
    },
    "2.5.0": {
      title: "FoF updated: setupContent + auto. rematch",
      message: "prepare function for content! automatic rematch for SPAs!",
      target: "#changelog"
    },
    "2.6.0": {
      title: "FoF updated: remote rules import",
      message: "Import remote rules from any URL. Share your rules with your colleagues.",
      target: "#help-settingsremoterules"
    }
  },
  findForVersion: function(version) {
    if(typeof this.changes[version] !== "undefined") {
      return this.changes[version];
    }
    return null;
  }
};
<|endoftext|>