var test = require('tape');
var AmpersandModel = require('ampersand-model');
var AmpersandView = require('../ampersand-view');
var contains = function (str1, str2) {
return str1.indexOf(str2) !== -1;
var Model = AmpersandModel.extend({
deps: ['something', 'fireDanger', 'active'],
return this.something + this.active;
function getView(bindings, model) {
if (!bindings.template) {
bindings.template = '<li><span></span><img></li>';
var View = AmpersandView.extend(bindings);
model: model || new Model()
return view.renderWithTemplate();
test('registerSubview', function (t) {
var SubView = AmpersandView.extend({
this.renderWithTemplate();
this.el.className = 'subview';
var View = AmpersandView.extend({
template: '<section><div id="parent"></div></section>',
this.renderWithTemplate();
this.renderSubview(new SubView(), this.query('#parent'));
this.renderSubview(new SubView(), '#parent');
this.registerSubview({remove: function () {
el: document.createElement('div')
t.equal(main.queryAll('.subview').length, 2);
t.equal(removeCalled, 3);
test('registerSubview: default container to this.el', function (t) {
var SubView = AmpersandView.extend({
this.renderWithTemplate();
this.el.className = 'subview';
var View = AmpersandView.extend({
template: '<section></section>',
this.renderWithTemplate();
this.renderSubview(new SubView());
this.renderSubview(new SubView());
el: document.createElement('div')
t.equal(main.queryAll('.subview').length, 2);
t.equal(main.el.childNodes.length, 2);
t.equal(removeCalled, 2);
test('caching elements', function(t) {
var View = AmpersandView.extend({
template: '<p><span></span></p>',
this.renderWithTemplate();
return this.cacheElements(({span:'span'}));
var instance = new View(),
rendered = instance.render();
t.equal(instance, rendered);
t.equal(typeof rendered.span, 'object');
test('listen to and run', function (t) {
var View = AmpersandView.extend({
initialize: function () {
this.listenToAndRun(this.model, 'something', this.handler);
test('text bindings', function (t) {
t.equal(view.query('span').textContent, '');
view.model.set('name', 'henrik');
t.equal(view.query('span').textContent, 'henrik');
test('src bindings', function (t) {
var img = view.query('img');
t.equal(img.getAttribute('src'), '');
view.model.set('url', 'http://robohash.com/whammo');
t.equal(img.getAttribute('src'), 'http://robohash.com/whammo');
test('href bindings', function (t) {
template: '<a href=""></a>',
t.equal(el.getAttribute('href'), '');
view.model.set('url', 'http://robohash.com/whammo');
t.equal(el.getAttribute('href'), 'http://robohash.com/whammo');
test('input bindings', function (t) {
template: '<li><input></li>',
var input = view.query('input');
t.equal(input.value, '');
view.model.set('something', 'yo');
t.equal(input.value, 'yo');
test('class bindings', function (t) {
var className = view.el.className;
t.ok(contains(className, 'active'));
t.ok(contains(className, 'high'));
model.set('fireDanger', 'low');
className = view.el.className;
t.ok(!contains(className, 'high'));
t.ok(contains(className, 'low'));
model.set('active', false);
className = view.el.className;
t.ok(!contains(className, 'active'));
t.ok(contains(className, 'low'));
test('nested binding definitions', function (t) {
var View = AmpersandView.extend({
template: '<li><div></div></li>',
type: 'booleanAttribute',
type: 'booleanAttribute',
var view = new View({model: model});
t.ok(div.hasAttribute('data-active'));
t.ok(div.hasAttribute('data-something'));
t.equal(div.textContent, 'true');
t.ok(contains(li.className, 'active'));
test('renderAndBind with no model', function (t) {
var View = AmpersandView.extend({
template: '<li><span></span><img></li>'
t.ok(view.renderWithTemplate());
test('queryByHook', function (t) {
var View = AmpersandView.extend({
template: '<li data-hook="list-item"><span data-hook="username"></span><img data-hook="user-avatar"></li>'
view.renderWithTemplate();
t.ok(view.queryByHook('username') instanceof Element, 'should find username element');
t.ok(view.queryByHook('user-avatar') instanceof Element, 'should find username');
t.ok(view.queryByHook('nothing') === undefined, 'should find username');
t.ok(view.queryByHook('list-item') instanceof Element, 'should also work for root element');
test('queryAllByHook', function (t) {
var View = AmpersandView.extend({
template: '<li data-hook="list-item"><span data-hook="username info"></span><img data-hook="user-avatar info"></li>'
view.renderWithTemplate();
t.ok(view.queryAllByHook('info') instanceof Array, 'should return array of results');
t.equal(view.queryAllByHook('info').length, 2, 'should find all relevant elements');
t.ok(view.queryAllByHook('info')[0] instanceof Element, 'should be able to access found elements');
t.ok(view.queryAllByHook('info')[1] instanceof Element, 'should be able to access found elements');
t.deepEqual(view.queryAllByHook('nothing'), [], 'should return empty array if no results found');
test('throw on multiple root elements', function (t) {
var View = AmpersandView.extend({
template: '<li></li><div></div>'
t.throws(view.renderWithTemplate, Error, 'Throws error on multiple root elements');
test('queryAll should return an array', function (t) {
var View = AmpersandView.extend({
template: '<ul><li></li><li></li><li></li></ul>'
var all = view.queryAll('li');
t.ok(all instanceof Array);
test('get should return undefined if no match', function (t) {
var View = AmpersandView.extend({
var el = view.query('div');
t.equal(typeof el, 'undefined');
t.strictEqual(view.query(''), view.el);
t.strictEqual(view.query(), view.el);
t.strictEqual(view.query(view.el), view.el);
test('get should work for root element too', function (t) {
var View = AmpersandView.extend({
t.equal(view.query('ul'), view.el);
test('queryAll should include root element if matches', function (t) {
var View = AmpersandView.extend({
template: '<div class="test"><div class="test deep"><div class="test deep"></div></div></div>'
var hasTestClass = view.queryAll('.test');
var hasDeepClass = view.queryAll('.deep');
t.equal(hasTestClass.length, 3);
t.equal(hasDeepClass.length, 2);
t.ok(hasTestClass instanceof Array);
t.ok(hasDeepClass instanceof Array);
t.ok(view.queryAll('bogus') instanceof Array);
test('ability to mix in state properties', function (t) {
var View = AmpersandView.extend({
this.el = document.createElement('div');
view.on('change:el', function () {
test('Ability to add other state properties', function (t) {
var View = AmpersandView.extend({
view.on('change:thing', function () {
test('Multi-inheritance of state properties works too', function (t) {
var View = AmpersandView.extend({
var SecondView = View.extend({
var view = window.view = new SecondView();
view.on('change:thing', function () {
view.on('change:otherThing', function () {
test('Setting an `el` should only fire change if new instance of element', function (t) {
var View = AmpersandView.extend({
t.once('change:el', function () {
t.pass('this should fire');
t.el = document.createElement('div');
t.once('change:el', function () {
t.fail('this should *not* fire');
el.innerHTML = '<span></span>';
test('Should be able to bind multiple models in bindings hash', function (t) {
var Person = Model.extend({
var View = AmpersandView.extend({
template: '<div><span id="model1"></span><span id="model2"></span></div>',
'model1.name': '#model1',
model1: new Person({name: 'henrik'}),
model2: new Person({name: 'larry'})
t.equal(view.el.firstChild.textContent, 'henrik');
t.equal(view.el.children[1].className.trim(), 'larry');
test('Should be able to declare bindings first, before model is added', function (t) {
var Person = Model.extend({props: {name: 'string'}});
var View = AmpersandView.extend({
t.equal(view.el.textContent, '');
view.model = new Person({name: 'henrik'});
t.equal(view.el.textContent, 'henrik');
view.model.name = 'something new';
t.equal(view.el.textContent, 'something new');
test('Should be able to swap out models and bindings should still work', function (t) {
var Person = Model.extend({props: {name: 'string'}});
var View = AmpersandView.extend({
var p1 = new Person({name: 'first'});
var p2 = new Person({name: 'second'});
t.equal(view.el.textContent, '');
t.equal(view.el.textContent, 'first');
t.equal(view.el.textContent, 'second');
t.equal(view.el.textContent, 'second');
test('Should be able to re-render and maintain bindings', function (t) {
var Person = Model.extend({props: {name: 'string'}});
var View = AmpersandView.extend({
var p1 = new Person({name: 'first'});
var view = new View({model: p1});
t.equal(view.el.textContent, 'first');
view.renderWithTemplate();
t.ok(el1 !== el2, 'sanity check to make sure it\'s a new element');
t.equal(el2.textContent, 'first', 'new one should have the binding still');
t.equal(el2.textContent, 'third', 'new element should also get the change');
test('trigger `remove` when view is removed', function (t) {
var View = AmpersandView.extend({
view.on('remove', function () {
test('declarative subViews basics', function (t) {
var Sub = AmpersandView.extend({
template: '<span></span>'
var View = AmpersandView.extend({
template: '<div><div class="container"></div></div>',
t.equal(view.el.innerHTML, '<span></span>');
test('subview hook can include special characters', function (t) {
var Sub = AmpersandView.extend({
template: '<span></span>'
var View = AmpersandView.extend({
template: '<div><div data-hook="test.hi-there"></div></div>',
t.equal(view.el.innerHTML, '<span></span>');
test('make sure subviews dont fire until their `waitFor` is done', function (t) {
var Sub = AmpersandView.extend({
template: '<span>yes</span>'
var View = AmpersandView.extend({
template: '<div><span class="container"></span><span data-hook="sub"></span></div>',
t.equal(view._events.change.length, 2);
t.equal(view.el.outerHTML, '<div><span class="container"></span><span data-hook="sub"></span></div>');
view.model = new Model();
t.equal(view._events.change.length, 1);
t.equal(view.el.outerHTML, '<div><span>yes</span><span data-hook="sub"></span></div>');
view.model2 = new Model();
t.equal(view.el.outerHTML, '<div><span>yes</span><span>yes</span></div>');
t.notOk(view._events.change);
test('make sure template can return a dom node', function (t) {
var Sub = AmpersandView.extend({
return document.createElement('div');
test('template can be passed as viewOption', function (t) {
var View = AmpersandView.extend({
template: '<span></span>'
t.equal(view.el.outerHTML, '<span></span>');
test('events are bound if there is an el in the constructor', function (t) {
var event = document.createEvent("MouseEvent");
var View = AmpersandView.extend({
return document.createElement('div');
'click div': 'divClicked'
divClicked: function (e) {
t.ok(true, 'event fired');
var view = new View({el: document.createElement('div')});
event.initMouseEvent('click');
view.el.dispatchEvent(event);