cycle-react
Advanced tools
Comparing version 1.0.0-beta2 to 1.0.0-beta3
# Changelog | ||
## 1.0.0-beta1 | ||
## 1.0.0-beta3 | ||
@@ -8,4 +8,12 @@ Add feature: `props.getAll()` returns the Observable of the whole properties object | ||
Alternatively, you can use `props.get('*')` or simply `props` for the exact same effect | ||
> Alternatively, you can use `props.get('*')` or simply `props` for the exact same effect. | ||
Breaking change: `props.get(propertyName)` uses (x === y) comparison instead of | ||
deep-equal comparison for the target property | ||
> Use `props.get('*')` or `props` if you have changes inside a nested object. | ||
> Alternatively, you can provide the customized comparer by | ||
`props.get(name, (x, y) => {})` | ||
Breaking change: The `h` hyperscript helper has been removed [#11] | ||
@@ -16,3 +24,3 @@ | ||
The selector API cannot handle both Cycle-React components and other React | ||
> The selector API cannot handle both Cycle-React components and other React | ||
components that use event handlers. We want to keep the compatibility with | ||
@@ -22,3 +30,3 @@ the original React while not confusing developers. Therefore, the selector API | ||
The migration guide can be found below. | ||
> The migration guide can be found below. | ||
@@ -25,0 +33,0 @@ Breaking change: `on` prefix is no longer appended to event handlers for |
@@ -11,4 +11,6 @@ # Working with React | ||
DOM `dispatchEvent` and selector API is used for the original Cycle.js' DOM | ||
driver. However, React components use event handler(from props) instead of | ||
If you have used Cycle.js (not Cycle-React), you must have known that DOM | ||
`dispatchEvent` and the selector API are used for dealing with interactions | ||
for the [Cycle Web driver](http://cycle.js.org/basic-examples.html). | ||
However, React components use event handler(from props) instead of | ||
DOM events for child-parent communications. As a result, Cycle-React provides | ||
@@ -15,0 +17,0 @@ interactions API that different from Cycle.js for receiving these events. You |
{ | ||
"name": "cycle-react", | ||
"version": "1.0.0-beta2", | ||
"version": "1.0.0-beta3", | ||
"author": { | ||
@@ -32,2 +32,3 @@ "name": "pH200", | ||
"devDependencies": { | ||
"@cycle/core": "^1.0.0", | ||
"babel": "^5.6.4", | ||
@@ -37,3 +38,5 @@ "babelify": "^6.1.2", | ||
"cheerio": "~0.19.0", | ||
"express": "^4.12.3", | ||
"cuid": "^1.2.5", | ||
"express": "^4.13.0", | ||
"immutable": "^3.7.4", | ||
"jscs": "^1.13.1", | ||
@@ -40,0 +43,0 @@ "jshint": "^2.8.0", |
'use strict'; | ||
var Rx = require('rx'); | ||
function isEqual(x, y) { | ||
return x === y; | ||
} | ||
function makeEmptyPropsObservable() { | ||
@@ -21,9 +25,5 @@ var empty = Rx.Observable.empty(); | ||
} | ||
var prop$ = propsSubject$.map(function mapProp(p) { | ||
return propsSubject$.map(function mapProp(p) { | ||
return p[propName]; | ||
}); | ||
if (comparer) { | ||
return prop$.distinctUntilChanged(Rx.helpers.identity, comparer); | ||
} | ||
return prop$.distinctUntilChanged(); | ||
}).distinctUntilChanged(Rx.helpers.identity, comparer || isEqual); | ||
}; | ||
@@ -30,0 +30,0 @@ propsSubject$.getAll = function getAllProps() { |
'use strict'; | ||
/* global describe, it, beforeEach */ | ||
let assert = require('assert'); | ||
let run = require('./lib/run'); | ||
let {run} = require('@cycle/core'); | ||
let Cycle = require('../../src/cycle'); | ||
@@ -6,0 +6,0 @@ let {Rx, React} = Cycle; |
'use strict'; | ||
require('../common/cycle.js'); | ||
require('./custom-elements.js'); | ||
require('./component.js'); | ||
require('./render.js'); | ||
require('./driver.js'); |
@@ -15,3 +15,3 @@ 'use strict'; | ||
describe('Rendering', function () { | ||
describe('Cycle.applyToDOM', function () { | ||
beforeEach(function () { | ||
@@ -26,153 +26,151 @@ let testDivs = Array.prototype.slice.call(document.querySelectorAll('.cycletest')); | ||
describe('Cycle.applyToDOM', function () { | ||
it('should accept a DOM element as input', function () { | ||
let element = createRenderTarget(); | ||
assert.doesNotThrow(function () { | ||
Cycle.applyToDOM(element, () => Rx.Observable.empty()); | ||
}); | ||
it('should accept a DOM element as input', function () { | ||
let element = createRenderTarget(); | ||
assert.doesNotThrow(function () { | ||
Cycle.applyToDOM(element, () => Rx.Observable.empty()); | ||
}); | ||
}); | ||
it('should accept a string selector to an existing element as input', function () { | ||
let id = 'testShouldAcceptSelectorToExisting'; | ||
let element = createRenderTarget(); | ||
element.id = id; | ||
assert.doesNotThrow(function () { | ||
Cycle.applyToDOM('#' + id, () => Rx.Observable.empty()); | ||
}); | ||
it('should accept a string selector to an existing element as input', function () { | ||
let id = 'testShouldAcceptSelectorToExisting'; | ||
let element = createRenderTarget(); | ||
element.id = id; | ||
assert.doesNotThrow(function () { | ||
Cycle.applyToDOM('#' + id, () => Rx.Observable.empty()); | ||
}); | ||
}); | ||
it('should not accept a selector to an unknown element as input', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM('#nonsenseIdToNothing', () => Rx.Observable.empty()); | ||
}, /Cannot render into unknown element/); | ||
}); | ||
it('should not accept a selector to an unknown element as input', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM('#nonsenseIdToNothing', () => Rx.Observable.empty()); | ||
}, /Cannot render into unknown element/); | ||
}); | ||
it('should throw if definitionFn returns bad output', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM(createRenderTarget(), () => ({})); | ||
}, /definitionFn given to/); | ||
}); | ||
it('should not accept a number as input', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM(123); | ||
}, /Given container is not a DOM element neither a selector string/); | ||
}); | ||
it('should not accept a number as input', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM(123); | ||
}, /Given container is not a DOM element neither a selector string/); | ||
it('should throw if definitionFn returns bad output', function () { | ||
assert.throws(function () { | ||
Cycle.applyToDOM(createRenderTarget(), () => ({})); | ||
}, /definitionFn given to/); | ||
}); | ||
it('should convert a simple <select> to DOM element', function () { | ||
let vtree$ = Rx.Observable.just( | ||
<select className="my-class"> | ||
<option value="foo">Foo</option> | ||
<option value="bar">Bar</option> | ||
<option value="baz">Baz</option> | ||
</select> | ||
); | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
let selectEl = document.querySelector('.my-class'); | ||
assert.notStrictEqual(selectEl, null); | ||
assert.notStrictEqual(typeof selectEl, 'undefined'); | ||
assert.strictEqual(selectEl.tagName, 'SELECT'); | ||
}); | ||
it('should accept a ReactClass as definitionFn', function () { | ||
let MyElement = Cycle.component('MyElement', function () { | ||
return Rx.Observable.just(<h3 className="myelementclass" />); | ||
}); | ||
Cycle.applyToDOM(createRenderTarget(), MyElement); | ||
let myelement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement, null); | ||
assert.notStrictEqual(typeof myelement, 'undefined'); | ||
assert.strictEqual(myelement.tagName, 'H3'); | ||
}); | ||
it('should convert a simple virtual-dom <select> to DOM element', function () { | ||
it('should catch interaction events', function (done) { | ||
function computer(interactions) { | ||
interactions.get('click').subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.innerHTML, 'Foobar'); | ||
done(); | ||
}); | ||
let vtree$ = Rx.Observable.just( | ||
<select className="my-class"> | ||
<option value="foo">Foo</option> | ||
<option value="bar">Bar</option> | ||
<option value="baz">Baz</option> | ||
</select> | ||
<h3 className="myelementclass" | ||
onClick={interactions.listener('click')}> | ||
Foobar | ||
</h3> | ||
); | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
let selectEl = document.querySelector('.my-class'); | ||
assert.notStrictEqual(selectEl, null); | ||
assert.notStrictEqual(typeof selectEl, 'undefined'); | ||
assert.strictEqual(selectEl.tagName, 'SELECT'); | ||
return vtree$; | ||
} | ||
Cycle.applyToDOM(createRenderTarget(), computer); | ||
// Make assertions | ||
let myElement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.tagName, 'H3'); | ||
assert.doesNotThrow(function () { | ||
myElement.click(); | ||
}); | ||
}); | ||
it('should accept a ReactClass as definitionFn', function () { | ||
let MyElement = Cycle.component('MyElement', function () { | ||
return Rx.Observable.just(<h3 className="myelementclass" />); | ||
it('should catch events from the inner span', function (done) { | ||
function computer(interactions) { | ||
interactions.get('spanClick').subscribe(function (ev) { | ||
assert.ok(/Wrapped by span/.test(ev.target.textContent)); | ||
done(); | ||
}); | ||
Cycle.applyToDOM(createRenderTarget(), MyElement); | ||
let myelement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement, null); | ||
assert.notStrictEqual(typeof myelement, 'undefined'); | ||
assert.strictEqual(myelement.tagName, 'H3'); | ||
return Rx.Observable.just( | ||
<div className="wrapperDiv" | ||
onClick={interactions.listener('spanClick')}> | ||
Wrapped by span | ||
<div className="innerDiv">Wrapped by div</div> | ||
</div> | ||
); | ||
} | ||
Cycle.applyToDOM(createRenderTarget(), computer); | ||
let span = document.querySelector('.wrapperDiv > *:first-child'); | ||
assert.notStrictEqual(span, null); | ||
assert.notStrictEqual(typeof span, 'undefined'); | ||
assert.strictEqual(span.tagName, 'SPAN'); | ||
assert.doesNotThrow(function () { | ||
span.click(); | ||
}); | ||
}) | ||
it('should catch interaction events', function (done) { | ||
function computer(interactions) { | ||
interactions.get('click').subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.innerHTML, 'Foobar'); | ||
done(); | ||
}); | ||
let vtree$ = Rx.Observable.just( | ||
<h3 className="myelementclass" | ||
onClick={interactions.listener('click')}> | ||
Foobar | ||
</h3> | ||
); | ||
return vtree$; | ||
} | ||
Cycle.applyToDOM(createRenderTarget(), computer); | ||
// Make assertions | ||
let myElement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.tagName, 'H3'); | ||
assert.doesNotThrow(function () { | ||
myElement.click(); | ||
}); | ||
}); | ||
it('should not set props.className to the root element', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let vtree$ = Rx.Observable.just(<MyElement className="ERR" />); | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
// Make assertions | ||
let myElement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.className.indexOf('ERR'), -1); | ||
}); | ||
it('should catch events from the inner span', function (done) { | ||
function computer(interactions) { | ||
interactions.get('spanClick').subscribe(function (ev) { | ||
assert.ok(/Wrapped by span/.test(ev.target.innerText)); | ||
done(); | ||
}); | ||
return Rx.Observable.just( | ||
<div className="wrapperDiv" | ||
onClick={interactions.listener('spanClick')}> | ||
Wrapped by span | ||
<div className="innerDiv">Wrapped by div</div> | ||
</div> | ||
); | ||
} | ||
Cycle.applyToDOM(createRenderTarget(), computer); | ||
let span = document.querySelector('.wrapperDiv > *:first-child'); | ||
assert.notStrictEqual(span, null); | ||
assert.notStrictEqual(typeof span, 'undefined'); | ||
assert.strictEqual(span.tagName, 'SPAN'); | ||
assert.doesNotThrow(function () { | ||
span.click(); | ||
}); | ||
}) | ||
it('should accept a view wrapping a custom element (#89)', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let number$ = Fixture89.makeModelNumber$(); | ||
let vtree$ = Fixture89.viewWithContainerFn(number$, MyElement); | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
it('should not set props.className to the root element', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let vtree$ = Rx.Observable.just(<MyElement className="ERR" />); | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
// Make assertions | ||
let myElement = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.className.indexOf('ERR'), -1); | ||
}); | ||
number$.request(1); | ||
let myelement1 = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement1, null); | ||
assert.strictEqual(myelement1.tagName, 'H3'); | ||
assert.strictEqual(myelement1.innerHTML, '123'); | ||
it('should accept a view wrapping a custom element (#89)', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let number$ = Fixture89.makeModelNumber$(); | ||
let vtree$ = Fixture89.viewWithContainerFn(number$, MyElement); | ||
number$.request(1); | ||
let myelement2 = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement2, null); | ||
assert.strictEqual(myelement2.tagName, 'H3'); | ||
assert.strictEqual(myelement2.innerHTML, '456'); | ||
}); | ||
it('should accept a view with custom element as the root of vtree$', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let number$ = Fixture89.makeModelNumber$(); | ||
let vtree$ = Fixture89.viewWithoutContainerFn(number$, MyElement); | ||
assert.doesNotThrow(() => { | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
number$.request(1); | ||
let myelement1 = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement1, null); | ||
assert.strictEqual(myelement1.tagName, 'H3'); | ||
assert.strictEqual(myelement1.innerHTML, '123'); | ||
number$.request(1); | ||
let myelement2 = document.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myelement2, null); | ||
assert.strictEqual(myelement2.tagName, 'H3'); | ||
assert.strictEqual(myelement2.innerHTML, '456'); | ||
}); | ||
it('should accept a view with custom element as the root of vtree$', function () { | ||
let MyElement = Cycle.component('MyElement', Fixture89.myelement); | ||
let number$ = Fixture89.makeModelNumber$(); | ||
let vtree$ = Fixture89.viewWithoutContainerFn(number$, MyElement); | ||
assert.doesNotThrow(() => { | ||
Cycle.applyToDOM(createRenderTarget(), () => vtree$); | ||
number$.request(1); | ||
}); | ||
}); | ||
}); | ||
}); |
3237426
15
43
62200