<commit_before>/**
 * @file Class测试用例
 * @author fe.xiaowu@gmail.com
 */

define([
    'zui'
], function (Zui) {
    'use strict';

    /**
     * 空参数
     *
     * @type {Array}
     */
    var emptyTypes = [
        '',
        false,
        0,
        null,
        undefined
    ];

    /**
     * 全部类型的参数
     *
     * @type {Array}
     */
    var types = emptyTypes.concat([function () {},
        /1/,
        '1',
        1,
        true,
        {},
        []
    ]);

    /**
     * 检查参数
     *
     * @param  {Function} callback 回调
     *
     * @return {undefined}
     */
    var checkParam = function (callback) {
        if ('function' !== typeof callback) {
            return;
        }

        types.forEach(function (key) {
            emptyTypes.forEach(function (emptyKey) {
                callback(key, emptyKey);
            });
        });
    };

    describe('zui', function () {
        it('Zui', function () {
            expect(typeof Zui).toBe('function');
        });

        it('Zui.extend', function () {
            expect(typeof Zui).toBe('function');
            expect(typeof Zui.extend).toBe('function');
        });

        it('options empty', function () {
            var app = new Zui();
            expect(app.get()).toEqual({});
        });

        it('options', function () {
            var app;

            app = new Zui({
                a: 1
            });
            expect(app.get('a')).toBe(1);

            app = new Zui({
                a: 1
            }, {
                b: 2
            });
            expect(app.get('a')).toBe(1);
            expect(app.get('b')).toBe(2);

            app = new Zui({
                a: 1
            }, {
                b: 2
            }, {
                c: 3,
                a: 4
            });
            expect(app.get('a')).toBe(4);
            expect(app.get('b')).toBe(2);
            expect(app.get('c')).toBe(3);
        });

        it('.on,.one,.off,.trigger,.set check param and return', function () {
            var app = new Zui();

            checkParam(function (key, key2) {
                expect(app.on(key, key2)).toEqual(app);
                expect(app.one(key, key2)).toEqual(app);
                expect(app.off(key, key2)).toEqual(app);
                expect(app.trigger(key, key2)).toEqual(app);
            });
        });

        it('on event', function (done) {
            var app = new Zui();
            var i = 0;

            app.on('event', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event');
            app.trigger('event');

            expect(i).toBe(3);
            setTimeout(done);
        });

        it('on events', function (done) {
            var app = new Zui();
            var i = 0;

            app.on('event event2', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event2');

            expect(i).toBe(2);
            setTimeout(done);
        });

        it('on callback this', function (done) {
            var app = new Zui();

            app.on('callback', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('callback');
        });

        it('on callback param', function (done) {
            var app = new Zui();

            app.on('callback0', function (a) {
                expect(a).toBeUndefined();
            }).trigger('callback0');

            app.on('callback1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
            }).trigger('callback1', 1);

            app.on('callback2', function (a) {
                expect(a).toBe(1);
            }).trigger('callback2', [
                1
            ]);

            app.on('callback3', function (a) {
                expect(a).toEqual({});
            }).trigger('callback3', {});

            app.on('callback4', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBe(3);
            }).trigger('callback4', [
                1,
                2,
                3
            ]);

            app.on('callback5', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback5', {
                a: 1
            });

            app.on('callback6', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback6', [
                {
                    a: 1
                }
            ]);

            setTimeout(done);
        });

        it('one event', function (done) {
            var app = new Zui();
            var i = 0;

            app.one('event', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event');
            app.trigger('event');

            expect(i).toBe(1);
            setTimeout(done);
        });

        it('one events', function (done) {
            var app = new Zui();
            var i = 0;

            app.one('event event2', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event2');

            expect(i).toBe(2);
            setTimeout(done);
        });

        it('one callback this', function (done) {
            var app = new Zui();

            app.one('callback', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('callback');
        });

        it('one callback param', function (done) {
            var app = new Zui();

            app.one('callback0', function (a) {
                expect(a).toBeUndefined();
            }).trigger('callback0');

            app.one('callback1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
            }).trigger('callback1', 1);

            app.one('callback2', function (a) {
                expect(a).toBe(1);
            }).trigger('callback2', [
                1
            ]);

            app.one('callback3', function (a) {
                expect(a).toEqual({});
            }).trigger('callback3', {});

            app.one('callback4', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBe(3);
            }).trigger('callback4', [
                1,
                2,
                3
            ]);

            app.one('callback5', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback5', {
                a: 1
            });

            app.one('callback6', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback6', [
                {
                    a: 1
                }
            ]);

            setTimeout(done);
        });

        it('.off(key, callback)', function () {
            var app = new Zui();
            var num = 0;

            var fn = function () {
                num += 1;
            };

            app.on('1', fn);

            app.one('1 1', function () {
                num += 1;
            });

            app.one('1', function () {
                num += 1;
            });

            app.off('1', fn);
            app.off('1', function () {});

            app.trigger('1');
            app.trigger('1');
            app.trigger('1');

            expect(num).toBe(3);
        });

        it('.off(key)', function () {
            var app = new Zui();
            var num = 0;

            app.on('1', function () {
                num += 1;
            });

            app.one('1 1', function () {
                num += 1;
            });

            app.off('1');

            app.trigger('1');

            expect(num).toBe(0);
        });

        it('.trigger(key)', function (done) {
            var app = new Zui();

            app.on('1', function (a) {
                expect(a).toBeUndefined();
                done();
            });

            app.trigger('1');
        });

        it('.trigger(key, str)', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toBe('str');
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', 'str');
        });

        it('.trigger(key, num)', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', 1);
        });

        it('.trigger(key, {})', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toEqual({
                    a: 1
                });
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', {
                a: 1
            });
        });

        it('.trigger(key, [1, 2])', function (done) {
            var app = new Zui();

            app.on('1', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBeUndefined();
                done();
            });

            app.trigger('1', [
                1,
                2
            ]);
        });

        it('.trigger this', function (done) {
            var app = new Zui();

            app.on('1', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('1');
        });

        it('.trigger(key, [1, 2])', function (done) {
            var app = new Zui();
            var num = 0;

            app.on('1', function () {
                num += 1;
            });

            app.trigger('1').trigger('1');

            expect(num).toBe(2);
            setTimeout(done);
        });

        // get && set
        it('.get', function () {
            var a = {
                a: 1
            };
            var app = new Zui(a);
            expect(app.get()).toEqual(a);

            app = new Zui();
            expect(app.get()).toEqual({});
        });

        it('.get(key)', function () {
            var a = {
                a: 1
            };
            var app = new Zui(a);
            expect(app.get('a')).toBe(1);
            expect(app.get('b')).toBeUndefined();
        });

        it('.set', function () {
            var app = new Zui();

            app.set('a', 1);
            app.set('b', undefined);

            expect(app.get('a')).toBe(1);
            expect(app.get('b')).toBeUndefined();
        });

        it('is', function () {
            var app = new Zui();

            expect(app.is('no')).toBe(false);
            app.is('yes', 1);
            expect(app.is('yes')).toBe(true);

            app.is(true, true);
            expect(app.is(true)).toBe(false);
        });

        it('event destroy', function (done) {
            var app = new Zui();

            app.a = 1;
            app.is('ok', 1);

            expect(app.a).toBe(1);
            expect(app.is('ok')).toBe(true);

            app.on('destroy', function () {
                expect(app.a).toBeUndefined();
                expect(app.is('ok')).toBe(true);

                app.on('test', function () {});
                app.set('destroy', true);
                expect(app.get('destroy')).toBe(true);

                setTimeout(done);
            });

            // 触发多个destroy
            app.trigger('destroy').trigger('destroy').trigger('destroy').on('test', function () {});
        });

        // _
        it('._getListener', function () {
            var app = new Zui();

            expect(Array.isArray(app._getListener('1'))).toBe(true);
        });

        it('._access', function () {
            var app = new Zui();
            var data = {};

            app._access('1 2  3  4  ', function (key) {
                data[key] = true;
            });

            expect(data).toEqual({
                1: true,
                2: true,
                3: true,
                4: true
            });
        });
    });
});
<commit_msg>update: 更新set, get测试<commit_after>/**
 * @file Class测试用例
 * @author fe.xiaowu@gmail.com
 */

