<commit_before>CT.stream.Video = CT.Class({
	CLASSNAME: "CT.stream.Video",
	_buffers: [],
	_lastTime: 0,
	_nextChunk: 0,
	_sourceOpen: function() {
		this.log("sourceopen");
		this.setSourceBuffer();
	},
	_sourceUpdate: function() {
		this.log("_sourceUpdate", this._buffers.length,
			(this.sourceBuffer && this.sourceBuffer.updating));//, this.mediaSource.readyState);
		if (this.video.error) {
			this.log("ERROR - RESET video");
			this.reset();
		} else if (this.sourceBuffer && !this.sourceBuffer.updating) {
			if (this._buffers.length)
				this.sourceBuffer.appendBuffer(this._buffers.shift());
			else if (this.mediaSource.readyState == "open")
				this.mediaSource.endOfStream();
		}
	},
	_error: function(e) {
		this.log("ERROR RESET", e.message, e.target.error);
		this.reset();
	},
	setSourceBuffer: function() {
		var mt = this.opts.mimeType;
		this.log("setSourceBuffer - exists:", !!this.sourceBuffer, "mimeType", mt);
		if (!this.sourceBuffer) {
			this.sourceBuffer = this.mediaSource.addSourceBuffer(mt);
			this.sourceBuffer.mode = 'sequence';
			this.sourceBuffer.addEventListener("updateend", this._sourceUpdate);
			this.sourceBuffer.addEventListener("error", this._error);
		}
	},
	bufferer: function(buffer) {
		var v = this.video;
		this._buffers.push(buffer);
		this._sourceUpdate();

		if (v.duration != Infinity && !isNaN(v.duration)) {
			if (this._duration == v.duration)
				this.reset();
			this._duration = v.duration;
			var target = v.duration - (CT.stream.opts.chunk / 1000);
			if (v.currentTime < this._lastTime) {
				this.log("SKIPPED RESET (backtracking)");
//				that.reset();
			}
			this._lastTime = v.currentTime;
			if (!v.paused && v.currentTime < target)
				v.currentTime = target;
		}
	},
	process: function(blob) {
		this.recorder && this.recorder.remember(blob);
		CT.stream.util.blob_to_buffer(blob, this.bufferer);
	},
	start: function() {
		this.log("start (attempting) - paused:", this.video.paused);
		var that = this, prom = this.video.play();
		prom && prom.then(function() {
			that.log("started!!! streaming!");
		})["catch"](function(error) {
			that.log("play failed! awaiting user input (android)", error.message);
			if (CT.stream.opts.requiresInput && !CT.stream.opts.requestedInput) {
				that.audio.node && CT.stream.opts.waiting.push(that.audio.node);
				CT.stream.opts.requestedInput = true;
				(new CT.modal.Prompt({
					cb: CT.stream.opts.startWaiting,
					transition: "fade",
					style: "single-choice",
					data: [ "Play Stream" ],
					prompt: "Ready to stream?"
				})).show();
			}
			CT.stream.opts.waiting.push(that.video);
		});
	},
	_miniRecorder: function() {
		var rec = {
			blobs: [],
			remember: function(blob) {
				if (rec.recording)
					rec.blobs.push(blob);
			},
			start: function() {
				rec.recording = true;
			},
			stop: function() {
				rec.recording = false;
			},
			_save: function(blob_or_blobs) {
				var fname = (new Date()).toString().split("-")[0].replace(/\W/g, "") + ".webm",
					ctf = CT.file.make(blob_or_blobs);
				(new CT.modal.Modal({
					content: [
						CT.dom.div("Your file is ready for download!", "bigger pb10"),
						ctf.download(fname, "click me!", "bold centered block")
					]
				})).show();
			},
			save: function() {
				if (CT.stream.opts.transcoder)
					CT.stream.opts.transcoder(rec._save)(new Blob(rec.blobs));
				else
					rec._save(rec.blobs);
				rec.blobs = [];
			}
		};
		return rec;
	},
	save: function() {
		if (this.recorder) { // stop recording
			this._saveButton.firstChild.classList.remove("buttonActive");
			this.recorder.stop();
			this.recorder.save();
			clearTimeout(this._saveTimer);
			delete this._saveTimer;
			delete this.recorder;
		} else {             // start recording
			this._saveButton.firstChild.classList.add("buttonActive");
			this.recorder = this._miniRecorder();
			this.recorder.start();
			this._saveTimer = setTimeout(this.save, CT.stream.opts.cutoff);
		}
	},
	remove: function() {
		this.video.src = "";
		CT.dom.remove(this.node);
		this.audio.remove();
	},
	_wakeup: function() {
		if (this.video._lastct == this.video.currentTime)
			this.video.currentTime += 1; // help video along....
		this.video._lastct = this.video.currentTime;
	},
	setVideo: function() {
		this.video = this.opts.video || CT.dom.video(this.opts.stream,
			this.opts.videoClass, this.opts.videoId, this.opts.attrs);
		this.video.on("canplay", this.start);
		this.video.on("pause", this.start);
		this.video.on("error", this._error);
	},
	setMediaSource: function() {
		this.mediaSource = new MediaSource();
		this.video.src = URL.createObjectURL(this.mediaSource);
		this.mediaSource.addEventListener("sourceopen", this._sourceOpen);
		this.mediaSource.addEventListener("error", this._error);
	},
	fullscreen: function() {
		var v = this.video;
		if (v.requestFullscreen)
			v.requestFullscreen();
		else if (v.webkitRequestFullscreen)
			v.webkitRequestFullscreen();
	},
	switchEncoding: function() {
		var codecs = CT.stream.opts.codecs, oz = this.opts;
		oz.mimeType = (oz.mimeType == codecs.av) ? codecs.video : codecs.av;
		this.log("switching encoding to", oz.mimeType);
	},
	reset: function() {
		if (!this.video.parentNode) return; //  node is removed -- we're done
		var n = Date.now(), chunk = CT.stream.opts.chunk;
		if (this._lastReset) {
			var diff = n - this._lastReset;
			this.log("RESET", diff);
			if (diff < chunk / 2)
				return; // wait
			if (diff < chunk * 8)
				this.opts.onreset && this.opts.onreset();
		}
		this._lastReset = n;
		this.video.removeEventListener("canplay", this.start);
		this.video.removeEventListener("pause", this.start);
		this.video.removeEventListener("error", this._error);
		this.setVideo();
		this.video.muted = !this.audio.active;
		this.node.insertBefore(this.video, this.node.video);
		CT.dom.remove(this.node.video);
		this.node.video = this.video;
		this.sourceBuffer.removeEventListener("updateend", this._sourceUpdate);
		this.sourceBuffer.removeEventListener("error", this._error);
		delete this.sourceBuffer;
		this.mediaSource.removeEventListener("sourceopen", this._sourceOpen);
		this.mediaSource.removeEventListener("error", this._error);
		this.setMediaSource();
		if (this._buffers.length > 10)
			this._buffers.length = 0;
		else
			this._buffers.shift();
		this.requiredInitChunk = this.receivedInitChunk;
		this.opts.onrefresh && this.opts.onrefresh();
	},
	init: function(opts) {
		this.opts = opts = CT.merge(opts, {
			video: null,
			stream: null,
			className: null,
			id: null,
			frame: true,
			videoClass: null,
			videoId: null,
			watermark: null,
			mimeType: CT.stream.opts.codecs.av,
			activeAudio: false,
			onreset: null,
			buttons: [], // only checked when frame is false
			record: { // recorder always shows when frame is true
				streamer: false,
				lurker: false,
				auto: false
			},
			fullscreen: {
				streamer: false,
				lurker: true
			},
			attrs: {
				autoplay: true
			}
		});
		if (opts.stream)
			opts.attrs.srcObject = opts.stream;
		this.setVideo();
		this.audio = new CT.stream.Audio(opts.activeAudio, this);
		this._fullscreenButton = CT.dom.img("/img/fullscreen.png",
			"defpointer hoverglow w80p", this.fullscreen);
		this._saveButton = CT.dom.img("/img/save.png",
			"defpointer hoverglow w80p", this.save);
		this.node = opts.frame ? CT.dom.div([
			this.video,
			CT.dom.div([
				this.audio.button,
				CT.dom.pad(2),
				this._fullscreenButton,
				CT.dom.pad(2),
				this._saveButton
			], "centered")
		], opts.className, opts.id) : CT.dom.div(this.video, opts.className || "fulldef", opts.id);
		this.node.video = this.video;
		var hasRecorder = opts.record[opts.stream ? "streamer" : "lurker"],
			hasFullscreen = opts.fullscreen[opts.stream ? "streamer" : "lurker"];
		if (!opts.frame) {
			var cbl = CT.dom.div(null, "abs cbl");
			if (hasRecorder)
				cbl.appendChild(this._saveButton);
			if (hasFullscreen) {
				if (cbl.childNodes.length)
					cbl.appendChild(CT.dom.pad());
				cbl.appendChild(this._fullscreenButton);
			}
			opts.buttons.forEach(function(b) {
				if (cbl.childNodes.length)
					cbl.appendChild(CT.dom.pad());
				cbl.appendChild(b);
			});
			if (cbl.childNodes.length)
				this.node.appendChild(cbl);
		}
		hasRecorder && opts.record.auto && this.save();
		if (opts.watermark)
			this.node.appendChild(CT.dom.img(opts.watermark, "abs ctr w1-4 mosthigh nofilt"));
		if (opts.stream)
			this.audio.disable();
		else {
			this.setMediaSource();
			this.audio.build();
			setInterval(this._wakeup, 1000);
		}
	}
});<commit_msg>stream.Video.setEncoding()<commit_after>CT.stream.Video = CT.Class({
	CLASSNAME: "CT.stream.Video",
	_buffers: [],
	_lastTime: 0,
	_nextChunk: 0,
	_sourceOpen: function() {
		this.log("sourceopen");
		this.setSourceBuffer();
	},
	_sourceUpdate: function() {
		this.log("_sourceUpdate", this._buffers.length,
			(this.sourceBuffer && this.sourceBuffer.updating));//, this.mediaSource.readyState);
		if (this.video.error) {
			this.log("ERROR - RESET video");
			this.reset();
		} else if (this.sourceBuffer && !this.sourceBuffer.updating) {
			if (this._buffers.length)
				this.sourceBuffer.appendBuffer(this._buffers.shift());
			else if (this.mediaSource.readyState == "open")
				this.mediaSource.endOfStream();
		}
	},
	_error: function(e) {
		this.log("ERROR RESET", e.message, e.target.error);
		this.reset();
	},
	setSourceBuffer: function() {
		var mt = this.opts.mimeType;
		this.log("setSourceBuffer - exists:", !!this.sourceBuffer, "mimeType", mt);
		if (!this.sourceBuffer) {
			this.sourceBuffer = this.mediaSource.addSourceBuffer(mt);
			this.sourceBuffer.mode = 'sequence';
			this.sourceBuffer.addEventListener("updateend", this._sourceUpdate);
			this.sourceBuffer.addEventListener("error", this._error);
		}
	},
	bufferer: function(buffer) {
		var v = this.video;
		this._buffers.push(buffer);
		this._sourceUpdate();

		if (v.duration != Infinity && !isNaN(v.duration)) {
			if (this._duration == v.duration)
				this.reset();
			this._duration = v.duration;
			var target = v.duration - (CT.stream.opts.chunk / 1000);
			if (v.currentTime < this._lastTime) {
				this.log("SKIPPED RESET (backtracking)");
//				that.reset();
			}
			this._lastTime = v.currentTime;
			if (!v.paused && v.currentTime < target)
				v.currentTime = target;
		}
	},
	process: function(blob) {
		this.recorder && this.recorder.remember(blob);
		CT.stream.util.blob_to_buffer(blob, this.bufferer);
	},
	start: function() {
		this.log("start (attempting) - paused:", this.video.paused);
		var that = this, prom = this.video.play();
		prom && prom.then(function() {
			that.log("started!!! streaming!");
		})["catch"](function(error) {
			that.log("play failed! awaiting user input (android)", error.message);
			if (CT.stream.opts.requiresInput && !CT.stream.opts.requestedInput) {
				that.audio.node && CT.stream.opts.waiting.push(that.audio.node);
				CT.stream.opts.requestedInput = true;
				(new CT.modal.Prompt({
					cb: CT.stream.opts.startWaiting,
					transition: "fade",
					style: "single-choice",
					data: [ "Play Stream" ],
					prompt: "Ready to stream?"
				})).show();
			}
			CT.stream.opts.waiting.push(that.video);
		});
	},
	_miniRecorder: function() {
		var rec = {
			blobs: [],
			remember: function(blob) {
				if (rec.recording)
					rec.blobs.push(blob);
			},
			start: function() {
				rec.recording = true;
			},
			stop: function() {
				rec.recording = false;
			},
			_save: function(blob_or_blobs) {
				var fname = (new Date()).toString().split("-")[0].replace(/\W/g, "") + ".webm",
					ctf = CT.file.make(blob_or_blobs);
				(new CT.modal.Modal({
					content: [
						CT.dom.div("Your file is ready for download!", "bigger pb10"),
						ctf.download(fname, "click me!", "bold centered block")
					]
				})).show();
			},
			save: function() {
				if (CT.stream.opts.transcoder)
					CT.stream.opts.transcoder(rec._save)(new Blob(rec.blobs));
				else
					rec._save(rec.blobs);
				rec.blobs = [];
			}
		};
		return rec;
	},
	save: function() {
		if (this.recorder) { // stop recording
			this._saveButton.firstChild.classList.remove("buttonActive");
			this.recorder.stop();
			this.recorder.save();
			clearTimeout(this._saveTimer);
			delete this._saveTimer;
			delete this.recorder;
		} else {             // start recording
			this._saveButton.firstChild.classList.add("buttonActive");
			this.recorder = this._miniRecorder();
			this.recorder.start();
			this._saveTimer = setTimeout(this.save, CT.stream.opts.cutoff);
		}
	},
	remove: function() {
		this.video.src = "";
		CT.dom.remove(this.node);
		this.audio.remove();
	},
	_wakeup: function() {
		if (this.video._lastct == this.video.currentTime)
			this.video.currentTime += 1; // help video along....
		this.video._lastct = this.video.currentTime;
	},
	setVideo: function() {
		this.video = this.opts.video || CT.dom.video(this.opts.stream,
			this.opts.videoClass, this.opts.videoId, this.opts.attrs);
		this.video.on("canplay", this.start);
		this.video.on("pause", this.start);
		this.video.on("error", this._error);
	},
	setMediaSource: function() {
		this.mediaSource = new MediaSource();
		this.video.src = URL.createObjectURL(this.mediaSource);
		this.mediaSource.addEventListener("sourceopen", this._sourceOpen);
		this.mediaSource.addEventListener("error", this._error);
	},
	fullscreen: function() {
		var v = this.video;
		if (v.requestFullscreen)
			v.requestFullscreen();
		else if (v.webkitRequestFullscreen)
			v.webkitRequestFullscreen();
	},
	switchEncoding: function() {
		var codecs = CT.stream.opts.codecs, oz = this.opts;
		oz.mimeType = (oz.mimeType == codecs.av) ? codecs.video : codecs.av;
		this.log("switching encoding to", oz.mimeType);
	},
	setEncoding: function(mode) {
		var oz = this.opts, prev = oz.mimeType;
		oz.mimeType = CT.stream.opts.modes[mode];
		this.log(mode, "mode:", oz.mimeType);
		if (this.sourceBuffer && oz.mimeType != prev) {
			this.log("mode swap reset!");
			this.reset();
		}
	},
	reset: function() {
		if (!this.video.parentNode) return; //  node is removed -- we're done
		var n = Date.now(), chunk = CT.stream.opts.chunk;
		if (this._lastReset) {
			var diff = n - this._lastReset;
			this.log("RESET", diff);
			if (diff < chunk / 2)
				return; // wait
			if (diff < chunk * 8)
				this.opts.onreset && this.opts.onreset();
		}
		this._lastReset = n;
		this.video.removeEventListener("canplay", this.start);
		this.video.removeEventListener("pause", this.start);
		this.video.removeEventListener("error", this._error);
		this.setVideo();
		this.video.muted = !this.audio.active;
		this.node.insertBefore(this.video, this.node.video);
		CT.dom.remove(this.node.video);
		this.node.video = this.video;
		this.sourceBuffer.removeEventListener("updateend", this._sourceUpdate);
		this.sourceBuffer.removeEventListener("error", this._error);
		delete this.sourceBuffer;
		this.mediaSource.removeEventListener("sourceopen", this._sourceOpen);
		this.mediaSource.removeEventListener("error", this._error);
		this.setMediaSource();
		if (this._buffers.length > 10)
			this._buffers.length = 0;
		else
			this._buffers.shift();
		this.requiredInitChunk = this.receivedInitChunk;
		this.opts.onrefresh && this.opts.onrefresh();
	},
	init: function(opts) {
		this.opts = opts = CT.merge(opts, {
			video: null,
			stream: null,
			className: null,
			id: null,
			frame: true,
			videoClass: null,
			videoId: null,
			watermark: null,
			mimeType: CT.stream.opts.codecs.av,
			activeAudio: false,
			onreset: null,
			buttons: [], // only checked when frame is false
			record: { // recorder always shows when frame is true
				streamer: false,
				lurker: false,
				auto: false
			},
			fullscreen: {
				streamer: false,
				lurker: true
			},
			attrs: {
				autoplay: true
			}
		});
		if (opts.stream)
			opts.attrs.srcObject = opts.stream;
		this.setVideo();
		this.audio = new CT.stream.Audio(opts.activeAudio, this);
		this._fullscreenButton = CT.dom.img("/img/fullscreen.png",
			"defpointer hoverglow w80p", this.fullscreen);
		this._saveButton = CT.dom.img("/img/save.png",
			"defpointer hoverglow w80p", this.save);
		this.node = opts.frame ? CT.dom.div([
			this.video,
			CT.dom.div([
				this.audio.button,
				CT.dom.pad(2),
				this._fullscreenButton,
				CT.dom.pad(2),
				this._saveButton
			], "centered")
		], opts.className, opts.id) : CT.dom.div(this.video, opts.className || "fulldef", opts.id);
		this.node.video = this.video;
		var hasRecorder = opts.record[opts.stream ? "streamer" : "lurker"],
			hasFullscreen = opts.fullscreen[opts.stream ? "streamer" : "lurker"];
		if (!opts.frame) {
			var cbl = CT.dom.div(null, "abs cbl");
			if (hasRecorder)
				cbl.appendChild(this._saveButton);
			if (hasFullscreen) {
				if (cbl.childNodes.length)
					cbl.appendChild(CT.dom.pad());
				cbl.appendChild(this._fullscreenButton);
			}
			opts.buttons.forEach(function(b) {
				if (cbl.childNodes.length)
					cbl.appendChild(CT.dom.pad());
				cbl.appendChild(b);
			});
			if (cbl.childNodes.length)
				this.node.appendChild(cbl);
		}
		hasRecorder && opts.record.auto && this.save();
		if (opts.watermark)
			this.node.appendChild(CT.dom.img(opts.watermark, "abs ctr w1-4 mosthigh nofilt"));
		if (opts.stream)
			this.audio.disable();
		else {
			this.setMediaSource();
			this.audio.build();
			setInterval(this._wakeup, 1000);
		}
	}
});<|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

