<commit_before>(function ($) {

    /**
     * findValue
     * Finds a value in parent (object) using the dot notation passed in dotNot.
     *
     * @name findValue
     * @function
     * @param {Object} parent The object containing the searched value
     * @param {String} dotNot Path to the value
     * @return {Anything} Found value or undefined
     */
    function findValue(parent, dotNot) {

        if (!dotNot || !dotNot) return undefined;

        var splits = dotNot.split(".");
        var value;

        for (var i = 0; i < splits.length; ++i) {
            value = parent[splits[i]];
            if (value === undefined) return undefined;
            if (typeof value === "object") parent = value;
        }

        return value;
    }

    /**
     * flattenObject
     * Converts an object to a flat one
     *
     * @name flattenObject
     * @function
     * @param {Object} obj The object that should be converted
     * @return {Object} Flatten object
     */
    function flattenObject(obj) {

        var result = {};

        for (var key in obj) {
            if (!obj.hasOwnProperty(key)) continue;

            if (obj[key] && obj[key].constructor === Object) {
                var flat = flattenObject (obj[key]);
                for (var x in flat) {
                    if (!flat.hasOwnProperty(x)) {
                         continue;
                    }

                    result[key + '.' + x] = flat[x];
                }
            } else {
                result[key] = obj[key];
            }
        }
        return result;
    }

    /**
     * unflattenObject
     * Converts a flat object to an unflatten one
     *
     * @name unflattenObject
     * @function
     * @param {Object} flat The flat object that should be converted
     * @return {Object} Unflatten object
     */
    function unflattenObject(flat) {

        var result = {};
        var parentObj = result;

        var keys = Object.keys(flat);
        for (var i = 0; i < keys.length; ++i) {

            var key = keys[i];
            var subkeys = key.split('.');
            var last = subkeys.pop();

            for (var ii = 0; ii < subkeys.length; ++ii) {
                var subkey = subkeys[ii];
                parentObj[subkey] = typeof parentObj[subkey] === 'undefined' ? {} : parentObj[subkey];
                parentObj = parentObj[subkey];
            }

            parentObj[last] = flat[key];
            parentObj = result;
        }

        return result;
    }

    function getTypeOf(o) {

        var types = {
            "undefined"                          : "undefined",
            "number"                             : "number",
            "boolean"                            : "boolean",
            "string"                             : "string",
            "[object Function]"                  : "function",
            "[object RegExp]"                    : "regexp",
            "function Array() { [native code] }" : "array",
            "function Date() { [native code] }"  : "date",
            "[object Error]"                     : "error"
        };

        return types[o && o.constructor] || types[typeof o] || types[o] || (o ? "object" : "null");
    }

    function mergeRecursive (obj1, obj2) {
        for (var p in obj2) {
            try {
                if (obj2[p].constructor == Object) {
                    obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                } else {
                    obj1[p] = obj2[p];
                }
            } catch (e) {
                obj1[p] = obj2[p];
            }
        }

        return obj1;
    }

    var JsonEdit = $.fn.jsonEdit = function (opt_options) {

        var settings = $.extend({
            data: {},
            schema: {},
            container: this,
            autoInit: true
        }, opt_options);

        var self = {
            ui: $.extend(JsonEdit.ui, opt_options.ui),
            labels: $.extend(JsonEdit.labels, opt_options.labels),
            groups: $.extend(JsonEdit.groups, opt_options.groups),
            inputs: $.extend(JsonEdit.inputs, opt_options.inputs),
            converters: $.extend(JsonEdit.converters, opt_options.converters),
            createGroup: function (field) {

                // Create form group
                var $group = self.groups[field.type].clone(true);

                // TODO Configurable
                // Add label
                $group.find("label").append(self.labels[field.type].clone(true).text(field.label));

                // Add input
                var $input = null;
                if (field.type == "array") {
                    // TODO Configurable
                    var $headers = null;
                    var $tbody = null;
                    $input = $("<table>").append([
                        $headers = $("<thead>").append("<tr>"),
                        $tbody = $("<tbody>")
                    ]);

                   var fieldData = self.getValue(field.path);
                   var headers = [];
                   // headers
                   for (var k in field.schema) {
                       var c = field.schema[k];
                       headers.push(c.name);
                       $headers.append(
                            $("<th>", { text: c.label || "Values" })
                       );
                   }

                   for (var i = 0; i < fieldData.length; ++i) {
                      var cFieldData = fieldData[i];
                      var $tr = $("<tr>").appendTo($tbody);
                      if (typeof field.schema.type === "string") {
                         $tr.append($("<td>").append(self.createGroup({
                             type: getTypeOf(cFieldData),
                             path: field.path + "." + i
                         })));
                      } else {
                          for (var ii = 0; ii < headers.length; ++ii) {
                             var sch = field.schema[headers[ii]];
                             $tr.append($("<td>").append(self.createGroup({
                                 type: sch.type,
                                 path: sch.path.replace(new RegExp("^.?" + field.name + "."), field.name + "." + i + ".")
                             })));
                          }
                      }
                      // TODO
                   }

                } else if (field.type === "object") {
                    $input = [];
                    for (var k in field.schema) {
                        $input.push(self.createGroup(field.schema[k]));
                    }
                } else {
                    $input = self.inputs[field.type].clone(true).attr({
                        "data-json-editor-path": field.path
                    });

                    // Set value in input
                    var value = self.getValue(field.path);
                    if (field.type === "boolean") {
                        $input.prop("checked", value);
                    } else if (field.type === "date") {
                        $input[0].valueAsDate = value;
                    } else {
                        $input.val(value);
                    }
                }

                $group.find("label").append($input);

                return $group;
            },
            elms: {}
        };

        self.getValue = function (fieldPath) {
            return findValue(settings.data, fieldPath);
        };

        self.initUi = function () {

            function create(obj) {
                for (var k in obj) {
                    var c = obj[k];
                    settings.container.append(self.createGroup(c));
                }
            }

            create(settings.schema);
        };

        function sch(obj, out, path) {

            var schema = out || {};
            path = path || "";

            var t = getTypeOf(obj);
            if (t !== "object") {
                return {
                    type: t
                };
            } else {
                for (var k in obj) {
                    var c = obj[k];
                    t = getTypeOf(c)

                    if (t === "array" || t === "object") {
                        schema[path + k] = {
                            schema: t === "array" ? sch(c[0]) : sch(c),
                            type: t
                        };
                        continue;
                    }

                    schema[path + k] = {
                        type: t
                    };
                }
            }

            return schema;
        }

        settings.schema = mergeRecursive(sch(settings.data), settings.schema);

        // Set fields in schema
        function visit(obj, path) {
            path = path || "";
            for (var k in obj) {
                var c = obj[k];
                if ((c.type === "array" && typeof Object(c.schema).type !== "string") || c.type === "object") {
                    visit(c.schema, path + k + ".");
                }
                c.label = c.label || k;
                c.path = path + k;
                c.name = k;
            }
        }

        visit(settings.schema);
        if (settings.autoInit === true) {
            self.initUi();
        }

        self.getData = function () {
            var data = {};
            $("[data-json-editor-path]", settings.container).each(function () {
                var $this = $(this);
                var path = $this.attr("data-json-editor-path");
                if ($this.attr("type") === "checkbox") {
                    data[path] = $this.prop("checked");
                } else {
                    data[path] = $this.val();
                }

                var converter = self.converters[settings.schema[path].type];
                if (typeof converter === "function") {
                    data[path] = converter(data[path]);
                }
            });
            return unflattenObject(data);
        };

        return self;
    };

    JsonEdit.converters = {
        boolean: function (value) {
            return (value === true || value === "true" || value === "on" || typeof value == "number" && value > 0 || value === "1");
        },
        string: function (value) {
            return value.toString();
        },
        number: function (value) {
            return Number (value);
        },
        regexp: function (value) {
            return new RegExp(value);
        },
        date: function (value) {
            return new Date(value);
        }
    };

    JsonEdit.groups = {
        "number":   $("<div>").append($("<label>")),
        "boolean":  $("<div>").append($("<label>")),
        "string":   $("<div>").append($("<label>")),
        "regexp":   $("<div>").append($("<label>")),
        "array":    $("<div>").append($("<label>")),
        "object":   $("<div>").append($("<label>")),
        "date":     $("<div>").append($("<label>"))
    };

    JsonEdit.labels = {
        "number": $("<span>"),
        "boolean": $("<span>"),
        "string": $("<span>"),
        "regexp": $("<span>"),
        "date": $("<span>"),
        "object": $("<h3>"),
        "array": $("<h3>")
    };

    JsonEdit.inputs = {
        "number": $("<input>", {type: "number"}),
        "boolean": $("<input>", {type: "checkbox"}),
        "string": $("<input>", {type: "text"}),
        "regexp": $("<input>", {type: "text"}),
        "date": $("<input>", {type: "date"}),
        "array": $("<input>", {type: "text"}),
    };
})($);