define([
    'zui'
], function (Zui) {
    'use strict';

    /**
     * 空参数
     *
     * @type {Array}
     */
    var emptyTypes = [
        '',
        false,
        0,
        null,
        undefined
    ];

    /**
     * 全部类型的参数
     *
     * @type {Array}
     */
    var types = emptyTypes.concat([function () {},
        /1/,
        '1',
        1,
        true,
        {},
        []
    ]);

    /**
     * 检查参数
     *
     * @param  {Function} callback 回调
     *
     * @return {undefined}
     */
    var checkParam = function (callback) {
        if ('function' !== typeof callback) {
            return;
        }

        types.forEach(function (key) {
            emptyTypes.forEach(function (emptyKey) {
                callback(key, emptyKey);
            });
        });
    };

    describe('zui', function () {
        it('Zui', function () {
            expect(typeof Zui).toBe('function');
        });

        it('Zui.extend', function () {
            expect(typeof Zui).toBe('function');
            expect(typeof Zui.extend).toBe('function');
        });

        it('options empty', function () {
            var app = new Zui();
            expect(app.get()).toEqual({});
        });

        it('options', function () {
            var app;

            app = new Zui({
                a: 1
            });
            expect(app.get('a')).toBe(1);

            app = new Zui({
                a: 1
            }, {
                b: 2
            });
            expect(app.get('a')).toBe(1);
            expect(app.get('b')).toBe(2);

            app = new Zui({
                a: 1
            }, {
                b: 2
            }, {
                c: 3,
                a: 4
            });
            expect(app.get('a')).toBe(4);
            expect(app.get('b')).toBe(2);
            expect(app.get('c')).toBe(3);
        });

        it('.on,.one,.off,.trigger,.set check param and return', function () {
            var app = new Zui();

            checkParam(function (key, key2) {
                expect(app.on(key, key2)).toEqual(app);
                expect(app.one(key, key2)).toEqual(app);
                expect(app.off(key, key2)).toEqual(app);
                expect(app.trigger(key, key2)).toEqual(app);
            });
        });

        it('on event', function (done) {
            var app = new Zui();
            var i = 0;

            app.on('event', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event');
            app.trigger('event');

            expect(i).toBe(3);
            setTimeout(done);
        });

        it('on events', function (done) {
            var app = new Zui();
            var i = 0;

            app.on('event event2', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event2');

            expect(i).toBe(2);
            setTimeout(done);
        });

        it('on callback this', function (done) {
            var app = new Zui();

            app.on('callback', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('callback');
        });

        it('on callback param', function (done) {
            var app = new Zui();

            app.on('callback0', function (a) {
                expect(a).toBeUndefined();
            }).trigger('callback0');

            app.on('callback1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
            }).trigger('callback1', 1);

            app.on('callback2', function (a) {
                expect(a).toBe(1);
            }).trigger('callback2', [
                1
            ]);

            app.on('callback3', function (a) {
                expect(a).toEqual({});
            }).trigger('callback3', {});

            app.on('callback4', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBe(3);
            }).trigger('callback4', [
                1,
                2,
                3
            ]);

            app.on('callback5', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback5', {
                a: 1
            });

            app.on('callback6', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback6', [
                {
                    a: 1
                }
            ]);

            setTimeout(done);
        });

        it('one event', function (done) {
            var app = new Zui();
            var i = 0;

            app.one('event', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event');
            app.trigger('event');

            expect(i).toBe(1);
            setTimeout(done);
        });

        it('one events', function (done) {
            var app = new Zui();
            var i = 0;

            app.one('event event2', function () {
                i += 1;
            });

            app.trigger('event');
            app.trigger('event2');

            expect(i).toBe(2);
            setTimeout(done);
        });

        it('one callback this', function (done) {
            var app = new Zui();

            app.one('callback', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('callback');
        });

        it('one callback param', function (done) {
            var app = new Zui();

            app.one('callback0', function (a) {
                expect(a).toBeUndefined();
            }).trigger('callback0');

            app.one('callback1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
            }).trigger('callback1', 1);

            app.one('callback2', function (a) {
                expect(a).toBe(1);
            }).trigger('callback2', [
                1
            ]);

            app.one('callback3', function (a) {
                expect(a).toEqual({});
            }).trigger('callback3', {});

            app.one('callback4', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBe(3);
            }).trigger('callback4', [
                1,
                2,
                3
            ]);

            app.one('callback5', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback5', {
                a: 1
            });

            app.one('callback6', function (a) {
                expect(a.a).toBe(1);
            }).trigger('callback6', [
                {
                    a: 1
                }
            ]);

            setTimeout(done);
        });

        it('.off(key, callback)', function () {
            var app = new Zui();
            var num = 0;

            var fn = function () {
                num += 1;
            };

            app.on('1', fn);

            app.one('1 1', function () {
                num += 1;
            });

            app.one('1', function () {
                num += 1;
            });

            app.off('1', fn);
            app.off('1', function () {});

            app.trigger('1');
            app.trigger('1');
            app.trigger('1');

            expect(num).toBe(3);
        });

        it('.off(key)', function () {
            var app = new Zui();
            var num = 0;

            app.on('1', function () {
                num += 1;
            });

            app.one('1 1', function () {
                num += 1;
            });

            app.off('1');

            app.trigger('1');

            expect(num).toBe(0);
        });

        it('.trigger(key)', function (done) {
            var app = new Zui();

            app.on('1', function (a) {
                expect(a).toBeUndefined();
                done();
            });

            app.trigger('1');
        });

        it('.trigger(key, str)', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toBe('str');
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', 'str');
        });

        it('.trigger(key, num)', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toBe(1);
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', 1);
        });

        it('.trigger(key, {})', function (done) {
            var app = new Zui();

            app.on('1', function (a, b) {
                expect(a).toEqual({
                    a: 1
                });
                expect(b).toBeUndefined();
                done();
            });

            app.trigger('1', {
                a: 1
            });
        });

        it('.trigger(key, [1, 2])', function (done) {
            var app = new Zui();

            app.on('1', function (a, b, c) {
                expect(a).toBe(1);
                expect(b).toBe(2);
                expect(c).toBeUndefined();
                done();
            });

            app.trigger('1', [
                1,
                2
            ]);
        });

        it('.trigger this', function (done) {
            var app = new Zui();

            app.on('1', function () {
                expect(this).toEqual(app);
                done();
            });

            app.trigger('1');
        });

        it('.trigger(key, [1, 2])', function (done) {
            var app = new Zui();
            var num = 0;

            app.on('1', function () {
                num += 1;
            });

            app.trigger('1').trigger('1');

            expect(num).toBe(2);
            setTimeout(done);
        });

        // get && set
        it('.get', function () {
            var a = {
                a: 1
            };
            var app = new Zui(a);
            expect(app.get()).toEqual(a);

            app = new Zui();
            expect(app.get()).toEqual({});
        });

        it('.get(key)', function () {
            var a = {
                a: 1,
                b: {
                    c: {
                        d: 1
                    }
                }
            };
            var app = new Zui(a);
            expect(app.get('a')).toBe(1);
            expect(app.get('b.c.d')).toBe(1);
            expect(app.get('b')).toBeUndefined();
        });

        it('.set', function () {
            var app = new Zui();

            app.set('a', 1);
            app.set('b', undefined);
            app.set('c.d.e.f', 1);

            expect(app.get('a')).toBe(1);
            expect(app.get('c.d.e.f')).toBe(1);
            expect(app.get('b')).toBeUndefined();
            expect(app.get('b.a.x.x.3')).toBeUndefined();
        });

        it('is', function () {
            var app = new Zui();

            expect(app.is('no')).toBe(false);
            app.is('yes', 1);
            expect(app.is('yes')).toBe(true);

            app.is(true, true);
            expect(app.is(true)).toBe(false);
        });

        it('event destroy', function (done) {
            var app = new Zui();

            app.a = 1;
            app.is('ok', 1);

            expect(app.a).toBe(1);
            expect(app.is('ok')).toBe(true);

            app.on('destroy', function () {
                expect(app.a).toBeUndefined();
                expect(app.is('ok')).toBe(true);

                app.on('test', function () {});
                app.set('destroy', true);
                expect(app.get('destroy')).toBe(true);

                setTimeout(done);
            });

            // 触发多个destroy
            app.trigger('destroy').trigger('destroy').trigger('destroy').on('test', function () {});
        });

        // _
        it('._getListener', function () {
            var app = new Zui();

            expect(Array.isArray(app._getListener('1'))).toBe(true);
        });

        it('._access', function () {
            var app = new Zui();
            var data = {};

            app._access('1 2  3  4  ', function (key) {
                data[key] = true;
            });

            expect(data).toEqual({
                1: true,
                2: true,
                3: true,
                4: true
            });
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var trackingSchema = new Schema({
    company: {
      type: String,
      default: ''
    },
    tracking_number: {
      type: Number,
      min: 0
    },
    status: {
      type: String,
      default: ''
    }
});

var shippingSchema = new Schema({
     street: {
      type: String,
      default: ''
     },
     city: {
      type: String,
      default: ''
     },
     state: {
      type: String,
      default: ''
     },
     zip: {
      type: Number,
     },
    tracking: [trackingSchema]
});

var paymentSchema = new Schema({
    pament_method: {
      type: String,
      default: ''
    },
    transaction_id: {
      type: String,
      default: ''
    }
});

var orderSchema = new Schema({
    created: {
      type: Date,
      default: Date.now
    },
    user: {
      type: Schema.ObjectId,
      ref: 'User'
    },
    state: {
      type: String,
      default: ''
    }
    shipping: [shippingSchema]
});

mongoose.model('Order', orderSchema);<commit_msg>product schema soon to be added<commit_after>'use strict';

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var trackingSchema = new Schema({
    company: {
      type: String,
      default: ''
    },
    tracking_number: {
      type: Number,
      min: 0
    },
    status: {
      type: String,
      default: ''
    }
});

var shippingSchema = new Schema({
     street: {
      type: String,
      default: ''
     },
     city: {
      type: String,
      default: ''
     },
     state: {
      type: String,
      default: ''
     },
     zip: {
      type: Number,
     },
    tracking: [trackingSchema]
});

var paymentSchema = new Schema({
    pament_method: {
      type: String,
      default: ''
    },
    transaction_id: {
      type: String,
      default: ''
    }
});

var productSchema = new Schema({
  sku: {} ,
});

var orderSchema = new Schema({
    created: {
      type: Date,
      default: Date.now
    },
    user: {
      type: Schema.ObjectId,
      ref: 'User'
    },
    state: {
      type: String,
      default: ''
    },
    line_items: [productSchema],
    shipping: [shippingSchema]

});

mongoose.model('Order', orderSchema);<|endoftext|>


<commit_before>module.exports = function (options, Cucumber) {
  var failedScenarioLogBuffer = "";
  var undefinedStepLogBuffer  = "";
  var failedStepResults       = Cucumber.Type.Collection();
  var statsJournal            = Cucumber.Listener.StatsJournal();
  var color                   = Cucumber.Util.ConsoleColor;

  var self = Cucumber.Listener.Formatter(options);

  var parentHear = self.hear;
  self.hear = function hear(event, callback) {
    statsJournal.hear(event, function () {
      parentHear(event, callback);
    });
  };

  self.handleStepResultEvent = function handleStepResult(event, callback) {
    var stepResult = event.getPayloadItem('stepResult');
    if (stepResult.isUndefined()) {
      self.handleUndefinedStepResult(stepResult);
    } else if (stepResult.isFailed()) {
      self.handleFailedStepResult(stepResult);
    }
    callback();
  };

  self.handleUndefinedStepResult = function handleUndefinedStepResult(stepResult) {
    var step = stepResult.getStep();
    self.storeUndefinedStep(step);
  };

  self.handleFailedStepResult = function handleFailedStepResult(stepResult) {
    self.storeFailedStepResult(stepResult);
  };

  self.handleAfterScenarioEvent = function handleAfterScenarioEvent(event, callback) {
    if (statsJournal.isCurrentScenarioFailing()) {
      var scenario = event.getPayloadItem('scenario');
      self.storeFailedScenario(scenario);
    }
    callback();
  };

  self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
    self.logSummary();
    callback();
  };

  self.storeFailedStepResult = function storeFailedStepResult(failedStepResult) {
    failedStepResults.add(failedStepResult);
  };

  self.storeFailedScenario = function storeFailedScenario(failedScenario) {
    var name = failedScenario.getName();
    var uri  = failedScenario.getUri();
    var line = failedScenario.getLine();
    self.appendStringToFailedScenarioLogBuffer(uri + ":" + line + " # Scenario: " + name);
  };

  self.storeUndefinedStep = function storeUndefinedStep(step) {
    var snippetBuilder = Cucumber.SupportCode.StepDefinitionSnippetBuilder(step, self.getStepDefinitionSyntax());
    var snippet        = snippetBuilder.buildSnippet();
    self.appendStringToUndefinedStepLogBuffer(snippet);
  };

  self.getStepDefinitionSyntax = function getStepDefinitionSyntax() {
    var syntax = options.coffeeScriptSnippets ? 'CoffeeScript' : 'JavaScript';
    return new Cucumber.SupportCode.StepDefinitionSnippetBuilderSyntax[syntax]();
  };

  self.appendStringToFailedScenarioLogBuffer = function appendStringToFailedScenarioLogBuffer(string) {
    failedScenarioLogBuffer += string + "\n";
  };

  self.appendStringToUndefinedStepLogBuffer = function appendStringToUndefinedStepLogBuffer(string) {
    if (undefinedStepLogBuffer.indexOf(string) == -1)
      undefinedStepLogBuffer += string + "\n";
  };

  self.getFailedScenarioLogBuffer = function getFailedScenarioLogBuffer() {
    return failedScenarioLogBuffer;
  };

  self.getUndefinedStepLogBuffer = function getUndefinedStepLogBuffer() {
    return undefinedStepLogBuffer;
  };

  self.logSummary = function logSummary() {
    if (statsJournal.witnessedAnyFailedStep())
      self.logFailedStepResults();
    self.logScenariosSummary();
    self.logStepsSummary();
    if (statsJournal.witnessedAnyUndefinedStep())
      self.logUndefinedStepSnippets();
  };

  self.logFailedStepResults = function logFailedStepResults() {
    self.log("Failing Scenarios: \n")
    failedStepResults.syncForEach(function(stepResult) {
      self.log(stepResult.getStep().getUri() + ':' + stepResult.getStep().getLine() + '\n');
      self.logFailedStepResult(stepResult)
    })
  };

  self.logFailedStepResult = function logFailedStepResult(stepResult) {
    var failureMessage = stepResult.getFailureException();
    self.log(require("./error_formatter")(failureMessage.stack || failureMessage));
    self.log("\n\n");
  };

  self.logScenariosSummary = function logScenariosSummary() {
    var scenarioCount          = statsJournal.getScenarioCount();
    var passedScenarioCount    = statsJournal.getPassedScenarioCount();
    var undefinedScenarioCount = statsJournal.getUndefinedScenarioCount();
    var pendingScenarioCount   = statsJournal.getPendingScenarioCount();
    var failedScenarioCount    = statsJournal.getFailedScenarioCount();
    var details                = [];

    self.log(scenarioCount + " scenario" + (scenarioCount != 1 ? "s" : ""));
    if (scenarioCount > 0 ) {
      if (failedScenarioCount > 0)
        details.push(color.format('failed', failedScenarioCount + " failed"));
      if (undefinedScenarioCount > 0)
        details.push(color.format('undefined', undefinedScenarioCount + " undefined"));
      if (pendingScenarioCount > 0)
        details.push(color.format('pending', pendingScenarioCount + " pending"));
      if (passedScenarioCount > 0)
        details.push(color.format('passed', passedScenarioCount + " passed"));
      self.log(" (" + details.join(', ') + ")");
    }
    self.log("\n");
  };

  self.logStepsSummary = function logStepsSummary() {
    var stepCount          = statsJournal.getStepCount();
    var passedStepCount    = statsJournal.getPassedStepCount();
    var undefinedStepCount = statsJournal.getUndefinedStepCount();
    var skippedStepCount   = statsJournal.getSkippedStepCount();
    var pendingStepCount   = statsJournal.getPendingStepCount();
    var failedStepCount    = statsJournal.getFailedStepCount();
    var details            = [];

    self.log(stepCount + " step" + (stepCount != 1 ? "s" : ""));
    if (stepCount > 0) {
      if (failedStepCount > 0)
        details.push(color.format('failed', failedStepCount    + " failed"));
      if (undefinedStepCount > 0)
        details.push(color.format('undefined', undefinedStepCount + " undefined"));
      if (pendingStepCount > 0)
        details.push(color.format('pending', pendingStepCount   + " pending"));
      if (skippedStepCount > 0)
        details.push(color.format('skipped', skippedStepCount   + " skipped"));
      if (passedStepCount > 0)
        details.push(color.format('passed', passedStepCount    + " passed"));
      self.log(" (" + details.join(', ') + ")");
    }
    self.log("\n");
  };

  self.logUndefinedStepSnippets = function logUndefinedStepSnippets() {
    var undefinedStepLogBuffer = self.getUndefinedStepLogBuffer();
    self.log(color.format('pending', "\nYou can implement step definitions for undefined steps with these snippets:\n\n"));
    self.log(color.format('pending', undefinedStepLogBuffer));
  };

  return self;
};
<commit_msg>Add source to undefined step result<commit_after>module.exports = function (options, Cucumber) {
  var failedScenarioLogBuffer = "";
  var undefinedStepLogBuffer  = "";
  var failedStepResults       = Cucumber.Type.Collection();
  var undefinedStepResults    = Cucumber.Type.Collection();
  var statsJournal            = Cucumber.Listener.StatsJournal();
  var color                   = Cucumber.Util.ConsoleColor;

  var self = Cucumber.Listener.Formatter(options);

  var parentHear = self.hear;
  self.hear = function hear(event, callback) {
    statsJournal.hear(event, function () {
      parentHear(event, callback);
    });
  };

  self.handleStepResultEvent = function handleStepResult(event, callback) {
    var stepResult = event.getPayloadItem('stepResult');
    if (stepResult.isUndefined()) {
      self.handleUndefinedStepResult(stepResult);
    } else if (stepResult.isFailed()) {
      self.handleFailedStepResult(stepResult);
    }
    callback();
  };

  self.handleUndefinedStepResult = function handleUndefinedStepResult(stepResult) {
    var step = stepResult.getStep();
    self.storeUndefinedStep(step);
  };

  self.handleFailedStepResult = function handleFailedStepResult(stepResult) {
    self.storeFailedStepResult(stepResult);
  };

  self.handleAfterScenarioEvent = function handleAfterScenarioEvent(event, callback) {
    if (statsJournal.isCurrentScenarioFailing()) {
      var scenario = event.getPayloadItem('scenario');
      self.storeFailedScenario(scenario);
    }
    callback();
  };

  self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
    self.logSummary();
    callback();
  };

  self.storeFailedStepResult = function storeFailedStepResult(failedStepResult) {
    failedStepResults.add(failedStepResult);
  };

  self.storeFailedScenario = function storeFailedScenario(failedScenario) {
    var name = failedScenario.getName();
    var uri  = failedScenario.getUri();
    var line = failedScenario.getLine();
    self.appendStringToFailedScenarioLogBuffer(uri + ":" + line + " # Scenario: " + name);
  };

  self.storeUndefinedStep = function storeUndefinedStep(step) {
    var snippetBuilder = Cucumber.SupportCode.StepDefinitionSnippetBuilder(step, self.getStepDefinitionSyntax());
    var snippet        = snippetBuilder.buildSnippet();
    self.appendStringToUndefinedStepLogBuffer(snippet);
    undefinedStepResults.add(step);
  };

  self.getStepDefinitionSyntax = function getStepDefinitionSyntax() {
    var syntax = options.coffeeScriptSnippets ? 'CoffeeScript' : 'JavaScript';
    return new Cucumber.SupportCode.StepDefinitionSnippetBuilderSyntax[syntax]();
  };

  self.appendStringToFailedScenarioLogBuffer = function appendStringToFailedScenarioLogBuffer(string) {
    failedScenarioLogBuffer += string + "\n";
  };

  self.appendStringToUndefinedStepLogBuffer = function appendStringToUndefinedStepLogBuffer(string) {
    if (undefinedStepLogBuffer.indexOf(string) == -1)
      undefinedStepLogBuffer += string + "\n";
  };

  self.getFailedScenarioLogBuffer = function getFailedScenarioLogBuffer() {
    return failedScenarioLogBuffer;
  };

  self.getUndefinedStepLogBuffer = function getUndefinedStepLogBuffer() {
    return undefinedStepLogBuffer;
  };

  self.logSummary = function logSummary() {
    if (statsJournal.witnessedAnyFailedStep())
      self.logFailedStepResults();
    self.logScenariosSummary();
    self.logStepsSummary();
    if (statsJournal.witnessedAnyUndefinedStep())
      self.logUndefinedStepSnippets();
  };

  self.logFailedStepResults = function logFailedStepResults() {
    self.log("Failing Scenarios: \n")
    failedStepResults.syncForEach(function(stepResult) {
      self.log(stepResult.getStep().getUri() + ':' + stepResult.getStep().getLine() + '\n');
      self.logFailedStepResult(stepResult)
    })
  };

  self.logFailedStepResult = function logFailedStepResult(stepResult) {
    var failureMessage = stepResult.getFailureException();
    self.log(require("./error_formatter")(failureMessage.stack || failureMessage));
    self.log("\n\n");
  };

  self.logScenariosSummary = function logScenariosSummary() {
    var scenarioCount          = statsJournal.getScenarioCount();
    var passedScenarioCount    = statsJournal.getPassedScenarioCount();
    var undefinedScenarioCount = statsJournal.getUndefinedScenarioCount();
    var pendingScenarioCount   = statsJournal.getPendingScenarioCount();
    var failedScenarioCount    = statsJournal.getFailedScenarioCount();
    var details                = [];

    self.log(scenarioCount + " scenario" + (scenarioCount != 1 ? "s" : ""));
    if (scenarioCount > 0 ) {
      if (failedScenarioCount > 0)
        details.push(color.format('failed', failedScenarioCount + " failed"));
      if (undefinedScenarioCount > 0)
        details.push(color.format('undefined', undefinedScenarioCount + " undefined"));
      if (pendingScenarioCount > 0)
        details.push(color.format('pending', pendingScenarioCount + " pending"));
      if (passedScenarioCount > 0)
        details.push(color.format('passed', passedScenarioCount + " passed"));
      self.log(" (" + details.join(', ') + ")");
    }
    self.log("\n");
  };

  self.logStepsSummary = function logStepsSummary() {
    var stepCount          = statsJournal.getStepCount();
    var passedStepCount    = statsJournal.getPassedStepCount();
    var undefinedStepCount = statsJournal.getUndefinedStepCount();
    var skippedStepCount   = statsJournal.getSkippedStepCount();
    var pendingStepCount   = statsJournal.getPendingStepCount();
    var failedStepCount    = statsJournal.getFailedStepCount();
    var details            = [];

    self.log(stepCount + " step" + (stepCount != 1 ? "s" : ""));
    if (stepCount > 0) {
      if (failedStepCount > 0)
        details.push(color.format('failed', failedStepCount    + " failed"));
      if (undefinedStepCount > 0)
        details.push(color.format('undefined', undefinedStepCount + " undefined"));
      if (pendingStepCount > 0)
        details.push(color.format('pending', pendingStepCount   + " pending"));
      if (skippedStepCount > 0)
        details.push(color.format('skipped', skippedStepCount   + " skipped"));
      if (passedStepCount > 0)
        details.push(color.format('passed', passedStepCount    + " passed"));
      self.log(" (" + details.join(', ') + ")");
    }
    self.log("\n");
  };

  self.logUndefinedStepSnippets = function logUndefinedStepSnippets() {
    var undefinedStepLogBuffer = self.getUndefinedStepLogBuffer();
    self.log(color.format('pending', "\nYou can implement step definitions for undefined steps with these snippets:\n\n"));
    undefinedStepResults.syncForEach(function(step){
      self.log(step.getUri() + ':' + step.getLine() + '\n');
      var snippetBuilder = Cucumber.SupportCode.StepDefinitionSnippetBuilder(step, self.getStepDefinitionSyntax());
      var snippet        = snippetBuilder.buildSnippet();
      self.log(color.format('pending', snippet))
    });
  };

  return self;
};
<|endoftext|>