describe('data binding library', function() {
  var Y, BindingEngine, utils, container;

  before(function(done) {
    var requires = ['juju-databinding', 'juju-tests-utils',
                    'base', 'handlebars',
                    'model', 'model-list'];
    Y = YUI(GlobalConfig).use(requires, function(Y) {
      utils = Y.namespace('juju-tests.utils');
      BindingEngine = Y.namespace('juju.views').BindingEngine;
      done();
    });
  });

  describe('supports declarative bindings', function() {
    var engine, form, model;

    describe('binding tests', function() {
      var engine;

      it('bind should fail on invalid DOM', function() {
        engine = new BindingEngine({interval: 0});
        assert.throws(function() {engine.bind(new Y.Model(), null);},
                      'Unable to bind, invalid Viewlet');
      });

      it('maintains proper binding references', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'b');
      });

      it('supports nested model bindings', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: {b: {c: {d: 'nested'}}
              }}), viewlet);
        // The default model object should be in place.
        assert.equal(container.one('[data-bind="a.b.c.d"]').getHTML(),
                     'nested');
      });

      it('inherits functions from object attributes to its properties',
         function() {
           container = utils.makeContainer();
           container.append('

');

           var viewlet = {
             container: container,
             _changedValues: [],
             _eventHandles: [],
             bindings: {
               a: {
                 format: function() {return 'parent';}
               }
             }
           };
           engine = new BindingEngine({interval: 0});

           engine.bind(new Y.Model({a: {b: 'child'}}), viewlet);
           assert.equal(container.one('[data-bind="a.b"]').getHTML(),
                        'parent');
         });

      it('does not override child property methods',
         function() {
           container = utils.makeContainer();
           container.append('

');

           var viewlet = {
             container: container,
             _changedValues: [],
             _eventHandles: [],
             bindings: {
               a: {
                 format: function() {return 'parent';}
               },
               'a.b': {
                 format: function() { return 'child format';}
               }
             }
           };
           engine = new BindingEngine({interval: 0});

           engine.bind(new Y.Model({a: {b: 'child'}}), viewlet);
           assert.equal(container.one('[data-bind="a.b"]').getHTML(),
           'child format');
         });

      it('supports formatters', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _eventHandles: [],
          bindings: {
            a: {
              format: function(value) {
                return value + 'FORMATTED';
              }
            }
          },
          _changedValues: []
        };
        engine = new BindingEngine({interval: 0});
        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'bFORMATTED');
      });

      it('supports callbacks on binding updates', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          bindings: {
            a: {
              update: function(node, value) {
                node.set('text', 'overide');
              }
            }
          },
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'overide');
      });

      it('update callback uses formatted value', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          bindings: {
            a: {
              format: function() {return 'hi';},
              update: function(node, value) {
                node.set('text', value);
              }
            }
          },
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'hi');
      });

      it('should be able to observe pojos', function(done) {
        var pojo = {id: 'a', name: 'test'};
        container = utils.makeContainer();
        container.append('

');
        var called = false;

        var engine = new BindingEngine({interval: 0});
        engine.bind(pojo, {
          get: function(m) { return m[this.name];},
          name: 'testViewlet',
          container: container,
          _changedValues: [],
          _eventHandles: [],
          bindings: {
            name: {
              update: function(node, value) {
                node.setHTML(value);
                called = true;
              }
            }
          }
        });

        called = false;
        // Should trigger binding update
        pojo.name = 'rising';
        setTimeout(function() {
          // The polyfll impl of Object.observe needs this,
          // the browser impl shouldn't.
          assert.equal(called, true);
          done();
        }, 125);
      });

      it('unbind method unbinds models and pojos (unit test)', function(done) {
        var _unobserve = Object.unobserve;
        Object.unobserve = function() { done(); };
        var engine = new BindingEngine({interval: 0});
        engine._viewlets = [];
        engine._models = {
          model1: [{
            detach: function() {
              // If this is called then it's working.
              assert(true);
            }}],
          model2: [{}] // intentional
        };
        engine.unbind();
        Object.unobserve = _unobserve;
      });
    });

    describe('field types', function() {
      var viewlet, engine, container;

      function generateEngine(input) {
        container = utils.makeContainer();
        container.setHTML(input);
        viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});
      }

      afterEach(function() {
        container.remove().destroy(true);
        viewlet = null;
        engine = null;
      });

      it('binds strings to inputs', function() {
        generateEngine('<input type="text" data-bind="a"></input>');
        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').get('value'), 'b');
      });

      it('binds strings to textareas', function() {
        generateEngine('<textarea data-bind="g"/></textarea>');
        engine.bind(new Y.Model({g: 'g'}), viewlet);
        assert.equal(container.one('[data-bind=g]').get('value'), 'g');
      });

      it('binds numbers to inputs', function() {
        generateEngine('<input type="number" data-bind="e"/>');
        engine.bind(new Y.Model({e: 7}), viewlet);
        assert.equal(container.one('[data-bind=e]').get('value'), '7');
      });

    });
  });

  describe('dependencies in bindings', function() {
    var TestModel, engine, viewlet, container;

    beforeEach(function() {
      TestModel = Y.Base.create('tester', Y.Model, [], {}, {
        ATTRS: {
          first: {},
          last: {},
          full: {
            getter: function() {return this.get('first') +
                  ' ' + this.get('last');}
          }
        }
      });

      container = utils.makeContainer();
      viewlet = {
        container: container,
        bindings: {
          full: {
            depends: ['first', 'last']
          }
        },
        _changedValues: [],
        _eventHandles: []
      };
    });

    it('should properly update dependent fields', function() {
      var model = new TestModel({first: 'Ned', last: 'Stark'});
      container.setHTML(
          '<input data-bind="first"><input data-bind="full">');
      engine = new BindingEngine({interval: 0});
      engine.bind(model, viewlet);
      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Ned Stark');
      // Update something full depends on.
      model.setAttrs({first: 'Sansa'});
      assert.equal(container.one('[data-bind="first"]')
                   .get('value'), 'Sansa');

      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Sansa Stark');

      // Last name isn't bound the DOM fragment but is a dep
      // of full. The system should allow for this
      model.set('last', 'Lannister');
      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Sansa Lannister');
    });

  });

  describe('modellist tests', function() {
    var engine;
    beforeEach(function(done) {
      engine = new BindingEngine({interval: 0});
      done();
    });

    it('should be able to observe modellists', function() {
      var list = new Y.ModelList();
      var template = Y.Handlebars.compile(
          '{{#modellist}}' +
          '   <input id="{{id}}" name="test" value="{{test}}"/>' +
          '{{/modellist}}'
          );
      list.add({test: 'alpha', id: 'a'});
      container = utils.makeContainer();
      engine.bind(list, {
        name: 'testViewlet',
        container: container,
        _eventHandles: [],
        update: function(modellist) {
          var data = modellist.map(function(m) {return m.getAttrs();});
          this.container.setHTML(template({modellist: data}));
        }
      });
      assert.equal(container.all('input').size(), 1);
      list.add({id: 'b', test: 'beta'});
      assert.equal(container.all('input').size(), 2);

      var output = [];
      container.all('input').each(function(n) {
        output.push(n.get('value'));
      });
      assert.deepEqual(output, ['alpha', 'beta']);
    });

  });

});
<commit_msg>fixed test<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