<commit_msg>Create tables recursivelly<commit_after>(function ($) {

    /**
     * findValue
     * Finds a value in parent (object) using the dot notation passed in dotNot.
     *
     * @name findValue
     * @function
     * @param {Object} parent The object containing the searched value
     * @param {String} dotNot Path to the value
     * @return {Anything} Found value or undefined
     */
    function findValue(parent, dotNot) {

        if (!dotNot || !dotNot) return undefined;

        var splits = dotNot.split(".");
        var value;

        for (var i = 0; i < splits.length; ++i) {
            value = parent[splits[i]];
            if (value === undefined) return undefined;
            if (typeof value === "object") parent = value;
        }

        return value;
    }

    /**
     * flattenObject
     * Converts an object to a flat one
     *
     * @name flattenObject
     * @function
     * @param {Object} obj The object that should be converted
     * @return {Object} Flatten object
     */
    function flattenObject(obj) {

        var result = {};

        for (var key in obj) {
            if (!obj.hasOwnProperty(key)) continue;

            if (obj[key] && obj[key].constructor === Object) {
                var flat = flattenObject (obj[key]);
                for (var x in flat) {
                    if (!flat.hasOwnProperty(x)) {
                         continue;
                    }

                    result[key + '.' + x] = flat[x];
                }
            } else {
                result[key] = obj[key];
            }
        }
        return result;
    }

    /**
     * unflattenObject
     * Converts a flat object to an unflatten one
     *
     * @name unflattenObject
     * @function
     * @param {Object} flat The flat object that should be converted
     * @return {Object} Unflatten object
     */
    function unflattenObject(flat) {

        var result = {};
        var parentObj = result;

        var keys = Object.keys(flat);
        for (var i = 0; i < keys.length; ++i) {

            var key = keys[i];
            var subkeys = key.split('.');
            var last = subkeys.pop();

            for (var ii = 0; ii < subkeys.length; ++ii) {
                var subkey = subkeys[ii];
                parentObj[subkey] = typeof parentObj[subkey] === 'undefined' ? {} : parentObj[subkey];
                parentObj = parentObj[subkey];
            }

            parentObj[last] = flat[key];
            parentObj = result;
        }

        return result;
    }

    function getTypeOf(o) {

        var types = {
            "undefined"                          : "undefined",
            "number"                             : "number",
            "boolean"                            : "boolean",
            "string"                             : "string",
            "[object Function]"                  : "function",
            "[object RegExp]"                    : "regexp",
            "function Array() { [native code] }" : "array",
            "function Date() { [native code] }"  : "date",
            "[object Error]"                     : "error"
        };

        return types[o && o.constructor] || types[typeof o] || types[o] || (o ? "object" : "null");
    }

    function mergeRecursive (obj1, obj2) {
        for (var p in obj2) {
            try {
                if (obj2[p].constructor == Object) {
                    obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                } else {
                    obj1[p] = obj2[p];
                }
            } catch (e) {
                obj1[p] = obj2[p];
            }
        }

        return obj1;
    }

    var JsonEdit = $.fn.jsonEdit = function (opt_options) {

        var settings = $.extend({
            data: {},
            schema: {},
            container: this,
            autoInit: true
        }, opt_options);

        var self = {
            ui: $.extend(JsonEdit.ui, opt_options.ui),
            labels: $.extend(JsonEdit.labels, opt_options.labels),
            groups: $.extend(JsonEdit.groups, opt_options.groups),
            inputs: $.extend(JsonEdit.inputs, opt_options.inputs),
            converters: $.extend(JsonEdit.converters, opt_options.converters),
            createGroup: function (field) {

                // Create form group
                var $group = self.groups[field.type].clone(true);

                // TODO Configurable
                // Add label
                $group.find("label").append(self.labels[field.type].clone(true).text(field.label));

                // Add input
                var $input = null;
                if (field.type == "array") {
                    // TODO Configurable
                    var $headers = null;
                    var $tbody = null;
                    $input = $("<table>").append([
                        $headers = $("<thead>").append("<tr>"),
                        $tbody = $("<tbody>")
                    ]);

                   var fieldData = self.getValue(field.path);
                   if (!fieldData) { return; }

                   var headers = [];
                   // headers
                   for (var k in field.schema) {
                       var c = field.schema[k];
                       headers.push(c.name);
                       $headers.append(
                            $("<th>", { text: c.label || "Values" })
                       );
                   }

                   for (var i = 0; i < fieldData.length; ++i) {
                      var cFieldData = fieldData[i];
                      var $tr = $("<tr>").appendTo($tbody);
                      if (typeof Object(field.schema).type === "string") {
                         $tr.append($("<td>").append(self.createGroup({
                             type: getTypeOf(cFieldData),
                             path: field.path + "." + i,
                         })));
                      } else {
                          for (var ii = 0; ii < headers.length; ++ii) {
                             var sch = field.schema[headers[ii]];
                             $tr.append($("<td>").append(self.createGroup({
                                 type: sch.type,
                                 path: sch.path.replace(new RegExp("^.?" + field.name + "."), field.name + "." + i + "."),
                                 schema: sch.schema,
                                 label: sch.label,
                                 name: sch.name
                             })));
                          }
                      }
                   }

                } else if (field.type === "object") {
                    $input = [];
                    for (var k in field.schema) {
                        $input.push(self.createGroup(field.schema[k]));
                    }
                } else {
                    $input = self.inputs[field.type].clone(true).attr({
                        "data-json-editor-path": field.path
                    });

                    // Set value in input
                    var value = self.getValue(field.path);
                    if (field.type === "boolean") {
                        $input.prop("checked", value);
                    } else if (field.type === "date") {
                        $input[0].valueAsDate = value;
                    } else {
                        $input.val(value);
                    }
                }

                $group.find("label").append($input);

                return $group;
            },
            elms: {}
        };

        self.getValue = function (fieldPath) {
            return findValue(settings.data, fieldPath);
        };

        self.initUi = function () {

            function create(obj) {
                for (var k in obj) {
                    var c = obj[k];
                    settings.container.append(self.createGroup(c));
                }
            }

            create(settings.schema);
        };

        function sch(obj, out, path) {

            var schema = out || {};
            path = path || "";

            var t = getTypeOf(obj);
            if (t !== "object") {
                return {
                    type: t
                };
            } else {
                for (var k in obj) {
                    var c = obj[k];
                    t = getTypeOf(c)

                    if (t === "array" || t === "object") {
                        schema[path + k] = {
                            schema: t === "array" ? sch(c[0]) : sch(c),
                            type: t
                        };
                        continue;
                    }

                    schema[path + k] = {
                        type: t
                    };
                }
            }

            return schema;
        }

        settings.schema = mergeRecursive(sch(settings.data), settings.schema);

        // Set fields in schema
        function visit(obj, path) {
            path = path || "";
            for (var k in obj) {
                var c = obj[k];
                if ((c.type === "array" && typeof Object(c.schema).type !== "string") || c.type === "object") {
                    visit(c.schema, path + k + ".");
                }
                c.label = c.label || k;
                c.path = path + k;
                c.name = k;
            }
        }

        visit(settings.schema);
        if (settings.autoInit === true) {
            self.initUi();
        }

        self.getData = function () {
            var data = {};
            $("[data-json-editor-path]", settings.container).each(function () {
                var $this = $(this);
                var path = $this.attr("data-json-editor-path");
                if ($this.attr("type") === "checkbox") {
                    data[path] = $this.prop("checked");
                } else {
                    data[path] = $this.val();
                }

                var converter = self.converters[settings.schema[path].type];
                if (typeof converter === "function") {
                    data[path] = converter(data[path]);
                }
            });
            return unflattenObject(data);
        };

        return self;
    };

    JsonEdit.converters = {
        boolean: function (value) {
            return (value === true || value === "true" || value === "on" || typeof value == "number" && value > 0 || value === "1");
        },
        string: function (value) {
            return value.toString();
        },
        number: function (value) {
            return Number (value);
        },
        regexp: function (value) {
            return new RegExp(value);
        },
        date: function (value) {
            return new Date(value);
        }
    };

    JsonEdit.groups = {
        "number":   $("<div>").append($("<label>")),
        "boolean":  $("<div>").append($("<label>")),
        "string":   $("<div>").append($("<label>")),
        "regexp":   $("<div>").append($("<label>")),
        "array":    $("<div>").append($("<label>")),
        "object":   $("<div>").append($("<label>")),
        "date":     $("<div>").append($("<label>"))
    };

    JsonEdit.labels = {
        "number": $("<span>"),
        "boolean": $("<span>"),
        "string": $("<span>"),
        "regexp": $("<span>"),
        "date": $("<span>"),
        "object": $("<h3>"),
        "array": $("<h3>")
    };

    JsonEdit.inputs = {
        "number": $("<input>", {type: "number"}),
        "boolean": $("<input>", {type: "checkbox"}),
        "string": $("<input>", {type: "text"}),
        "regexp": $("<input>", {type: "text"}),
        "date": $("<input>", {type: "date"}),
        "array": $("<input>", {type: "text"}),
    };
})($);