<commit_before>/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Text Object will create a line(s) of text using bitmap font
 * You can generate the fnt files using
 * http://www.angelcode.com/products/bmfont/ for windows of
 * http://www.bmglyph.com/ for mac.
 * @class BitmapText
 * @extends DisplayObjectContainer
 * @constructor
 * @param {String} text The copy that you would like the text to display
 * @param {Object} [style] The style parameters
 * @param {String} [style.font] default is "20pt Arial" The size and bitmap font id (must have loaded previously)
 * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right")
 */
 //* @param {Object} [style.font="bold 20pt Arial"] The style and size of the font
PIXI.BitmapText = function(text, style)
{
    PIXI.DisplayObjectContainer.call(this);

    this.setText(text);
    this.setStyle(style);
    this.updateText();
    this.dirty = false

};

// constructor
PIXI.BitmapText.constructor = PIXI.BitmapText;
PIXI.BitmapText.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);

/**
 * Set the copy for the text object
 * @methos setText
 * @param {String} text The copy that you would like the text to display
 */
PIXI.BitmapText.prototype.setText = function(text)
{
    this.text = text || " ";
    this.dirty = true;
};

/**
 * Set the style of the text
 * @method setStyle
 * @param {Object} [style] The style parameters
 * @param {Object} style.font The style and size of the font. If font size is not specified, it uses default bitmap font size. Font name is required
 * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right")
 */