describe('data binding library', function() {
  var Y, BindingEngine, utils, container;

  before(function(done) {
    var requires = ['juju-databinding', 'juju-tests-utils',
                    'base', 'handlebars',
                    'model', 'model-list'];
    Y = YUI(GlobalConfig).use(requires, function(Y) {
      utils = Y.namespace('juju-tests.utils');
      BindingEngine = Y.namespace('juju.views').BindingEngine;
      done();
    });
  });

  describe('supports declarative bindings', function() {
    var engine, form, model;

    describe('binding tests', function() {
      var engine;

      it('bind should fail on invalid DOM', function() {
        engine = new BindingEngine({interval: 0});
        assert.throws(function() {engine.bind(new Y.Model(), null);},
                      'Unable to bind, invalid Viewlet');
      });

      it('maintains proper binding references', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'b');
      });

      it('supports nested model bindings', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: {b: {c: {d: 'nested'}}
              }}), viewlet);
        // The default model object should be in place.
        assert.equal(container.one('[data-bind="a.b.c.d"]').getHTML(),
                     'nested');
      });

      it('inherits functions from object attributes to its properties',
         function() {
           container = utils.makeContainer();
           container.append('

');

           var viewlet = {
             container: container,
             _changedValues: [],
             _eventHandles: [],
             bindings: {
               a: {
                 format: function() {return 'parent';}
               }
             }
           };
           engine = new BindingEngine({interval: 0});

           engine.bind(new Y.Model({a: {b: 'child'}}), viewlet);
           assert.equal(container.one('[data-bind="a.b"]').getHTML(),
                        'parent');
         });

      it('does not override child property methods',
         function() {
           container = utils.makeContainer();
           container.append('

');

           var viewlet = {
             container: container,
             _changedValues: [],
             _eventHandles: [],
             bindings: {
               a: {
                 format: function() {return 'parent';}
               },
               'a.b': {
                 format: function() { return 'child format';}
               }
             }
           };
           engine = new BindingEngine({interval: 0});

           engine.bind(new Y.Model({a: {b: 'child'}}), viewlet);
           assert.equal(container.one('[data-bind="a.b"]').getHTML(),
           'child format');
         });

      it('supports formatters', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          _eventHandles: [],
          bindings: {
            a: {
              format: function(value) {
                return value + 'FORMATTED';
              }
            }
          },
          _changedValues: []
        };
        engine = new BindingEngine({interval: 0});
        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'bFORMATTED');
      });

      it('supports callbacks on binding updates', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          bindings: {
            a: {
              update: function(node, value) {
                node.set('text', 'overide');
              }
            }
          },
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'overide');
      });

      it('update callback uses formatted value', function() {
        container = utils.makeContainer();
        container.append('

');

        var viewlet = {
          container: container,
          bindings: {
            a: {
              format: function() {return 'hi';},
              update: function(node, value) {
                node.set('text', value);
              }
            }
          },
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});

        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').getHTML(), 'hi');
      });

      it('should be able to observe pojos', function(done) {
        var pojo = {id: 'a', name: 'test'};
        container = utils.makeContainer();
        container.append('

');
        var called = false;

        var engine = new BindingEngine({interval: 0});
        engine.bind(pojo, {
          get: function(m) { return m[this.name];},
          name: 'testViewlet',
          container: container,
          _changedValues: [],
          _eventHandles: [],
          bindings: {
            name: {
              update: function(node, value) {
                node.setHTML(value);
                called = true;
              }
            }
          }
        });

        called = false;
        // Should trigger binding update
        pojo.name = 'rising';
        setTimeout(function() {
          // The polyfll impl of Object.observe needs this,
          // the browser impl shouldn't.
          assert.equal(called, true);
          done();
        }, 125);
      });

      it('unbind method unbinds models and pojos (unit test)', function(done) {
        var _unobserve = Object.unobserve,
            counter = 0;
        Object.unobserve = function() {
          assert(counter, 1);
          done();
        };
        var engine = new BindingEngine({interval: 0});
        engine._viewlets = [];
        engine._models = {
          model1: [{
            detach: function() {
              // If this is called then it's working.
              counter = 1;
            }}],
          model2: [{}] // intentional
        };
        engine.unbind();
        Object.unobserve = _unobserve;
      });
    });

    describe('field types', function() {
      var viewlet, engine, container;

      function generateEngine(input) {
        container = utils.makeContainer();
        container.setHTML(input);
        viewlet = {
          container: container,
          _changedValues: [],
          _eventHandles: []
        };
        engine = new BindingEngine({interval: 0});
      }

      afterEach(function() {
        container.remove().destroy(true);
        viewlet = null;
        engine = null;
      });

      it('binds strings to inputs', function() {
        generateEngine('<input type="text" data-bind="a"></input>');
        engine.bind(new Y.Model({a: 'b'}), viewlet);
        assert.equal(container.one('[data-bind=a]').get('value'), 'b');
      });

      it('binds strings to textareas', function() {
        generateEngine('<textarea data-bind="g"/></textarea>');
        engine.bind(new Y.Model({g: 'g'}), viewlet);
        assert.equal(container.one('[data-bind=g]').get('value'), 'g');
      });

      it('binds numbers to inputs', function() {
        generateEngine('<input type="number" data-bind="e"/>');
        engine.bind(new Y.Model({e: 7}), viewlet);
        assert.equal(container.one('[data-bind=e]').get('value'), '7');
      });

    });
  });

  describe('dependencies in bindings', function() {
    var TestModel, engine, viewlet, container;

    beforeEach(function() {
      TestModel = Y.Base.create('tester', Y.Model, [], {}, {
        ATTRS: {
          first: {},
          last: {},
          full: {
            getter: function() {return this.get('first') +
                  ' ' + this.get('last');}
          }
        }
      });

      container = utils.makeContainer();
      viewlet = {
        container: container,
        bindings: {
          full: {
            depends: ['first', 'last']
          }
        },
        _changedValues: [],
        _eventHandles: []
      };
    });

    it('should properly update dependent fields', function() {
      var model = new TestModel({first: 'Ned', last: 'Stark'});
      container.setHTML(
          '<input data-bind="first"><input data-bind="full">');
      engine = new BindingEngine({interval: 0});
      engine.bind(model, viewlet);
      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Ned Stark');
      // Update something full depends on.
      model.setAttrs({first: 'Sansa'});
      assert.equal(container.one('[data-bind="first"]')
                   .get('value'), 'Sansa');

      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Sansa Stark');

      // Last name isn't bound the DOM fragment but is a dep
      // of full. The system should allow for this
      model.set('last', 'Lannister');
      assert.equal(container.one('[data-bind="full"]')
                   .get('value'), 'Sansa Lannister');
    });

  });

  describe('modellist tests', function() {
    var engine;
    beforeEach(function(done) {
      engine = new BindingEngine({interval: 0});
      done();
    });

    it('should be able to observe modellists', function() {
      var list = new Y.ModelList();
      var template = Y.Handlebars.compile(
          '{{#modellist}}' +
          '   <input id="{{id}}" name="test" value="{{test}}"/>' +
          '{{/modellist}}'
          );
      list.add({test: 'alpha', id: 'a'});
      container = utils.makeContainer();
      engine.bind(list, {
        name: 'testViewlet',
        container: container,
        _eventHandles: [],
        update: function(modellist) {
          var data = modellist.map(function(m) {return m.getAttrs();});
          this.container.setHTML(template({modellist: data}));
        }
      });
      assert.equal(container.all('input').size(), 1);
      list.add({id: 'b', test: 'beta'});
      assert.equal(container.all('input').size(), 2);

      var output = [];
      container.all('input').each(function(n) {
        output.push(n.get('value'));
      });
      assert.deepEqual(output, ['alpha', 'beta']);
    });

  });

});
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;