<|endoftext|>


<commit_before>var expect = require('chai').expect;
var docker = require('./spec_helper').docker;
var MemoryStream = require('memorystream');

describe("#container", function() {

  var testContainer;
  before(function(done) {
    docker.createContainer({
      Image: 'ubuntu',
      AttachStdin: false,
      AttachStdout: true,
      AttachStderr: true,
      Tty: true,
      Cmd: ['/bin/bash', '-c', 'tail -f /var/log/dmesg'],
      OpenStdin: false,
      StdinOnce: false
    }, function(err, container) {
      if (err) done(err);
      testContainer = container.id;
      done();
    });
  });

  describe("#inspect", function() {
    it("should inspect a container", function(done) {
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.inspect(handler);
    });
  });

  describe("#start", function() {
    it("should start a container", function(done) {
      this.timeout(60000);

      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.start(handler);
    });
  });

  describe("#resize", function() {
    it("should resize tty a container", function(done) {
      var container = docker.getContainer(testContainer);

      function handle(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.start(function(err, data) {
        var opts = {
          h: process.stdout.rows,
          w: process.stdout.columns
        };
        container.resize(opts, handle);
      });
    });
  });

  describe("#attach", function() {
    var optsc = {
      'Hostname': '',
      'User': '',
      'AttachStdin': false,
      'AttachStdout': true,
      'AttachStderr': true,
      'Tty': false,
      'OpenStdin': false,
      'StdinOnce': false,
      'Env': null,
      'Cmd': ['/bin/bash', '-c', 'uptime'],
      'Dns': ['8.8.8.8', '8.8.4.4'],
      'Image': 'ubuntu',
      'Volumes': {},
      'VolumesFrom': ''
    };

    it("should attach and wait for a container", function(done) {
      this.timeout(120000);

      function handler(err, container) {
        expect(err).to.be.null;
        expect(container).to.be.ok;

        container.attach({
          stream: true,
          stdout: true,
          stderr: true
        }, function handler(err, stream) {
          expect(err).to.be.null;
          expect(stream).to.be.ok;

          var memStream = new MemoryStream();
          var output = '';
          memStream.on('data', function(data) {
            output += data.toString();
          });

          container.modem.demuxStream(stream, memStream, memStream);

          container.start(function(err, data) {
            expect(err).to.be.null;

            container.wait(function(err, data) {
              expect(err).to.be.null;
              expect(data).to.be.ok;
              expect(output).to.match(/.*users.*load average.*/);
              done();
            });
          });
        });

      }

      optsc.AttachStdin = false;
      optsc.OpenStdin = false;
      optsc.Cmd = ['bash', '-c', 'uptime'];

      docker.createContainer(optsc, handler);
    });

    it("should support attach with stdin enable", function(done) {
      this.timeout(120000);

      function handler(err, container) {
        expect(err).to.be.null;
        expect(container).to.be.ok;

        var attach_opts = {
          stream: true,
          stdin: true,
          stdout: true,
          stderr: true
        };
        container.attach(attach_opts, function handler(err, stream) {
          expect(err).to.be.null;
          expect(stream).to.be.ok;

          var memStream = new MemoryStream();
          var output = '';
          memStream.on('data', function(data) {
            output += data.toString();
          });

          stream.pipe(memStream);

          container.start(function(err, data) {
            expect(err).to.be.null;

            stream.write("uptime; exit\n");

            container.wait(function(err, data) {
              expect(err).to.be.null;
              expect(data).to.be.ok;
              expect(output).to.match(/.*users.*load average.*/);
              done();
            });
          });
        });
      }

      optsc.AttachStdin = true;
      optsc.OpenStdin = true;
      optsc.Cmd = ['bash'];

      docker.createContainer(optsc, handler);
    })
  });

  describe("#restart", function() {
    it("should restart a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.restart(handler);
    });
  });

  describe("#export", function() {
    it("should export a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, stream) {
        expect(err).to.be.null;
        expect(stream).to.be.ok;
        done();
      }

      container.export(handler);
    });
  });

  describe("#top", function() {
    it("should return top", function(done) {
      this.timeout(10000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.top(handler);
    });
  });

  describe("#changes", function() {
    this.timeout(10000);

    var container;
    beforeEach(function(done) {
      docker.run(
        'ubuntu', ['/bin/bash', '-c', 'echo "xfoo" > foo.txt'],
        null,
        function(err, result, subject) {
          // subject is the resulting container from the operation
          container = subject;
          done(err);
        }
      );
    });

    afterEach(function(done) {
      container.remove(done);
    });

    it("should container changes", function(done) {
      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.changes(handler);
    });
  });

  describe("#logs", function() {

    it("should get the logs for a container as a stream", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);
      var logs_opts = {
        follow: true,
        stdout: true,
        stderr: true,
        timestamps: true
      };

      function handler(err, stream) {
        expect(err).to.be.null;
        expect(stream.pipe).to.be.ok;
        done();
      }

      container.logs(logs_opts, handler);

    });
  });

  describe("#exec", function() {
    it("should run exec on a container", function(done) {
      this.timeout(10000);
      var options = {
        Cmd: ["echo", "'foo'"]
      };

      var container = docker.getContainer(testContainer);

      function start_handler(err, stream) {
        expect(err).to.be.null;
        expect(stream.pipe).to.be.ok;
        done();
      }

      function handler(err, exec) {
        expect(err).to.be.null;
        exec.start(start_handler);
      }

      container.exec(options, handler);
    });
  });

  describe("#stop", function() {
    it("should stop a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.stop(handler);
    });
  });
});