PIXI.BitmapText.prototype.setStyle = function(style)
{
    style = style || {};
    style.align = style.align || "left";
    this.style = style;

    var font = style.font.split(" ");
    this.fontName = font[font.length - 1];
    this.fontSize = font.length >= 2 ? parseInt(font[font.length - 2], 10) : PIXI.BitmapText.fonts[this.fontName].size;

    this.dirty = true;
};

/**
 * Renders text
 * @private
 */
PIXI.BitmapText.prototype.updateText = function()
{
    var data = PIXI.BitmapText.fonts[this.fontName];
    var pos = new PIXI.Point();
    var prevCharCode = null;
    var chars = [];
    var maxLineWidth = 0;
    var lineWidths = [];
    var line = 0;
    var scale = this.fontSize / data.size;
    for(var i = 0; i < this.text.length; i++)
    {
        var charCode = this.text.charCodeAt(i);
        if(charCode == "\n".charCodeAt(0))
        {
            lineWidths.push(pos.x);
            maxLineWidth = Math.max(maxLineWidth, pos.x);
            line++;

            pos.x = 0;
            pos.y += data.lineHeight;
            prevCharCode = null;
            continue;
        }

        var charData = data.chars[charCode];
        if(!charData) continue;

        if(prevCharCode && charData[prevCharCode])
        {
           pos.x += charData.kerning[prevCharCode];
        }
        chars.push({line: line, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)});
        pos.x += charData.xAdvance;

        prevCharCode = charCode;
    }

    lineWidths.push(pos.x);
    maxLineWidth = Math.max(maxLineWidth, pos.x);

    var lineAlignOffsets = [];
    for(i = 0; i <= line; i++)
    {
        var alignOffset = 0;
        if(this.style.align == "right")
        {
            alignOffset = maxLineWidth - lineWidths[i];
        }
        else if(this.style.align == "center")
        {
            alignOffset = (maxLineWidth - lineWidths[i]) / 2;
        }
        lineAlignOffsets.push(alignOffset);
    }

    for(i = 0; i < chars.length; i++)
    {
        var char = PIXI.Sprite.fromFrame(chars[i].charCode);
        char.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale;
        char.position.y = chars[i].position.y * scale;
        char.scale.x = char.scale.y = scale;
        this.addChild(char);
    }

    this.width = pos.x * scale;
    this.height = (pos.y + data.lineHeight) * scale;
};

/**
 * @private
 */
PIXI.BitmapText.prototype.updateTransform = function()
{
	if(this.dirty)
	{
        while(this.children.length > 0)
        {
            this.removeChild(this.getChildAt(0));
        }
        this.updateText();

        this.dirty = false;
	}

	PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
};

PIXI.BitmapText.fonts = {};<commit_msg>docs update<commit_after>/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */

/**
 * A Text Object will create a line(s) of text using bitmap font
 * You can generate the fnt files using
 * http://www.angelcode.com/products/bmfont/ for windows or
 * http://www.bmglyph.com/ for mac.
 * @class BitmapText
 * @extends DisplayObjectContainer
 * @constructor
 * @param {String} text The copy that you would like the text to display
 * @param {Object} style The style parameters
 * @param {String} style.font The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously)
 * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right")
 */
PIXI.BitmapText = function(text, style)
{
    PIXI.DisplayObjectContainer.call(this);

    this.setText(text);
    this.setStyle(style);
    this.updateText();
    this.dirty = false

};

// constructor
PIXI.BitmapText.constructor = PIXI.BitmapText;
PIXI.BitmapText.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);

/**
 * Set the copy for the text object
 * @methos setText
 * @param {String} text The copy that you would like the text to display
 */
PIXI.BitmapText.prototype.setText = function(text)
{
    this.text = text || " ";
    this.dirty = true;
};

/**
 * Set the style of the text
 * @method setStyle
 * @param {Object} style The style parameters
 * @param {String} style.font The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously)
 * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right")
 */
PIXI.BitmapText.prototype.setStyle = function(style)
{
    style = style || {};
    style.align = style.align || "left";
    this.style = style;

    var font = style.font.split(" ");
    this.fontName = font[font.length - 1];
    this.fontSize = font.length >= 2 ? parseInt(font[font.length - 2], 10) : PIXI.BitmapText.fonts[this.fontName].size;

    this.dirty = true;
};

/**
 * Renders text
 * @private
 */
PIXI.BitmapText.prototype.updateText = function()
{
    var data = PIXI.BitmapText.fonts[this.fontName];
    var pos = new PIXI.Point();
    var prevCharCode = null;
    var chars = [];
    var maxLineWidth = 0;
    var lineWidths = [];
    var line = 0;
    var scale = this.fontSize / data.size;
    for(var i = 0; i < this.text.length; i++)
    {
        var charCode = this.text.charCodeAt(i);
        if(charCode == "\n".charCodeAt(0))
        {
            lineWidths.push(pos.x);
            maxLineWidth = Math.max(maxLineWidth, pos.x);
            line++;

            pos.x = 0;
            pos.y += data.lineHeight;
            prevCharCode = null;
            continue;
        }

        var charData = data.chars[charCode];
        if(!charData) continue;

        if(prevCharCode && charData[prevCharCode])
        {
           pos.x += charData.kerning[prevCharCode];
        }
        chars.push({line: line, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)});
        pos.x += charData.xAdvance;

        prevCharCode = charCode;
    }

    lineWidths.push(pos.x);
    maxLineWidth = Math.max(maxLineWidth, pos.x);

    var lineAlignOffsets = [];
    for(i = 0; i <= line; i++)
    {
        var alignOffset = 0;
        if(this.style.align == "right")
        {
            alignOffset = maxLineWidth - lineWidths[i];
        }
        else if(this.style.align == "center")
        {
            alignOffset = (maxLineWidth - lineWidths[i]) / 2;
        }
        lineAlignOffsets.push(alignOffset);
    }

    for(i = 0; i < chars.length; i++)
    {
        var char = PIXI.Sprite.fromFrame(chars[i].charCode);
        char.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale;
        char.position.y = chars[i].position.y * scale;
        char.scale.x = char.scale.y = scale;
        this.addChild(char);
    }

    this.width = pos.x * scale;
    this.height = (pos.y + data.lineHeight) * scale;
};

/**
 * @private
 */
PIXI.BitmapText.prototype.updateTransform = function()
{
	if(this.dirty)
	{
        while(this.children.length > 0)
        {
            this.removeChild(this.getChildAt(0));
        }
        this.updateText();

        this.dirty = false;
	}

	PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
};

PIXI.BitmapText.fonts = {};<|endoftext|>


<commit_before>import {Observable as O} from 'rx';
import Cycle from '@cycle/core';
import {makeDOMDriver} from '@cycle/dom';
import {div, button, svg} from '@cycle/dom';

import {preventDefaultDriver} from '../../drivers/prevent-default';
import {keyboardDriver} from '../../drivers/keyboard';
import {globalEventDriver} from '../../drivers/global-events';

import {IF} from '../../lib/h-helper';

import './index.styl';

const renderArc = (dots, selected, size, radius) =>
  IF(selected !== null, () => {
    const dot = dots[selected];
    const angle = dot.angle;
    const x = Math.sin(angle) * radius;
    const y = -Math.cos(angle) * radius;
    const center = size / 2;
    const mid = angle > Math.PI;
    const midAngle = dots[dots.length / 2 - 1].angle;
    const reverse = angle > midAngle;

    return svg('path', {
      class: 'selection-arc',
      d: `M ${center} ${center - radius}
          A ${radius} ${radius} 0
          ${mid !== reverse ? 1 : 0} ${reverse ? 0 : 1}
          ${x + center} ${y + center}`,
      fill: 'none',
      'stroke-width': 10,
      'marker-end': 'url(#markerArrow)',
    });
  })
;

// the the horizontal offset for text at the given angle
// (in radians)
const textAnchor = (angle) => {
  const sin = Math.sin(angle);

  if (sin > 0.01) {
    return 'start';
  } else if (sin < -0.01) {
    return 'end';
  } else {
    return 'middle';
  }
};

// get the vertical offset for text at the given angle
// (in radians)
const baseLine = (angle) => {
  const cos = -Math.cos(angle);

  if (cos > 0.01) {
    return 'text-before-edge';
  } else if (cos < -0.01) {
    return 'text-after-edge';
  } else {
    return 'central';
  }
};

// render the dots as svg element
const renderDots = (dots, selected = null) => {
  const dotRadius = 50;
  const radius = 150 * Math.sqrt(dots.length);
  const size = 2 * (radius + dotRadius);
  const center = size / 2;
  const padding = 100;

  const overflowAngle = (
  dots[
    Math.floor((dots.length - 1) / 2)
  ].angle +
  dots[
    Math.ceil((dots.length - 1) / 2)
  ].angle
  ) / 2;

  return svg('svg', {
    attributes: {
      width: 500,
      height: 500,
      class: 'number-circle',
      viewBox: `
        ${-padding}
        ${-padding}
        ${size + 2 * padding}
        ${size + 2 * padding}
      `,
      preserveAspectRatio: 'xMidYMid meet',
    },
  }, [
    svg('defs', [
      svg('marker', {
        id: 'markerArrow',
        markerWidth: 3,
        markerHeight: 4,
        refX: 2,
        refY: 2,
        orient: 'auto',
        class: 'arrow-head',
      }, [
        svg('path', {
          d: 'M0,0 L0,4 L3,2 L0,0',
        }),
      ]),
    ]),
    dots.map((dot, dotIndex) => svg('g', {
      attributes: {
        class: 'number-dot' +
          (selected === dotIndex ? ' state-selected' : ''),
        'data-dot-index': dotIndex,
      },
    }, [
      svg('circle', {
        class: 'number-dot-background',
        cx: center + Math.sin(dot.angle) * radius,
        cy: center - Math.cos(dot.angle) * radius,
        r: 50,
      }),
      svg('text', {
        class: 'number-dot-label',
        x: center + Math.sin(dot.angle) * radius,
        y: center - Math.cos(dot.angle) * radius,
        fill: '#fff',
        'font-size': '50px',
        'text-anchor': 'middle',
        'dominant-baseline': 'central',
      }, dot.value.toString()),
      svg('text', {
        class: 'number-dot-pattern',
        x: center + Math.sin(dot.angle) * (radius * 1 + 70),
        y: center - Math.cos(dot.angle) * (radius * 1 + 70),
        fill: '#000',
        'font-size': '50px',
        'text-anchor': textAnchor(dot.angle),
        'dominant-baseline': baseLine(dot.angle),
      }, dot.pattern.toString()),
    ])),
    renderArc(dots, selected, size, radius - dotRadius * 1.5),
    svg('line', {
      class: 'overflow-line',
      x1: center,
      y1: center,
      x2: center + Math.sin(overflowAngle) * (radius + dotRadius * 3),
      y2: center - Math.cos(overflowAngle) * (radius + dotRadius * 3),
      stroke: 'black',
      'stroke-width': '10',
    }),
  ]);
};

