ractive
Version:
Next-generation DOM manipulation
462 lines (358 loc) • 14.4 kB
JavaScript
define([ 'ractive' ], function ( Ractive ) {
'use strict';
return function () {
var fixture = document.getElementById( 'qunit-fixture' );
module( 'Two-way bindings' );
test( 'Two-way bindings work with index references', function ( t ) {
var input, ractive;
ractive = new Ractive({
el: fixture,
template: '{{#items:i}}<label><input value="{{items[i].name}}"> {{name}}</label>{{/items}}',
data: { items: [{ name: 'foo' }, { name: 'bar' }] }
});
input = ractive.find( 'input' );
input.value = 'baz';
simulant.fire( input, 'change' );
t.equal( ractive.get( 'items[0].name' ), 'baz' );
t.htmlEqual( fixture.innerHTML, '<label><input> baz</label><label><input> bar</label>' );
});
test( 'Two-way bindings work with foo["bar"] type notation', function ( t ) {
var input, ractive;
ractive = new Ractive({
el: fixture,
template: '<label><input value={{foo["bar"]["baz"]}}> {{foo.bar.baz}}</label>',
data: { foo: { bar: { baz: 1 } } }
});
input = ractive.find( 'input' );
input.value = 2;
simulant.fire( input, 'change' );
t.equal( ractive.get( 'foo.bar.baz' ), 2 );
t.htmlEqual( fixture.innerHTML, '<label><input> 2</label>' );
});
test( 'Two-way bindings work with arbitrary expressions that resolve to keypaths', function ( t ) {
var input, ractive;
ractive = new Ractive({
el: fixture,
template: '<label><input value={{foo["bar"][ a+1 ].baz[ b ][ 1 ] }}> {{ foo.bar[1].baz.qux[1] }}</label>',
data: {
foo: {
bar: [
null,
{ baz: { qux: [ null, 'yes' ] } }
]
},
a: 0,
b: 'qux'
}
});
input = ractive.find( 'input' );
input.value = 'it works';
simulant.fire( input, 'change' );
t.equal( ractive.get( 'foo.bar[1].baz.qux[1]' ), 'it works' );
t.htmlEqual( fixture.innerHTML, '<label><input> it works</label>' );
});
test( 'An input whose value is updated programmatically will update the model on blur (#644)', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input value="{{foo}}">',
data: { foo: 'bar' }
});
try {
ractive.find( 'input' ).value = 'baz';
simulant.fire( ractive.find( 'input' ), 'blur' );
t.equal( ractive.get( 'foo' ), 'baz' );
} catch ( err ) {
t.ok( true ); // otherwise phantomjs throws a hissy fit
}
});
test( 'Model is validated on blur, and the view reflects the validate model (#644)', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input value="{{foo}}">',
data: { foo: 'bar' }
});
ractive.observe( 'foo', function ( foo ) {
this.set( 'foo', foo.toUpperCase() );
});
try {
ractive.find( 'input' ).value = 'baz';
simulant.fire( ractive.find( 'input' ), 'blur' );
t.equal( ractive.find( 'input' ).value, 'BAZ' );
} catch ( err ) {
t.ok( true ); // phantomjs
}
});
test( 'Two-way data binding is not attempted on elements with no mustache binding', function ( t ) {
expect(0);
// This will throw an error if the binding is attempted (Issue #750)
var ractive = new Ractive({
el: fixture,
template: '<input type="radio"><input type="checkbox"><input type="file"><select></select><textarea></textarea><div contenteditable="true"></div>'
});
});
test( 'Uninitialised values should be initialised with whatever the \'empty\' value is (#775)', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input value="{{foo}}">'
});
t.equal( ractive.get( 'foo' ), '' );
});
test( 'Contenteditable elements can be bound via the value attribute', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<div contenteditable="true" value="{{content}}"><strong>some content</strong></div>',
});
t.equal( ractive.get( 'content' ), '<strong>some content</strong>' );
t.htmlEqual( fixture.innerHTML, '<div contenteditable="true"><strong>some content</strong></div>' );
ractive.set( 'content', '<p>some different content</p>' );
t.htmlEqual( fixture.innerHTML, '<div contenteditable="true"><p>some different content</p></div>' );
});
test( 'Existing model data overrides contents of contenteditable elements', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<div contenteditable="true" value="{{content}}"><strong>some content</strong></div>',
data: { content: 'overridden' }
});
t.equal( ractive.get( 'content' ), 'overridden' );
t.htmlEqual( fixture.innerHTML, '<div contenteditable="true">overridden</div>' );
});
[ 'number', 'range' ].forEach( function ( type ) {
test( 'input type=' + type + ' values are coerced', function ( t ) {
var ractive, inputs;
ractive = new Ractive({
el: fixture,
template: '<input value="{{a}}" type="' + type + '"><input value="{{b}}" type="' + type + '">{{a}}+{{b}}={{a+b}}'
});
t.equal( ractive.get( 'a' ), undefined );
t.equal( ractive.get( 'b' ), undefined );
inputs = ractive.findAll( 'input' );
inputs[0].value = '40';
inputs[1].value = '2';
ractive.updateModel();
t.htmlEqual( fixture.innerHTML, '<input type="' + type + '"><input type="' + type + '">40+2=42' );
});
});
test( 'The model updates to reflect which checkbox inputs are checked at render time', function ( t ) {
var ractive;
ractive = new Ractive({
el: fixture,
template: '<input id="red" type="checkbox" name="{{colors}}" value="red"><input id="green" type="checkbox" name="{{colors}}" value="blue" checked><input id="blue" type="checkbox" name="{{colors}}" value="green" checked>'
});
t.deepEqual( ractive.get( 'colors' ), [ 'blue', 'green' ] );
t.ok( !ractive.nodes.red.checked );
t.ok( ractive.nodes.blue.checked );
t.ok( ractive.nodes.green.checked );
ractive = new Ractive({
el: fixture,
template: '<input id="red" type="checkbox" name="{{colors}}" value="red"><input id="green" type="checkbox" name="{{colors}}" value="blue"><input id="blue" type="checkbox" name="{{colors}}" value="green">'
});
t.deepEqual( ractive.get( 'colors' ), [] );
t.ok( !ractive.nodes.red.checked );
t.ok( !ractive.nodes.blue.checked );
t.ok( !ractive.nodes.green.checked );
});
test( 'The model overrides which checkbox inputs are checked at render time', function ( t ) {
var ractive;
ractive = new Ractive({
el: fixture,
template: '<input id="red" type="checkbox" name="{{colors}}" value="red"><input id="blue" type="checkbox" name="{{colors}}" value="blue" checked><input id="green" type="checkbox" name="{{colors}}" value="green" checked>',
data: { colors: [ 'red', 'blue' ] }
});
t.deepEqual( ractive.get( 'colors' ), [ 'red', 'blue' ] );
t.ok( ractive.nodes.red.checked );
t.ok( ractive.nodes.blue.checked );
t.ok( !ractive.nodes.green.checked );
});
test( 'The model updates to reflect which radio input is checked at render time', function ( t ) {
var ractive;
ractive = new Ractive({
el: fixture,
template: '<input type="radio" name="{{color}}" value="red"><input type="radio" name="{{color}}" value="blue" checked><input type="radio" name="{{color}}" value="green">'
});
t.deepEqual( ractive.get( 'color' ), 'blue' );
ractive = new Ractive({
el: fixture,
template: '<input type="radio" name="{{color}}" value="red"><input type="radio" name="{{color}}" value="blue"><input type="radio" name="{{color}}" value="green">'
});
t.deepEqual( ractive.get( 'color' ), undefined );
});
test( 'The model overrides which radio input is checked at render time', function ( t ) {
var ractive;
ractive = new Ractive({
el: fixture,
template: '<input id="red" type="radio" name="{{color}}" value="red"><input id="blue" type="radio" name="{{color}}" value="blue" checked><input id="green" type="radio" name="{{color}}" value="green">',
data: { color: 'green' }
});
t.deepEqual( ractive.get( 'color' ), 'green' );
t.ok( !ractive.nodes.red.checked );
t.ok( !ractive.nodes.blue.checked );
t.ok( ractive.nodes.green.checked );
});
test( 'updateModel correctly updates the value of a text input', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input value="{{name}}">',
data: { name: 'Bob' }
});
ractive.find( 'input' ).value = 'Jim';
ractive.updateModel( 'name' );
t.equal( ractive.get( 'name' ), 'Jim' );
});
test( 'updateModel correctly updates the value of a select', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<select value="{{selected}}"><option selected value="red">red</option><option value="blue">blue</option><option value="green">green</option></select>'
});
t.equal( ractive.get( 'selected' ), 'red' );
ractive.findAll( 'option' )[1].selected = true;
ractive.updateModel();
t.equal( ractive.get( 'selected' ), 'blue' );
});
test( 'updateModel correctly updates the value of a textarea', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<textarea value="{{name}}"></textarea>',
data: { name: 'Bob' }
});
ractive.find( 'textarea' ).value = 'Jim';
ractive.updateModel( 'name' );
t.equal( ractive.get( 'name' ), 'Jim' );
});
test( 'updateModel correctly updates the value of a checkbox', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input type="checkbox" checked="{{active}}">',
data: { active: true }
});
ractive.find( 'input' ).checked = false;
ractive.updateModel();
t.equal( ractive.get( 'active' ), false );
});
test( 'updateModel correctly updates the value of a radio', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input type="radio" checked="{{active}}">',
data: { active: true }
});
ractive.find( 'input' ).checked = false;
ractive.updateModel();
t.equal( ractive.get( 'active' ), false );
});
test( 'updateModel correctly updates the value of an indirect (name-value) checkbox', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input type="checkbox" name="{{colour}}" value="red"><input type="checkbox" name="{{colour}}" value="blue" checked><input type="checkbox" name="{{colour}}" value="green">'
});
t.deepEqual( ractive.get( 'colour' ), [ 'blue' ] );
ractive.findAll( 'input' )[2].checked = true;
ractive.updateModel();
t.deepEqual( ractive.get( 'colour' ), [ 'blue', 'green' ] );
});
test( 'updateModel correctly updates the value of an indirect (name-value) radio', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '<input type="radio" name="{{colour}}" value="red"><input type="radio" name="{{colour}}" value="blue" checked><input type="radio" name="{{colour}}" value="green">'
});
t.deepEqual( ractive.get( 'colour' ), 'blue' );
ractive.findAll( 'input' )[2].checked = true;
ractive.updateModel();
t.deepEqual( ractive.get( 'colour' ), 'green' );
});
test( 'Radio inputs will update the model if another input in their group is checked', function ( t ) {
var ractive, inputs;
ractive = new Ractive({
el: fixture,
template: '{{#items}}<input type="radio" name="plan" checked="{{ checked }}"/>{{/items}}',
data: {
items: [
{ key: 'a', checked: true },
{ key: 'b', checked: false },
{ key: 'c', checked: false }
]
}
});
inputs = ractive.findAll( 'input' );
t.equal( inputs[0].checked, true );
inputs[1].checked = true;
simulant.fire( inputs[1], 'change' );
t.equal( ractive.get( 'items[0].checked' ), false );
t.equal( ractive.get( 'items[1].checked' ), true );
});
test( 'Radio name inputs respond to model changes (regression, see #783)', function ( t ) {
var ractive, inputs;
ractive = new Ractive({
el: fixture,
template: '{{#items}}<input type="radio" name="{{foo}}" value="{{this}}"/>{{/items}}',
data: {
items: [ 'a', 'b', 'c' ]
}
});
inputs = ractive.findAll( 'input' );
t.equal( ractive.get( 'foo' ), undefined );
ractive.set( 'foo', 'b' );
t.ok( inputs[1].checked );
ractive.set( 'items', [ 'd', 'e', 'f' ]);
t.equal( ractive.get( 'foo' ), undefined );
t.ok( !inputs[1].checked );
});
test( 'Post-blur validation works (#771)', function ( t ) {
var ractive, input;
ractive = new Ractive({
el: fixture,
template: '<input value="{{foo}}">{{foo}}'
});
ractive.observe( 'foo', function ( foo ) {
this.set( 'foo', foo.toUpperCase() );
});
input = ractive.find( 'input' );
input.value = 'bar';
simulant.fire( input, 'change' );
t.equal( input.value, 'bar' );
t.equal( ractive.get( 'foo' ), 'BAR' );
t.htmlEqual( fixture.innerHTML, '<input>BAR' );
simulant.fire( input, 'change' );
try {
simulant.fire( input, 'blur' );
t.equal( input.value, 'BAR' );
t.equal( ractive.get( 'foo' ), 'BAR' );
t.htmlEqual( fixture.innerHTML, '<input>BAR' );
} catch ( err ) {
// Oh PhantomJS. You are so very WTF
}
});
test( 'Reference expression radio bindings rebind correctly inside reference expression sections (#904)', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: '{{#with steps[current] }}<input type="radio" name="{{~/selected[name]}}" value="{{value}}">{{/with}}',
data: {
steps: [{ name: 'one', value: 'a' }, { name: 'two', value: 'b' }],
current: 0
}
});
ractive.find( 'input' ).checked = true;
ractive.updateModel();
t.deepEqual( ractive.get( 'selected' ), { one: 'a' });
ractive.set( 'current', 1 );
ractive.find( 'input' ).checked = true;
ractive.updateModel();
t.deepEqual( ractive.get( 'selected' ), { one: 'a', two: 'b' });
});
test( 'Ambiguous reference expressions in two-way bindings attach to the root (#900)', function ( t ) {
var ractive = new Ractive({
el: fixture,
template: `
<p>foo[{{bar}}]: {{foo[bar]}}</p>
{{#with whatever}}
<input value='{{foo[bar]}}'>
{{/with}}`,
data: {
bar: 0
}
});
ractive.find( 'input' ).value = 'test';
ractive.updateModel();
t.deepEqual( ractive.get( 'foo' ), [ 'test' ] );
t.htmlEqual( fixture.innerHTML, '<p>foo[0]: test</p><input>' );
});
};
});