describe("#non-responsive container", function() {

  var testContainer;
  before(function(done) {
    docker.createContainer({
      Image: 'ubuntu',
      AttachStdin: false,
      AttachStdout: true,
      AttachStderr: true,
      Tty: true,
      Cmd: ['/bin/sh', '-c', "trap 'echo no' TERM; while true; do sleep 1; done"],
      OpenStdin: false,
      StdinOnce: false
    }, function(err, container) {
      if (err) done(err);
      testContainer = container.id;
      done();
    });
  });

  describe("#stop", function() {
    it("forced after timeout", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).not.to.be.null;
        done();
      }

      container.stop({
        t: 1000
      }, handler);
    });
  });

  describe("#restart", function() {
    it("forced after timeout", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.restart({
        t: 1000
      }, handler);
    });
  });

});
<commit_msg>Test: exec/inspect<commit_after>var expect = require('chai').expect;
var docker = require('./spec_helper').docker;
var MemoryStream = require('memorystream');

describe("#container", function() {

  var testContainer;
  before(function(done) {
    docker.createContainer({
      Image: 'ubuntu',
      AttachStdin: false,
      AttachStdout: true,
      AttachStderr: true,
      Tty: true,
      Cmd: ['/bin/bash', '-c', 'tail -f /var/log/dmesg'],
      OpenStdin: false,
      StdinOnce: false
    }, function(err, container) {
      if (err) done(err);
      testContainer = container.id;
      done();
    });
  });

  describe("#inspect", function() {
    it("should inspect a container", function(done) {
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.inspect(handler);
    });
  });

  describe("#start", function() {
    it("should start a container", function(done) {
      this.timeout(60000);

      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.start(handler);
    });
  });

  describe("#resize", function() {
    it("should resize tty a container", function(done) {
      var container = docker.getContainer(testContainer);

      function handle(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.start(function(err, data) {
        var opts = {
          h: process.stdout.rows,
          w: process.stdout.columns
        };
        container.resize(opts, handle);
      });
    });
  });

  describe("#attach", function() {
    var optsc = {
      'Hostname': '',
      'User': '',
      'AttachStdin': false,
      'AttachStdout': true,
      'AttachStderr': true,
      'Tty': false,
      'OpenStdin': false,
      'StdinOnce': false,
      'Env': null,
      'Cmd': ['/bin/bash', '-c', 'uptime'],
      'Dns': ['8.8.8.8', '8.8.4.4'],
      'Image': 'ubuntu',
      'Volumes': {},
      'VolumesFrom': ''
    };

    it("should attach and wait for a container", function(done) {
      this.timeout(120000);

      function handler(err, container) {
        expect(err).to.be.null;
        expect(container).to.be.ok;

        container.attach({
          stream: true,
          stdout: true,
          stderr: true
        }, function handler(err, stream) {
          expect(err).to.be.null;
          expect(stream).to.be.ok;

          var memStream = new MemoryStream();
          var output = '';
          memStream.on('data', function(data) {
            output += data.toString();
          });

          container.modem.demuxStream(stream, memStream, memStream);

          container.start(function(err, data) {
            expect(err).to.be.null;

            container.wait(function(err, data) {
              expect(err).to.be.null;
              expect(data).to.be.ok;
              expect(output).to.match(/.*users.*load average.*/);
              done();
            });
          });
        });

      }

      optsc.AttachStdin = false;
      optsc.OpenStdin = false;
      optsc.Cmd = ['bash', '-c', 'uptime'];

      docker.createContainer(optsc, handler);
    });

    it("should support attach with stdin enable", function(done) {
      this.timeout(120000);

      function handler(err, container) {
        expect(err).to.be.null;
        expect(container).to.be.ok;

        var attach_opts = {
          stream: true,
          stdin: true,
          stdout: true,
          stderr: true
        };
        container.attach(attach_opts, function handler(err, stream) {
          expect(err).to.be.null;
          expect(stream).to.be.ok;

          var memStream = new MemoryStream();
          var output = '';
          memStream.on('data', function(data) {
            output += data.toString();
          });

          stream.pipe(memStream);

          container.start(function(err, data) {
            expect(err).to.be.null;

            stream.write("uptime; exit\n");

            container.wait(function(err, data) {
              expect(err).to.be.null;
              expect(data).to.be.ok;
              expect(output).to.match(/.*users.*load average.*/);
              done();
            });
          });
        });
      }

      optsc.AttachStdin = true;
      optsc.OpenStdin = true;
      optsc.Cmd = ['bash'];

      docker.createContainer(optsc, handler);
    })
  });

  describe("#restart", function() {
    it("should restart a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.restart(handler);
    });
  });

  describe("#export", function() {
    it("should export a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, stream) {
        expect(err).to.be.null;
        expect(stream).to.be.ok;
        done();
      }

      container.export(handler);
    });
  });

  describe("#top", function() {
    it("should return top", function(done) {
      this.timeout(10000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.top(handler);
    });
  });

  describe("#changes", function() {
    this.timeout(10000);

    var container;
    beforeEach(function(done) {
      docker.run(
        'ubuntu', ['/bin/bash', '-c', 'echo "xfoo" > foo.txt'],
        null,
        function(err, result, subject) {
          // subject is the resulting container from the operation
          container = subject;
          done(err);
        }
      );
    });

    afterEach(function(done) {
      container.remove(done);
    });

    it("should container changes", function(done) {
      function handler(err, data) {
        expect(err).to.be.null;
        expect(data).to.be.ok;
        done();
      }

      container.changes(handler);
    });
  });

  describe("#logs", function() {

    it("should get the logs for a container as a stream", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);
      var logs_opts = {
        follow: true,
        stdout: true,
        stderr: true,
        timestamps: true
      };

      function handler(err, stream) {
        expect(err).to.be.null;
        expect(stream.pipe).to.be.ok;
        done();
      }

      container.logs(logs_opts, handler);

    });
  });

  describe("#exec", function() {
    it("should run exec on a container", function(done) {
      this.timeout(10000);
      var options = {
        Cmd: ["echo", "'foo'"]
      };

      var container = docker.getContainer(testContainer);

      function handler(err, exec) {
        expect(err).to.be.null;

        exec.start(function(err, stream) {
          expect(err).to.be.null;
          expect(stream.pipe).to.be.ok;

          exec.inspect(function(err, data) {
            expect(err).to.be.null;
            expect(data).to.be.ok;

            done();
          });
        });
      }

      container.exec(options, handler);
    });
  });

  describe("#stop", function() {
    it("should stop a container", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.stop(handler);
    });
  });
});