const renderButtons = (state) => div('.button-bar',[
  // The button for removing a bit
  button('.bit-button', {
    attributes: {
      'data-action': 'remove-bit',
    },
    disabled: state.canRemoveBits ? void 0 : 'true',
  }, 'Remove Bit'),

  // The button for adding a bit
  button('.bit-button', {
    attributes: {
      'data-action': 'add-bit',
    },
    disabled: state.canAddBits ? void 0 : 'true',
  }, 'Add Bit'),
]);

// convert array of angles into svg
const render = (state) =>
  div('.number-circle-container', [
    div('.bit-count', ['Number of bits: ', state.bitCount]),
    renderButtons(state),
    renderDots(state.dots, state.selected),
  ])
;

// convert numbert into bit pattern string of length
// bitCount.
const bitPattern = (number, bitCount) =>
  Array
    .apply(Array, {length: bitCount})
    .map((__, b) =>
      (1 << (bitCount - b - 1)) & number ? 1 : 0
    ).join('')
;

const intValue = (number, bitCountPow2) =>
  number >= bitCountPow2 / 2 ? number - bitCountPow2 : number
;

// generate array of angles for a number circle of
// given bitCount.
const dotArray = (bitCount) =>
  Array
  // init array of length 2^bits.
  .apply(Array, {length: Math.pow(2, bitCount)})
  // map array to angles
  .map((_, index, all) => ({
    angle: 2 * Math.PI * index / all.length,
    value: intValue(index, all.length),
    pattern: bitPattern(index, bitCount),
  }))
;

const intent = (DOM) => {
  return {
    addBit$: DOM.select('[data-action="add-bit"]').events('click'),
    removeBit$: DOM.select('[data-action="remove-bit"]').events('click'),

    selectBit$: DOM.select('[data-dot-index]')
      .events('click').map(
        (evt) => parseInt(evt.ownerTarget.getAttribute('data-dot-index'), 10)
      ),
  };
};

const model = (initialBitCount, actions) => {
  const modifierFunction$ = O.merge([
    actions.addBit$.map(() => (state) =>
      ({bitCount: state.bitCount + 1})
    ),
    actions.removeBit$.map(() => (state) =>
      ({bitCount: state.bitCount - 1})
    ),
    actions.selectBit$.map((bitIndex) => (state) =>
      ({bitCount: state.bitCount, selected: bitIndex})
    ),
  ]);

  return modifierFunction$
  // number of bits to generate the number circle for
  .startWith({bitCount: initialBitCount, selected: null})
  // apply the function
  .scan((state, modifierFunction) => modifierFunction(state))
  // convert number of bit's into array of angles
  .map(({bitCount, selected}) => ({
    // number of bits
    bitCount,
    // the dots to draw
    dots: dotArray(bitCount),
    // the index of the selected dot
    selected,
    // boolean if more bits can be added
    canAddBits: bitCount < 5,
    // boolean fi a bit can be removed
    canRemoveBits: bitCount > 1,
  }));
};

const view = (state$) =>
  state$.map(render)
;

const numberCircleApp = (sources) => {
  const actions = intent(sources.DOM);
  const state$ = model(3, actions);
  const vtree$ = view(state$);

  return {
    DOM: vtree$,
  };
};

const drivers = {
  DOM: makeDOMDriver('#app'),
  preventDefault: preventDefaultDriver,
  keydown: keyboardDriver,
  globalEvents: globalEventDriver,
};

Cycle.run(numberCircleApp, drivers);
<commit_msg>simplify mapping<commit_after>import {Observable as O} from 'rx';
import Cycle from '@cycle/core';
import {makeDOMDriver} from '@cycle/dom';
import {div, button, svg} from '@cycle/dom';

import {preventDefaultDriver} from '../../drivers/prevent-default';
import {keyboardDriver} from '../../drivers/keyboard';
import {globalEventDriver} from '../../drivers/global-events';

import {IF} from '../../lib/h-helper';

import './index.styl';

const renderArc = (dots, selected, size, radius) =>
  IF(selected !== null, () => {
    const dot = dots[selected];
    const angle = dot.angle;
    const x = Math.sin(angle) * radius;
    const y = -Math.cos(angle) * radius;
    const center = size / 2;
    const mid = angle > Math.PI;
    const midAngle = dots[dots.length / 2 - 1].angle;
    const reverse = angle > midAngle;

    return svg('path', {
      class: 'selection-arc',
      d: `M ${center} ${center - radius}
          A ${radius} ${radius} 0
          ${mid !== reverse ? 1 : 0} ${reverse ? 0 : 1}
          ${x + center} ${y + center}`,
      fill: 'none',
      'stroke-width': 10,
      'marker-end': 'url(#markerArrow)',
    });
  })
;

// the the horizontal offset for text at the given angle
// (in radians)
const textAnchor = (angle) => {
  const sin = Math.sin(angle);

  if (sin > 0.01) {
    return 'start';
  } else if (sin < -0.01) {
    return 'end';
  } else {
    return 'middle';
  }
};

// get the vertical offset for text at the given angle
// (in radians)
const baseLine = (angle) => {
  const cos = -Math.cos(angle);

  if (cos > 0.01) {
    return 'text-before-edge';
  } else if (cos < -0.01) {
    return 'text-after-edge';
  } else {
    return 'central';
  }
};

// render the dots as svg element
const renderDots = (dots, selected = null) => {
  const dotRadius = 50;
  const radius = 150 * Math.sqrt(dots.length);
  const size = 2 * (radius + dotRadius);
  const center = size / 2;
  const padding = 100;

  const overflowAngle = (
  dots[
    Math.floor((dots.length - 1) / 2)
  ].angle +
  dots[
    Math.ceil((dots.length - 1) / 2)
  ].angle
  ) / 2;

  return svg('svg', {
    attributes: {
      width: 500,
      height: 500,
      class: 'number-circle',
      viewBox: `
        ${-padding}
        ${-padding}
        ${size + 2 * padding}
        ${size + 2 * padding}
      `,
      preserveAspectRatio: 'xMidYMid meet',
    },
  }, [
    svg('defs', [
      svg('marker', {
        id: 'markerArrow',
        markerWidth: 3,
        markerHeight: 4,
        refX: 2,
        refY: 2,
        orient: 'auto',
        class: 'arrow-head',
      }, [
        svg('path', {
          d: 'M0,0 L0,4 L3,2 L0,0',
        }),
      ]),
    ]),
    dots.map((dot, dotIndex) => svg('g', {
      attributes: {
        class: 'number-dot' +
          (selected === dotIndex ? ' state-selected' : ''),
        'data-dot-index': dotIndex,
      },
    }, [
      svg('circle', {
        class: 'number-dot-background',
        cx: center + Math.sin(dot.angle) * radius,
        cy: center - Math.cos(dot.angle) * radius,
        r: 50,
      }),
      svg('text', {
        class: 'number-dot-label',
        x: center + Math.sin(dot.angle) * radius,
        y: center - Math.cos(dot.angle) * radius,
        fill: '#fff',
        'font-size': '50px',
        'text-anchor': 'middle',
        'dominant-baseline': 'central',
      }, dot.value.toString()),
      svg('text', {
        class: 'number-dot-pattern',
        x: center + Math.sin(dot.angle) * (radius * 1 + 70),
        y: center - Math.cos(dot.angle) * (radius * 1 + 70),
        fill: '#000',
        'font-size': '50px',
        'text-anchor': textAnchor(dot.angle),
        'dominant-baseline': baseLine(dot.angle),
      }, dot.pattern.toString()),
    ])),
    renderArc(dots, selected, size, radius - dotRadius * 1.5),
    svg('line', {
      class: 'overflow-line',
      x1: center,
      y1: center,
      x2: center + Math.sin(overflowAngle) * (radius + dotRadius * 3),
      y2: center - Math.cos(overflowAngle) * (radius + dotRadius * 3),
      stroke: 'black',
      'stroke-width': '10',
    }),
  ]);
};

const renderButtons = (state) => div('.button-bar',[
  // The button for removing a bit
  button('.bit-button', {
    attributes: {
      'data-action': 'remove-bit',
    },
    disabled: state.canRemoveBits ? void 0 : 'true',
  }, 'Remove Bit'),

  // The button for adding a bit
  button('.bit-button', {
    attributes: {
      'data-action': 'add-bit',
    },
    disabled: state.canAddBits ? void 0 : 'true',
  }, 'Add Bit'),
]);

// convert array of angles into svg
const render = (state) =>
  div('.number-circle-container', [
    div('.bit-count', ['Number of bits: ', state.bitCount]),
    renderButtons(state),
    renderDots(state.dots, state.selected),
  ])
;

// convert numbert into bit pattern string of length
// bitCount.
const bitPattern = (number, bitCount) =>
  Array
    .apply(Array, {length: bitCount})
    .map((__, b) =>
      (1 << (bitCount - b - 1)) & number ? 1 : 0
    ).join('')
;

const intValue = (number, bitCountPow2) =>
  number >= bitCountPow2 / 2 ? number - bitCountPow2 : number
;

// generate array of angles for a number circle of
// given bitCount.
const dotArray = (bitCount) =>
  Array
  // init array of length 2^bits.
  .apply(Array, {length: Math.pow(2, bitCount)})
  // map array to angles
  .map((_, index, {length}) => ({
    angle: 2 * Math.PI * index / length,
    value: intValue(index, length),
    pattern: bitPattern(index, bitCount),
  }))
;

const intent = (DOM) => {
  return {
    addBit$: DOM.select('[data-action="add-bit"]').events('click'),
    removeBit$: DOM.select('[data-action="remove-bit"]').events('click'),

    selectBit$: DOM.select('[data-dot-index]')
      .events('click').map(
        (evt) => parseInt(evt.ownerTarget.getAttribute('data-dot-index'), 10)
      ),
  };
};

