aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/browser/components.js19
-rw-r--r--test/browser/context.js32
-rw-r--r--test/browser/devtools.js234
-rw-r--r--test/browser/lifecycle.js4
-rw-r--r--test/browser/linked-state.js20
-rw-r--r--test/browser/refs.js22
-rw-r--r--test/browser/spec.js11
-rw-r--r--test/karma.conf.js55
-rw-r--r--test/polyfills.js5
-rw-r--r--test/shared/h.js53
10 files changed, 378 insertions, 77 deletions
diff --git a/test/browser/components.js b/test/browser/components.js
index b4649a719..9ef43cb1c 100644
--- a/test/browser/components.js
+++ b/test/browser/components.js
@@ -70,7 +70,7 @@ describe('Components', () => {
expect(C3)
.to.have.been.calledOnce
- .and.to.have.been.calledWith(PROPS)
+ .and.to.have.been.calledWithMatch(PROPS)
.and.to.have.returned(sinon.match({
nodeName: 'div',
attributes: PROPS
@@ -197,7 +197,7 @@ describe('Components', () => {
expect(Outer)
.to.have.been.calledOnce
- .and.to.have.been.calledWith(PROPS)
+ .and.to.have.been.calledWithMatch(PROPS)
.and.to.have.returned(sinon.match({
nodeName: Inner,
attributes: PROPS
@@ -205,7 +205,7 @@ describe('Components', () => {
expect(Inner)
.to.have.been.calledOnce
- .and.to.have.been.calledWith(PROPS)
+ .and.to.have.been.calledWithMatch(PROPS)
.and.to.have.returned(sinon.match({
nodeName: 'div',
attributes: PROPS,
@@ -247,7 +247,7 @@ describe('Components', () => {
expect(Inner).to.have.been.calledTwice;
expect(Inner.secondCall)
- .to.have.been.calledWith({ foo:'bar', i:2 })
+ .to.have.been.calledWithMatch({ foo:'bar', i:2 })
.and.to.have.returned(sinon.match({
attributes: {
j: 2,
@@ -269,7 +269,7 @@ describe('Components', () => {
expect(Inner).to.have.been.calledThrice;
expect(Inner.thirdCall)
- .to.have.been.calledWith({ foo:'bar', i:3 })
+ .to.have.been.calledWithMatch({ foo:'bar', i:3 })
.and.to.have.returned(sinon.match({
attributes: {
j: 3,
@@ -344,7 +344,7 @@ describe('Components', () => {
expect(Inner.prototype.render).to.have.been.calledTwice;
expect(Inner.prototype.render.secondCall)
- .to.have.been.calledWith({ foo:'bar', i:2 })
+ .to.have.been.calledWithMatch({ foo:'bar', i:2 })
.and.to.have.returned(sinon.match({
attributes: {
j: 2,
@@ -372,7 +372,7 @@ describe('Components', () => {
expect(Inner.prototype.render).to.have.been.calledThrice;
expect(Inner.prototype.render.thirdCall)
- .to.have.been.calledWith({ foo:'bar', i:3 })
+ .to.have.been.calledWithMatch({ foo:'bar', i:3 })
.and.to.have.returned(sinon.match({
attributes: {
j: 3,
@@ -435,7 +435,7 @@ describe('Components', () => {
expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
expect(Inner.prototype.componentWillMount).to.have.been.calledBefore(Inner.prototype.componentDidMount);
- root = render(<asdf />, scratch, root);
+ render(<asdf />, scratch, root);
expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce;
expect(Inner.prototype.componentDidUnmount).to.have.been.calledOnce;
@@ -689,8 +689,7 @@ describe('Components', () => {
expect(C1.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C1').not.to.have.been.called;
expect(C2.prototype.componentDidUnmount, 'unmount innermost w/ intermediary div, C2 ummount').not.to.have.been.called;
- // @TODO this was just incorrect?
- // expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C2').not.to.have.been.called;
+ expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C2').not.to.have.been.called;
expect(C3.prototype.componentDidUnmount, 'unmount innermost w/ intermediary div, C3').to.have.been.calledOnce;
reset();
diff --git a/test/browser/context.js b/test/browser/context.js
index e62a948a4..ed5f81471 100644
--- a/test/browser/context.js
+++ b/test/browser/context.js
@@ -1,6 +1,8 @@
import { h, render, Component } from '../../src/preact';
/** @jsx h */
+const CHILDREN_MATCHER = sinon.match( v => v==null || Array.isArray(v) && !v.length , '[empty children]');
+
describe('context', () => {
let scratch;
@@ -57,18 +59,19 @@ describe('context', () => {
expect(Outer.prototype.getChildContext).to.have.been.calledOnce;
// initial render does not invoke anything but render():
- expect(Inner.prototype.render).to.have.been.calledWith({}, {}, CONTEXT);
+ expect(Inner.prototype.render).to.have.been.calledWith({ children:CHILDREN_MATCHER }, {}, CONTEXT);
CONTEXT.foo = 'bar';
render(<Outer {...PROPS} />, scratch, scratch.lastChild);
expect(Outer.prototype.getChildContext).to.have.been.calledTwice;
- expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(PROPS, {}, CONTEXT);
- expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(PROPS, CONTEXT);
- expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(PROPS, {});
- expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({}, {});
- expect(Inner.prototype.render).to.have.been.calledWith(PROPS, {}, CONTEXT);
+ let props = { children: CHILDREN_MATCHER, ...PROPS };
+ expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(props, {}, CONTEXT);
+ expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(props, CONTEXT);
+ expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(props, {});
+ expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({ children:CHILDREN_MATCHER }, {});
+ expect(Inner.prototype.render).to.have.been.calledWith(props, {}, CONTEXT);
/* Future:
@@ -115,18 +118,19 @@ describe('context', () => {
expect(Outer.prototype.getChildContext).to.have.been.calledOnce;
// initial render does not invoke anything but render():
- expect(Inner.prototype.render).to.have.been.calledWith({}, {}, CONTEXT);
+ expect(Inner.prototype.render).to.have.been.calledWith({ children: CHILDREN_MATCHER }, {}, CONTEXT);
CONTEXT.foo = 'bar';
render(<Outer {...PROPS} />, scratch, scratch.lastChild);
expect(Outer.prototype.getChildContext).to.have.been.calledTwice;
- expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(PROPS, {}, CONTEXT);
- expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(PROPS, CONTEXT);
- expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(PROPS, {});
- expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({}, {});
- expect(Inner.prototype.render).to.have.been.calledWith(PROPS, {}, CONTEXT);
+ let props = { children: CHILDREN_MATCHER, ...PROPS };
+ expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(props, {}, CONTEXT);
+ expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(props, CONTEXT);
+ expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(props, {});
+ expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({ children: CHILDREN_MATCHER }, {});
+ expect(Inner.prototype.render).to.have.been.calledWith(props, {}, CONTEXT);
// make sure render() could make use of context.a
expect(Inner.prototype.render).to.have.returned(sinon.match({ children:['a'] }));
@@ -164,7 +168,7 @@ describe('context', () => {
render(<Outer />, scratch);
- expect(Inner.prototype.render).to.have.been.calledWith({}, {}, { outerContext });
- expect(InnerMost.prototype.render).to.have.been.calledWith({}, {}, { outerContext, innerContext });
+ expect(Inner.prototype.render).to.have.been.calledWith({ children: CHILDREN_MATCHER }, {}, { outerContext });
+ expect(InnerMost.prototype.render).to.have.been.calledWith({ children: CHILDREN_MATCHER }, {}, { outerContext, innerContext });
});
});
diff --git a/test/browser/devtools.js b/test/browser/devtools.js
new file mode 100644
index 000000000..12c0e3369
--- /dev/null
+++ b/test/browser/devtools.js
@@ -0,0 +1,234 @@
+import { h, Component, render } from '../../src/preact';
+import { initDevTools } from '../../devtools/devtools';
+import { unmountComponent } from '../../src/vdom/component';
+
+class StatefulComponent extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {count: 0};
+ }
+
+ render() {
+ return h('span', {}, String(this.state.count));
+ }
+}
+
+function FunctionalComponent() {
+ return h('span', {class: 'functional'}, 'Functional');
+}
+
+function Label({label}) {
+ return label;
+}
+
+class MultiChild extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {count: props.initialCount};
+ }
+
+ render() {
+ return h('div', {}, Array(this.state.count).fill('child'));
+ }
+}
+
+let describe_ = describe;
+if (!('name' in Function.prototype)) {
+ // Skip these tests under Internet Explorer
+ describe_ = describe.skip;
+}
+
+describe_('React Developer Tools integration', () => {
+ let cleanup;
+ let container;
+ let renderer;
+
+ // Maps of DOM node to React*Component-like objects.
+ // For composite components, there will be two instances for each node, one
+ // for the composite component (instanceMap) and one for the root child DOM
+ // component rendered by that component (domInstanceMap)
+ let instanceMap = new Map();
+ let domInstanceMap = new Map();
+
+ beforeEach(() => {
+ container = document.createElement('div');
+ document.body.appendChild(container);
+
+ const onMount = instance => {
+ if (instance._renderedChildren) {
+ domInstanceMap.set(instance.node, instance);
+ } else {
+ instanceMap.set(instance.node, instance);
+ }
+ };
+
+ const onUnmount = instance => {
+ instanceMap.delete(instance.node);
+ domInstanceMap.delete(instance.node);
+ };
+
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
+ inject: sinon.spy(_renderer => {
+ renderer = _renderer;
+ renderer.Mount._renderNewRootComponent = sinon.stub();
+ renderer.Reconciler.mountComponent = sinon.spy(onMount);
+ renderer.Reconciler.unmountComponent = sinon.spy(onUnmount);
+ renderer.Reconciler.receiveComponent = sinon.stub();
+ })
+ };
+ cleanup = initDevTools();
+ });
+
+ afterEach(() => {
+ container.remove();
+ cleanup();
+ });
+
+ it('registers preact as a renderer with the React DevTools hook', () => {
+ expect(global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject).to.be.called;
+ });
+
+ // Basic component addition/update/removal tests
+ it('notifies dev tools about new components', () => {
+ render(h(StatefulComponent), container);
+ expect(renderer.Reconciler.mountComponent).to.be.called;
+ });
+
+ it('notifies dev tools about component updates', () => {
+ const node = render(h(StatefulComponent), container);
+ node._component.forceUpdate();
+ expect(renderer.Reconciler.receiveComponent).to.be.called;
+ });
+
+ it('notifies dev tools when components are removed', () => {
+ const node = render(h(StatefulComponent), container);
+ unmountComponent(node._component, true);
+ expect(renderer.Reconciler.unmountComponent).to.be.called;
+ });
+
+ // Test properties of DOM components exposed to devtools via
+ // ReactDOMComponent-like instances
+ it('exposes the tag name of DOM components', () => {
+ const node = render(h(StatefulComponent), container);
+ const domInstance = domInstanceMap.get(node);
+ expect(domInstance._currentElement.type).to.equal('span');
+ });
+
+ it('exposes DOM component props', () => {
+ const node = render(h(FunctionalComponent), container);
+ const domInstance = domInstanceMap.get(node);
+ expect(domInstance._currentElement.props.class).to.equal('functional');
+ });
+
+ it('exposes text component contents', () => {
+ const node = render(h(Label, {label: 'Text content'}), container);
+ const textInstance = domInstanceMap.get(node);
+ expect(textInstance._stringText).to.equal('Text content');
+ });
+
+ // Test properties of composite components exposed to devtools via
+ // ReactCompositeComponent-like instances
+ it('exposes the name of composite component classes', () => {
+ const node = render(h(StatefulComponent), container);
+ expect(instanceMap.get(node).getName()).to.equal('StatefulComponent');
+ });
+
+ it('exposes composite component props', () => {
+ const node = render(h(Label, {label: 'Text content'}), container);
+ const instance = instanceMap.get(node);
+ expect(instance._currentElement.props.label).to.equal('Text content');
+ });
+
+ it('exposes composite component state', () => {
+ const node = render(h(StatefulComponent), container);
+
+ node._component.setState({count: 42});
+ node._component.forceUpdate();
+
+ expect(instanceMap.get(node).state).to.deep.equal({count: 42});
+ });
+
+ // Test setting state via devtools
+ it('updates component when setting state from devtools', () => {
+ const node = render(h(StatefulComponent), container);
+
+ instanceMap.get(node).setState({count: 10});
+ instanceMap.get(node).forceUpdate();
+
+ expect(node.textContent).to.equal('10');
+ });
+
+ // Test that the original instance is exposed via `_instance` so it can
+ // be accessed conveniently via `$r` in devtools
+
+ // Functional component handling tests
+ it('wraps functional components with stateful ones', () => {
+ const vnode = h(FunctionalComponent);
+ expect(vnode.nodeName.prototype).to.have.property('render');
+ });
+
+ it('exposes the name of functional components', () => {
+ const node = render(h(FunctionalComponent), container);
+ const instance = instanceMap.get(node);
+ expect(instance.getName()).to.equal('FunctionalComponent');
+ });
+
+ it('exposes a fallback name if the component has no useful name', () => {
+ const node = render(h(() => h('div')), container);
+ const instance = instanceMap.get(node);
+ expect(instance.getName()).to.equal('(Function.name missing)');
+ });
+
+ // Test handling of DOM children
+ it('notifies dev tools about DOM children', () => {
+ const node = render(h(StatefulComponent), container);
+ const domInstance = domInstanceMap.get(node);
+ expect(renderer.Reconciler.mountComponent).to.have.been.calledWith(domInstance);
+ });
+
+ it('notifies dev tools when a component update adds DOM children', () => {
+ const node = render(h(MultiChild, {initialCount: 2}), container);
+
+ node._component.setState({count: 4});
+ node._component.forceUpdate();
+
+ expect(renderer.Reconciler.mountComponent).to.have.been.called.twice;
+ });
+
+ it('notifies dev tools when a component update modifies DOM children', () => {
+ const node = render(h(StatefulComponent), container);
+
+ instanceMap.get(node).setState({count: 10});
+ instanceMap.get(node).forceUpdate();
+
+ const textInstance = domInstanceMap.get(node.childNodes[0]);
+ expect(textInstance._stringText).to.equal('10');
+ });
+
+ it('notifies dev tools when a component update removes DOM children', () => {
+ const node = render(h(MultiChild, {initialCount: 1}), container);
+
+ node._component.setState({count: 0});
+ node._component.forceUpdate();
+
+ expect(renderer.Reconciler.unmountComponent).to.be.called;
+ });
+
+ // Root component info
+ it('exposes root components on the _instancesByReactRootID map', () => {
+ render(h(StatefulComponent), container);
+ expect(Object.keys(renderer.Mount._instancesByReactRootID).length).to.equal(1);
+ });
+
+ it('notifies dev tools when new root components are mounted', () => {
+ render(h(StatefulComponent), container);
+ expect(renderer.Mount._renderNewRootComponent).to.be.called;
+ });
+
+ it('removes root components when they are unmounted', () => {
+ const node = render(h(StatefulComponent), container);
+ unmountComponent(node._component, true);
+ expect(Object.keys(renderer.Mount._instancesByReactRootID).length).to.equal(0);
+ });
+});
diff --git a/test/browser/lifecycle.js b/test/browser/lifecycle.js
index d6204ca8f..4deb92163 100644
--- a/test/browser/lifecycle.js
+++ b/test/browser/lifecycle.js
@@ -3,6 +3,8 @@ import { h, render, rerender, Component } from '../../src/preact';
let spyAll = obj => Object.keys(obj).forEach( key => sinon.spy(obj,key) );
+const EMPTY_CHILDREN = [];
+
describe('Lifecycle methods', () => {
let scratch;
@@ -50,7 +52,7 @@ describe('Lifecycle methods', () => {
}
class Inner extends Component {
componentWillUpdate(nextProps, nextState) {
- expect(nextProps).to.be.deep.equal({i: 1});
+ expect(nextProps).to.be.deep.equal({ children:EMPTY_CHILDREN, i: 1 });
expect(nextState).to.be.deep.equal({});
}
render() {
diff --git a/test/browser/linked-state.js b/test/browser/linked-state.js
index 1ca84cdc6..03db2a7b8 100644
--- a/test/browser/linked-state.js
+++ b/test/browser/linked-state.js
@@ -26,7 +26,10 @@ describe('linked-state', () => {
element.type= 'text';
element.value = 'newValue';
- linkFunction({ currentTarget: element });
+ linkFunction({
+ currentTarget: element,
+ target: element
+ });
expect(TestComponent.prototype.setState).to.have.been.calledOnce;
expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': 'newValue'});
@@ -42,7 +45,10 @@ describe('linked-state', () => {
checkboxElement.type= 'checkbox';
checkboxElement.checked = true;
- linkFunction({ currentTarget: checkboxElement });
+ linkFunction({
+ currentTarget: checkboxElement,
+ target: checkboxElement
+ });
expect(TestComponent.prototype.setState).to.have.been.calledOnce;
expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': true});
@@ -53,7 +59,10 @@ describe('linked-state', () => {
radioElement.type= 'radio';
radioElement.checked = true;
- linkFunction({ currentTarget: radioElement });
+ linkFunction({
+ currentTarget: radioElement,
+ target: radioElement
+ });
expect(TestComponent.prototype.setState).to.have.been.calledOnce;
expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': true});
@@ -66,7 +75,10 @@ describe('linked-state', () => {
element.type= 'text';
element.value = 'newValue';
- linkFunction({ currentTarget: element });
+ linkFunction({
+ currentTarget: element,
+ target: element
+ });
expect(TestComponent.prototype.setState).to.have.been.calledOnce;
expect(TestComponent.prototype.setState).to.have.been.calledWith({nested: {state: {key: 'newValue'}}});
diff --git a/test/browser/refs.js b/test/browser/refs.js
index 89678b76e..337a9717b 100644
--- a/test/browser/refs.js
+++ b/test/browser/refs.js
@@ -200,8 +200,8 @@ describe('refs', () => {
</div>
), scratch);
- expect(Foo.prototype.render).to.have.been.calledWithExactly({ a:'a' }, { }, { });
- expect(Bar).to.have.been.calledWithExactly({ b:'b', ref:bar }, { });
+ expect(Foo.prototype.render).to.have.been.calledWithMatch({ ref:sinon.match.falsy, a:'a' }, { }, { });
+ expect(Bar).to.have.been.calledWithMatch({ b:'b', ref:bar }, { });
});
// Test for #232
@@ -284,4 +284,22 @@ describe('refs', () => {
expect(inst.handleMount.firstCall).to.have.been.calledWith(null);
expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#div'));
});
+
+
+ it('should add refs to components representing DOM nodes with no attributes if they have been pre-rendered', () => {
+ // Simulate pre-render
+ let parent = document.createElement('div');
+ let child = document.createElement('div');
+ parent.appendChild(child);
+ scratch.appendChild(parent); // scratch contains: <div><div></div></div>
+
+ let ref = spy('ref');
+
+ function Wrapper() {
+ return <div></div>;
+ }
+
+ render(<div><Wrapper ref={ref} /></div>, scratch, scratch.firstChild);
+ expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild.firstChild);
+ });
});
diff --git a/test/browser/spec.js b/test/browser/spec.js
index eb48151f0..d33cdb93f 100644
--- a/test/browser/spec.js
+++ b/test/browser/spec.js
@@ -1,6 +1,8 @@
import { h, render, rerender, Component } from '../../src/preact';
/** @jsx h */
+const EMPTY_CHILDREN = [];
+
describe('Component spec', () => {
let scratch;
@@ -24,6 +26,7 @@ describe('Component spec', () => {
constructor(props, context) {
super(props, context);
expect(props).to.be.deep.equal({
+ children: EMPTY_CHILDREN,
fieldA: 1, fieldB: 2,
fieldC: 1, fieldD: 2
});
@@ -81,14 +84,14 @@ describe('Component spec', () => {
fieldC: 1, fieldD: 2
};
- expect(proto.ctor).to.have.been.calledWith(PROPS1);
- expect(proto.render).to.have.been.calledWith(PROPS1);
+ expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
+ expect(proto.render).to.have.been.calledWithMatch(PROPS1);
rerender();
// expect(proto.ctor).to.have.been.calledWith(PROPS2);
- expect(proto.componentWillReceiveProps).to.have.been.calledWith(PROPS2);
- expect(proto.render).to.have.been.calledWith(PROPS2);
+ expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(PROPS2);
+ expect(proto.render).to.have.been.calledWithMatch(PROPS2);
});
// @TODO: migrate this to preact-compat
diff --git a/test/karma.conf.js b/test/karma.conf.js
index 6ed5397fb..3236e944b 100644
--- a/test/karma.conf.js
+++ b/test/karma.conf.js
@@ -1,45 +1,66 @@
/*eslint no-var:0, object-shorthand:0 */
var coverage = String(process.env.COVERAGE)!=='false',
- sauceLabs = String(process.env.SAUCELABS).match(/^(1|true)$/gi) && !String(process.env.TRAVIS_PULL_REQUEST).match(/^(1|true)$/gi),
- performance = !coverage && !sauceLabs && String(process.env.PERFORMANCE)!=='false',
+ ci = String(process.env.CI).match(/^(1|true)$/gi),
+ pullRequest = !String(process.env.TRAVIS_PULL_REQUEST).match(/^(0|false|undefined)$/gi),
+ realBrowser = String(process.env.BROWSER).match(/^(1|true)$/gi),
+ sauceLabs = realBrowser && ci && !pullRequest,
+ performance = !coverage && !realBrowser && String(process.env.PERFORMANCE)!=='false',
webpack = require('webpack');
var sauceLabsLaunchers = {
sl_chrome: {
base: 'SauceLabs',
- browserName: 'chrome'
+ browserName: 'chrome',
+ platform: 'Windows 10'
},
sl_firefox: {
base: 'SauceLabs',
- browserName: 'firefox'
+ browserName: 'firefox',
+ platform: 'Windows 10'
},
- sl_ios_safari: {
+ sl_safari: {
base: 'SauceLabs',
- browserName: 'iphone',
- platform: 'OS X 10.9',
- version: '7.1'
+ browserName: 'safari',
+ platform: 'OS X 10.11'
+ },
+ sl_edge: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ platform: 'Windows 10'
},
sl_ie_11: {
base: 'SauceLabs',
browserName: 'internet explorer',
- version: '11'
+ version: '11.103',
+ platform: 'Windows 10'
},
sl_ie_10: {
base: 'SauceLabs',
browserName: 'internet explorer',
- version: '10'
+ version: '10.0',
+ platform: 'Windows 7'
},
sl_ie_9: {
base: 'SauceLabs',
browserName: 'internet explorer',
- version: '9'
+ version: '9.0',
+ platform: 'Windows 7'
}
};
+var travisLaunchers = {
+ chrome_travis: {
+ base: 'Chrome',
+ flags: ['--no-sandbox']
+ }
+};
+
+var localBrowsers = realBrowser ? Object.keys(travisLaunchers) : ['PhantomJS'];
+
module.exports = function(config) {
config.set({
- browsers: sauceLabs ? Object.keys(sauceLabsLaunchers) : ['PhantomJS'],
+ browsers: sauceLabs ? Object.keys(sauceLabsLaunchers) : localBrowsers,
frameworks: ['source-map-support', 'mocha', 'chai-sinon'],
@@ -69,14 +90,18 @@ module.exports = function(config) {
browserNoActivityTimeout: 5 * 60 * 1000,
+ // Use only two browsers concurrently, works better with open source Sauce Labs remote testing
+ concurrency: 2,
+
// sauceLabs: {
// tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER || ('local'+require('./package.json').version),
// startConnect: false
// },
- customLaunchers: sauceLabsLaunchers,
+ customLaunchers: sauceLabs ? sauceLabsLaunchers : travisLaunchers,
files: [
+ { pattern: 'polyfills.js', watched: false },
{ pattern: '{browser,shared}/**.js', watched: false }
],
@@ -107,6 +132,10 @@ module.exports = function(config) {
} : [])
},
resolve: {
+ // The React DevTools integration requires preact as a module
+ // rather than referencing source files inside the module
+ // directly
+ alias: { preact: '../src/preact' },
modulesDirectories: [__dirname, 'node_modules']
},
plugins: [
diff --git a/test/polyfills.js b/test/polyfills.js
new file mode 100644
index 000000000..51f256e6e
--- /dev/null
+++ b/test/polyfills.js
@@ -0,0 +1,5 @@
+// ES2015 APIs used by developer tools integration
+import 'core-js/es6/map';
+import 'core-js/fn/array/fill';
+import 'core-js/fn/array/from';
+import 'core-js/fn/object/assign';
diff --git a/test/shared/h.js b/test/shared/h.js
index b0cf7f0e8..ae692e3e5 100644
--- a/test/shared/h.js
+++ b/test/shared/h.js
@@ -6,7 +6,12 @@ import { expect } from 'chai';
/** @jsx h */
-let flatten = obj => JSON.parse(JSON.stringify(obj));
+const buildVNode = (nodeName, attributes, children=[]) => ({
+ nodeName,
+ children,
+ attributes,
+ key: attributes && attributes.key
+});
describe('h(jsx)', () => {
it('should return a VNode', () => {
@@ -16,7 +21,7 @@ describe('h(jsx)', () => {
expect(r).to.be.an.instanceof(VNode);
expect(r).to.have.property('nodeName', 'foo');
expect(r).to.have.property('attributes', undefined);
- expect(r).to.have.property('children', undefined);
+ expect(r).to.have.property('children').that.eql([]);
});
it('should perserve raw attributes', () => {
@@ -38,8 +43,8 @@ describe('h(jsx)', () => {
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([
- new VNode('bar'),
- new VNode('baz')
+ buildVNode('bar'),
+ buildVNode('baz')
]);
});
@@ -51,15 +56,13 @@ describe('h(jsx)', () => {
h('baz', null, h('test'))
);
- r = flatten(r);
-
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([
- { nodeName:'bar' },
- { nodeName:'baz', children:[
- { nodeName:'test' }
- ]}
+ buildVNode('bar'),
+ buildVNode('baz', undefined, [
+ buildVNode('test')
+ ])
]);
});
@@ -73,15 +76,13 @@ describe('h(jsx)', () => {
]
);
- r = flatten(r);
-
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([
- { nodeName:'bar' },
- { nodeName:'baz', children:[
- { nodeName:'test' }
- ]}
+ buildVNode('bar'),
+ buildVNode('baz', undefined, [
+ buildVNode('test')
+ ])
]);
});
@@ -95,15 +96,13 @@ describe('h(jsx)', () => {
]
);
- r = flatten(r);
-
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([
- { nodeName:'bar' },
- { nodeName:'baz', children:[
- { nodeName:'test' }
- ]}
+ buildVNode('bar'),
+ buildVNode('baz', undefined, [
+ buildVNode('test')
+ ])
]);
});
@@ -164,16 +163,14 @@ describe('h(jsx)', () => {
'six'
);
- r = flatten(r);
-
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([
'onetwo',
- { nodeName:'bar' },
+ buildVNode('bar'),
'three',
- { nodeName:'baz' },
- { nodeName:'baz' },
+ buildVNode('baz'),
+ buildVNode('baz'),
'fourfivesix'
]);
});
@@ -190,8 +187,6 @@ describe('h(jsx)', () => {
null
);
- r = flatten(r);
-
expect(r).to.be.an('object')
.with.property('children')
.that.deep.equals([