describe("#non-responsive container", function() {

  var testContainer;
  before(function(done) {
    docker.createContainer({
      Image: 'ubuntu',
      AttachStdin: false,
      AttachStdout: true,
      AttachStderr: true,
      Tty: true,
      Cmd: ['/bin/sh', '-c', "trap 'echo no' TERM; while true; do sleep 1; done"],
      OpenStdin: false,
      StdinOnce: false
    }, function(err, container) {
      if (err) done(err);
      testContainer = container.id;
      done();
    });
  });

  describe("#stop", function() {
    it("forced after timeout", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).not.to.be.null;
        done();
      }

      container.stop({
        t: 1000
      }, handler);
    });
  });

  describe("#restart", function() {
    it("forced after timeout", function(done) {
      this.timeout(30000);
      var container = docker.getContainer(testContainer);

      function handler(err, data) {
        expect(err).to.be.null;
        done();
      }

      container.restart({
        t: 1000
      }, handler);
    });
  });

});
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import Promise from 'promise';

export default class StepZilla extends Component {
  constructor(props) {
    super(props);

    this.state = {
      ...this.getPrevNextBtnState(this.props.startAtStep),
      compState: this.props.startAtStep,
      navState: this.getNavStates(this.props.startAtStep, this.props.steps.length),
    };

    this.hidden = {
      display: 'none'
    };

    // if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
    this.nextTextOnFinalActionStep = (this.props.nextTextOnFinalActionStep) ? this.props.nextTextOnFinalActionStep : this.props.nextButtonText;

    this.applyValidationFlagsToSteps();
  }

  // extend the "steps" array with flags to indicate if they have been validated
  applyValidationFlagsToSteps() {
    this.props.steps.map((i) => {
      if (this.props.dontValidate) {
        i.validated = true;
      }
      else {
        // check if isValidated was exposed in the step, if yes then set initial state as not validated (false) or vice versa
        i.validated = (typeof i.component.type === 'undefined' ||
          typeof i.component.type.prototype.isValidated === 'undefined') ? true : false;
      }

      return i;
    });
  }

  // update the header nav states via classes so they can be styled via css
  getNavStates(indx, length) {
    let styles = [];

    for (let i=0; i<length; i++) {
      if (i < indx) {
        styles.push('done');
      }
      else if (i === indx) {
        styles.push('doing');
      }
      else {
        styles.push('todo');
      }
    }

    return { current: indx, styles }
  }

  getPrevNextBtnState(currentStep) {
    let correctNextText = this.props.nextButtonText;

    if (currentStep > 0 && currentStep !== this.props.steps.length - 1) {
      if (currentStep === this.props.steps.length - 2) {
        correctNextText = this.nextTextOnFinalActionStep; // we are in the one before final step
      }
      return {
        showPreviousBtn: true,
        showNextBtn: true,
        nextStepText: correctNextText
      };
    } else if (currentStep === 0) {
      return {
        showPreviousBtn: false,
        showNextBtn: true,
        nextStepText: correctNextText
      };
    }
    return {
      showPreviousBtn: this.props.prevBtnOnLastStep,
      showNextBtn: false,
      nextStepText: correctNextText
    };
  }

  // which step are we in?
  checkNavState(currentStep) {
    this.setState(this.getPrevNextBtnState(currentStep));
  }

  // set the nav state
  setNavState(next) {
    this.setState({navState: this.getNavStates(next, this.props.steps.length)});

    if (next < this.props.steps.length) {
      this.setState({compState: next});
    }

    this.checkNavState(next);
  }

  // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next
  handleKeyDown(evt) {
    if (evt.which === 13) {
      if (!this.props.preventEnterSubmission) {
        this.next();
      }
      else {
        evt.preventDefault();
      }
    }
  }

  // this utility method lets Child components invoke a direct jump to another step
  jumpToStep(evt) {
    if (evt.target == undefined) {
      // a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
      this.setNavState(evt);
    }
    else { // the main navigation step ui is invoking a jump between steps
      if (!this.props.stepsNavigation || evt.target.value == this.state.compState) { // if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
        evt.preventDefault();
        evt.stopPropagation();

        return;
      }

      evt.persist(); // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)

      const movingBack = evt.target.value < this.state.compState; // are we trying to move back or front?
      let passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
      let proceed = false; // flag on if we should move on

      this.abstractStepMoveAllowedToPromise(movingBack)
        .then((valid = true) => { // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync v
          proceed = valid;

          if (!movingBack) {
            this.updateStepValidationFlag(proceed);
          }

          if (proceed) {
            if (!movingBack) {
              // looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and 'some' that to get a decision on if we should allow moving forward
              passThroughStepsNotValid = this.props.steps
                .reduce((a, c, i) => {
                  if (i >= this.state.compState && i < evt.target.value) {
                    a.push(c.validated);
                  }
                  return a;
                }, [])
                .some((c) => {
                  return c === false
                })
            }
          }
        })
        .catch((e) => {
          // Promise based validation was a fail (i.e reject())
          if (!movingBack) {
            this.updateStepValidationFlag(false);
          }
        })
        .then(() => {
          // this is like finally(), executes if error no no error
          if (proceed && !passThroughStepsNotValid) {
            if (evt.target.value === (this.props.steps.length - 1) &&
              this.state.compState === (this.props.steps.length - 1)) {
                this.setNavState(this.props.steps.length);
            }
            else {
              this.setNavState(evt.target.value);
            }
          }
        })
        .catch(e => {
          if (e) {
            // see note below called "CatchRethrowing"
            // ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
            setTimeout(function() { throw e; });
          }
        });
    }
  }

  // move next via next button
  next() {
    this.abstractStepMoveAllowedToPromise()
      .then((proceed = true) => {
        // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync validation
        this.updateStepValidationFlag(proceed);

        if (proceed) {
          this.setNavState(this.state.compState + 1);
        }
      })
      .catch((e) => {
        if (e) {
          // CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
          // ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
          // ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
          //
          setTimeout(function() { throw e; });
        }

        // Promise based validation was a fail (i.e reject())
        this.updateStepValidationFlag(false);
      });
  }

  // move behind via previous button
  previous() {
    if (this.state.compState > 0) {
      this.setNavState(this.state.compState - 1);
    }
  }

