UNPKG

ractive

Version:

Next-generation DOM manipulation

1,346 lines (1,061 loc) 33.5 kB
define([ 'ractive' ], function ( Ractive ) { 'use strict'; return function () { var fixture; module( 'Components' ); // some set-up fixture = document.getElementById( 'qunit-fixture' ); test( 'Static data is propagated from parent to child', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="blah"/>', components: { widget: Widget } }); widget = ractive.findComponent( 'widget' ); t.equal( widget.get( 'foo' ), 'blah' ); t.htmlEqual( fixture.innerHTML, '<p>blah</p>' ); }); test( 'Static object data is propagated from parent to child', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo.bar}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{ { bar: \'biz\' } }}"/>', components: { widget: Widget } }); widget = ractive.findComponent( 'widget' ); t.deepEqual( widget.get( 'foo' ), { bar: 'biz' } ); t.htmlEqual( fixture.innerHTML, '<p>biz</p>' ); widget.set('foo.bar', 'bah') t.deepEqual( widget.get( 'foo' ), { bar: 'bah' } ); t.htmlEqual( fixture.innerHTML, '<p>bah</p>' ); }); test( 'Dynamic data is propagated from parent to child, and (two-way) bindings are created', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{bar}}"/>', components: { widget: Widget }, data: { bar: 'blah' } }); widget = ractive.findComponent( 'widget' ); t.equal( widget.get( 'foo' ), 'blah' ); t.htmlEqual( fixture.innerHTML, '<p>blah</p>' ); ractive.set( 'bar', 'flup' ); t.equal( widget.get( 'foo' ), 'flup' ); t.htmlEqual( fixture.innerHTML, '<p>flup</p>' ); widget.set( 'foo', 'shmup' ); t.equal( ractive.get( 'bar' ), 'shmup' ); t.htmlEqual( fixture.innerHTML, '<p>shmup</p>' ); }); // Commenting out this test for the moment - is this a desirable feature? // It prevents JavaScript closure-like behaviour with data contexts /*test( 'Missing data on the parent is not propagated', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{missing}}"/>', components: { widget: Widget } }); widget = ractive.findComponent( 'widget' ); t.ok( !( widget.data.hasOwnProperty( 'foo' ) ) ); t.htmlEqual( fixture.innerHTML, '<p></p>' ); });*/ test( 'Missing data on the parent is added when set', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{missing}}"/>', components: { widget: Widget } }); widget = ractive.findComponent( 'widget' ); t.htmlEqual( fixture.innerHTML, '<p></p>' ); ractive.set('missing', 'found') t.ok( widget.data.hasOwnProperty( 'foo' ) ); t.htmlEqual( fixture.innerHTML, '<p>found</p>' ); }); test( 'Data on the child is propagated to the parent, if it is not missing', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}{{bar}}</p>', data: { foo: 'yes' } }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{one}}" bar="{{two}}"/>', components: { widget: Widget } }); widget = ractive.findComponent( 'widget' ); t.equal( ractive.get( 'one' ), 'yes' ); t.ok( !( ractive.data.hasOwnProperty( 'two' ) ) ); t.htmlEqual( fixture.innerHTML, '<p>yes</p>' ); }); test( 'Parent data overrides child data during child model creation', function ( t ) { var Widget, ractive, widget; Widget = Ractive.extend({ template: '<p>{{foo}}{{bar}}</p>', data: { foo: 'yes', bar: 'no' } }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{one}}" bar="{{two}}"/>', components: { widget: Widget }, data: { one: 'uno', two: 'dos' } }); widget = ractive.findComponent( 'widget' ); t.equal( ractive.get( 'one' ), 'uno' ); t.equal( ractive.get( 'two' ), 'dos' ); t.equal( widget.get( 'foo' ), 'uno' ); t.equal( widget.get( 'bar' ), 'dos' ); t.htmlEqual( fixture.innerHTML, '<p>unodos</p>' ); }); test( 'Components are rendered in the correct place', function ( t ) { var Component, ractive; Component = Ractive.extend({ template: '<p>this is a component!</p>' }); ractive = new Ractive({ el: fixture, template: '<h2>Here is a component:</h2><component/><p>(that was a component)</p>', components: { component: Component } }); t.htmlEqual( fixture.innerHTML, '<h2>Here is a component:</h2><p>this is a component!</p><p>(that was a component)</p>' ); }); test( 'Top-level sections in components are updated correctly', function ( t ) { var ractive, Component, component; Component = Ractive.extend({ template: '{{#foo}}foo is truthy{{/foo}}{{^foo}}foo is falsy{{/foo}}' }); ractive = new Ractive({ el: fixture, template: '<component foo="{{foo}}"/>', components: { component: Component } }); t.htmlEqual( fixture.innerHTML, 'foo is falsy' ); ractive.set( 'foo', true ); t.htmlEqual( fixture.innerHTML, 'foo is truthy' ); }); test( 'Element order is maintained correctly with components with multiple top-level elements', function ( t ) { var ractive, TestComponent; TestComponent = Ractive.extend({ template: '{{#bool}}TRUE{{/bool}}{{^bool}}FALSE{{/bool}}' }); ractive = new Ractive({ el: fixture, template: '<p>before</p> <test bool="{{bool}}"/> <p>after</p>', components: { test: TestComponent } }); t.htmlEqual( fixture.innerHTML, '<p>before</p> FALSE <p>after</p>' ); ractive.set( 'bool', true ); t.htmlEqual( fixture.innerHTML, '<p>before</p> TRUE <p>after</p>' ); ractive.set( 'bool', false ); t.htmlEqual( fixture.innerHTML, '<p>before</p> FALSE <p>after</p>' ); }); test( 'Regression test for #317', function ( t ) { var Widget, widget, ractive, items; Widget = Ractive.extend({ template: '<ul>{{#items:i}}<li>{{i}}: {{.}}</li>{{/items}}</ul>', init: function () { widget = this; } }); ractive = new Ractive({ el: fixture, template: '<widget items="{{items}}"/><p>{{ items.join( " " ) }}</p>', data: { items: [ 'a', 'b', 'c', 'd' ] }, components: { widget: Widget } }); items = ractive.get( 'items' ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: a</li><li>1: b</li><li>2: c</li><li>3: d</li></ul><p>a b c d</p>' ); items.push( 'e' ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: a</li><li>1: b</li><li>2: c</li><li>3: d</li><li>4: e</li></ul><p>a b c d e</p>' ); items.splice( 2, 1 ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: a</li><li>1: b</li><li>2: d</li><li>3: e</li></ul><p>a b d e</p>' ); items.pop(); t.htmlEqual( fixture.innerHTML, '<ul><li>0: a</li><li>1: b</li><li>2: d</li></ul><p>a b d</p>' ); ractive.set( 'items[0]', 'f' ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: f</li><li>1: b</li><li>2: d</li></ul><p>f b d</p>' ); // reset items from within widget widget.set( 'items', widget.get( 'items' ).slice() ); items = ractive.get( 'items' ); items.push( 'g' ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: f</li><li>1: b</li><li>2: d</li><li>3: g</li></ul><p>f b d g</p>' ); items.splice( 1, 1 ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: f</li><li>1: d</li><li>2: g</li></ul><p>f d g</p>' ); items.pop(); t.htmlEqual( fixture.innerHTML, '<ul><li>0: f</li><li>1: d</li></ul><p>f d</p>' ); widget.set( 'items[0]', 'h' ); t.htmlEqual( fixture.innerHTML, '<ul><li>0: h</li><li>1: d</li></ul><p>h d</p>' ); }); asyncTest( 'Component complete() methods are called', function ( t ) { var ractive, Widget, counter, done; expect( 2 ); counter = 2; done = function () { --counter || start(); }; Widget = Ractive.extend({ complete: function () { t.ok( true, 'complete in component' ); done(); } }); ractive = new Ractive({ el: fixture, template: '<widget/>', complete: function () { t.ok( true, 'complete in ractive' ); done(); }, components: { widget: Widget } }); }); test( 'Components can access outer data context, in the same way JavaScript functions can access outer lexical scope', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '<p>{{foo || "missing"}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget/><widget foo="{{bar}}"/><widget foo="{{baz}}"/>', data: { foo: 'one', bar: 'two' }, components: { widget: Widget } }); t.htmlEqual( fixture.innerHTML, '<p>one</p><p>two</p><p>missing</p>' ); ractive.set({ foo: 'three', bar: 'four', baz: 'five' }); t.htmlEqual( fixture.innerHTML, '<p>three</p><p>four</p><p>five</p>' ); }); test( 'Nested components can access outer-most data context', function ( t ) { var ractive, Widget; ractive = new Ractive({ el: fixture, template: '<widget/>', components: { widget: Ractive.extend({ template: '<grandwidget/>', components: { grandwidget: Ractive.extend({ template: 'hello {{world}}' }) }, }) }, data: { world: 'mars' } }); t.htmlEqual( fixture.innerHTML, 'hello mars' ); ractive.set('world', 'venus'); t.htmlEqual( fixture.innerHTML, 'hello venus' ); }); test( 'Nested components registered at global Ractive can access outer-most data context', function ( t ) { var ractive, Widget; Ractive.components.widget = Ractive.extend({ template: '<grandwidget/>' }); Ractive.components.grandwidget = Ractive.extend({ template: 'hello {{world}}' }); ractive = new Ractive({ el: fixture, template: '<widget/>', data: { world: 'mars' } }); t.htmlEqual( fixture.innerHTML, 'hello mars' ); ractive.set('world', 'venus'); t.htmlEqual( fixture.innerHTML, 'hello venus' ); delete Ractive.components.widget delete Ractive.components.grandwidget }); if ( Ractive.magic ) { asyncTest( 'Data passed into component updates inside component in magic mode', function ( t ) { var ractive, Widget; expect( 1 ); Widget = Ractive.extend({ template: '{{world}}', magic: true, complete: function(){ this.data.world = 'venus' t.htmlEqual( fixture.innerHTML, 'venusvenus' ); start(); } }); var data = { world: 'mars' } ractive = new Ractive({ el: fixture, template: '{{world}}<widget world="{{world}}"/>', magic: true, components: { widget: Widget }, data: data }); }); test( 'Data passed into component updates from outside component in magic mode', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '{{world}}', magic: true }); var data = { world: 'mars' } ractive = new Ractive({ el: fixture, template: '{{world}}<widget world="{{world}}"/>', magic: true, components: { widget: Widget }, data: data }); data.world = 'venus' t.htmlEqual( fixture.innerHTML, 'venusvenus' ); }); test( 'Indirect changes propagate across components in magic mode (#480)', function ( t ) { var Blocker, ractive, blocker; Blocker = Ractive.extend({ template: '{{foo.bar.baz}}' }); ractive = new Ractive({ el: fixture, template: '<input value="{{foo.bar.baz}}"><blocker foo="{{foo}}"/>', data: { foo: { bar: { baz: 50 } } }, magic: true, components: { blocker: Blocker } }); ractive.set( 'foo.bar.baz', 42 ); t.equal( ractive.get( 'foo.bar.baz' ), 42 ); ractive.data.foo.bar.baz = 1337; t.equal( ractive.data.foo.bar.baz, 1337 ); t.equal( ractive.get( 'foo.bar.baz' ), 1337 ); blocker = ractive.findComponent( 'blocker' ); blocker.set( 'foo.bar.baz', 42 ); t.equal( blocker.get( 'foo.bar.baz' ), 42 ); blocker.data.foo.bar.baz = 1337; t.equal( blocker.data.foo.bar.baz, 1337 ); t.equal( blocker.get( 'foo.bar.baz' ), 1337 ); }); } test( 'Component data passed but non-existent on parent data', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '{{exists}}{{missing}}' }); ractive = new Ractive({ el: fixture, template: '<widget exists="{{exists}}" missing="{{missing}}"/>', components: { widget: Widget }, data: { exists: 'exists' } }); t.htmlEqual( fixture.innerHTML, 'exists' ); }); test( 'Some component data not included in invocation parameters', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '{{exists}}{{missing}}', }); ractive = new Ractive({ el: fixture, template: '<widget exists="{{exists}}"/>', components: { widget: Widget }, data: { exists: 'exists' } }); t.htmlEqual( fixture.innerHTML, 'exists' ); }); test( 'Some component data not included, with implicit sibling', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '{{exists}}{{also}}{{missing}}', }); ractive = new Ractive({ el: fixture, template: '{{#stuff:exists}}<widget exists="{{exists}}" also="{{.}}"/>{{/stuff}}', components: { widget: Widget }, data: { stuff: { exists: 'also' } } }); t.htmlEqual( fixture.innerHTML, 'existsalso' ); }); test( 'Isolated components do not interact with ancestor viewmodels', function ( t ) { var ractive, Widget; Widget = Ractive.extend({ template: '{{foo}}.{{bar}}', isolated: true }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{foo}}"/>', components: { widget: Widget }, data: { foo: 'you should see me', bar: 'but not me' } }); t.htmlEqual( fixture.innerHTML, 'you should see me.' ); }); test( 'Top-level list sections in components do not cause elements to be out of order (#412 regression)', function ( t ) { var Widget, ractive; Widget = Ractive.extend({ template: '{{#numbers:o}}<p>{{.}}</p>{{/numbers}}' }); ractive = new Ractive({ el: fixture, template: '<h1>Names</h1><widget numbers="{{first}}"/><widget numbers="{{second}}"/>', components: { widget: Widget }, data: { first: { one: 'one', two: 'two' }, second: { three: 'three', four: 'four' } } }); t.htmlEqual( fixture.innerHTML, '<h1>Names</h1><p>one</p><p>two</p><p>three</p><p>four</p>' ); }); test( 'Children do not nuke parent data when inheriting from ancestors', function ( t ) { var Widget, Block, ractive; Widget = Ractive.extend({ template: '<p>value: {{thing.value}}</p>' }); Block = Ractive.extend({ template: '<widget thing="{{things.one}}"/><widget thing="{{things.two}}"/><widget thing="{{things.three}}"/>', components: { widget: Widget } }); // YOUR CODE GOES HERE ractive = new Ractive({ el: fixture, template: '<block/>', data: { things: { one: { value: 1 }, two: { value: 2 }, three: { value: 3 } } }, components: { block: Block } }); t.deepEqual( ractive.get( 'things' ), { one: { value: 1 }, two: { value: 2 }, three: { value: 3 } } ) }); test( 'Uninitialised implicit dependencies of evaluators that use inherited functions are handled', function ( t ) { var Widget, ractive; Widget = Ractive.extend({ template: '{{status()}}' }); ractive = new Ractive({ el: fixture, template: '{{status()}}-<widget/>', data: { status: function () { return this.get( '_status' ); } }, components: { widget: Widget } }); t.htmlEqual( fixture.innerHTML, '-' ); ractive.set( '_status', 'foo' ); t.htmlEqual( fixture.innerHTML, 'foo-foo' ); ractive.set( '_status', 'bar' ); t.htmlEqual( fixture.innerHTML, 'bar-bar' ); }); asyncTest( 'Instances with multiple components still fire complete() handlers (#486 regression)', function ( t ) { var Widget, ractive, counter, done; Widget = Ractive.extend({ template: 'foo', complete: function () { t.ok( true ); done(); } }); expect( 3 ); counter = 3; done = function () { --counter || start(); }; ractive = new Ractive({ el: fixture, template: '<widget/><widget/>', components: { widget: Widget }, complete: function () { t.ok( true ); done(); } }); }); test( 'findComponent and findAllComponents work through {{>content}}', function ( t ) { var Wrapper, Component, ractive; Component = Ractive.extend({}); Wrapper = Ractive.extend({ template: '<p>{{>content}}</p>', components: { component: Component } }); ractive = new Ractive({ el: fixture, template: '<wrapper><component/></wrapper>', components: { wrapper: Wrapper, component: Component } }); var find = ractive.findComponent('component'), findAll = ractive.findAllComponents('component'); t.ok( find, 'component not found' ); t.equal( findAll.length, 1); }); test( 'Correct value is given to node._ractive.keypath when a component is torn down and re-rendered (#470)', function ( t ) { var ractive; ractive = new Ractive({ el: fixture, template: '{{#foo}}<widget visible="{{visible}}"/>{{/foo}}', data: { foo: {}, visible: true }, components: { widget: Ractive.extend({ template: '{{#visible}}<p>{{test}}</p>{{/visible}}' }) } }); t.equal( ractive.find( 'p' )._ractive.keypath, '' ); ractive.set( 'visible', false ); ractive.set( 'visible', true ); t.equal( ractive.find( 'p' )._ractive.keypath, '' ); }); test( 'Nested components fire the init() event correctly (#511)', function ( t ) { var ractive, Outer, Inner, outerInitCount = 0, innerInitCount = 0; Inner = Ractive.extend({ init: function () { innerInitCount += 1; } }); Outer = Ractive.extend({ template: '<inner/>', init: function () { outerInitCount += 1; }, components: { inner: Inner } }); ractive = new Ractive({ el: fixture, template: '{{#foo}}<outer/>{{/foo}}', data: { foo: false }, components: { outer: Outer } }); ractive.set( 'foo', true ); // initCounts should have incremented synchronously t.equal( outerInitCount, 1, '<outer/> component should call init()' ); t.equal( innerInitCount, 1, '<inner/> component should call init()' ); }); test( 'foo.bar should stay in sync between <one foo="{{foo}}"/> and <two foo="{{foo}}"/>', function ( t ) { var ractive = new Ractive({ el: fixture, template: '<one foo="{{foo}}"/><two foo="{{foo}}"/>', components: { one: Ractive.extend({ template: '<p>{{foo.bar}}</p>' }), two: Ractive.extend({ template: '<p>{{foo.bar}}</p>' }) } }); ractive.set( 'foo', {} ); t.htmlEqual( fixture.innerHTML, '<p></p><p></p>' ); ractive.findComponent( 'one' ).set( 'foo.bar', 'baz' ); t.htmlEqual( fixture.innerHTML, '<p>baz</p><p>baz</p>' ); ractive.findComponent( 'two' ).set( 'foo.bar', 'qux' ); t.htmlEqual( fixture.innerHTML, '<p>qux</p><p>qux</p>' ); }); test( 'Index references propagate down to non-isolated components', function ( t ) { var ractive = new Ractive({ el: fixture, template: '{{#items:i}}<widget letter="{{.}}"/>{{/items}}', data: { items: [ 'a', 'b', 'c' ] }, components: { widget: Ractive.extend({ template: '<p>{{i}}: {{letter}}</p>' }) } }); t.htmlEqual( fixture.innerHTML, '<p>0: a</p><p>1: b</p><p>2: c</p>' ); ractive.get( 'items' ).splice( 1, 1 ); t.htmlEqual( fixture.innerHTML, '<p>0: a</p><p>1: c</p>' ); }); test( 'Component removed from DOM on tear-down with teardown override that calls _super', function ( t ) { var Widget = Ractive.extend({ template: 'foo', teardown: function(){ this._super(); } }); var ractive = new Ractive({ el: fixture, template: '{{#item}}<widget/>{{/item}}', data: { item: {} }, components: { widget: Widget } }); t.htmlEqual( fixture.innerHTML, 'foo' ); ractive.set( 'item' ); t.htmlEqual( fixture.innerHTML, '' ); }); test( 'Component names cannot include underscores (#483)', function ( t ) { var Component, ractive; expect( 1 ); Component = Ractive.extend({ template: '{{foo}}' }); try { ractive = new Ractive({ el: fixture, template: '<no_lo_dash/>', components: { no_lo_dash: Component } }); t.ok( false ); } catch ( err ) { t.ok( true ); } }); test( 'Data will propagate up through multiple component boundaries (#520)', function ( t ) { var ractive, Outer, Inner, inner; Inner = Ractive.extend({ template: '{{input.value}}', update: function ( val ) { this.set( 'input', { value: val }); } }); Outer = Ractive.extend({ template: '{{#inputs}}<inner input="{{this}}"/>{{/inputs}}', components: { inner: Inner } }); ractive = new Ractive({ el: fixture, template: '{{#simulation}}<outer inputs="{{inputs}}"/>{{/simulation}}', components: { outer: Outer }, data: { simulation: { inputs: [{ value: 1 }] } } }); t.equal( ractive.get( 'simulation.inputs[0].value' ), 1 ); inner = ractive.findComponent( 'inner' ); inner.update( 2 ); t.equal( ractive.get( 'simulation.inputs[0].value' ), 2 ); t.htmlEqual( fixture.innerHTML, '2' ); }); test( 'Components can have names that happen to be Array.prototype or Object.prototype methods', function ( t ) { var Map, ractive; Map = Ractive.extend({ template: '<div class="map"></div>' }); ractive = new Ractive({ el: fixture, template: '<map/>', components: { map: Map } }); t.htmlEqual( fixture.innerHTML, '<div class="map"></div>' ); }); test( 'Component in template has data function called on initialize', function ( t ) { var Component, ractive, data = { foo: 'bar' } ; Component = Ractive.extend({ template: '{{foo}}', data: function(){ return data } }); ractive = new Ractive({ el: fixture, template: '<widget/>', components: { widget: Component }, data: { foo: 'no' } }); t.equal( fixture.innerHTML, 'bar' ); }); test( 'Component in template having data function with no return uses existing data instance', function ( t ) { var Component, ractive, data = { foo: 'bar' } ; Component = Ractive.extend({ template: '{{foo}}{{bim}}', data: function(d){ d.bim = 'bam' } }); ractive = new Ractive({ el: fixture, template: '<widget/>', components: { widget: Component }, data: { foo: 'bar' } }); t.equal( fixture.innerHTML, 'barbam' ); }); test( 'Component in template passed parameters with data function', function ( t ) { var Component, ractive, data = { foo: 'bar' } ; Component = Ractive.extend({ template: '{{foo}}{{bim}}', data: function(d){ d.bim = d.foo } }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{outer}}"/>', components: { widget: Component }, data: { outer: 'bar' } }); t.equal( fixture.innerHTML, 'barbar' ); }); test( 'Component in template with dynamic template function', function ( t ) { var Component, ractive; Component = Ractive.extend({ template: function( data, parser ){ return data.useFoo ? '{{foo}}' : '{{fizz}}' } }); ractive = new Ractive({ el: fixture, template: '<widget foo="{{one}}" fizz="{{two}}" useFoo="true"/>', components: { widget: Component }, data: { one: 'bar', two: 'bizz' } }); t.equal( fixture.innerHTML, 'bar' ); }); test( 'Set operations inside an inline component\'s init() method update the DOM synchronously', function ( t ) { var ListWidget, ractive, previousHeight = -1; ListWidget = Ractive.extend({ template: '<ul>{{#visibleItems}}<li>{{this}}</li>{{/visibleItems}}</ul>', init: function () { var ul, lis, items, height, i; ul = this.find( 'ul' ); lis = this.findAll( 'li', { live: true }); items = this.get( 'items' ); for ( i = 0; i < items.length; i += 1 ) { this.set( 'visibleItems', items.slice( 0, i ) ); t.equal( lis.length, i ); height = ul.offsetHeight; t.ok( height > previousHeight ); previousHeight = height; } } }); ractive = new Ractive({ el: fixture, template: '<list-widget items="{{items}}"/>', data: { items: [ 'a', 'b', 'c', 'd' ]}, components: { 'list-widget': ListWidget } }); }); test( 'Inline component attributes are passed through correctly', function ( t ) { var Widget, ractive; Widget = Ractive.extend({ template: '<p>{{foo.bar}}</p><p>{{typeof answer}}: {{answer}}</p><p>I got {{string}} but type coercion ain\'t one</p><p>{{dynamic.yes}}</p>' }); ractive = new Ractive({ el: fixture, template: '<widget foo="{bar:10}" answer="42 " string="99 problems" dynamic="{yes:{{but}}}"/>', data: { but: 'no' }, components: { widget: Widget } }); t.htmlEqual( fixture.innerHTML, '<p>10</p><p>number: 42</p><p>I got 99 problems but type coercion ain\'t one</p><p>no</p>' ); ractive.set( 'but', 'maybe' ); t.htmlEqual( fixture.innerHTML, '<p>10</p><p>number: 42</p><p>I got 99 problems but type coercion ain\'t one</p><p>maybe</p>' ); }); // See issue #681 test( 'Inline component attributes update the value of bindings pointing to them even if they are old values', function ( t ) { var Widget, ractive; Widget = Ractive.extend({ template: '{{childdata}}' }); ractive = new Ractive({ el: fixture, template: '{{parentdata}} - <widget childdata="{{parentdata}}" />', data: { parentdata: 'old' }, components: { widget: Widget } }); t.htmlEqual( fixture.innerHTML, 'old - old' ); ractive.findComponent( 'widget' ).set( 'childdata', 'new' ); t.htmlEqual( fixture.innerHTML, 'new - new' ); ractive.set( 'parentdata', 'old' ); t.htmlEqual( fixture.innerHTML, 'old - old' ); }); asyncTest( 'Component render methods called in consistent order (gh #589)', function ( t ) { var Simpson, ractive, order = { beforeInit: [], init: [], complete: [] }, simpsons = ["Homer", "Marge", "Lisa", "Bart", "Maggie"]; Simpson = Ractive.extend({ template: "{{simpson}}", beforeInit: function(o) { order.beforeInit.push( o.data.simpson ); }, init: function() { order.init.push( this.get("simpson") ); }, complete: function() { order.complete.push( this.get("simpson") ); } }); ractive = new Ractive({ el: fixture, template: '{{#simpsons}}<simpson simpson="{{this}}"/>{{/}}', data: { simpsons: simpsons }, components: { simpson: Simpson }, complete: function(){ // TODO this doesn't work in PhantomJS, presumably because // promises aren't guaranteed to fulfil in a particular order // since they use setTimeout (perhaps they shouldn't?) //t.deepEqual( order.complete, simpsons, 'complete order' ); start(); } }); t.equal( fixture.innerHTML, simpsons.join('') ); t.deepEqual( order.beforeInit, simpsons, 'beforeInit order' ); t.deepEqual( order.init, simpsons, 'init order' ); }); test( 'Insane variable shadowing bug doesn\'t appear (#710)', function ( t ) { var List, ractive; List = Ractive.extend({ template: '{{#items:i}}<p>{{i}}:{{ foo.bar.length }}</p>{{/items}}' }); ractive = new Ractive({ el: fixture, template: '<list items="{{sorted_items}}"/>', components: { list: List }, computed: { sorted_items: function () { return this.get( 'items' ).slice().sort( function ( a, b ) { return ( a.rank - b.rank ); }); } } }); ractive.set( 'items', [ { rank: 2, "foo": {"bar": []} }, { rank: 1, "foo": {} }, { rank: 3, "foo": {"bar": []} } ]); t.htmlEqual( fixture.innerHTML, '<p>0:</p><p>1:0</p><p>2:0</p>' ); }); test( 'Components found in view hierarchy', function ( t ) { var FooComponent, BarComponent, ractive; FooComponent = Ractive.extend({ template: 'foo' }); BarComponent = Ractive.extend({ template: '<foo/>' }); ractive = new Ractive({ el: fixture, template: '<bar/>', components: { foo: FooComponent, bar: BarComponent } }); t.equal( fixture.innerHTML, 'foo' ); }); test( 'Components not found in view hierarchy when isolated is true', function ( t ) { var FooComponent, BarComponent, ractive; FooComponent = Ractive.extend({ template: 'foo' }); BarComponent = Ractive.extend({ template: '<foo/>', isolated: true }); ractive = new Ractive({ el: fixture, template: '<bar/>', components: { foo: FooComponent, bar: BarComponent } }); t.equal( fixture.innerHTML, '<foo></foo>' ); }); test( 'Evaluator in against in component more than once (gh-844)', function ( t ) { var Component, BarComponent, ractive; Component = Ractive.extend({ template: '{{getLabels(foo)}}{{getLabels(boo)}}', data: { getLabels: function (x) { return x; }, foo: 'foo', boo: 'boo' } }); var r = new Ractive({ el: fixture, components: { c: Component }, template: '<c>' }); t.equal( fixture.innerHTML, 'fooboo' ); }); test( 'Removing inline components causes teardown events to fire (#853)', function ( t ) { var ractive = new Ractive({ el: fixture, template: '{{#if foo}}<widget/>{{/if}}', data: { foo: true }, components: { widget: Ractive.extend({ template: 'widget', init: function () { this.on( 'teardown', function () { t.ok( true ); }) } }) } }); expect( 1 ); ractive.toggle( 'foo' ); }); test( 'Regression test for #871', function ( t ) { var ractive = new Ractive({ el: fixture, template: '{{#items:i}}<p>outside component: {{i}}-{{uppercase(.)}}</p><widget text="{{uppercase(.)}}" />{{/items}}', data: { items: [ 'a', 'b', 'c' ], uppercase: function ( letter ) { return letter.toUpperCase(); } }, components: { widget: Ractive.extend({ template: '<p>inside component: {{i}}-{{text}}</p>' }) } }); ractive.splice( 'items', 1, 1 ); t.htmlEqual( fixture.innerHTML, '<p>outside component: 0-A</p><p>inside component: 0-A</p><p>outside component: 1-C</p><p>inside component: 1-C</p>' ); }); test( 'Specify component by function', function ( t ) { var Widget1, Widget2, ractive; Widget1 = Ractive.extend({ template: 'widget1' }); Widget2 = Ractive.extend({ template: 'widget2' }); ractive = new Ractive({ el: fixture, template: '{{#items}}<widget/>{{/items}}', components: { widget: function( data ) { return data.foo ? Widget1 : Widget2; } }, data: { foo: true, items: [1] } }); t.htmlEqual( fixture.innerHTML, 'widget1' ); ractive.set( 'foo', false ); ractive.push( 'items', 2); t.htmlEqual( fixture.innerHTML, 'widget1widget1', 'Component pinned until reset' ); ractive.reset( ractive.data ); t.htmlEqual( fixture.innerHTML, 'widget2widget2' ); }); test( 'Specify component by function as string', function ( t ) { var Widget, ractive; Widget = Ractive.extend({ template: 'foo' }); ractive = new Ractive({ el: fixture, template: '<widget/>', components: { widget: function( data ) { return 'widget1'; }, widget1: Widget } }); t.htmlEqual( fixture.innerHTML, 'foo' ); }); if ( console && console.warn ) { test( 'no return of component warns in debug', function ( t ) { var ractive, warn = console.warn; expect( 1 ); console.warn = function( msg ) { t.ok( msg ); } ractive = new Ractive({ el: fixture, template: '<widget/>', debug: true, components: { widget: function( data ) { // where's my component? } } }); console.warn = warn; }); } test( '`this` in function refers to ractive instance', function ( t ) { var thisForFoo, thisForBar, ractive, Component; Component = Ractive.extend({}) ractive = new Ractive({ el: fixture, template: '<foo/><widget/>', data: { foo: true }, components: { widget: Ractive.extend({ template: '<bar/>' }), foo: function ( ) { thisForFoo = this; return Component; }, bar: function ( ) { thisForBar = this; return Component; } } }); t.equal( thisForFoo, ractive ); t.equal( thisForBar, ractive ); }); }; });