var errors = require('./errors');

function TChannelRequest(channel, options) {
    options = options || {};
    assert(!options.streamed, "streaming request federation not implemented");
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.responseEvent = self.defineEvent('response');

    self.channel = channel;
    self.services = self.channel.services;
    self.logger = self.channel.logger;
    self.random = self.channel.random;
    self.timers = self.channel.timers;

    self.options = options;

    if (!self.options.retryFlags) {
        self.options.retryFlags = {
            never: false,
            onConnectionError: true,
            onTimeout: false
        };
    }

    self.triedRemoteAddrs = {};
    self.outReqs = [];
    self.timeout = self.options.timeout || TChannelRequest.defaultTimeout;
    self.limit = self.options.retryLimit || TChannelRequest.defaultRetryLimit;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;
    self.resendSanity = 0;
    self.trackPending = self.options.trackPending || false;

    self.serviceName = options.serviceName || '';
    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.err = null;
    self.res = null;

    self.errorEvent.on(self.onError);
    self.responseEvent.on(self.onResponse);
}

inherits(TChannelRequest, EventEmitter);

TChannelRequest.defaultRetryLimit = 5;
TChannelRequest.defaultTimeout = 5000;

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.onError = function onError(err, self) {
    if (!self.end) self.end = self.timers.now();
    self.err = err;
};

TChannelRequest.prototype.onResponse = function onResponse(res, self) {
    if (!self.end) self.end = self.timers.now();
    self.res = res;
};

TChannelRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    throw new Error('not implemented');
};

TChannelRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.errorEvent.on(onError);
    self.responseEvent.on(onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        callback(err, null, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        res.withArg23(function gotArg23(err, arg2, arg3) {
            callback(err, res, arg2, arg3);
        });
    }

    return self;
};

TChannelRequest.prototype.choosePeer = function choosePeer() {
    var self = this;
    return self.channel.peers.choosePeer(self, self.options);
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    if (callback) {
        self.hookupCallback(callback);
    }
    self.start = self.timers.now();
    self.resendSanity = self.limit + 1;

    self.resend();
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;

    if (self.trackPending && self.checkPending()) return;

    if (self.checkTimeout()) return;

    var peer = self.choosePeer();
    if (!peer) {
        if (self.outReqs.length) {
            var lastReq = self.outReqs[self.outReqs.length - 1];
            if (lastReq.err) {
                self.errorEvent.emit(self, lastReq.err);
            } else {
                self.responseEvent.emit(self, lastReq.res);
            }
        } else {
            self.errorEvent.emit(self, errors.NoPeerAvailable());
        }
        return;
    }

    if (self.checkTimeout()) return;

    var opts = {};
    var keys = Object.keys(self.options);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        opts[key] = self.options[key];
    }
    opts.timeout = self.timeout - self.elapsed;
    var outReq = peer.request(opts);
    self.outReqs.push(outReq);

    if (self.outReqs.length === 1) {
        self.channel.outboundCallsSentStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1)
        });
    } else {
        self.channel.outboundCallsRetriesStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1),
            'retry-count': self.outReqs.length - 1
        });
    }

    self.triedRemoteAddrs[outReq.remoteAddr] = (self.triedRemoteAddrs[outReq.remoteAddr] || 0) + 1;
    outReq.responseEvent.on(onResponse);
    outReq.errorEvent.on(onError);
    outReq.send(self.arg1, self.arg2, self.arg3);

    self.services.onRequest(self);

    function onError(err) {
        self.onSubreqError(err);
        self.services.onRequestError(self);
    }

    function onResponse(res) {
        self.onSubreqResponse(res);
    }
};

TChannelRequest.prototype.onSubreqError = function onSubreqError(err) {
    var self = this;
    if (self.checkTimeout(err)) return;
    if (self.shouldRetryError(err)) {
        self.deferResend();
    } else {
        self.errorEvent.emit(self, err);
    }
};

TChannelRequest.prototype.onSubreqResponse = function onSubreqResponse(res) {
    var self = this;
    if (self.checkTimeout(null, res)) return;
    if (res.ok) {
        self.responseEvent.emit(self, res);
    } else if (self.options.shouldApplicationRetry) {
        self.maybeAppRetry(res);
    } else {
        self.responseEvent.emit(self, res);
    }
};

TChannelRequest.prototype.deferResend = function deferResend() {
    var self = this;
    if (--self.resendSanity <= 0) {
        self.errorEvent.emit(self, new Error('TChannelRequest out of resend sanity'));
    } else {
        process.nextTick(doResend);
    }
    function doResend() {
        self.resend();
    }
};