const model = (initialBitCount, actions) => {
  const modifierFunction$ = O.merge([
    actions.addBit$.map(() => (state) =>
      ({bitCount: state.bitCount + 1})
    ),
    actions.removeBit$.map(() => (state) =>
      ({bitCount: state.bitCount - 1})
    ),
    actions.selectBit$.map((bitIndex) => (state) =>
      ({bitCount: state.bitCount, selected: bitIndex})
    ),
  ]);

  return modifierFunction$
  // number of bits to generate the number circle for
  .startWith({bitCount: initialBitCount, selected: null})
  // apply the function
  .scan((state, modifierFunction) => modifierFunction(state))
  // convert number of bit's into array of angles
  .map(({bitCount, selected}) => ({
    // number of bits
    bitCount,
    // the dots to draw
    dots: dotArray(bitCount),
    // the index of the selected dot
    selected,
    // boolean if more bits can be added
    canAddBits: bitCount < 5,
    // boolean fi a bit can be removed
    canRemoveBits: bitCount > 1,
  }));
};

const view = (state$) =>
  state$.map(render)
;

const numberCircleApp = (sources) => {
  const actions = intent(sources.DOM);
  const state$ = model(3, actions);
  const vtree$ = view(state$);

  return {
    DOM: vtree$,
  };
};

const drivers = {
  DOM: makeDOMDriver('#app'),
  preventDefault: preventDefaultDriver,
  keydown: keyboardDriver,
  globalEvents: globalEventDriver,
};

Cycle.run(numberCircleApp, drivers);
<|endoftext|>


<commit_before><commit_msg>rm _data<commit_after><|endoftext|>


<commit_before>import React from 'react';
import assert from 'assert';
import { mount } from 'enzyme';
import talkClient from 'panoptes-client/lib/talk-client';
import EmailSettings from './email';

const subscriptionPreferences = [
  {
    id: 1,
    category: 'participating_discussions',
    email_digest: 'daily',
    update(changes) {
      subscriptionPreferences[0] = Object.assign(subscriptionPreferences[0], changes);
      return { save: () => null };
    }
  },
  {
    id: 2,
    category: 'followed_discussions',
    email_digest: 'daily',
    update(changes) {
      subscriptionPreferences[1] = Object.assign(subscriptionPreferences[1], changes);
      return { save: () => null };
    }
  },
  {
    id: 3,
    category: 'mentions',
    email_digest: 'immediate',
    update(changes) {
      subscriptionPreferences[2] = Object.assign(subscriptionPreferences[2], changes);
      return { save: () => null };
    }
  },
  {
    id: 4,
    category: 'group_mentions',
    email_digest: 'immediate',
    update(changes) {
      subscriptionPreferences[3] = Object.assign(subscriptionPreferences[3], changes);
      return { save: () => null };
    }
  },
  {
    id: 5,
    category: 'messages',
    email_digest: 'never',
    update(changes) {
      subscriptionPreferences[4] = Object.assign(subscriptionPreferences[4], changes);
      return { save: () => null };
    }
  },
  {
    id: 6,
    category: 'started_discussions',
    email_digest: 'weekly',
    update(changes) {
      subscriptionPreferences[5] = Object.assign(subscriptionPreferences[5], changes);
      return { save: () => null };
    }
  }
];

talkClient.type = () => {
  return {
    get() {
      return Promise.resolve(subscriptionPreferences);
    }
  };
};

const project = {
  display_name: 'A test project',
  title: 'A test project'
};

const anotherProject = {
  display_name: 'Another test project',
  title: 'Another test project'
};

const preferences = [
  {
    email_communication: true,
    get() {
      return Promise.resolve(project);
    },
    getMeta() {
      return {};
    }
  },
  {
    email_communication: false,
    get() {
      return Promise.resolve(anotherProject);
    },
    getMeta() {
      return {};
    }
  }
];

const user = {
  email: 'An email address',
  beta_email_communication: false,
  global_email_communication: true,
  project_email_communication: true,
  get() {
    return Promise.resolve(preferences);
  }
};

describe('EmailSettings', () => {
  const wrapper = mount(<EmailSettings user={user} />);

  beforeEach(() => wrapper.update());

  it('renders the email address', () => {
    const email = wrapper.find('input[name="email"]');
    assert.equal(email.prop('value'), user.email);
  });

  it('shows global email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="global_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.global_email_communication);
  });

  it('shows project email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="project_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.project_email_communication);
  });

  it('shows beta email preference correctly', () => {
    const betaEmail = wrapper.find('input[name="beta_email_communication"]');
    assert.equal(betaEmail.prop('checked'), user.beta_email_communication);
  });

  describe('project listing', () => {
    let projects;

    beforeEach(() => {
      projects = wrapper.update().find('table').last().find('tbody tr');
    });

    it('lists two projects (plus pagination)', () => {
      assert.equal(projects.length, 3);
    });

    it('shows the first project email input correctly', () => {
      const email = projects.first().find('td').first();
      assert.equal(email.find('input').prop('checked'), preferences[0].email_communication);
    });

    it('shows the first project name correctly', () => {
      const name = projects.first().find('td').last();
      assert.equal(name.text(), project.display_name);
    });

    it('shows the second project email input correctly', () => {
      const email = projects.at(1).find('td').first();
      assert.equal(email.find('input').prop('checked'), preferences[1].email_communication);
    });

    it('shows the second project name correctly', () => {
      const name = projects.at(1).find('td').last();
      assert.equal(name.text(), anotherProject.display_name);
    });
  });

  describe('Talk email preferences', () => {
    let talkPreferences;

    beforeEach(() => {
      talkPreferences = wrapper.update().find('table').first().find('tbody tr');
    });

    it('lists Talk preferences correctly', () => {
      talkPreferences.forEach((preference, i) => {
        const subscriptionPreference = subscriptionPreferences[i];
        const selector = `input[name="${subscriptionPreference.category}"][value="${subscriptionPreference.email_digest}"]`;
        const input = preference.find(selector);
        assert.equal(input.prop('checked'), true);
      });
    });

    subscriptionPreferences.forEach((preference) => {
      it(`${preference.category} updates correctly when preferences are changed`, () => {
        const selector = `input[name="${preference.category}"][value="never"]`;
        wrapper.find(selector).simulate('change');
        assert.equal(preference.email_digest, 'never');
      });
    });
  });
});
<commit_msg>Simplify Talk preference tests Use actual talk resources for the preference tests. Loop over the resources array and test each in turn.<commit_after>import React from 'react';
import assert from 'assert';
import { mount } from 'enzyme';
import talkClient from 'panoptes-client/lib/talk-client';
import EmailSettings from './email';

const subscriptionPreferences = [
  talkClient.type('subscription_preferences').create({ category: 'participating_discussions', email_digest: 'immediately' }),
  talkClient.type('subscription_preferences').create({ category: 'followed_discussions', email_digest: 'daily' }),
  talkClient.type('subscription_preferences').create({ category: 'mentions', email_digest: 'immediate' }),
  talkClient.type('subscription_preferences').create({ category: 'group_mentions', email_digest: 'immediate' }),
  talkClient.type('subscription_preferences').create({ category: 'messages', email_digest: 'daily' }),
  talkClient.type('subscription_preferences').create({ category: 'started_discussions', email_digest: 'weekly' })
];

talkClient.type = () => {
  return {
    get() {
      return Promise.resolve(subscriptionPreferences);
    }
  };
};

const project = {
  display_name: 'A test project',
  title: 'A test project'
};

const anotherProject = {
  display_name: 'Another test project',
  title: 'Another test project'
};

const preferences = [
  {
    email_communication: true,
    get() {
      return Promise.resolve(project);
    },
    getMeta() {
      return {};
    }
  },
  {
    email_communication: false,
    get() {
      return Promise.resolve(anotherProject);
    },
    getMeta() {
      return {};
    }
  }
];

const user = {
  email: 'An email address',
  beta_email_communication: false,
  global_email_communication: true,
  project_email_communication: true,
  get() {
    return Promise.resolve(preferences);
  }
};

describe('EmailSettings', () => {
  const wrapper = mount(<EmailSettings user={user} />);

  beforeEach(() => wrapper.update());

  it('renders the email address', () => {
    const email = wrapper.find('input[name="email"]');
    assert.equal(email.prop('value'), user.email);
  });

  it('shows global email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="global_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.global_email_communication);
  });

  it('shows project email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="project_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.project_email_communication);
  });

  it('shows beta email preference correctly', () => {
    const betaEmail = wrapper.find('input[name="beta_email_communication"]');
    assert.equal(betaEmail.prop('checked'), user.beta_email_communication);
  });

  describe('project listing', () => {
    let projects;

    beforeEach(() => {
      projects = wrapper.update().find('table').last().find('tbody tr');
    });

    it('lists two projects (plus pagination)', () => {
      assert.equal(projects.length, 3);
    });

    it('shows the first project email input correctly', () => {
      const email = projects.first().find('td').first();
      assert.equal(email.find('input').prop('checked'), preferences[0].email_communication);
    });

    it('shows the first project name correctly', () => {
      const name = projects.first().find('td').last();
      assert.equal(name.text(), project.display_name);
    });

    it('shows the second project email input correctly', () => {
      const email = projects.at(1).find('td').first();
      assert.equal(email.find('input').prop('checked'), preferences[1].email_communication);
    });

    it('shows the second project name correctly', () => {
      const name = projects.at(1).find('td').last();
      assert.equal(name.text(), anotherProject.display_name);
    });
  });

  describe('Talk email preferences', () => {

    subscriptionPreferences.forEach((preference) => {
      it(`lists ${preference.category} preferences correctly`, () => {
        const selector = `input[name="${preference.category}"][value="${preference.email_digest}"]`;
        assert.equal(wrapper.find(selector).prop('checked'), true);
      });
    });

    subscriptionPreferences.forEach((preference) => {
      it(`${preference.category} updates correctly when preferences are changed`, () => {
        const selector = `input[name="${preference.category}"][value="never"]`;
        wrapper.find(selector).simulate('change');
        assert.equal(preference.email_digest, 'never');
      });
    });
  });
});
<|endoftext|>


<commit_before>var _ = require('underscore'),
	async = require('async'),
	keystone = require('../'),
	utils = require('keystone-utils');

/**
 * UpdateHandler Class
 *
 * @param {Object} item to update
 * @api public
 */

function UpdateHandler(list, item, req, res, options) {

	if (!(this instanceof UpdateHandler))
		return new UpdateHandler(list, item);

	this.list = list;
	this.item = item;
	this.req = req;
	this.res = res;
	this.user = req.user;
	this.options = options || {};

	if (!this.options.errorMessage) {
		this.options.errorMessage = 'There was a problem saving your changes:';
	}

	if (this.options.user) {
		this.user = this.options.user;
	}

	this.validationMethods = {};
	this.validationErrors = {};

}

/**
 * Adds a custom validation method for a given path
 *
 * @param {string} path to call method for
 * @param {function} method to call
 * @api public
 */

UpdateHandler.prototype.validate = function(path, fn) {
	this.validationMethods[path] = fn;
	return this;
};