  // update step's validation flag
  updateStepValidationFlag(val = true) {
    this.props.steps[this.state.compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
  }

  // are we allowed to move forward? via the next button or via jumpToStep?
  stepMoveAllowed(skipValidationExecution = false) {
    let proceed = false;

    if (this.props.dontValidate) {
      proceed = true;
    }
    else {
      if (skipValidationExecution) {
        // we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
        proceed = true;
      }
      else if (this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(this.state.compState) > -1) {
        // the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC, so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
        proceed = this.refs.activeComponent.refs.component.isValidated();
      }
      else if (Object.keys(this.refs).length == 0 || typeof this.refs.activeComponent.isValidated == 'undefined') {
        // if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
        proceed = true;
      }
      else {
        // user is moving forward in steps, invoke validation as its available
        proceed = this.refs.activeComponent.isValidated();
      }
    }

    return proceed;
  }

  // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
  abstractStepMoveAllowedToPromise(movingBack) {
    return Promise.resolve(this.stepMoveAllowed(movingBack));
  }

  // get the classmame of steps
  getClassName(className, i){
    let liClassName = className + "-" + this.state.navState.styles[i];

    // if step ui based navigation is disabled, then dont highlight step
    if (!this.props.stepsNavigation)
        liClassName += " no-hl";

    return liClassName;
  }

  // render the steps as stepsNavigation
  renderSteps() {
    return this.props.steps.map((s, i)=> (

 {this.jumpToStep(evt)}} key={i} value={i}>
        {i+1}
        {this.props.steps[i].name}

    ));
  }

  // main render of stepzilla container
  render() {
    let compToRender;

    // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
    let cloneExtensions = {
      jumpToStep: (t) => {
        this.jumpToStep(t);
      }
    };

    const componentPointer = this.props.steps[this.state.compState].component;

    // can only update refs if its a regular React component (not a pure component), so lets check that
    if (componentPointer instanceof Component || // unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
        (componentPointer.type && componentPointer.type.prototype instanceof Component)) {
          cloneExtensions.ref = 'activeComponent';
    }

    compToRender = React.cloneElement(componentPointer, cloneExtensions);

    return (

 {this.handleKeyDown(evt)}}>
        {
          this.props.showSteps
          ?

              {this.renderSteps()}

          :
        }

        {compToRender}

          <button style={this.state.showPreviousBtn ? {} : this.hidden}
                  className="btn btn-prev btn-primary btn-lg pull-left"
                  onClick={() => {this.previous()}}>{this.props.backButtonText}</button>

          <button style={this.state.showNextBtn ? {} : this.hidden}
                  className="btn btn-next btn-primary btn-lg pull-right"
                  onClick={() => {this.next()}}>{this.state.nextStepText}</button>

    );
  }
}

StepZilla.defaultProps = {
  showSteps: true,
  showNavigation: true,
  stepsNavigation: true,
  prevBtnOnLastStep: true,
  dontValidate: false,
  preventEnterSubmission: false,
  startAtStep: 0,
  nextButtonText: "Next",
  backButtonText: "Previous",
  hocValidationAppliedTo: []
};

StepZilla.propTypes = {
  steps: PropTypes.arrayOf(PropTypes.shape({
    name: PropTypes.string.isRequired,
    component: PropTypes.element.isRequired
  })).isRequired,
  showSteps: PropTypes.bool,
  showNavigation: PropTypes.bool,
  stepsNavigation: PropTypes.bool,
  prevBtnOnLastStep: PropTypes.bool,
  dontValidate: PropTypes.bool,
  preventEnterSubmission: PropTypes.bool,
  preventEnterSubmission: PropTypes.bool,
  startAtStep: PropTypes.number,
  nextButtonText: PropTypes.string,
  backButtonText: PropTypes.string,
  hocValidationAppliedTo: PropTypes.array
}
<commit_msg>WIP #27 - needs more testing<commit_after>import React, { Component, PropTypes } from 'react';
import Promise from 'promise';

export default class StepZilla extends Component {
  constructor(props) {
    super(props);

    this.state = {
      ...this.getPrevNextBtnState(this.props.startAtStep),
      compState: this.props.startAtStep,
      navState: this.getNavStates(this.props.startAtStep, this.props.steps.length),
    };

    this.hidden = {
      display: 'none'
    };

    // if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
    this.nextTextOnFinalActionStep = (this.props.nextTextOnFinalActionStep) ? this.props.nextTextOnFinalActionStep : this.props.nextButtonText;

    this.applyValidationFlagsToSteps();
  }

  // extend the "steps" array with flags to indicate if they have been validated
  applyValidationFlagsToSteps() {
    this.props.steps.map((i, idx) => {
      if (this.props.dontValidate) {
        i.validated = true;
      }
      else {
        // check if isValidated was exposed in the step, if yes then set initial state as not validated (false) or vice versa
        // if HOCValidation is used for the step then mark it as "requires to be validated. i.e. false"
        i.validated = (typeof i.component.type === 'undefined' ||
          (typeof i.component.type.prototype.isValidated === 'undefined' &&
            !this.isStepAtIndexHOCValidationBased(idx))) ? true : false;
      }

      return i;
    });
  }

  // update the header nav states via classes so they can be styled via css
  getNavStates(indx, length) {
    let styles = [];

    for (let i=0; i<length; i++) {
      if (i < indx) {
        styles.push('done');
      }
      else if (i === indx) {
        styles.push('doing');
      }
      else {
        styles.push('todo');
      }
    }

    return { current: indx, styles }
  }

  getPrevNextBtnState(currentStep) {
    let correctNextText = this.props.nextButtonText;

    if (currentStep > 0 && currentStep !== this.props.steps.length - 1) {
      if (currentStep === this.props.steps.length - 2) {
        correctNextText = this.nextTextOnFinalActionStep; // we are in the one before final step
      }
      return {
        showPreviousBtn: true,
        showNextBtn: true,
        nextStepText: correctNextText
      };
    } else if (currentStep === 0) {
      return {
        showPreviousBtn: false,
        showNextBtn: true,
        nextStepText: correctNextText
      };
    }
    return {
      showPreviousBtn: this.props.prevBtnOnLastStep,
      showNextBtn: false,
      nextStepText: correctNextText
    };
  }

  // which step are we in?
  checkNavState(currentStep) {
    this.setState(this.getPrevNextBtnState(currentStep));
  }

  // set the nav state
  setNavState(next) {
    this.setState({navState: this.getNavStates(next, this.props.steps.length)});

    if (next < this.props.steps.length) {
      this.setState({compState: next});
    }

    this.checkNavState(next);
  }

  // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next
  handleKeyDown(evt) {
    if (evt.which === 13) {
      if (!this.props.preventEnterSubmission) {
        this.next();
      }
      else {
        evt.preventDefault();
      }
    }
  }