TChannelRequest.prototype.checkPending = function checkPending() {
    var self = this;
    var err = self.services.errorIfExceedsMaxPending(self);
    if (err) {
        self.errorEvent.emit(self, err);
        return true;
    }
    return false;
};

TChannelRequest.prototype.checkTimeout = function checkTimeout(err, res) {
    var self = this;
    var now = self.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout) return false;
    if (err) {
        if (!self.err) {
            self.errorEvent.emit(self, err);
        }
    } else if (res) {
        if (!self.err && !self.res) {
            self.responseEvent.emit(self, res);
        }
    } else if (!self.err) {
        self.errorEvent.emit(self, errors.TimeoutError({
            start: self.start,
            elapsed: self.elapsed,
            timeout: self.timeout
        }));
    }
    return true;
};

TChannelRequest.prototype.shouldRetryError = function shouldRetryError(err) {
    var self = this;

    if (self.outReqs.length >= self.limit) {
        return false;
    }

    if (self.options.retryFlags.never) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.busy':
            case 'tchannel.declined':
                return true;

            case 'tchannel.timeout':
                return !!self.options.retryFlags.onTimeout;

            case 'tchannel.socket':
            case 'tchannel.socket-closed':
            case 'tchannel.network':
            case 'tchannel.protocol':
            case 'tchannel.unexpected':
                return !!self.options.retryFlags.onConnectionError;

            default:
                self.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    return false;
};

TChannelRequest.prototype.maybeAppRetry = function maybeAppRetry(res) {
    var self = this;
    self.options.shouldApplicationRetry(self, res, retry, done);

    function retry() {
        if (self.checkTimeout(null, res)) return;
        self.deferResend();
    }

    function done(err) {
        if (err) {
            self.errorEvent.emit(self, err);
        } else {
            self.responseEvent.emit(self, res);
        }
    }
};

module.exports = TChannelRequest;
<commit_msg>request: eliminate duplicate timeout<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;

var errors = require('./errors');

function TChannelRequest(channel, options) {
    options = options || {};
    assert(!options.streamed, "streaming request federation not implemented");
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.responseEvent = self.defineEvent('response');

    self.channel = channel;
    self.services = self.channel.services;
    self.logger = self.channel.logger;
    self.random = self.channel.random;
    self.timers = self.channel.timers;

    self.options = options;

    if (!self.options.retryFlags) {
        self.options.retryFlags = {
            never: false,
            onConnectionError: true,
            onTimeout: false
        };
    }

    self.triedRemoteAddrs = {};
    self.outReqs = [];
    self.timeout = self.options.timeout || TChannelRequest.defaultTimeout;
    self.limit = self.options.retryLimit || TChannelRequest.defaultRetryLimit;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;
    self.resendSanity = 0;
    self.trackPending = self.options.trackPending || false;

    self.serviceName = options.serviceName || '';
    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.err = null;
    self.res = null;

    self.errorEvent.on(self.onError);
    self.responseEvent.on(self.onResponse);
}

inherits(TChannelRequest, EventEmitter);

TChannelRequest.defaultRetryLimit = 5;
TChannelRequest.defaultTimeout = 5000;

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.onError = function onError(err, self) {
    if (!self.end) self.end = self.timers.now();
    self.err = err;
};

TChannelRequest.prototype.onResponse = function onResponse(res, self) {
    if (!self.end) self.end = self.timers.now();
    self.res = res;
};

TChannelRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    throw new Error('not implemented');
};

TChannelRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.errorEvent.on(onError);
    self.responseEvent.on(onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        callback(err, null, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        res.withArg23(function gotArg23(err, arg2, arg3) {
            callback(err, res, arg2, arg3);
        });
    }

    return self;
};

TChannelRequest.prototype.choosePeer = function choosePeer() {
    var self = this;
    return self.channel.peers.choosePeer(self, self.options);
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    if (callback) {
        self.hookupCallback(callback);
    }
    self.start = self.timers.now();
    self.resendSanity = self.limit + 1;

    self.resend();
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;

    if (self.trackPending && self.checkPending()) return;

    if (self.checkTimeout()) return;

    var peer = self.choosePeer();
    if (!peer) {
        if (self.outReqs.length) {
            var lastReq = self.outReqs[self.outReqs.length - 1];
            if (lastReq.err) {
                self.errorEvent.emit(self, lastReq.err);
            } else {
                self.responseEvent.emit(self, lastReq.res);
            }
        } else {
            self.errorEvent.emit(self, errors.NoPeerAvailable());
        }
        return;
    }

    var opts = {};
    var keys = Object.keys(self.options);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        opts[key] = self.options[key];
    }
    opts.timeout = self.timeout - self.elapsed;
    var outReq = peer.request(opts);
    self.outReqs.push(outReq);

    if (self.outReqs.length === 1) {
        self.channel.outboundCallsSentStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1)
        });
    } else {
        self.channel.outboundCallsRetriesStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1),
            'retry-count': self.outReqs.length - 1
        });
    }

    self.triedRemoteAddrs[outReq.remoteAddr] = (self.triedRemoteAddrs[outReq.remoteAddr] || 0) + 1;
    outReq.responseEvent.on(onResponse);
    outReq.errorEvent.on(onError);
    outReq.send(self.arg1, self.arg2, self.arg3);

    self.services.onRequest(self);

    function onError(err) {
        self.onSubreqError(err);
        self.services.onRequestError(self);
    }

    function onResponse(res) {
        self.onSubreqResponse(res);
    }
};

TChannelRequest.prototype.onSubreqError = function onSubreqError(err) {
    var self = this;
    if (self.checkTimeout(err)) return;
    if (self.shouldRetryError(err)) {
        self.deferResend();
    } else {
        self.errorEvent.emit(self, err);
    }
};

TChannelRequest.prototype.onSubreqResponse = function onSubreqResponse(res) {
    var self = this;
    if (self.checkTimeout(null, res)) return;
    if (res.ok) {
        self.responseEvent.emit(self, res);
    } else if (self.options.shouldApplicationRetry) {
        self.maybeAppRetry(res);
    } else {
        self.responseEvent.emit(self, res);
    }
};

TChannelRequest.prototype.deferResend = function deferResend() {
    var self = this;
    if (--self.resendSanity <= 0) {
        self.errorEvent.emit(self, new Error('TChannelRequest out of resend sanity'));
    } else {
        process.nextTick(doResend);
    }
    function doResend() {
        self.resend();
    }
};

TChannelRequest.prototype.checkPending = function checkPending() {
    var self = this;
    var err = self.services.errorIfExceedsMaxPending(self);
    if (err) {
        self.errorEvent.emit(self, err);
        return true;
    }
    return false;
};

TChannelRequest.prototype.checkTimeout = function checkTimeout(err, res) {
    var self = this;
    var now = self.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout) return false;
    if (err) {
        if (!self.err) {
            self.errorEvent.emit(self, err);
        }
    } else if (res) {
        if (!self.err && !self.res) {
            self.responseEvent.emit(self, res);
        }
    } else if (!self.err) {
        self.errorEvent.emit(self, errors.TimeoutError({
            start: self.start,
            elapsed: self.elapsed,
            timeout: self.timeout
        }));
    }
    return true;
};

TChannelRequest.prototype.shouldRetryError = function shouldRetryError(err) {
    var self = this;

    if (self.outReqs.length >= self.limit) {
        return false;
    }

    if (self.options.retryFlags.never) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.busy':
            case 'tchannel.declined':
                return true;

            case 'tchannel.timeout':
                return !!self.options.retryFlags.onTimeout;

            case 'tchannel.socket':
            case 'tchannel.socket-closed':
            case 'tchannel.network':
            case 'tchannel.protocol':
            case 'tchannel.unexpected':
                return !!self.options.retryFlags.onConnectionError;

            default:
                self.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    return false;
};

TChannelRequest.prototype.maybeAppRetry = function maybeAppRetry(res) {
    var self = this;
    self.options.shouldApplicationRetry(self, res, retry, done);

    function retry() {
        if (self.checkTimeout(null, res)) return;
        self.deferResend();
    }

    function done(err) {
        if (err) {
            self.errorEvent.emit(self, err);
        } else {
            self.responseEvent.emit(self, res);
        }
    }
};

module.exports = TChannelRequest;
<|endoftext|>


<commit_before>/*jslint browser: true, plusplus: true, regexp: true */
/*global $, jQuery, Handlebars, moment, LTIConfig, confirm */