/**
 * Adds a validationError to the updateHandler; can be used before
 * `.process()` is called to handle errors generated by custom pre-
 * processing.
 *
 * @param {string} path that failed validation
 * @param {string} message to display
 * @param {string} error type (defaults to 'required')
 * @api public
 */

UpdateHandler.prototype.addValidationError = function(path, msg, type) {
	this.validationErrors[path] = {
		name: 'ValidatorError',
		path: path,
		message: msg,
		type: type || 'required'
	};
	return this;
};

/**
 * Processes data from req.body, req.query, or any data source.
 *
 * Options:
 * - fields (comma-delimited list or array of field paths)
 * - flashErrors (boolean, default false; whether to push validation errors to req.flash)
 * - ignoreNoedit (boolean, default false; whether to ignore noedit settings on fields)
 * - validationErrors (object; validation errors from previous form handling that should be included)
 *
 * @param {Object} data
 * @param {Object} options (can be comma-delimited list of fields) (optional)
 * @param {Function} callback (optional)
 * @api public
 */

UpdateHandler.prototype.process = function(data, options, callback) {

	var usingDefaultFields = false;

	if ('function' == typeof options) {
		callback = options;
		options = null;
	}

	// Initialise options

	if (!options) {
		options = {};
	} else if ('string' == typeof options) {
		options = { fields: options };
	}

	if (!options.fields) {
		options.fields = _.keys(this.list.fields);
		usingDefaultFields = true;
	} else if ('string' == typeof options.fields) {
		options.fields = options.fields.split(',').map(function(i) { return i.trim(); });
	}

	options.required = options.required || {};
	options.errorMessage = options.errorMessage || this.options.errorMessage;
	options.invalidMessages = options.invalidMessages || {};
	options.requiredMessages = options.requiredMessages || {};

	// Parse a string of required fields into field paths
	if ('string' == typeof options.required) {
		var requiredFields = options.required.split(',').map(function(i) { return i.trim(); });
		options.required = {};
		requiredFields.forEach(function(path) {
			options.required[path] = true;
		});
	}

	// Make sure fields with the required option set are included in the required paths
	options.fields.forEach(function(path) {
		var field = (path instanceof keystone.Field) ? path : this.list.field(path);
		if (field && field.required) {
			options.required[path] = true;
		}
	}, this);

	// TODO: The whole progress queue management code could be a lot neater...

	var actionQueue = [],
		addValidationError = this.addValidationError.bind(this),
		validationErrors = this.validationErrors;

	var progress = (function(err) {
		if (err) {
			if (options.logErrors) {
				console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
				console.log(err);
			}
			callback(err, this);
		} else if (_.size(validationErrors)) {
			if (options.flashErrors) {
				this.req.flash('error', {
					type: 'ValidationError',
					title: this.options.errorMessage,
					list: _.pluck(validationErrors, 'message')
				});
			}
			callback({
				message: 'Validation failed',
				name: 'ValidationError',
				errors: validationErrors
			}, this);
		} else if (actionQueue.length) {
			// TODO: parallel queue handling for cloudinary uploads?
			actionQueue.pop()();
		} else {
			saveItem();
		}
	}).bind(this);

	var saveItem = (function() {
		this.item.save((function(err) {
			if (err) {
				if (err.name == 'ValidationError') {
					// don't log simple validation errors
					if (options.flashErrors) {
						this.req.flash('error', {
							type: 'ValidationError',
							title: this.options.errorMessage,
							list: _.pluck(err.errors, 'message')
						});
					}
				} else {
					if (options.logErrors) {
						console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
						console.log(err);
					}
					if (options.flashErrors) {
						this.req.flash('error', 'There was an error saving your changes: ' + err.message + ' (' + err.name + (err.type ? ': ' + err.type : '') + ')');
					}
				}
			}
			return callback(err, this);
		}).bind(this));
	}).bind(this);

	options.fields.forEach(function(path) {

		// console.log('Processing field ' + path);
		var message;

		var field = (path instanceof keystone.Field) ? path : this.list.field(path),
			invalidated = false;

		if (!field) {
			throw new Error('UpdateHandler.process called with invalid field path: ' + path);
		}

		// skip uneditable fields
		if (usingDefaultFields && field.noedit && !options.ignoreNoedit) {
			// console.log('Skipping field ' + path + ' (noedit: true)');
			return;
		}

		// Some field types have custom behaviours for queueing or validation
		switch (field.type) {

			case 'localfile':
			case 'cloudinaryimage':
			case 'cloudinaryimages':
			case 'azurefile':
			case 's3file':
				actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, (function(err) {
				    if (err && options.flashErrors) {
				        this.req.flash('error', field.label + ' upload failed - ' + err.message);
				    }
				    progress(err);
				}).bind(this)));
			break;

			case 'location':
				actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, (function(err) {
					if (err && options.flashErrors) {
						this.req.flash('error', field.label + ' improve failed - ' + (err.status_text || err.status));
					}
					progress(err);
				}).bind(this)));
			break;

			case 'password':
				// passwords should only be set if a value is provided.
				// if no value is provided, as long as the field isn't required or empty, bail.
				if (!data[field.path] && (!options.required[field.path] || this.item.get(field.path))) {
					return;
				}
				// validate the password fields match, with a custom error message.
				if (data[field.path] != data[field.paths.confirm]) {
					message = options.invalidMessages[field.path + '_match'] || 'Passwords must match';
					addValidationError(field.path, message);
					invalidated = true;
				}
			break;

		}

		// validate field input, unless it's already been invalidated by field-specific behaviour
		if (!invalidated && !field.validateInput(data)) {
			// console.log('Field ' + field.path + ' is invalid');
			message = options.invalidMessages[field.path] || field.options.invalidMessage || 'Please enter a valid ' + field.typeDescription + ' in the ' + field.label + ' field';
			addValidationError(field.path, message);
			invalidated = true;
		}

		// validate required fields, unless they've already been invalidated by field-specific behaviour
		// console.log('Field ' + path + ' required: ' + options.required[field.path]);
		if (!invalidated && options.required[field.path] && !field.validateInput(data, true, this.item)) {
			// console.log('Field ' + field.path + ' is required, but not provided.');
			message = options.requiredMessages[field.path] || field.options.requiredMessage || field.label + ' is required';
			addValidationError(field.path, message);
			invalidated = true;
		}

		// check for a custom validation rule at the path, and run it (unless the field is already invalid)
		if (!invalidated && this.validationMethods[field.path]) {
			message = this.validationMethods[field.path](data);
			if (message) {
				addValidationError(field.path, message);
			}
		}

		// console.log('Setting field value: ' + path);

		field.updateItem(this.item, data);

	}, this);

	progress();

};

/*!
 * Export class
 */

exports = module.exports = UpdateHandler;
<commit_msg>enabled errorMessage option at UpdateHandler.process<commit_after>var _ = require('underscore'),
	async = require('async'),
	keystone = require('../'),
	utils = require('keystone-utils');

/**
 * UpdateHandler Class
 *
 * @param {Object} item to update
 * @api public
 */

function UpdateHandler(list, item, req, res, options) {

	if (!(this instanceof UpdateHandler))
		return new UpdateHandler(list, item);

	this.list = list;
	this.item = item;
	this.req = req;
	this.res = res;
	this.user = req.user;
	this.options = options || {};

	if (!this.options.errorMessage) {
		this.options.errorMessage = 'There was a problem saving your changes:';
	}

	if (this.options.user) {
		this.user = this.options.user;
	}

	this.validationMethods = {};
	this.validationErrors = {};

}

/**
 * Adds a custom validation method for a given path
 *
 * @param {string} path to call method for
 * @param {function} method to call
 * @api public
 */

UpdateHandler.prototype.validate = function(path, fn) {
	this.validationMethods[path] = fn;
	return this;
};

/**
 * Adds a validationError to the updateHandler; can be used before
 * `.process()` is called to handle errors generated by custom pre-
 * processing.
 *
 * @param {string} path that failed validation
 * @param {string} message to display
 * @param {string} error type (defaults to 'required')
 * @api public
 */

UpdateHandler.prototype.addValidationError = function(path, msg, type) {
	this.validationErrors[path] = {
		name: 'ValidatorError',
		path: path,
		message: msg,
		type: type || 'required'
	};
	return this;
};

/**
 * Processes data from req.body, req.query, or any data source.
 *
 * Options:
 * - fields (comma-delimited list or array of field paths)
 * - flashErrors (boolean, default false; whether to push validation errors to req.flash)
 * - ignoreNoedit (boolean, default false; whether to ignore noedit settings on fields)
 * - validationErrors (object; validation errors from previous form handling that should be included)
 *
 * @param {Object} data
 * @param {Object} options (can be comma-delimited list of fields) (optional)
 * @param {Function} callback (optional)
 * @api public
 */