  // this utility method lets Child components invoke a direct jump to another step
  jumpToStep(evt) {
    if (evt.target == undefined) {
      // a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
      this.setNavState(evt);
    }
    else { // the main navigation step ui is invoking a jump between steps
      if (!this.props.stepsNavigation || evt.target.value == this.state.compState) { // if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
        evt.preventDefault();
        evt.stopPropagation();

        return;
      }

      evt.persist(); // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)

      const movingBack = evt.target.value < this.state.compState; // are we trying to move back or front?
      let passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
      let proceed = false; // flag on if we should move on

      this.abstractStepMoveAllowedToPromise(movingBack)
        .then((valid = true) => { // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync v
          proceed = valid;

          if (!movingBack) {
            this.updateStepValidationFlag(proceed);
          }

          if (proceed) {
            if (!movingBack) {
              // looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and 'some' that to get a decision on if we should allow moving forward
              passThroughStepsNotValid = this.props.steps
                .reduce((a, c, i) => {
                  if (i >= this.state.compState && i < evt.target.value) {
                    a.push(c.validated);
                  }
                  return a;
                }, [])
                .some((c) => {
                  return c === false
                })
            }
          }
        })
        .catch((e) => {
          // Promise based validation was a fail (i.e reject())
          if (!movingBack) {
            this.updateStepValidationFlag(false);
          }
        })
        .then(() => {
          // this is like finally(), executes if error no no error
          if (proceed && !passThroughStepsNotValid) {
            if (evt.target.value === (this.props.steps.length - 1) &&
              this.state.compState === (this.props.steps.length - 1)) {
                this.setNavState(this.props.steps.length);
            }
            else {
              this.setNavState(evt.target.value);
            }
          }
        })
        .catch(e => {
          if (e) {
            // see note below called "CatchRethrowing"
            // ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
            setTimeout(function() { throw e; });
          }
        });
    }
  }

  // move next via next button
  next() {
    this.abstractStepMoveAllowedToPromise()
      .then((proceed = true) => {
        // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync validation
        this.updateStepValidationFlag(proceed);

        if (proceed) {
          this.setNavState(this.state.compState + 1);
        }
      })
      .catch((e) => {
        if (e) {
          // CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
          // ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
          // ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
          //
          setTimeout(function() { throw e; });
        }

        // Promise based validation was a fail (i.e reject())
        this.updateStepValidationFlag(false);
      });
  }

  // move behind via previous button
  previous() {
    if (this.state.compState > 0) {
      this.setNavState(this.state.compState - 1);
    }
  }

  // update step's validation flag
  updateStepValidationFlag(val = true) {
    this.props.steps[this.state.compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
  }

  // are we allowed to move forward? via the next button or via jumpToStep?
  stepMoveAllowed(skipValidationExecution = false) {
    let proceed = false;

    if (this.props.dontValidate) {
      proceed = true;
    }
    else {
      if (skipValidationExecution) {
        // we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
        proceed = true;
      }
      else if (this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(this.state.compState) > -1) {
        // the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
        // so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
        proceed = this.refs.activeComponent.refs.component.isValidated();
      }
      else if (Object.keys(this.refs).length == 0 || typeof this.refs.activeComponent.isValidated == 'undefined') {
        // if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
        proceed = true;
      }
      else {
        // user is moving forward in steps, invoke validation as its available
        proceed = this.refs.activeComponent.isValidated();
      }
    }

    return proceed;
  }

  isStepAtIndexHOCValidationBased(stepIndex) {
    return (this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(stepIndex) > -1);
  }

  // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
  abstractStepMoveAllowedToPromise(movingBack) {
    return Promise.resolve(this.stepMoveAllowed(movingBack));
  }

  // get the classmame of steps
  getClassName(className, i){
    let liClassName = className + "-" + this.state.navState.styles[i];

    // if step ui based navigation is disabled, then dont highlight step
    if (!this.props.stepsNavigation)
        liClassName += " no-hl";

    return liClassName;
  }

  // render the steps as stepsNavigation
  renderSteps() {
    return this.props.steps.map((s, i)=> (

 {this.jumpToStep(evt)}} key={i} value={i}>
        {i+1}
        {this.props.steps[i].name}

    ));
  }

  // main render of stepzilla container
  render() {
    let compToRender;

    // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
    let cloneExtensions = {
      jumpToStep: (t) => {
        this.jumpToStep(t);
      }
    };

    const componentPointer = this.props.steps[this.state.compState].component;

    // can only update refs if its a regular React component (not a pure component), so lets check that
    if (componentPointer instanceof Component || // unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
        (componentPointer.type && componentPointer.type.prototype instanceof Component)) {
          cloneExtensions.ref = 'activeComponent';
    }

    compToRender = React.cloneElement(componentPointer, cloneExtensions);

    return (

 {this.handleKeyDown(evt)}}>
        {
          this.props.showSteps
          ?

              {this.renderSteps()}

          :
        }

        {compToRender}

          <button style={this.state.showPreviousBtn ? {} : this.hidden}
                  className="btn btn-prev btn-primary btn-lg pull-left"
                  onClick={() => {this.previous()}}>{this.props.backButtonText}</button>

          <button style={this.state.showNextBtn ? {} : this.hidden}
                  className="btn btn-next btn-primary btn-lg pull-right"
                  onClick={() => {this.next()}}>{this.state.nextStepText}</button>

    );
  }
}

StepZilla.defaultProps = {
  showSteps: true,
  showNavigation: true,
  stepsNavigation: true,
  prevBtnOnLastStep: true,
  dontValidate: false,
  preventEnterSubmission: false,
  startAtStep: 0,
  nextButtonText: "Next",
  backButtonText: "Previous",
  hocValidationAppliedTo: []
};

StepZilla.propTypes = {
  steps: PropTypes.arrayOf(PropTypes.shape({
    name: PropTypes.string.isRequired,
    component: PropTypes.element.isRequired
  })).isRequired,
  showSteps: PropTypes.bool,
  showNavigation: PropTypes.bool,
  stepsNavigation: PropTypes.bool,
  prevBtnOnLastStep: PropTypes.bool,
  dontValidate: PropTypes.bool,
  preventEnterSubmission: PropTypes.bool,
  preventEnterSubmission: PropTypes.bool,
  startAtStep: PropTypes.number,
  nextButtonText: PropTypes.string,
  backButtonText: PropTypes.string,
  hocValidationAppliedTo: PropTypes.array
}
<|endoftext|>


<commit_before>import { expect } from 'chai';

import * as core from '../lib/core';
import { readFile } from '../lib/core';