(function ($) {
    "use strict";

    $.ajaxSetup({
        headers: { "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val() }
    });

    Handlebars.registerHelper('stringify', function (object) {
        return new Handlebars.SafeString(JSON.stringify(object, null, 4));
    });

    Handlebars.registerHelper('format_date', function (dt) {
        return (dt !== null) ? moment(dt).format('MM/DD/YYYY h:mm a') : '';
    });

    function draw_error(xhr) {
        var json;
        try {
            json = $.parseJSON(xhr.responseText);
            $('.et-errors').html(json.error);
            $('#et-alert').show();
        } catch (e) {
            console.log('Unknown admin service error');
        }
    }

    function draw_updated_tool() {
        $('#external-tool-editor').modal('hide');
        load_external_tools();
    }

    function gather_form_data() {
        var data = {
            'canvas_id': $('#et-id-input').val(),
            'config': $.parseJSON($('#et-config-input').val()),
            'account_id': $('#et-account-input').val()
        };
        return {'external_tool': data};
    }

    function save_external_tool() {
        var json_data = gather_form_data(),
            tool_id = json_data.external_tool.canvas_id,
            url = 'lti_manager/api/v1/external_tool/',
            type = 'POST';

        if (tool_id.length) {
            url += tool_id;
            type = 'PUT';
        }

        $.ajax({
            url: url,
            type: type,
            dataType: 'json',
            data: JSON.stringify(json_data)
        }).done(draw_updated_tool).fail(draw_error);
    }

    function load_external_tool(tool_id, done_fn) {
        $.ajax({
            url: 'lti_manager/api/v1/external_tool/' + tool_id,
            type: 'GET',
            dataType: 'json'
        }).done(done_fn).fail(draw_error);
    }

    function delete_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        if (confirm('Really delete this external tool?')) {
            $.ajax({
                url: 'lti_manager/api/v1/external_tool/' + tool_id,
                type: 'DELETE',
                dataType: 'json'
            }).done(draw_updated_tool).fail(draw_error);
        }
    }

    function open_editor(title) {
        $('#et-modal-title').html(title);
        $('#et-alert').hide();
        $('#external-tool-editor').modal({
            backdrop: 'static',
            show: true
        });
    }

    function update_textarea(json) {
        $('#et-config-input').val(JSON.stringify(json));
    }

    function prepare_json(source) {
        var json = $.extend(true, {}, LTIConfig);
        if (source === null) {
            return json;
        }
        return $.extend(true, json, source);
    }

    function load_form_data(data) {
        var json = prepare_json(data.external_tool.config);

        $('#et-id-input').val(data.external_tool.canvas_id);
        $('#et-account-input').val(data.external_tool.account.canvas_id);
        if (data.external_tool.canvas_id && data.external_tool.account.canvas_id) {
            $('#et-account-input').prop('disabled', true);
        } else {
            $('#et-account-input').prop('disabled', false);
        }
        update_textarea(json);
        $('#et-json-editor').jsonEditor(json, {
            change: update_textarea
        });
    }

    function load_add_external_tool() {
        open_editor('Add an External Tool');
        load_form_data({'external_tool': {
            'id': null,
            'account_id': null,
            'config': null
        }});
    }

    function load_clone_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        open_editor('Clone an External Tool');
        load_external_tool(tool_id, function (data) {
            data.external_tool.canvas_id = '';
            data.external_tool.config.id = '';
            load_form_data(data);
        });
    }

    function load_edit_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        open_editor('Edit an External Tool');
        load_external_tool(tool_id, load_form_data);
    }

    function draw_external_tools(data) {
        var tpl = Handlebars.compile($('#tool-table-row').html());

        if ($.fn.dataTable.isDataTable('#external-tools-table')) {
            $('#external-tools-table').DataTable().destroy();
        }

        $('#external-tools-table tbody').html(tpl(data));
        $('#external-tools-table').DataTable({
            'aaSorting': [[ 0, 'asc' ]],
            'bPaginate': false,
            'searching': false,
            'bScrollCollapse': true
        });

        $('.et-add').click(load_add_external_tool);
        $('.et-edit').click(load_edit_external_tool);
        $('.et-clone').click(load_clone_external_tool);
        $('.et-delete').click(delete_external_tool);
    }

    function load_external_tools() {
        $.ajax({
            url: 'lti_manager/api/v1/external_tools',
            type: 'GET',
            dataType: 'json'
        }).done(draw_external_tools).fail(draw_error);
    }

    function initialize() {
        load_external_tools();
        $('.save-btn').click(save_external_tool);
    }

    $(document).ready(function () {
        initialize();
    });
}(jQuery));
<commit_msg>update api path for external_tool client<commit_after>/*jslint browser: true, plusplus: true, regexp: true */
/*global $, jQuery, Handlebars, moment, LTIConfig, confirm */

(function ($) {
    "use strict";

    $.ajaxSetup({
        headers: { "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val() }
    });

    Handlebars.registerHelper('stringify', function (object) {
        return new Handlebars.SafeString(JSON.stringify(object, null, 4));
    });

    Handlebars.registerHelper('format_date', function (dt) {
        return (dt !== null) ? moment(dt).format('MM/DD/YYYY h:mm a') : '';
    });

    function draw_error(xhr) {
        var json;
        try {
            json = $.parseJSON(xhr.responseText);
            $('.et-errors').html(json.error);
            $('#et-alert').show();
        } catch (e) {
            console.log('Unknown admin service error');
        }
    }

    function draw_updated_tool() {
        $('#external-tool-editor').modal('hide');
        load_external_tools();
    }

    function gather_form_data() {
        var data = {
            'canvas_id': $('#et-id-input').val(),
            'config': $.parseJSON($('#et-config-input').val()),
            'account_id': $('#et-account-input').val()
        };
        return {'external_tool': data};
    }

    function save_external_tool() {
        var json_data = gather_form_data(),
            tool_id = json_data.external_tool.canvas_id,
            url = '/api/v1/external_tool/',
            type = 'POST';

        if (tool_id.length) {
            url += tool_id;
            type = 'PUT';
        }

        $.ajax({
            url: url,
            type: type,
            dataType: 'json',
            data: JSON.stringify(json_data)
        }).done(draw_updated_tool).fail(draw_error);
    }

    function load_external_tool(tool_id, done_fn) {
        $.ajax({
            url: '/api/v1/external_tool/' + tool_id,
            type: 'GET',
            dataType: 'json'
        }).done(done_fn).fail(draw_error);
    }

    function delete_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        if (confirm('Really delete this external tool?')) {
            $.ajax({
                url: '/api/v1/external_tool/' + tool_id,
                type: 'DELETE',
                dataType: 'json'
            }).done(draw_updated_tool).fail(draw_error);
        }
    }

    function open_editor(title) {
        $('#et-modal-title').html(title);
        $('#et-alert').hide();
        $('#external-tool-editor').modal({
            backdrop: 'static',
            show: true
        });
    }

    function update_textarea(json) {
        $('#et-config-input').val(JSON.stringify(json));
    }

    function prepare_json(source) {
        var json = $.extend(true, {}, LTIConfig);
        if (source === null) {
            return json;
        }
        return $.extend(true, json, source);
    }

    function load_form_data(data) {
        var json = prepare_json(data.external_tool.config);

        $('#et-id-input').val(data.external_tool.canvas_id);
        $('#et-account-input').val(data.external_tool.account.canvas_id);
        if (data.external_tool.canvas_id && data.external_tool.account.canvas_id) {
            $('#et-account-input').prop('disabled', true);
        } else {
            $('#et-account-input').prop('disabled', false);
        }
        update_textarea(json);
        $('#et-json-editor').jsonEditor(json, {
            change: update_textarea
        });
    }

    function load_add_external_tool() {
        open_editor('Add an External Tool');
        load_form_data({'external_tool': {
            'id': null,
            'account_id': null,
            'config': null
        }});
    }

    function load_clone_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        open_editor('Clone an External Tool');
        load_external_tool(tool_id, function (data) {
            data.external_tool.canvas_id = '';
            data.external_tool.config.id = '';
            load_form_data(data);
        });
    }

    function load_edit_external_tool() {
        /*jshint validthis: true */
        var tool_id = $(this).attr('data-tool-id');
        open_editor('Edit an External Tool');
        load_external_tool(tool_id, load_form_data);
    }

    function draw_external_tools(data) {
        var tpl = Handlebars.compile($('#tool-table-row').html());

        if ($.fn.dataTable.isDataTable('#external-tools-table')) {
            $('#external-tools-table').DataTable().destroy();
        }

        $('#external-tools-table tbody').html(tpl(data));
        $('#external-tools-table').DataTable({
            'aaSorting': [[ 0, 'asc' ]],
            'bPaginate': false,
            'searching': false,
            'bScrollCollapse': true
        });

        $('.et-add').click(load_add_external_tool);
        $('.et-edit').click(load_edit_external_tool);
        $('.et-clone').click(load_clone_external_tool);
        $('.et-delete').click(delete_external_tool);
    }

    function load_external_tools() {
        $.ajax({
            url: '/api/v1/external_tools',
            type: 'GET',
            dataType: 'json'
        }).done(draw_external_tools).fail(draw_error);
    }

    function initialize() {
        load_external_tools();
        $('.save-btn').click(save_external_tool);
    }

    $(document).ready(function () {
        initialize();
    });
}(jQuery));
<|endoftext|>


<commit_before>/*global describe,it,before*/
/*eslint-disable max-nested-callbacks*/

'use strict';

var kss = require('../index.js'),
  testUtils = require('./testUtils');

var custom = ['custom', 'custom2', 'custom property'];