UpdateHandler.prototype.process = function(data, options, callback) {

	var usingDefaultFields = false;

	if ('function' == typeof options) {
		callback = options;
		options = null;
	}

	// Initialise options

	if (!options) {
		options = {};
	} else if ('string' == typeof options) {
		options = { fields: options };
	}

	if (!options.fields) {
		options.fields = _.keys(this.list.fields);
		usingDefaultFields = true;
	} else if ('string' == typeof options.fields) {
		options.fields = options.fields.split(',').map(function(i) { return i.trim(); });
	}

	options.required = options.required || {};
	options.errorMessage = options.errorMessage || this.options.errorMessage;
	options.invalidMessages = options.invalidMessages || {};
	options.requiredMessages = options.requiredMessages || {};

	// Parse a string of required fields into field paths
	if ('string' == typeof options.required) {
		var requiredFields = options.required.split(',').map(function(i) { return i.trim(); });
		options.required = {};
		requiredFields.forEach(function(path) {
			options.required[path] = true;
		});
	}

	// Make sure fields with the required option set are included in the required paths
	options.fields.forEach(function(path) {
		var field = (path instanceof keystone.Field) ? path : this.list.field(path);
		if (field && field.required) {
			options.required[path] = true;
		}
	}, this);

	// TODO: The whole progress queue management code could be a lot neater...

	var actionQueue = [],
		addValidationError = this.addValidationError.bind(this),
		validationErrors = this.validationErrors;

	var progress = (function(err) {
		if (err) {
			if (options.logErrors) {
				console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
				console.log(err);
			}
			callback(err, this);
		} else if (_.size(validationErrors)) {
			if (options.flashErrors) {
				this.req.flash('error', {
					type: 'ValidationError',
					title: options.errorMessage,
					list: _.pluck(validationErrors, 'message')
				});
			}
			callback({
				message: 'Validation failed',
				name: 'ValidationError',
				errors: validationErrors
			}, this);
		} else if (actionQueue.length) {
			// TODO: parallel queue handling for cloudinary uploads?
			actionQueue.pop()();
		} else {
			saveItem();
		}
	}).bind(this);

	var saveItem = (function() {
		this.item.save((function(err) {
			if (err) {
				if (err.name == 'ValidationError') {
					// don't log simple validation errors
					if (options.flashErrors) {
						this.req.flash('error', {
							type: 'ValidationError',
							title: options.errorMessage,
							list: _.pluck(err.errors, 'message')
						});
					}
				} else {
					if (options.logErrors) {
						console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
						console.log(err);
					}
					if (options.flashErrors) {
						this.req.flash('error', 'There was an error saving your changes: ' + err.message + ' (' + err.name + (err.type ? ': ' + err.type : '') + ')');
					}
				}
			}
			return callback(err, this);
		}).bind(this));
	}).bind(this);

	options.fields.forEach(function(path) {

		// console.log('Processing field ' + path);
		var message;

		var field = (path instanceof keystone.Field) ? path : this.list.field(path),
			invalidated = false;

		if (!field) {
			throw new Error('UpdateHandler.process called with invalid field path: ' + path);
		}

		// skip uneditable fields
		if (usingDefaultFields && field.noedit && !options.ignoreNoedit) {
			// console.log('Skipping field ' + path + ' (noedit: true)');
			return;
		}

		// Some field types have custom behaviours for queueing or validation
		switch (field.type) {

			case 'localfile':
			case 'cloudinaryimage':
			case 'cloudinaryimages':
			case 'azurefile':
			case 's3file':
				actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, (function(err) {
				    if (err && options.flashErrors) {
				        this.req.flash('error', field.label + ' upload failed - ' + err.message);
				    }
				    progress(err);
				}).bind(this)));
			break;

			case 'location':
				actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, (function(err) {
					if (err && options.flashErrors) {
						this.req.flash('error', field.label + ' improve failed - ' + (err.status_text || err.status));
					}
					progress(err);
				}).bind(this)));
			break;

			case 'password':
				// passwords should only be set if a value is provided.
				// if no value is provided, as long as the field isn't required or empty, bail.
				if (!data[field.path] && (!options.required[field.path] || this.item.get(field.path))) {
					return;
				}
				// validate the password fields match, with a custom error message.
				if (data[field.path] != data[field.paths.confirm]) {
					message = options.invalidMessages[field.path + '_match'] || 'Passwords must match';
					addValidationError(field.path, message);
					invalidated = true;
				}
			break;

		}

		// validate field input, unless it's already been invalidated by field-specific behaviour
		if (!invalidated && !field.validateInput(data)) {
			// console.log('Field ' + field.path + ' is invalid');
			message = options.invalidMessages[field.path] || field.options.invalidMessage || 'Please enter a valid ' + field.typeDescription + ' in the ' + field.label + ' field';
			addValidationError(field.path, message);
			invalidated = true;
		}

		// validate required fields, unless they've already been invalidated by field-specific behaviour
		// console.log('Field ' + path + ' required: ' + options.required[field.path]);
		if (!invalidated && options.required[field.path] && !field.validateInput(data, true, this.item)) {
			// console.log('Field ' + field.path + ' is required, but not provided.');
			message = options.requiredMessages[field.path] || field.options.requiredMessage || field.label + ' is required';
			addValidationError(field.path, message);
			invalidated = true;
		}

		// check for a custom validation rule at the path, and run it (unless the field is already invalid)
		if (!invalidated && this.validationMethods[field.path]) {
			message = this.validationMethods[field.path](data);
			if (message) {
				addValidationError(field.path, message);
			}
		}

		// console.log('Setting field value: ' + path);

		field.updateItem(this.item, data);

	}, this);

	progress();

};

/*!
 * Export class
 */

exports = module.exports = UpdateHandler;
<|endoftext|>


<commit_before>var fs = require('fs');
var iconv = require('iconv-lite');
var Buffer = require('safe-buffer').Buffer;
var isUtf8 = require('./is-utf8');

var isWin32 = process.platform === 'win32';
var MAX_SIZE = 1024 * 1024 * 64;
var CRLF = Buffer.from('\r\n');
var RSLASH_RE = /\\/g;
var noop = function(_) {
  return _;
};

function convertSlash(filePath) {
  return isWin32 ? filePath : filePath.replace(RSLASH_RE, '/');
}

function decode(buf) {
  if (!Buffer.isBuffer(buf)) {
    return buf ? String(buf) : '';
  }
  if (!isUtf8(buf)) {
    try {
      return iconv.decode(buf, 'GB18030');
    } catch(e) {}
  }
  return String(buf);
}

function isString(path) {
  return path && typeof path === 'string';
}

function readSingleFile(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  var stream = fs.createReadStream(convertSlash(path));
  var done, buf;
  var execCallback = function(err) {
    if (done) {
      return;
    }
    done = true;
    stream.close();
    callback(err ? null : buf);
  };
  stream.on('data', function(data) {
    if (done) {
      return;
    }
    buf = buf ? Buffer.concat([buf, data]) : data;
    if (buf.length > MAX_SIZE) {
      execCallback();
    }
  });
  stream.on('error', execCallback);
  stream.on('end', execCallback);
}

function getFileMap(list) {
  if (Array.isArray(list)) {
    list = list.join('|');
  }
  if (!isString(list)) {
    return '';
  }
  var fileMap = {};
  list = list.split('|');
  list.forEach(function(file) {
    fileMap[file || ''] = 1;
  });
  return fileMap;
}

function readFileMap(list, callback, isText) {
  var fileMap = getFileMap(list);
  if (!fileMap) {
    return callback('');
  }
  var files = Object.keys(fileMap);
  var len = files.length;
  files.forEach(function(file) {
    readSingleFile(file, function(data) {
      fileMap[file || ''] = isText ? decode(data) : data;
      if (--len <= 0) {
        callback(fileMap);
      }
    });
  });
}

function joinData(list, isText) {
  if (!list || !list.length) {
    return '';
  }
  if (isText) {
    return list.filter(noop).join('\r\n');
  }
  var result = [];
  list.forEach(function(buf) {
    if (buf) {
      buf = Buffer.isBuffer(buf) ? buf : Buffer.from(buf + '');
      result.push(buf, CRLF);
    }
  });
  result.pop();
  return result.length ? Buffer.concat(result) : '';
}

function readFileFromMap(path, fileMap, isText) {
  if (!isString(path)) {
    return '';
  }
  path = path.split('|');
  return joinData(path.map(function(file) {
    return fileMap[file || ''];
  }), isText);
}

function readFileList(list, callback, isText) {
  readFileMap(list, function(fileMap) {
    if (!fileMap) {
      return callback('');
    }
    var result = [];
    list.forEach(function(file) {
      result.push(readFileFromMap(file, fileMap, isText));
    });
    callback(result);
  }, isText);
}

function readFile(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  readFileList(path.split('|'), function(result) {
    callback(joinData(result));
  });
}

function readFilesText(list, callback) {
  readFileList(list, callback, true);
}

function readFileText(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  readFilesText(path.split('|'), function(result) {
    callback(joinData(result, true));
  }, true);
}

exports.joinData = joinData;
exports.decode = decode;
exports.readFile = readFile;
exports.readFileList = readFileList;
exports.readFileText = readFileText;
exports.readFilesText = readFilesText;
exports.convertSlash = convertSlash;
<commit_msg>feat: replace / or / with real directory path<commit_after>var fs = require('fs');
var iconv = require('iconv-lite');
var Buffer = require('safe-buffer').Buffer;
var config = require('../config');
var isUtf8 = require('./is-utf8');

var isWin32 = process.platform === 'win32';
var MAX_SIZE = 1024 * 1024 * 64;
var LOCAL_FILES = config.LOCAL_FILES;
var LOCAL_FILE_PATH_RE = /^\$(?:whistle|w2)\//i;
var CRLF = Buffer.from('\r\n');
var RSLASH_RE = /\\/g;
var noop = function(_) {
  return _;
};

function convertSlash(filePath) {
  if (LOCAL_FILE_PATH_RE.test(filePath)) {
    filePath = LOCAL_FILES + '/' + filePath.substring(RegExp['&$'].length);
  }
  return isWin32 ? filePath : filePath.replace(RSLASH_RE, '/');
}

function decode(buf) {
  if (!Buffer.isBuffer(buf)) {
    return buf ? String(buf) : '';
  }
  if (!isUtf8(buf)) {
    try {
      return iconv.decode(buf, 'GB18030');
    } catch(e) {}
  }
  return String(buf);
}

function isString(path) {
  return path && typeof path === 'string';
}

function readSingleFile(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  var stream = fs.createReadStream(convertSlash(path));
  var done, buf;
  var execCallback = function(err) {
    if (done) {
      return;
    }
    done = true;
    stream.close();
    callback(err ? null : buf);
  };
  stream.on('data', function(data) {
    if (done) {
      return;
    }
    buf = buf ? Buffer.concat([buf, data]) : data;
    if (buf.length > MAX_SIZE) {
      execCallback();
    }
  });
  stream.on('error', execCallback);
  stream.on('end', execCallback);
}

function getFileMap(list) {
  if (Array.isArray(list)) {
    list = list.join('|');
  }
  if (!isString(list)) {
    return '';
  }
  var fileMap = {};
  list = list.split('|');
  list.forEach(function(file) {
    fileMap[file || ''] = 1;
  });
  return fileMap;
}

function readFileMap(list, callback, isText) {
  var fileMap = getFileMap(list);
  if (!fileMap) {
    return callback('');
  }
  var files = Object.keys(fileMap);
  var len = files.length;
  files.forEach(function(file) {
    readSingleFile(file, function(data) {
      fileMap[file || ''] = isText ? decode(data) : data;
      if (--len <= 0) {
        callback(fileMap);
      }
    });
  });
}

function joinData(list, isText) {
  if (!list || !list.length) {
    return '';
  }
  if (isText) {
    return list.filter(noop).join('\r\n');
  }
  var result = [];
  list.forEach(function(buf) {
    if (buf) {
      buf = Buffer.isBuffer(buf) ? buf : Buffer.from(buf + '');
      result.push(buf, CRLF);
    }
  });
  result.pop();
  return result.length ? Buffer.concat(result) : '';
}

function readFileFromMap(path, fileMap, isText) {
  if (!isString(path)) {
    return '';
  }
  path = path.split('|');
  return joinData(path.map(function(file) {
    return fileMap[file || ''];
  }), isText);
}

function readFileList(list, callback, isText) {
  readFileMap(list, function(fileMap) {
    if (!fileMap) {
      return callback('');
    }
    var result = [];
    list.forEach(function(file) {
      result.push(readFileFromMap(file, fileMap, isText));
    });
    callback(result);
  }, isText);
}

function readFile(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  readFileList(path.split('|'), function(result) {
    callback(joinData(result));
  });
}

function readFilesText(list, callback) {
  readFileList(list, callback, true);
}

function readFileText(path, callback) {
  if (!isString(path)) {
    return callback();
  }
  readFilesText(path.split('|'), function(result) {
    callback(joinData(result, true));
  }, true);
}

exports.joinData = joinData;
exports.decode = decode;
exports.readFile = readFile;
exports.readFileList = readFileList;
exports.readFileText = readFileText;
exports.readFilesText = readFilesText;
exports.convertSlash = convertSlash;
<|endoftext|>