describe('core', () => {

	describe('removeExtension', () => {
		it('should remove extension from file path', () => {
			let result = core.removeExtension('ru/markdown.md');
			expect(result).to.eql('ru/markdown');
		});
	});

	describe('filepathToUrl', () => {
		it('should transform file path to relative URL', () => {
			let result = core.filepathToUrl('ru/markdown.md');
			expect(result).to.eql('/ru/markdown');
		});
	});

	describe('parsePage', () => {
		it('should parse Markdown source with frontmatter to an object', () => {
			let filepath = 'test/samples/markdown-with-frontmatter.md';
			let result = core.parsePage(readFile(filepath), filepath);
			expect(result).to.eql(require('./expected/markdown-with-frontmatter.md.json'));
		});
		it('should parse HTML source with frontmatter to an object', () => {
			let filepath = 'test/samples/markdown-with-frontmatter.html';
			let result = core.parsePage(readFile(filepath), filepath);
			expect(result).to.eql(require('./expected/markdown-with-frontmatter.html.json'));
		});
	});

	describe('getSourceFilesList', () => {
		it('should return a list of source files', () => {
			let result = core.getSourceFilesList('test/source');
			expect(result).to.eql([
				'test/source/en/plugins-requirejs.md',
				'test/source/en/read-less-tech-books.md',
				'test/source/ru/debug-mode.md'
			]);
		});
	});

	describe('readFile', () => {
		it('should return a file content', () => {
			let result = core.readFile('test/samples/file.txt');
			expect(result).to.eql('Hello.');
		});
	});

	describe('readYamlFile', () => {
		it('should read and parse YAML file', () => {
			let result = core.readYamlFile('test/samples/file.yml');
			expect(result).to.eql({hello: 'world'});
		});
	});

	describe('readFiles', () => {
		it('should return an array with the content of every file from a given array', () => {
			let result = core.readFiles([
				'test/source/en/read-less-tech-books.md',
				'test/source/ru/debug-mode.md'
			]);
			expect(result).to.eql(require('./expected/files.json'));
		});
	});

	describe('getConfigFilesList', () => {
		it('should return a list of config files', () => {
			let result = core.getConfigFilesList('test/config');
			expect(result).to.eql([
				'test/config/default.yml',
				'test/config/en.yml',
				'test/config/ru.yml'
			]);
		});
	});

	describe('readConfigFiles', () => {
		it('should read config files to an object', () => {
			let result = core.readConfigFiles([
				'test/config/default.yml'
			]);
			expect(result).to.eql(require('./expected/configs.json'));
		});
	});

	describe('readConfigFiles', () => {
		it('should read config files (with langs) to an object', () => {
			let result = core.readConfigFiles([
				'test/config/default.yml',
				'test/config/en.yml',
				'test/config/ru.yml'
			]);
			expect(result).to.eql(require('./expected/configs-langs.json'));
		});
	});

	describe('mergeConfigs', () => {
		it('should merge config objects', () => {
			let result = core.mergeConfigs(require('./expected/configs.json'));
			expect(result).to.eql(require('./expected/configs-merged.json'));
		});
	});

	describe('mergeConfigs', () => {
		it('should merge config objects (with langs)', () => {
			let result = core.mergeConfigs(require('./expected/configs-langs.json'));
			expect(result).to.eql(require('./expected/configs-langs-merged.json'));
		});
	});

	describe('readConfig', () => {
		it('should return merged config object', () => {
			let result = core.readConfig('test/config');
			// console.log(JSON.stringify(result));
			expect(result).to.eql(require('./expected/configs-langs-merged.json'));
		});
	});

	describe('makeContext', () => {
		it('should return merged config object', () => {
			let result = core.makeContext(
				{
					title: 'Hello',
					content: 'Test'
				}, {
					default: {
						title: 'Blog',
						author: 'Artem Sapegin'
					}
				}, {
					siteTitle: function() { return this.config.default.title },
					heading: function(l) { return `<h${l}>${this.title}</h${l}>` }
				}
			);
			expect(result.title).to.eql('Hello');
			expect(result.config.default.title).to.eql('Blog');
			expect(result.siteTitle()).to.eql('Blog');
			expect(result.heading(2)).to.eql('
Hello
');
		});
	});

});
<commit_msg>Change formatting in test.<commit_after>import { expect } from 'chai';

import * as core from '../lib/core';
import { readFile } from '../lib/core';

describe('core', () => {

	describe('removeExtension', () => {
		it('should remove extension from file path', () => {
			let result = core.removeExtension('ru/markdown.md');
			expect(result).to.eql('ru/markdown');
		});
	});

	describe('filepathToUrl', () => {
		it('should transform file path to relative URL', () => {
			let result = core.filepathToUrl('ru/markdown.md');
			expect(result).to.eql('/ru/markdown');
		});
	});

	describe('parsePage', () => {
		it('should parse Markdown source with frontmatter to an object', () => {
			let filepath = 'test/samples/markdown-with-frontmatter.md';
			let result = core.parsePage(readFile(filepath), filepath);
			expect(result).to.eql(require('./expected/markdown-with-frontmatter.md.json'));
		});
		it('should parse HTML source with frontmatter to an object', () => {
			let filepath = 'test/samples/markdown-with-frontmatter.html';
			let result = core.parsePage(readFile(filepath), filepath);
			expect(result).to.eql(require('./expected/markdown-with-frontmatter.html.json'));
		});
	});

	describe('getSourceFilesList', () => {
		it('should return a list of source files', () => {
			let result = core.getSourceFilesList('test/source');
			expect(result).to.eql([
				'test/source/en/plugins-requirejs.md',
				'test/source/en/read-less-tech-books.md',
				'test/source/ru/debug-mode.md'
			]);
		});
	});

	describe('readFile', () => {
		it('should return a file content', () => {
			let result = core.readFile('test/samples/file.txt');
			expect(result).to.eql('Hello.');
		});
	});

	describe('readYamlFile', () => {
		it('should read and parse YAML file', () => {
			let result = core.readYamlFile('test/samples/file.yml');
			expect(result).to.eql({hello: 'world'});
		});
	});

	describe('readFiles', () => {
		it('should return an array with the content of every file from a given array', () => {
			let result = core.readFiles([
				'test/source/en/read-less-tech-books.md',
				'test/source/ru/debug-mode.md'
			]);
			expect(result).to.eql(require('./expected/files.json'));
		});
	});

	describe('getConfigFilesList', () => {
		it('should return a list of config files', () => {
			let result = core.getConfigFilesList('test/config');
			expect(result).to.eql([
				'test/config/default.yml',
				'test/config/en.yml',
				'test/config/ru.yml'
			]);
		});
	});

	describe('readConfigFiles', () => {
		it('should read config files to an object', () => {
			let result = core.readConfigFiles([
				'test/config/default.yml'
			]);
			expect(result).to.eql(require('./expected/configs.json'));
		});
	});

	describe('readConfigFiles', () => {
		it('should read config files (with langs) to an object', () => {
			let result = core.readConfigFiles([
				'test/config/default.yml',
				'test/config/en.yml',
				'test/config/ru.yml'
			]);
			expect(result).to.eql(require('./expected/configs-langs.json'));
		});
	});

	describe('mergeConfigs', () => {
		it('should merge config objects', () => {
			let result = core.mergeConfigs(require('./expected/configs.json'));
			expect(result).to.eql(require('./expected/configs-merged.json'));
		});
	});

	describe('mergeConfigs', () => {
		it('should merge config objects (with langs)', () => {
			let result = core.mergeConfigs(require('./expected/configs-langs.json'));
			expect(result).to.eql(require('./expected/configs-langs-merged.json'));
		});
	});

	describe('readConfig', () => {
		it('should return merged config object', () => {
			let result = core.readConfig('test/config');
			// console.log(JSON.stringify(result));
			expect(result).to.eql(require('./expected/configs-langs-merged.json'));
		});
	});

	describe('makeContext', () => {
		it('should return merged config object', () => {
			let result = core.makeContext({
				title: 'Hello',
				content: 'Test'
			}, {
				default: {
					title: 'Blog',
					author: 'Artem Sapegin'
				}
			}, {
				siteTitle: function() { return this.config.default.title },
				heading: function(l) { return `<h${l}>${this.title}</h${l}>` }
			});
			expect(result.title).to.eql('Hello');
			expect(result.config.default.title).to.eql('Blog');
			expect(result.siteTitle()).to.eql('Blog');
			expect(result.heading(2)).to.eql('
Hello
');
		});
	});

});
<|endoftext|>