describe('KssSection object API', function() {
  before(function(done) {
    var self = this;
    testUtils.traverseFixtures({mask: '*.less|*.css', custom: custom}, function(styleguide) {
      self.styleguide = styleguide;
      done();
    });
  });

  /*eslint-disable guard-for-in,no-loop-func*/
  [
    'header',
    'description',
    'deprecated',
    'experimental',
    'reference',
    'depth',
    'encodeReferenceURI',
    'referenceURI',
    'modifiers',
    'firstModifier',
    'parameters'
  ].forEach(function(method) {
    it('has ' + method + '() method', function(done) {
      (new kss.KssSection()).should.have.method(method);
      done();
    });
  });
  /*eslint-enable guard-for-in,no-loop-func*/

  describe('KssSection constructor', function() {
    it('should initialize the data', function(done) {
      var obj = new kss.KssSection();
      obj.should.have.property('styleguide');
      obj.should.have.property('data');
      obj.data.should.have.property('header');
      obj.data.should.have.property('description');
      obj.data.should.have.property('deprecated');
      obj.data.should.have.property('experimental');
      obj.data.should.have.property('reference');
      obj.data.should.have.property('depth');
      obj.data.should.have.property('weight');
      obj.data.should.have.property('referenceURI');
      obj.data.should.have.property('markup');
      obj.data.should.have.property('modifiers');
      obj.data.should.have.property('parameters');
      done();
    });

    it('should return a KssSection object when called normally', function(done) {
      /*eslint-disable new-cap*/
      var obj = kss.KssSection();
      obj.should.be.an.Object().and.an.instanceof(kss.KssSection);
      done();
      /*eslint-enable new-cap*/
    });
  });

  describe('.toJSON()', function() {
    it('should return valid JSON object', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var str;
        section.toJSON().should.be.an.Object();
        // Verify it converts to a JSON string.
        (function() {
          str = JSON.stringify(section.toJSON());
        }).should.not.throw();
        str.should.be.String();
        // Compare JSON string to original.
        JSON.parse(str).should.eql(section.toJSON());
      });
      done();
    });

    it('should return custom properties given array of property names', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var str;
        section.toJSON(custom).should.be.an.Object();
        // Verify it converts to a JSON string.
        (function() {
          str = JSON.stringify(section.toJSON(custom));
        }).should.not.throw();
        str.should.be.String();
        // Compare JSON string to original.
        JSON.parse(str).should.eql(section.toJSON(custom));
      });
      done();
    });
  });

  describe('.header()', function() {
    it('should return data.header', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.header().should.be.equal(section.data.header);
      });
      done();
    });
  });

  describe('.description()', function() {
    it('should return data.description', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.description().should.be.equal(section.data.description);
      });
      done();
    });
  });

  describe('.deprecated()', function() {
    it('should return data.deprecated', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.deprecated().should.be.equal(section.data.deprecated);
      });
      done();
    });
  });

  describe('.experimental()', function() {
    it('should return data.experimental', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.experimental().should.be.equal(section.data.experimental);
      });
      done();
    });
  });

  describe('.reference()', function() {
    it('should return data.reference', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.reference().should.be.equal(section.data.reference);
      });
      done();
    });
  });

  describe('.depth()', function() {
    it('should return data.depth', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.depth().should.be.equal(section.data.depth);
        section.depth().should.be.equal(section.reference().split(section.styleguide.referenceDelimiter).length);
        section.depth().should.be.a.Number();
      });
      done();
    });
  });

  describe('.weight()', function() {
    it('should return data.weight', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.weight().should.be.equal(section.data.weight ? section.data.weight : 0);
        section.weight().should.be.a.Number();
      });
      done();
    });
  });

  describe('.encodeReferenceURI()', function() {
    it('should return .referenceURI() when given reference()', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.encodeReferenceURI(section.reference()).should.be.equal(section.referenceURI());
      });
      done();
    });
  });

  describe('.referenceURI()', function() {
    it('should return data.referenceURI', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.referenceURI().should.be.equal(section.data.referenceURI);
        section.referenceURI().should.be.equal(section.encodeReferenceURI(section.reference()));
      });
      done();
    });
  });

  describe('.referenceURI() changes all delimiters to dashes', function() {
    it('should return data.referenceURI', function() {
      var section = new kss.KssSection({});

      section.encodeReferenceURI('test.section.with . depth@the-end').should.be.equal('test-section-with-depth-the-end');
    });
  });

  describe('.modifiers()', function() {
    it('should return data.modifiers', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers().should.be.equal(section.data.modifiers);
      });
      done();
    });

    it('should return array of KssModifier', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers().map(function(modifier) {
          modifier.should.be.instanceof(kss.KssModifier);
        });
      });
      done();
    });

    it('should return data.modifiers[n] given an integer as number or string', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var i = 0;
        section.data.modifiers.map(function(modifier) {
          section.modifiers(i).should.be.eql(modifier);
          section.modifiers(i.toString()).should.be.eql(modifier);
          i++;
        });
      });
      done();
    });

    it('should return false if number is not found', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers(section.data.modifiers.length + 1).should.be.false();
      });
      done();
    });

    it('should search by name when given a string', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var i, j, queries = ['.red', '.yellow', ':hover', ':disabled'],
          q = queries.length,
          l = section.data.modifiers.length;

        // Loop through each modifier.
        for (i = 0; i < l; i += 1) {
          // If a modifier is equal to a query, run the search.
          for (j = 0; j < q; j += 1) {
            if (section.data.modifiers[i].data.name === queries[j]) {
              section.modifiers(queries[j]).should.be.eql(section.data.modifiers[i]);
            }
          }
        }
      });
      done();
    });

    it('should return false if name not found', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers('__should_not_find___').should.be.false();
      });
      done();
    });
  });

  describe('.firstModifier()', function() {
    it('should return data.modifiers[0]', function(done) {
      this.styleguide.data.sections.map(function(section) {
        if (section.data.modifiers.length) {
          section.firstModifier().should.be.equal(section.modifiers(0));
        } else {
          section.firstModifier().should.be.false();
        }
      });
      done();
    });
  });

  describe('.parameters()', function() {
    it('should return data.parameters', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.parameters().should.be.equal(section.data.parameters);
      });
      done();
    });

    it('should return array of KssParameter', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.parameters().map(function(parameter) {
          parameter.should.be.instanceof(kss.KssParameter);
        });
      });
      done();
    });
  });
});
<commit_msg>Re-work new test.<commit_after>/*global describe,it,before*/
/*eslint-disable max-nested-callbacks*/

'use strict';

var kss = require('../index.js'),
  testUtils = require('./testUtils');

var custom = ['custom', 'custom2', 'custom property'];

describe('KssSection object API', function() {
  before(function(done) {
    var self = this;
    testUtils.traverseFixtures({mask: '*.less|*.css', custom: custom}, function(styleguide) {
      self.styleguide = styleguide;
      done();
    });
  });

  /*eslint-disable guard-for-in,no-loop-func*/
  [
    'header',
    'description',
    'deprecated',
    'experimental',
    'reference',
    'depth',
    'encodeReferenceURI',
    'referenceURI',
    'modifiers',
    'firstModifier',
    'parameters'
  ].forEach(function(method) {
    it('has ' + method + '() method', function(done) {
      (new kss.KssSection()).should.have.method(method);
      done();
    });
  });
  /*eslint-enable guard-for-in,no-loop-func*/

  describe('KssSection constructor', function() {
    it('should initialize the data', function(done) {
      var obj = new kss.KssSection();
      obj.should.have.property('styleguide');
      obj.should.have.property('data');
      obj.data.should.have.property('header');
      obj.data.should.have.property('description');
      obj.data.should.have.property('deprecated');
      obj.data.should.have.property('experimental');
      obj.data.should.have.property('reference');
      obj.data.should.have.property('depth');
      obj.data.should.have.property('weight');
      obj.data.should.have.property('referenceURI');
      obj.data.should.have.property('markup');
      obj.data.should.have.property('modifiers');
      obj.data.should.have.property('parameters');
      done();
    });

    it('should return a KssSection object when called normally', function(done) {
      /*eslint-disable new-cap*/
      var obj = kss.KssSection();
      obj.should.be.an.Object().and.an.instanceof(kss.KssSection);
      done();
      /*eslint-enable new-cap*/
    });
  });

  describe('.toJSON()', function() {
    it('should return valid JSON object', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var str;
        section.toJSON().should.be.an.Object();
        // Verify it converts to a JSON string.
        (function() {
          str = JSON.stringify(section.toJSON());
        }).should.not.throw();
        str.should.be.String();
        // Compare JSON string to original.
        JSON.parse(str).should.eql(section.toJSON());
      });
      done();
    });

    it('should return custom properties given array of property names', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var str;
        section.toJSON(custom).should.be.an.Object();
        // Verify it converts to a JSON string.
        (function() {
          str = JSON.stringify(section.toJSON(custom));
        }).should.not.throw();
        str.should.be.String();
        // Compare JSON string to original.
        JSON.parse(str).should.eql(section.toJSON(custom));
      });
      done();
    });
  });

  describe('.header()', function() {
    it('should return data.header', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.header().should.be.equal(section.data.header);
      });
      done();
    });
  });

  describe('.description()', function() {
    it('should return data.description', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.description().should.be.equal(section.data.description);
      });
      done();
    });
  });

  describe('.deprecated()', function() {
    it('should return data.deprecated', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.deprecated().should.be.equal(section.data.deprecated);
      });
      done();
    });
  });

  describe('.experimental()', function() {
    it('should return data.experimental', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.experimental().should.be.equal(section.data.experimental);
      });
      done();
    });
  });

  describe('.reference()', function() {
    it('should return data.reference', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.reference().should.be.equal(section.data.reference);
      });
      done();
    });
  });

  describe('.depth()', function() {
    it('should return data.depth', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.depth().should.be.equal(section.data.depth);
        section.depth().should.be.equal(section.reference().split(section.styleguide.referenceDelimiter).length);
        section.depth().should.be.a.Number();
      });
      done();
    });
  });

  describe('.weight()', function() {
    it('should return data.weight', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.weight().should.be.equal(section.data.weight ? section.data.weight : 0);
        section.weight().should.be.a.Number();
      });
      done();
    });
  });

  describe('.encodeReferenceURI()', function() {
    it('should return .referenceURI() when given reference()', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.encodeReferenceURI(section.reference()).should.be.equal(section.referenceURI());
      });
      done();
    });
  });

  describe('.referenceURI()', function() {
    it('should return data.referenceURI', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.referenceURI().should.be.equal(section.data.referenceURI);
        section.referenceURI().should.be.equal(section.encodeReferenceURI(section.reference()));
      });
      done();
    });

    it('should replace all runs of non-word characters with a hyphen', function() {
      var section = new kss.KssSection();
      section.encodeReferenceURI('test - section - with.multiple.runs..of--non-word.characters').should.be.equal('test-section-with-multiple-runs-of--non-word-characters');
    });
  });

  describe('.modifiers()', function() {
    it('should return data.modifiers', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers().should.be.equal(section.data.modifiers);
      });
      done();
    });

    it('should return array of KssModifier', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers().map(function(modifier) {
          modifier.should.be.instanceof(kss.KssModifier);
        });
      });
      done();
    });

    it('should return data.modifiers[n] given an integer as number or string', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var i = 0;
        section.data.modifiers.map(function(modifier) {
          section.modifiers(i).should.be.eql(modifier);
          section.modifiers(i.toString()).should.be.eql(modifier);
          i++;
        });
      });
      done();
    });

    it('should return false if number is not found', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers(section.data.modifiers.length + 1).should.be.false();
      });
      done();
    });

    it('should search by name when given a string', function(done) {
      this.styleguide.data.sections.map(function(section) {
        var i, j, queries = ['.red', '.yellow', ':hover', ':disabled'],
          q = queries.length,
          l = section.data.modifiers.length;

        // Loop through each modifier.
        for (i = 0; i < l; i += 1) {
          // If a modifier is equal to a query, run the search.
          for (j = 0; j < q; j += 1) {
            if (section.data.modifiers[i].data.name === queries[j]) {
              section.modifiers(queries[j]).should.be.eql(section.data.modifiers[i]);
            }
          }
        }
      });
      done();
    });

    it('should return false if name not found', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.modifiers('__should_not_find___').should.be.false();
      });
      done();
    });
  });

  describe('.firstModifier()', function() {
    it('should return data.modifiers[0]', function(done) {
      this.styleguide.data.sections.map(function(section) {
        if (section.data.modifiers.length) {
          section.firstModifier().should.be.equal(section.modifiers(0));
        } else {
          section.firstModifier().should.be.false();
        }
      });
      done();
    });
  });

  describe('.parameters()', function() {
    it('should return data.parameters', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.parameters().should.be.equal(section.data.parameters);
      });
      done();
    });

    it('should return array of KssParameter', function(done) {
      this.styleguide.data.sections.map(function(section) {
        section.parameters().map(function(parameter) {
          parameter.should.be.instanceof(kss.KssParameter);
        });
      });
      done();
    });
  });
});
<|endoftext|>


<commit_before><commit_msg>update plotly dep<commit_after><|endoftext|>


<commit_before>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory;
    } else {
        root.request = factory(root);
    }
})(this, function (root) {

    'use strict';

    var exports = {},
        utils = {},
        xhr;

    utils.parse = function(req){
        var result;
        try {
            result = JSON.parse(req.responseText);
        } catch (e) {
            result = req.responseText;
        }
        return [result, req];
    };

    utils.toQuery = function(q){
        var key,
            params = [];
        for(key in q){
            params.push(key+'='+q[key]);
        }
        return params.join('&');
    };

    utils.verifyQuery = function(url,query){
        var result = {},
            parameter = null;
        for(parameter in query){
            if(!(new RegExp('[?|&]'+parameter+'=')).test(url)){
                result[parameter] = query[parameter];
            }
        }
        return result;
    };

    xhr = function(method, url, data, query){
        var methods = {
                success: function(){},
                error: function(){},
                always: function(){}
            },
            request = null,
            callbacks = {},
            protocol = (window.location.protocol === 'file:') ? 'https:' : window.location.protocol;

        if(method === 'GET'){
            query = utils.verifyQuery(url,query);
        }

        if(window.XDomainRequest){
            request = new XDomainRequest();
        }else if(window.ActiveXObject){
            request = new ActiveXObject('Microsoft.XMLHTTP');
        }else if(window.XMLHttpRequest){
            request = new XMLHttpRequest();
        }

        if(request){
            if(query) url += ((url.indexOf('?') > -1) ? '&' : '?') + utils.toQuery(query);
            request.open(method, (url.indexOf('http') > -1) ? url : protocol+url, true);
            request.onload = function(){
                if(request.statusText === 'OK' && request.status === 200){
                    methods.success.apply(methods, utils.parse(request));
                    methods.always.apply();
                } else {
                    methods.error.apply(methods, utils.parse(request));
                    methods.always.apply();
                }
            };
            request.onerror = function(){
                methods.error.apply(methods, utils.parse(request));
                methods.always.apply();
            };
            if(method === 'POST'){
                request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                data = utils.toQuery(data);
            }
            if(method === 'GET'){
                data = null;
            }
            setTimeout(function(){
                request.send(data);
            },0);
        }

        callbacks = {
            success: function(callback){
                methods.success = callback;
                return callbacks;
            },
            error: function(callback){
                methods.error = callback;
                return callbacks;
            },
            always: function(callback){
                methods.always = callback;
                return callbacks;
            }
        };

        return callbacks;
    };

    exports.['get'] = function (url, query) {
        return xhr('GET', url, {}, query);
    };

    exports.['put'] = function (url, data, query) {
        return xhr('PUT', url, data, query);
    };

    exports.['post'] = function (url, data, query) {
        return xhr('POST', url, data, query);
    };

    exports.['delete'] = function (url, query) {
        return xhr('DELETE', url, {}, query);
    };

    return exports;
});<commit_msg>Removed dot notation to avoid issues with IE<commit_after>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory;
    } else {
        root.request = factory(root);
    }
})(this, function (root) {

    'use strict';

    var exports = {},
        utils = {},
        xhr;

    utils.parse = function(req){
        var result;
        try {
            result = JSON.parse(req.responseText);
        } catch (e) {
            result = req.responseText;
        }
        return [result, req];
    };

    utils.toQuery = function(q){
        var key,
            params = [];
        for(key in q){
            params.push(key+'='+q[key]);
        }
        return params.join('&');
    };

    utils.verifyQuery = function(url,query){
        var result = {},
            parameter = null;
        for(parameter in query){
            if(!(new RegExp('[?|&]'+parameter+'=')).test(url)){
                result[parameter] = query[parameter];
            }
        }
        return result;
    };

    xhr = function(method, url, data, query){
        var methods = {
                success: function(){},
                error: function(){},
                always: function(){}
            },
            request = null,
            callbacks = {},
            protocol = (window.location.protocol === 'file:') ? 'https:' : window.location.protocol;

        if(method === 'GET'){
            query = utils.verifyQuery(url,query);
        }

        if(window.XDomainRequest){
            request = new XDomainRequest();
        }else if(window.ActiveXObject){
            request = new ActiveXObject('Microsoft.XMLHTTP');
        }else if(window.XMLHttpRequest){
            request = new XMLHttpRequest();
        }

        if(request){
            if(query) url += ((url.indexOf('?') > -1) ? '&' : '?') + utils.toQuery(query);
            request.open(method, (url.indexOf('http') > -1) ? url : protocol+url, true);
            request.onload = function(){
                if(request.statusText === 'OK' && request.status === 200){
                    methods.success.apply(methods, utils.parse(request));
                    methods.always.apply();
                } else {
                    methods.error.apply(methods, utils.parse(request));
                    methods.always.apply();
                }
            };
            request.onerror = function(){
                methods.error.apply(methods, utils.parse(request));
                methods.always.apply();
            };
            if(method === 'POST'){
                request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                data = utils.toQuery(data);
            }
            if(method === 'GET'){
                data = null;
            }
            setTimeout(function(){
                request.send(data);
            },0);
        }

        callbacks = {
            success: function(callback){
                methods.success = callback;
                return callbacks;
            },
            error: function(callback){
                methods.error = callback;
                return callbacks;
            },
            always: function(callback){
                methods.always = callback;
                return callbacks;
            }
        };

        return callbacks;
    };

    exports['get'] = function (url, query) {
        return xhr('GET', url, {}, query);
    };

    exports['put'] = function (url, data, query) {
        return xhr('PUT', url, data, query);
    };

    exports['post'] = function (url, data, query) {
        return xhr('POST', url, data, query);
    };

    exports['delete'] = function (url, query) {
        return xhr('DELETE', url, {}, query);
    };

    return exports;
});<|endoftext|>


<commit_before>'use strict'

import axios from 'axios'

import {
  RANKINGS_REQUEST,
  RANKINGS_SUCCESS,
  RANKINGS_ERROR
} from './../constants'

function rankingsRequest() {
  return {
    type: RANKINGS_REQUEST
  }
}

function rankingsSuccess(data) {
  return {
    type: RANKINGS_SUCCESS,
    data
  }
}

function rankingsError(data) {
  return {
    type: RANKINGS_ERROR,
    data
  }
}

function fetchRankings(params = {country: 'BR'}) {
  return dispatch => {
    dispatch(rankingsRequest())

    return axios.get('https://localhost:3001/rankings', {
      params: {
        country: params.country,
        state: params.state,
        city: params.city,
        limit: params.limit
      }
    })
    .then(({ data }) => dispatch(rankingsSuccess(data)))
    .catch(err => dispatch(rankingsError(err)))
  }
}

export default fetchRankings
<commit_msg>API Url<commit_after>'use strict'

import axios from 'axios'

import {
  RANKINGS_REQUEST,
  RANKINGS_SUCCESS,
  RANKINGS_ERROR
} from './../constants'

function rankingsRequest() {
  return {
    type: RANKINGS_REQUEST
  }
}

function rankingsSuccess(data) {
  return {
    type: RANKINGS_SUCCESS,
    data
  }
}

function rankingsError(data) {
  return {
    type: RANKINGS_ERROR,
    data
  }
}

function fetchRankings(params = {country: 'BR'}) {
  return dispatch => {
    dispatch(rankingsRequest())

    return axios.get('https://staging.ritoplz.com/rankings', {
      params: {
        country: params.country,
        state: params.state,
        city: params.city,
        limit: params.limit
      }
    })
    .then(({ data }) => dispatch(rankingsSuccess(data)))
    .catch(err => dispatch(rankingsError(err)))
  }
}

export default fetchRankings
<|endoftext|>