aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-11-08 15:07:07 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-11-08 15:19:39 +0100
commitafb9fba64be1f15a3ce3ed31214a704e73e5e8bb (patch)
tree6f69712a8c976178c05144483ff0c8e9b09445c8
parentb37e7762bb5492cbd6788863232e7d2634ab5e5c (diff)
parent6e5fb04d3f3f9a6cd43ac20896d73321dd079f96 (diff)
downloadwallet-core-afb9fba64be1f15a3ce3ed31214a704e73e5e8bb.tar.xz
Update preact version
-rw-r--r--lib/vendor/preact.js70
-rw-r--r--thirdparty/preact/.gitignore6
-rw-r--r--thirdparty/preact/.travis.yml8
-rw-r--r--thirdparty/preact/README.md45
-rw-r--r--thirdparty/preact/config/rollup.config.devtools.js20
-rw-r--r--thirdparty/preact/devtools/devtools.js427
-rw-r--r--thirdparty/preact/devtools/index.js4
-rw-r--r--thirdparty/preact/package.json36
-rw-r--r--thirdparty/preact/src/dom/index.js9
-rw-r--r--thirdparty/preact/src/h.js9
-rw-r--r--thirdparty/preact/src/linked-state.js24
-rw-r--r--thirdparty/preact/src/options.js9
-rw-r--r--thirdparty/preact/src/preact.d.ts21
-rw-r--r--thirdparty/preact/src/vdom/component.js27
-rw-r--r--thirdparty/preact/src/vdom/diff.js25
-rw-r--r--thirdparty/preact/src/vdom/index.js7
-rw-r--r--thirdparty/preact/test/browser/components.js19
-rw-r--r--thirdparty/preact/test/browser/context.js32
-rw-r--r--thirdparty/preact/test/browser/devtools.js234
-rw-r--r--thirdparty/preact/test/browser/lifecycle.js4
-rw-r--r--thirdparty/preact/test/browser/linked-state.js20
-rw-r--r--thirdparty/preact/test/browser/refs.js22
-rw-r--r--thirdparty/preact/test/browser/spec.js11
-rw-r--r--thirdparty/preact/test/karma.conf.js55
-rw-r--r--thirdparty/preact/test/polyfills.js5
-rw-r--r--thirdparty/preact/test/shared/h.js53
26 files changed, 1019 insertions, 183 deletions
diff --git a/lib/vendor/preact.js b/lib/vendor/preact.js
index e0239355e..cfab56635 100644
--- a/lib/vendor/preact.js
+++ b/lib/vendor/preact.js
@@ -8,7 +8,7 @@
this.key = attributes && attributes.key;
}
function h(nodeName, attributes) {
- var children, lastSimple, child, simple, i;
+ var lastSimple, child, simple, i, children = [];
for (i = arguments.length; i-- > 2; ) stack.push(arguments[i]);
if (attributes && attributes.children) {
if (!stack.length) stack.push(attributes.children);
@@ -18,7 +18,7 @@
if ('number' == typeof child || child === !0) child = String(child);
simple = 'string' == typeof child;
if (simple && lastSimple) children[children.length - 1] += child; else {
- if (children) children.push(child); else children = [ child ];
+ children.push(child);
lastSimple = simple;
}
}
@@ -55,16 +55,12 @@
return h(vnode.nodeName, extend(clone(vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children);
}
function createLinkedState(component, key, eventPath) {
- var path = key.split('.'), p0 = path[0];
+ var path = key.split('.');
return function(e) {
- var _component$setState;
- var i, t = e && e.currentTarget || this, s = component.state, obj = s, v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? (t.nodeName + t.type).match(/^input(che|rad)/i) ? t.checked : t.value : e;
- if (path.length > 1) {
- for (i = 0; i < path.length - 1; i++) obj = obj[path[i]] || (obj[path[i]] = {});
- obj[path[i]] = v;
- v = s[p0];
- }
- component.setState((_component$setState = {}, _component$setState[p0] = v, _component$setState));
+ var t = e && e.target || this, state = {}, obj = state, v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? t.type.match(/^che|rad/) ? t.checked : t.value : e, i = 0;
+ for (;i < path.length - 1; i++) obj = obj[path[i]] || (obj[path[i]] = !i && component.state[path[i]] || {});
+ obj[path[i]] = v;
+ component.setState(state);
};
}
function enqueueRender(component) {
@@ -91,20 +87,20 @@
return node.normalizedNodeName === nodeName || toLowerCase(node.nodeName) === toLowerCase(nodeName);
}
function getNodeProps(vnode) {
- var defaultProps = vnode.nodeName.defaultProps, props = clone(vnode.attributes);
+ var props = clone(vnode.attributes);
+ props.children = vnode.children;
+ var defaultProps = vnode.nodeName.defaultProps;
if (defaultProps) for (var i in defaultProps) if (void 0 === props[i]) props[i] = defaultProps[i];
- if (vnode.children) props.children = vnode.children;
return props;
}
function removeNode(node) {
var p = node.parentNode;
if (p) p.removeChild(node);
}
- function setAccessor(node, name, value, old, isSvg) {
- node[ATTR_KEY][name] = value;
+ function setAccessor(node, name, old, value, isSvg) {
if ('className' === name) name = 'class';
if ('class' === name && value && 'object' == typeof value) value = hashToClassName(value);
- if ('key' === name || 'children' === name || 'innerHTML' === name) ; else if ('class' === name && !isSvg) node.className = value || ''; else if ('style' === name) {
+ if ('key' === name) ; else if ('class' === name && !isSvg) node.className = value || ''; else if ('style' === name) {
if (!value || isString(value) || isString(old)) node.style.cssText = value || '';
if (value && 'object' == typeof value) {
if (!isString(old)) for (var i in old) if (!(i in value)) node.style[i] = '';
@@ -150,7 +146,10 @@
}
function flushMounts() {
var c;
- while (c = mounts.pop()) if (c.componentDidMount) c.componentDidMount();
+ while (c = mounts.pop()) {
+ if (options.afterMount) options.afterMount(c);
+ if (c.componentDidMount) c.componentDidMount();
+ }
}
function diff(dom, vnode, context, mountAll, parent, componentRoot) {
if (!diffLevel++) isSvgMode = parent instanceof SVGElement;
@@ -166,7 +165,7 @@
if (isString(vnode)) {
if (dom) {
if (dom instanceof Text && dom.parentNode) {
- dom.nodeValue = vnode;
+ if (dom.nodeValue != vnode) dom.nodeValue = vnode;
return dom;
}
recollectNodeTree(dom);
@@ -174,7 +173,7 @@
return document.createTextNode(vnode);
}
if (isFunction(vnode.nodeName)) return buildComponentFromVNode(dom, vnode, context, mountAll);
- var out = dom, nodeName = vnode.nodeName, prevSvgMode = isSvgMode;
+ var out = dom, nodeName = vnode.nodeName, prevSvgMode = isSvgMode, vchildren = vnode.children;
if (!isString(nodeName)) nodeName = String(nodeName);
isSvgMode = 'svg' === nodeName ? !0 : 'foreignObject' === nodeName ? !1 : isSvgMode;
if (!dom) out = createNode(nodeName, isSvgMode); else if (!isNamedNode(dom, nodeName)) {
@@ -182,7 +181,9 @@
while (dom.firstChild) out.appendChild(dom.firstChild);
recollectNodeTree(dom);
}
- if (vnode.children && 1 === vnode.children.length && 'string' == typeof vnode.children[0] && 1 === out.childNodes.length && out.firstChild instanceof Text) out.firstChild.nodeValue = vnode.children[0]; else if (vnode.children || out.firstChild) innerDiffNode(out, vnode.children, context, mountAll);
+ if (vchildren && 1 === vchildren.length && 'string' == typeof vchildren[0] && 1 === out.childNodes.length && out.firstChild instanceof Text) {
+ if (out.firstChild.nodeValue != vchildren[0]) out.firstChild.nodeValue = vchildren[0];
+ } else if (vchildren && vchildren.length || out.firstChild) innerDiffNode(out, vchildren, context, mountAll);
var props = out[ATTR_KEY];
if (!props) {
out[ATTR_KEY] = props = {};
@@ -246,8 +247,8 @@
}
}
function diffAttributes(dom, attrs, old) {
- for (var _name in old) if (!(attrs && _name in attrs) && null != old[_name]) setAccessor(dom, _name, null, old[_name], isSvgMode);
- if (attrs) for (var _name2 in attrs) if (!(_name2 in old) || attrs[_name2] !== ('value' === _name2 || 'checked' === _name2 ? dom[_name2] : old[_name2])) setAccessor(dom, _name2, attrs[_name2], old[_name2], isSvgMode);
+ for (var _name in old) if (!(attrs && _name in attrs) && null != old[_name]) setAccessor(dom, _name, old[_name], old[_name] = void 0, isSvgMode);
+ if (attrs) for (var _name2 in attrs) if (!('children' === _name2 || 'innerHTML' === _name2 || _name2 in old && attrs[_name2] === ('value' === _name2 || 'checked' === _name2 ? dom[_name2] : old[_name2]))) setAccessor(dom, _name2, old[_name2], old[_name2] = attrs[_name2], isSvgMode);
}
function collectComponent(component) {
var name = component.constructor.name, list = components[name];
@@ -325,22 +326,27 @@
}
if (initialBase && base !== initialBase && inst !== initialChildComponent) {
var baseParent = initialBase.parentNode;
- if (baseParent && base !== baseParent) baseParent.replaceChild(base, initialBase);
- if (!cbase && !toUnmount && component._parentComponent) {
- initialBase._component = null;
- recollectNodeTree(initialBase);
+ if (baseParent && base !== baseParent) {
+ baseParent.replaceChild(base, initialBase);
+ if (!toUnmount) {
+ initialBase._component = null;
+ recollectNodeTree(initialBase);
+ }
}
}
if (toUnmount) unmountComponent(toUnmount, base !== initialBase);
component.base = base;
if (base && !isChild) {
var componentRef = component, t = component;
- while (t = t._parentComponent) componentRef = t;
+ while (t = t._parentComponent) (componentRef = t).base = base;
base._component = componentRef;
base._componentConstructor = componentRef.constructor;
}
}
- if (!isUpdate || mountAll) mounts.unshift(component); else if (!skip && component.componentDidUpdate) component.componentDidUpdate(previousProps, previousState, previousContext);
+ if (!isUpdate || mountAll) mounts.unshift(component); else if (!skip) {
+ if (component.componentDidUpdate) component.componentDidUpdate(previousProps, previousState, previousContext);
+ if (options.afterUpdate) options.afterUpdate(component);
+ }
var fn, cb = component._renderCallbacks;
if (cb) while (fn = cb.pop()) fn.call(component);
if (!diffLevel && !isChild) flushMounts();
@@ -358,7 +364,10 @@
dom = oldDom = null;
}
c = createComponent(vnode.nodeName, props, context);
- if (dom && !c.nextBase) c.nextBase = dom;
+ if (dom && !c.nextBase) {
+ c.nextBase = dom;
+ oldDom = null;
+ }
setComponentProps(c, props, 1, context, mountAll);
dom = c.base;
if (oldDom && dom !== oldDom) {
@@ -369,6 +378,7 @@
return dom;
}
function unmountComponent(component, remove) {
+ if (options.beforeUnmount) options.beforeUnmount(component);
var base = component.base;
component._disable = !0;
if (component.componentWillUnmount) component.componentWillUnmount();
@@ -467,4 +477,4 @@
exports.rerender = rerender;
exports.options = options;
});
-//# sourceMappingURL=preact.js.map \ No newline at end of file
+//# sourceMappingURL=preact.js.map
diff --git a/thirdparty/preact/.gitignore b/thirdparty/preact/.gitignore
index 17acf3f20..9fe87e641 100644
--- a/thirdparty/preact/.gitignore
+++ b/thirdparty/preact/.gitignore
@@ -4,5 +4,7 @@
/dist
/_dev
/coverage
-aliases.js
-aliases.js.map
+
+# Additional bundles
+/*.js
+/*.js.map
diff --git a/thirdparty/preact/.travis.yml b/thirdparty/preact/.travis.yml
index 5953c1a84..8a5ff0a0d 100644
--- a/thirdparty/preact/.travis.yml
+++ b/thirdparty/preact/.travis.yml
@@ -9,13 +9,19 @@ cache:
directories:
- node_modules
+# Make chrome browser available for testing
+before_install:
+ - export CHROME_BIN=chromium-browser
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+
install:
- npm install
script:
- npm run build
- npm run test
- - SAUCELABS=true COVERAGE=false FLAKEY=false PERFORMANCE=false npm run test:karma
+ - BROWSER=true COVERAGE=false FLAKEY=false PERFORMANCE=false npm run test:karma
# Necessary to compile native modules for io.js v3 or Node.js v4
env:
diff --git a/thirdparty/preact/README.md b/thirdparty/preact/README.md
index 582c55216..38f0f1b3a 100644
--- a/thirdparty/preact/README.md
+++ b/thirdparty/preact/README.md
@@ -2,13 +2,14 @@
<img alt="Preact" title="Preact" src="https://cdn.rawgit.com/developit/b4416d5c92b743dbaec1e68bc4c27cda/raw/3235dc508f7eb834ebf48418aea212a05df13db1/preact-logo-trans.svg" width="550">
</a>
-**Preact is a fast, `3kb` alternative to React, with the same ES2015 API.**
+**Preact is a fast, `3kB` alternative to React, with the same ES2015 API.**
Preact retains a large amount of compatibility with React, but only the modern ([ES6 Classes] and [stateless functional components](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components)) interfaces.
As one would expect coming from React, Components are simple building blocks for composing a User Interface.
### :information_desk_person: Full documentation is available at the [Preact Website ➞](https://preactjs.com)
+[![CDNJS](https://img.shields.io/cdnjs/v/preact.svg)](https://cdnjs.com/libraries/preact)
[![npm](https://img.shields.io/npm/v/preact.svg)](http://npm.im/preact)
[![travis](https://travis-ci.org/developit/preact.svg?branch=master)](https://travis-ci.org/developit/preact)
[![gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/developit/preact)
@@ -24,7 +25,7 @@ As one would expect coming from React, Components are simple building blocks for
- [**ESBench**](http://esbench.com) is built using Preact.
- [**Nectarine.rocks**](http://nectarine.rocks) _([Github Project](https://github.com/developit/nectarine))_ :peach:
- [**Documentation Viewer**](https://documentation-viewer.firebaseapp.com) _([Github Project](https://github.com/developit/documentation-viewer))_
-- [**TodoMVC**](http://developit.github.io/preact-todomvc/) _([Github Project](https://github.com/developit/preact-todomvc))_
+- [**TodoMVC**](https://preact-todomvc.surge.sh) _([Github Project](https://github.com/developit/preact-todomvc))_
- [**Hacker News Minimal**](https://developit.github.io/hn_minimal/) _([Github Project](https://github.com/developit/hn_minimal))_
- [**Preact Boilerplate**](https://preact-boilerplate.surge.sh) _([Github Project](https://github.com/developit/preact-boilerplate))_ :zap:
- [**Preact Redux Example**](https://github.com/developit/preact-redux-example) :star:
@@ -41,17 +42,25 @@ As one would expect coming from React, Components are simple building blocks for
## Libraries & Add-ons
-- :earth_americas: [**preact-router**](https://git.io/preact-router): URL routing for your components.
+- :raised_hands: [**preact-compat**](https://git.io/preact-compat): use any React library with Preact *([full example](http://git.io/preact-compat-example))*
+- :repeat: [**preact-cycle**](https://git.io/preact-cycle): Functional-reactive paradigm for Preact
- :page_facing_up: [**preact-render-to-string**](https://git.io/preact-render-to-string): Universal rendering.
-- :raised_hands: [**preact-compat**](https://git.io/preact-compat): use any React library with Preact. *([full example](http://git.io/preact-compat-example))*
-- :rocket: [**preact-photon**](https://git.io/preact-photon): build beautiful desktop UI with [photon](http://photonkit.com).
-- :microscope: [**preact-jsx-chai**](https://git.io/preact-jsx-chai): JSX assertion testing _(no DOM, right in Node)_
+- :earth_americas: [**preact-router**](https://git.io/preact-router): URL routing for your components
- :bookmark_tabs: [**preact-markup**](https://git.io/preact-markup): Render HTML & Custom Elements as JSX & Components
-- :pencil: [**preact-richtextarea**](https://git.io/preact-richtextarea): Simple HTML editor component
-- :repeat: [**preact-cycle**](https://git.io/preact-cycle): Functional-reactive paradigm for Preact.
- :satellite: [**preact-portal**](https://git.io/preact-portal): Render Preact components into (a) SPACE :milky_way:
-- :construction: [**preact-classless-component**](https://github.com/ld0rman/preact-classless-component): A utility method to create components without the `class` keyword
+- :pencil: [**preact-richtextarea**](https://git.io/preact-richtextarea): Simple HTML editor component
+- :bookmark: [**preact-token-input**](https://github.com/developit/preact-token-input): Text field that tokenizes input, for things like tags
+- :card_index: [**preact-virtual-list**](https://github.com/developit/preact-virtual-list): Easily render lists with millions of rows ([demo](https://jsfiddle.net/developit/qqan9pdo/))
+- :triangular_ruler: [**preact-layout**](https://download.github.io/preact-layout/): Small and simple layout library
+- :thought_balloon: [**preact-socrates**](https://github.com/matthewmueller/preact-socrates): Preact plugin for [Socrates](http://github.com/matthewmueller/socrates)
+- :rowboat: [**preact-flyd**](https://github.com/xialvjun/preact-flyd): Use [flyd](https://github.com/paldepind/flyd) FRP streams in Preact + JSX
+- :speech_balloon: [**preact-i18nline**](https://github.com/download/preact-i18nline): Integrates the ecosystem around [i18n-js](https://github.com/everydayhero/i18n-js) with Preact via [i18nline](https://github.com/download/i18nline).
+- :white_square_button: [**preact-mdl**](https://git.io/preact-mdl): Use [MDL](https://getmdl.io) as Preact components
+- :rocket: [**preact-photon**](https://git.io/preact-photon): build beautiful desktop UI with [photon](http://photonkit.com)
+- :microscope: [**preact-jsx-chai**](https://git.io/preact-jsx-chai): JSX assertion testing _(no DOM, right in Node)_
+- :tophat: [**preact-classless-component**](https://github.com/ld0rman/preact-classless-component): create preact components without the class keyword
- :hammer: [**preact-hyperscript**](https://github.com/queckezz/preact-hyperscript): Hyperscript-like syntax for creating elements
+- :white_check_mark: [**shallow-compare**](https://github.com/tkh44/shallow-compare): simplified `shouldComponentUpdate` helper.
## Getting Started
@@ -328,6 +337,24 @@ class MixedComponent extends Component {
}
```
+## Developer Tools
+
+You can inspect and modify the state of your Preact UI components at runtime using the
+[React Developer Tools](https://github.com/facebook/react-devtools) browser extension.
+
+1. Install the [React Developer Tools](https://github.com/facebook/react-devtools) extension
+2. Import the "preact/devtools" module in your app
+3. Reload and go to the 'React' tab in the browser's development tools
+
+
+```js
+import { h, Component, render } from 'preact';
+
+// Enable devtools. You can reduce the size of your app by only including this
+// module in development builds. eg. In Webpack, wrap this with an `if (module.hot) {...}`
+// check.
+require('preact/devtools');
+```
## License
diff --git a/thirdparty/preact/config/rollup.config.devtools.js b/thirdparty/preact/config/rollup.config.devtools.js
new file mode 100644
index 000000000..1fb90b238
--- /dev/null
+++ b/thirdparty/preact/config/rollup.config.devtools.js
@@ -0,0 +1,20 @@
+import nodeResolve from 'rollup-plugin-node-resolve';
+import babel from 'rollup-plugin-babel';
+
+export default {
+ entry: 'devtools/index.js',
+ external: ['preact'],
+ format: 'umd',
+ globals: {
+ preact: 'preact'
+ },
+ moduleName: 'preactDevTools',
+ plugins: [
+ babel({
+ sourceMap: true,
+ loose: 'all',
+ blacklist: ['es6.tailCall'],
+ exclude: 'node_modules/**'
+ })
+ ]
+}
diff --git a/thirdparty/preact/devtools/devtools.js b/thirdparty/preact/devtools/devtools.js
new file mode 100644
index 000000000..4bcbeae1e
--- /dev/null
+++ b/thirdparty/preact/devtools/devtools.js
@@ -0,0 +1,427 @@
+/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
+
+import { options, Component } from 'preact';
+
+// Internal helpers from preact
+import { ATTR_KEY } from '../src/constants';
+import { isFunctionalComponent } from '../src/vdom/functional-component';
+
+/**
+ * Return a ReactElement-compatible object for the current state of a preact
+ * component.
+ */
+function createReactElement(component) {
+ return {
+ type: component.constructor,
+ key: component.key,
+ ref: null, // Unsupported
+ props: component.props
+ };
+}
+
+/**
+ * Create a ReactDOMComponent-compatible object for a given DOM node rendered
+ * by preact.
+ *
+ * This implements the subset of the ReactDOMComponent interface that
+ * React DevTools requires in order to display DOM nodes in the inspector with
+ * the correct type and properties.
+ *
+ * @param {Node} node
+ */
+function createReactDOMComponent(node) {
+ const childNodes = node.nodeType === Node.ELEMENT_NODE ?
+ Array.from(node.childNodes) : [];
+
+ const isText = node.nodeType === Node.TEXT_NODE;
+
+ return {
+ // --- ReactDOMComponent interface
+ _currentElement: isText ? node.textContent : {
+ type: node.nodeName.toLowerCase(),
+ props: node[ATTR_KEY]
+ },
+ _renderedChildren: childNodes.map(child => {
+ if (child._component) {
+ return updateReactComponent(child._component);
+ }
+ return updateReactComponent(child);
+ }),
+ _stringText: isText ? node.textContent : null,
+
+ // --- Additional properties used by preact devtools
+
+ // A flag indicating whether the devtools have been notified about the
+ // existence of this component instance yet.
+ // This is used to send the appropriate notifications when DOM components
+ // are added or updated between composite component updates.
+ _inDevTools: false,
+ node
+ };
+}
+
+/**
+ * Return the name of a component created by a `ReactElement`-like object.
+ *
+ * @param {ReactElement} element
+ */
+function typeName(element) {
+ if (typeof element.type === 'function') {
+ return element.type.displayName || element.type.name;
+ }
+ return element.type;
+}
+
+/**
+ * Return a ReactCompositeComponent-compatible object for a given preact
+ * component instance.
+ *
+ * This implements the subset of the ReactCompositeComponent interface that
+ * the DevTools requires in order to walk the component tree and inspect the
+ * component's properties.
+ *
+ * See https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/getData.js
+ */
+function createReactCompositeComponent(component) {
+ const _currentElement = createReactElement(component);
+ const node = component.base;
+
+ let instance = {
+ // --- ReactDOMComponent properties
+ getName() {
+ return typeName(_currentElement);
+ },
+ _currentElement: createReactElement(component),
+ props: component.props,
+ state: component.state,
+ forceUpdate: component.forceUpdate.bind(component),
+ setState: component.setState.bind(component),
+
+ // --- Additional properties used by preact devtools
+ node
+ };
+
+ // React DevTools exposes the `_instance` field of the selected item in the
+ // component tree as `$r` in the console. `_instance` must refer to a
+ // React Component (or compatible) class instance with `props` and `state`
+ // fields and `setState()`, `forceUpdate()` methods.
+ instance._instance = component;
+
+ // If the root node returned by this component instance's render function
+ // was itself a composite component, there will be a `_component` property
+ // containing the child component instance.
+ if (component._component) {
+ instance._renderedComponent = updateReactComponent(component._component);
+ } else {
+ // Otherwise, if the render() function returned an HTML/SVG element,
+ // create a ReactDOMComponent-like object for the DOM node itself.
+ instance._renderedComponent = updateReactComponent(node);
+ }
+
+ return instance;
+}
+
+/**
+ * Map of Component|Node to ReactDOMComponent|ReactCompositeComponent-like
+ * object.
+ *
+ * The same React*Component instance must be used when notifying devtools
+ * about the initial mount of a component and subsequent updates.
+ */
+let instanceMap = new Map();
+
+/**
+ * Update (and create if necessary) the ReactDOMComponent|ReactCompositeComponent-like
+ * instance for a given preact component instance or DOM Node.
+ *
+ * @param {Component|Node} componentOrNode
+ */
+function updateReactComponent(componentOrNode) {
+ const newInstance = componentOrNode instanceof Node ?
+ createReactDOMComponent(componentOrNode) :
+ createReactCompositeComponent(componentOrNode);
+ if (instanceMap.has(componentOrNode)) {
+ let inst = instanceMap.get(componentOrNode);
+ Object.assign(inst, newInstance);
+ return inst;
+ }
+ instanceMap.set(componentOrNode, newInstance);
+ return newInstance;
+}
+
+function nextRootKey(roots) {
+ return '.' + Object.keys(roots).length;
+}
+
+/**
+ * Find all root component instances rendered by preact in `node`'s children
+ * and add them to the `roots` map.
+ *
+ * @param {DOMElement} node
+ * @param {[key: string] => ReactDOMComponent|ReactCompositeComponent}
+ */
+function findRoots(node, roots) {
+ Array.from(node.childNodes).forEach(child => {
+ if (child._component) {
+ roots[nextRootKey(roots)] = updateReactComponent(child._component);
+ } else {
+ findRoots(child, roots);
+ }
+ });
+}
+
+/**
+ * Map of functional component name -> wrapper class.
+ */
+let functionalComponentWrappers = new Map();
+
+/**
+ * Wrap a functional component with a stateful component.
+ *
+ * preact does not record any information about the original hierarchy of
+ * functional components in the rendered DOM nodes. Wrapping functional components
+ * with a trivial wrapper allows us to recover information about the original
+ * component structure from the DOM.
+ *
+ * @param {VNode} vnode
+ */
+function wrapFunctionalComponent(vnode) {
+ const originalRender = vnode.nodeName;
+ const name = vnode.nodeName.name || '(Function.name missing)';
+ const wrappers = functionalComponentWrappers;
+ if (!wrappers.has(originalRender)) {
+ let wrapper = class extends Component {
+ render(props, state, context) {
+ return originalRender(props, context);
+ }
+ };
+
+ // Expose the original component name. React Dev Tools will use
+ // this property if it exists or fall back to Function.name
+ // otherwise.
+ wrapper.displayName = name;
+
+ wrappers.set(originalRender, wrapper);
+ }
+ vnode.nodeName = wrappers.get(originalRender);
+}
+
+/**
+ * Create a bridge for exposing preact's component tree to React DevTools.
+ *
+ * It creates implementations of the interfaces that ReactDOM passes to
+ * devtools to enable it to query the component tree and hook into component
+ * updates.
+ *
+ * See https://github.com/facebook/react/blob/59ff7749eda0cd858d5ee568315bcba1be75a1ca/src/renderers/dom/ReactDOM.js
+ * for how ReactDOM exports its internals for use by the devtools and
+ * the `attachRenderer()` function in
+ * https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/attachRenderer.js
+ * for how the devtools consumes the resulting objects.
+ */
+function createDevToolsBridge() {
+ // The devtools has different paths for interacting with the renderers from
+ // React Native, legacy React DOM and current React DOM.
+ //
+ // Here we emulate the interface for the current React DOM (v15+) lib.
+
+ // ReactDOMComponentTree-like object
+ const ComponentTree = {
+ getNodeFromInstance(instance) {
+ return instance.node;
+ },
+ getClosestInstanceFromNode(node) {
+ while (node && !node._component) {
+ node = node.parentNode;
+ }
+ return node ? updateReactComponent(node._component) : null;
+ }
+ };
+
+ // Map of root ID (the ID is unimportant) to component instance.
+ let roots = {};
+ findRoots(document.body, roots);
+
+ // ReactMount-like object
+ //
+ // Used by devtools to discover the list of root component instances and get
+ // notified when new root components are rendered.
+ const Mount = {
+ _instancesByReactRootID: roots,
+
+ // Stub - React DevTools expects to find this method and replace it
+ // with a wrapper in order to observe new root components being added
+ _renderNewRootComponent(/* instance, ... */) { }
+ };
+
+ // ReactReconciler-like object
+ const Reconciler = {
+ // Stubs - React DevTools expects to find these methods and replace them
+ // with wrappers in order to observe components being mounted, updated and
+ // unmounted
+ mountComponent(/* instance, ... */) { },
+ performUpdateIfNecessary(/* instance, ... */) { },
+ receiveComponent(/* instance, ... */) { },
+ unmountComponent(/* instance, ... */) { }
+ };
+
+ /** Notify devtools that a new component instance has been mounted into the DOM. */
+ const componentAdded = component => {
+ const instance = updateReactComponent(component);
+ if (isRootComponent(component)) {
+ instance._rootID = nextRootKey(roots);
+ roots[instance._rootID] = instance;
+ Mount._renderNewRootComponent(instance);
+ }
+ visitNonCompositeChildren(instance, childInst => {
+ childInst._inDevTools = true;
+ Reconciler.mountComponent(childInst);
+ });
+ Reconciler.mountComponent(instance);
+ };
+
+ /** Notify devtools that a component has been updated with new props/state. */
+ const componentUpdated = component => {
+ const prevRenderedChildren = [];
+ visitNonCompositeChildren(instanceMap.get(component), childInst => {
+ prevRenderedChildren.push(childInst);
+ });
+
+ // Notify devtools about updates to this component and any non-composite
+ // children
+ const instance = updateReactComponent(component);
+ Reconciler.receiveComponent(instance);
+ visitNonCompositeChildren(instance, childInst => {
+ if (!childInst._inDevTools) {
+ // New DOM child component
+ childInst._inDevTools = true;
+ Reconciler.mountComponent(childInst);
+ } else {
+ // Updated DOM child component
+ Reconciler.receiveComponent(childInst);
+ }
+ });
+
+ // For any non-composite children that were removed by the latest render,
+ // remove the corresponding ReactDOMComponent-like instances and notify
+ // the devtools
+ prevRenderedChildren.forEach(childInst => {
+ if (!document.body.contains(childInst.node)) {
+ instanceMap.delete(childInst.node);
+ Reconciler.unmountComponent(childInst);
+ }
+ });
+ };
+
+ /** Notify devtools that a component has been unmounted from the DOM. */
+ const componentRemoved = component => {
+ const instance = updateReactComponent(component);
+ visitNonCompositeChildren(childInst => {
+ instanceMap.delete(childInst.node);
+ Reconciler.unmountComponent(childInst);
+ });
+ Reconciler.unmountComponent(instance);
+ instanceMap.delete(component);
+ if (instance._rootID) {
+ delete roots[instance._rootID];
+ }
+ };
+
+ return {
+ componentAdded,
+ componentUpdated,
+ componentRemoved,
+
+ // Interfaces passed to devtools via __REACT_DEVTOOLS_GLOBAL_HOOK__.inject()
+ ComponentTree,
+ Mount,
+ Reconciler
+ };
+}
+
+/**
+ * Return `true` if a preact component is a top level component rendered by
+ * `render()` into a container Element.
+ */
+function isRootComponent(component) {
+ return !component.base.parentElement || !component.base.parentElement[ATTR_KEY];
+}
+
+/**
+ * Visit all child instances of a ReactCompositeComponent-like object that are
+ * not composite components (ie. they represent DOM elements or text)
+ *
+ * @param {Component} component
+ * @param {(Component) => void} visitor
+ */
+function visitNonCompositeChildren(component, visitor) {
+ if (component._renderedComponent) {
+ if (!component._renderedComponent._component) {
+ visitor(component._renderedComponent);
+ visitNonCompositeChildren(component._renderedComponent, visitor);
+ }
+ } else if (component._renderedChildren) {
+ component._renderedChildren.forEach(child => {
+ visitor(child);
+ if (!child._component) visitNonCompositeChildren(child, visitor);
+ });
+ }
+}
+
+/**
+ * Create a bridge between the preact component tree and React's dev tools
+ * and register it.
+ *
+ * After this function is called, the React Dev Tools should be able to detect
+ * "React" on the page and show the component tree.
+ *
+ * This function hooks into preact VNode creation in order to expose functional
+ * components correctly, so it should be called before the root component(s)
+ * are rendered.
+ *
+ * Returns a cleanup function which unregisters the hooks.
+ */
+export function initDevTools() {
+ if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
+ // React DevTools are not installed
+ return;
+ }
+
+ // Hook into preact element creation in order to wrap functional components
+ // with stateful ones in order to make them visible in the devtools
+ const nextVNode = options.vnode;
+ options.vnode = (vnode) => {
+ if (isFunctionalComponent(vnode)) wrapFunctionalComponent(vnode);
+ if (nextVNode) return nextVNode(vnode);
+ };
+
+ // Notify devtools when preact components are mounted, updated or unmounted
+ const bridge = createDevToolsBridge();
+
+ const nextAfterMount = options.afterMount;
+ options.afterMount = component => {
+ bridge.componentAdded(component);
+ if (nextAfterMount) nextAfterMount(component);
+ };
+
+ const nextAfterUpdate = options.afterUpdate;
+ options.afterUpdate = component => {
+ bridge.componentUpdated(component);
+ if (nextAfterUpdate) nextAfterUpdate(component);
+ };
+
+ const nextBeforeUnmount = options.beforeUnmount;
+ options.beforeUnmount = component => {
+ bridge.componentRemoved(component);
+ if (nextBeforeUnmount) nextBeforeUnmount(component);
+ };
+
+ // Notify devtools about this instance of "React"
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.inject(bridge);
+
+ return () => {
+ options.afterMount = nextAfterMount;
+ options.afterUpdate = nextAfterUpdate;
+ options.beforeUnmount = nextBeforeUnmount;
+ };
+}
diff --git a/thirdparty/preact/devtools/index.js b/thirdparty/preact/devtools/index.js
new file mode 100644
index 000000000..7c361c8ee
--- /dev/null
+++ b/thirdparty/preact/devtools/index.js
@@ -0,0 +1,4 @@
+import { initDevTools } from './devtools';
+
+initDevTools();
+
diff --git a/thirdparty/preact/package.json b/thirdparty/preact/package.json
index 293597fae..eba36d0fe 100644
--- a/thirdparty/preact/package.json
+++ b/thirdparty/preact/package.json
@@ -1,7 +1,7 @@
{
"name": "preact",
"amdName": "preact",
- "version": "6.2.1",
+ "version": "6.4.0",
"description": "Tiny & fast Component-based virtual DOM framework.",
"main": "dist/preact.js",
"jsnext:main": "src/preact.js",
@@ -9,25 +9,27 @@
"dev:main": "dist/preact.dev.js",
"minified:main": "dist/preact.min.js",
"scripts": {
- "clean": "rimraf dist/ $npm_package_aliases_main ${npm_package_aliases_main}.map",
- "copy-flow-definition": "cp src/preact.js.flow dist/preact.js.flow",
- "copy-typescript-definition": "cp src/preact.d.ts dist/preact.d.ts",
+ "clean": "rimraf dist/ aliases.js aliases.js.map devtools.js devtools.js.map",
+ "copy-flow-definition": "copyfiles src/preact.js.flow dist/preact.js.flow",
+ "copy-typescript-definition": "copyfiles src/preact.d.ts dist/preact.d.ts",
"build": "npm-run-all --silent clean transpile copy-flow-definition copy-typescript-definition strip optimize minify size",
- "transpile:main": "rollup -c config/rollup.config.js -m ${npm_package_dev_main}.map -f umd -n $npm_package_amdName $npm_package_jsnext_main -o $npm_package_dev_main",
- "transpile:aliases": "rollup -c config/rollup.config.aliases.js -m ${npm_package_aliases_main}.map -f umd -n $npm_package_amdName $npm_package_jsnext_main -o $npm_package_aliases_main",
- "transpile": "npm-run-all transpile:main transpile:aliases",
- "optimize": "uglifyjs $npm_package_dev_main -c conditionals=false,sequences=false,loops=false,join_vars=false,collapse_vars=false --pure-funcs=Object.defineProperty -b width=120,quote_style=3 -o $npm_package_main -p relative --in-source-map ${npm_package_dev_main}.map --source-map ${npm_package_main}.map",
- "minify": "uglifyjs $npm_package_main -c collapse_vars,evaluate,screw_ie8,unsafe,loops=false,keep_fargs=false,pure_getters,unused,dead_code -m -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map",
+ "transpile:main": "rollup -c config/rollup.config.js -m dist/preact.dev.js.map -f umd -n preact src/preact.js -o dist/preact.dev.js",
+ "transpile:devtools": "rollup -c config/rollup.config.devtools.js -o devtools.js -m devtools.js.map",
+ "transpile:aliases": "rollup -c config/rollup.config.aliases.js -m aliases.js.map -f umd -n preact src/preact.js -o aliases.js",
+ "transpile": "npm-run-all transpile:main transpile:aliases transpile:devtools",
+ "optimize": "uglifyjs dist/preact.dev.js -c conditionals=false,sequences=false,loops=false,join_vars=false,collapse_vars=false --pure-funcs=Object.defineProperty -b width=120,quote_style=3 -o dist/preact.js -p relative --in-source-map dist/preact.dev.js.map --source-map dist/preact.js.map",
+ "minify": "uglifyjs dist/preact.js -c collapse_vars,evaluate,screw_ie8,unsafe,loops=false,keep_fargs=false,pure_getters,unused,dead_code -m -o dist/preact.min.js -p relative --in-source-map dist/preact.js.map --source-map dist/preact.min.js.map",
"strip": "jscodeshift --run-in-band -s -t config/codemod-strip-tdz.js dist/preact.dev.js && jscodeshift --run-in-band -s -t config/codemod-const.js dist/preact.dev.js",
- "size": "size=$(gzip-size $npm_package_minified_main) && echo \"gzip size: $size / $(pretty-bytes $size)\"",
+ "size": "node -e \"process.stdout.write('gzip size: ')\" && gzip-size dist/preact.min.js",
"test": "npm-run-all lint --parallel test:mocha test:karma",
"test:mocha": "mocha --recursive --compilers js:babel/register test/shared test/node",
"test:karma": "karma start test/karma.conf.js --single-run",
"test:mocha:watch": "npm run test:mocha -- --watch",
"test:karma:watch": "npm run test:karma -- no-single-run",
- "lint": "eslint src test",
+ "lint": "eslint devtools src test",
"prepublish": "npm run build",
- "release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
+ "smart-release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish",
+ "release": "cross-env npm run smart-release"
},
"eslintConfig": {
"extends": "./config/eslint-config.js"
@@ -38,10 +40,13 @@
"url": "https://github.com/developit/preact.git"
},
"files": [
+ "devtools",
"src",
"dist",
"aliases.js",
"aliases.js.map",
+ "devtools.js",
+ "devtools.js.map",
"typings.json"
],
"author": "Jason Miller <jason@developit.ca>",
@@ -57,6 +62,9 @@
"babel-loader": "^5.3.2",
"babel-runtime": "^5.8.24",
"chai": "^3.4.1",
+ "copyfiles": "^1.0.0",
+ "core-js": "^2.4.1",
+ "cross-env": "^3.1.3",
"diff": "^3.0.0",
"eslint": "^3.0.0",
"eslint-plugin-react": "^6.0.0",
@@ -67,18 +75,18 @@
"karma-babel-preprocessor": "^5.2.2",
"karma-chai": "^0.1.0",
"karma-chai-sinon": "^0.1.5",
+ "karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.0.0",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.0.4",
"karma-phantomjs-launcher": "^1.0.1",
- "karma-sauce-launcher": "^1.0.0",
+ "karma-sauce-launcher": "^1.1.0",
"karma-source-map-support": "^1.1.0",
"karma-sourcemap-loader": "^0.3.6",
"karma-webpack": "^1.7.0",
"mocha": "^3.0.1",
"npm-run-all": "^3.0.0",
"phantomjs-prebuilt": "^2.1.7",
- "pretty-bytes-cli": "^2.0.0",
"rimraf": "^2.5.3",
"rollup": "^0.34.1",
"rollup-plugin-babel": "^1.0.0",
diff --git a/thirdparty/preact/src/dom/index.js b/thirdparty/preact/src/dom/index.js
index 248a3cdc5..b72d056af 100644
--- a/thirdparty/preact/src/dom/index.js
+++ b/thirdparty/preact/src/dom/index.js
@@ -1,4 +1,4 @@
-import { ATTR_KEY, NON_DIMENSION_PROPS, NON_BUBBLING_EVENTS } from '../constants';
+import { NON_DIMENSION_PROPS, NON_BUBBLING_EVENTS } from '../constants';
import options from '../options';
import { toLowerCase, isString, isFunction, hashToClassName } from '../util';
@@ -20,8 +20,7 @@ export function removeNode(node) {
* @param {any} previousValue The last value that was set for this name/node pair
* @private
*/
-export function setAccessor(node, name, value, old, isSvg) {
- node[ATTR_KEY][name] = value;
+export function setAccessor(node, name, old, value, isSvg) {
if (name==='className') name = 'class';
@@ -29,8 +28,8 @@ export function setAccessor(node, name, value, old, isSvg) {
value = hashToClassName(value);
}
- if (name==='key' || name==='children' || name==='innerHTML') {
- // skip these
+ if (name==='key') {
+ // ignore
}
else if (name==='class' && !isSvg) {
node.className = value || '';
diff --git a/thirdparty/preact/src/h.js b/thirdparty/preact/src/h.js
index e57ce4bde..c137bec84 100644
--- a/thirdparty/preact/src/h.js
+++ b/thirdparty/preact/src/h.js
@@ -2,8 +2,7 @@ import { VNode } from './vnode';
import options from './options';
-let stack = [];
-
+const stack = [];
/** JSX/hyperscript reviver
@@ -16,7 +15,8 @@ let stack = [];
* render(<span>foo</span>, document.body);
*/
export function h(nodeName, attributes) {
- let children, lastSimple, child, simple, i;
+ let children = [],
+ lastSimple, child, simple, i;
for (i=arguments.length; i-- > 2; ) {
stack.push(arguments[i]);
}
@@ -35,8 +35,7 @@ export function h(nodeName, attributes) {
children[children.length-1] += child;
}
else {
- if (children) children.push(child);
- else children = [child];
+ children.push(child);
lastSimple = simple;
}
}
diff --git a/thirdparty/preact/src/linked-state.js b/thirdparty/preact/src/linked-state.js
index ed72bd8bc..b6959df73 100644
--- a/thirdparty/preact/src/linked-state.js
+++ b/thirdparty/preact/src/linked-state.js
@@ -8,21 +8,17 @@ import { isString, delve } from './util';
* @private
*/
export function createLinkedState(component, key, eventPath) {
- let path = key.split('.'),
- p0 = path[0];
+ let path = key.split('.');
return function(e) {
- let t = e && e.currentTarget || this,
- s = component.state,
- obj = s,
- v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? ((t.nodeName+t.type).match(/^input(che|rad)/i) ? t.checked : t.value) : e,
- i;
- if (path.length>1) {
- for (i=0; i<path.length-1; i++) {
- obj = obj[path[i]] || (obj[path[i]] = {});
- }
- obj[path[i]] = v;
- v = s[p0];
+ let t = e && e.target || this,
+ state = {},
+ obj = state,
+ v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) : e,
+ i = 0;
+ for ( ; i<path.length-1; i++) {
+ obj = obj[path[i]] || (obj[path[i]] = !i && component.state[path[i]] || {});
}
- component.setState({ [p0]: v });
+ obj[path[i]] = v;
+ component.setState(state);
};
}
diff --git a/thirdparty/preact/src/options.js b/thirdparty/preact/src/options.js
index 35b7418fc..49869604e 100644
--- a/thirdparty/preact/src/options.js
+++ b/thirdparty/preact/src/options.js
@@ -15,4 +15,13 @@ export default {
* @param {VNode} vnode A newly-created VNode to normalize/process
*/
//vnode(vnode) { }
+
+ /** Hook invoked after a component is mounted. */
+ // afterMount(component) { }
+
+ /** Hook invoked after the DOM is updated with a component's latest render. */
+ // afterUpdate(component) { }
+
+ /** Hook invoked immediately before a component is unmounted. */
+ // beforeUnmount(component) { }
};
diff --git a/thirdparty/preact/src/preact.d.ts b/thirdparty/preact/src/preact.d.ts
index 2dd8299a9..784844152 100644
--- a/thirdparty/preact/src/preact.d.ts
+++ b/thirdparty/preact/src/preact.d.ts
@@ -4,8 +4,14 @@ declare namespace preact {
key?:string;
}
+ interface DangerouslySetInnerHTML {
+ __html: string;
+ }
+
interface PreactHTMLAttributes {
+ dangerouslySetInnerHTML?:DangerouslySetInnerHTML;
key?:string;
+ ref?:(el?: Element) => void;
}
interface VNode {
@@ -51,8 +57,8 @@ declare namespace preact {
abstract render(props:PropsType & ComponentProps, state:any):JSX.Element;
}
- function h<PropsType>(node:ComponentConstructor<PropsType, any>, params:PropsType, ...children:(JSX.Element|string)[]):JSX.Element;
- function h(node:string, params:JSX.HTMLAttributes&JSX.SVGAttributes, ...children:(JSX.Element|string)[]):JSX.Element;
+ function h<PropsType>(node:ComponentConstructor<PropsType, any>, params:PropsType, ...children:(JSX.Element|JSX.Element[]|string)[]):JSX.Element;
+ function h(node:string, params:JSX.HTMLAttributes&JSX.SVGAttributes&{[propName: string]: any}, ...children:(JSX.Element|JSX.Element[]|string)[]):JSX.Element;
function render(node:JSX.Element, parent:Element, merge?:boolean):Element;
@@ -72,6 +78,11 @@ declare module "preact" {
export = preact;
}
+declare module "preact/devtools" {
+ // Empty. This module initializes the React Developer Tools integration
+ // when imported.
+}
+
declare namespace JSX {
interface Element extends preact.VNode {
@@ -277,8 +288,8 @@ declare namespace JSX {
charSet?:string;
challenge?:string;
checked?:boolean;
- class?:string;
- className?:string;
+ class?:string | { [key:string]: boolean };
+ className?:string | { [key:string]: boolean };
cols?:number;
colSpan?:number;
content?:string;
@@ -551,4 +562,4 @@ declare namespace JSX {
tspan:SVGAttributes;
use:SVGAttributes;
}
-}
+} \ No newline at end of file
diff --git a/thirdparty/preact/src/vdom/component.js b/thirdparty/preact/src/vdom/component.js
index bb2e4fa5d..64e7ff81f 100644
--- a/thirdparty/preact/src/vdom/component.js
+++ b/thirdparty/preact/src/vdom/component.js
@@ -154,11 +154,11 @@ export function renderComponent(component, opts, mountAll, isChild) {
let baseParent = initialBase.parentNode;
if (baseParent && base!==baseParent) {
baseParent.replaceChild(base, initialBase);
- }
- if (!cbase && !toUnmount && component._parentComponent) {
- initialBase._component = null;
- recollectNodeTree(initialBase);
+ if (!toUnmount) {
+ initialBase._component = null;
+ recollectNodeTree(initialBase);
+ }
}
}
@@ -170,7 +170,9 @@ export function renderComponent(component, opts, mountAll, isChild) {
if (base && !isChild) {
let componentRef = component,
t = component;
- while ((t=t._parentComponent)) { componentRef = t; }
+ while ((t=t._parentComponent)) {
+ (componentRef = t).base = base;
+ }
base._component = componentRef;
base._componentConstructor = componentRef.constructor;
}
@@ -179,8 +181,11 @@ export function renderComponent(component, opts, mountAll, isChild) {
if (!isUpdate || mountAll) {
mounts.unshift(component);
}
- else if (!skip && component.componentDidUpdate) {
- component.componentDidUpdate(previousProps, previousState, previousContext);
+ else if (!skip) {
+ if (component.componentDidUpdate) {
+ component.componentDidUpdate(previousProps, previousState, previousContext);
+ }
+ if (options.afterUpdate) options.afterUpdate(component);
}
let cb = component._renderCallbacks, fn;
@@ -218,7 +223,11 @@ export function buildComponentFromVNode(dom, vnode, context, mountAll) {
}
c = createComponent(vnode.nodeName, props, context);
- if (dom && !c.nextBase) c.nextBase = dom;
+ if (dom && !c.nextBase) {
+ c.nextBase = dom;
+ // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L241:
+ oldDom = null;
+ }
setComponentProps(c, props, SYNC_RENDER, context, mountAll);
dom = c.base;
@@ -239,6 +248,8 @@ export function buildComponentFromVNode(dom, vnode, context, mountAll) {
* @private
*/
export function unmountComponent(component, remove) {
+ if (options.beforeUnmount) options.beforeUnmount(component);
+
// console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`);
let base = component.base;
diff --git a/thirdparty/preact/src/vdom/diff.js b/thirdparty/preact/src/vdom/diff.js
index 691434e98..794a79aaa 100644
--- a/thirdparty/preact/src/vdom/diff.js
+++ b/thirdparty/preact/src/vdom/diff.js
@@ -6,6 +6,7 @@ import { buildComponentFromVNode } from './component';
import { setAccessor } from '../dom/index';
import { createNode, collectNode } from '../dom/recycler';
import { unmountComponent } from './component';
+import options from '../options';
/** Diff recursion count, used to track the end of the diff cycle. */
@@ -20,6 +21,7 @@ let isSvgMode = false;
export function flushMounts() {
let c;
while ((c=mounts.pop())) {
+ if (options.afterMount) options.afterMount(c);
if (c.componentDidMount) c.componentDidMount();
}
}
@@ -52,7 +54,9 @@ function idiff(dom, vnode, context, mountAll) {
if (isString(vnode)) {
if (dom) {
if (dom instanceof Text && dom.parentNode) {
- dom.nodeValue = vnode;
+ if (dom.nodeValue!=vnode) {
+ dom.nodeValue = vnode;
+ }
return dom;
}
recollectNodeTree(dom);
@@ -66,7 +70,8 @@ function idiff(dom, vnode, context, mountAll) {
let out = dom,
nodeName = vnode.nodeName,
- prevSvgMode = isSvgMode;
+ prevSvgMode = isSvgMode,
+ vchildren = vnode.children;
if (!isString(nodeName)) {
nodeName = String(nodeName);
@@ -86,11 +91,13 @@ function idiff(dom, vnode, context, mountAll) {
}
// fast-path for elements containing a single TextNode:
- if (vnode.children && vnode.children.length===1 && typeof vnode.children[0]==='string' && out.childNodes.length===1 && out.firstChild instanceof Text) {
- out.firstChild.nodeValue = vnode.children[0];
+ if (vchildren && vchildren.length===1 && typeof vchildren[0]==='string' && out.childNodes.length===1 && out.firstChild instanceof Text) {
+ if (out.firstChild.nodeValue!=vchildren[0]) {
+ out.firstChild.nodeValue = vchildren[0];
+ }
}
- else if (vnode.children || out.firstChild) {
- innerDiffNode(out, vnode.children, context, mountAll);
+ else if (vchildren && vchildren.length || out.firstChild) {
+ innerDiffNode(out, vchildren, context, mountAll);
}
let props = out[ATTR_KEY];
@@ -232,15 +239,15 @@ export function recollectNodeTree(node, unmountOnly) {
function diffAttributes(dom, attrs, old) {
for (let name in old) {
if (!(attrs && name in attrs) && old[name]!=null) {
- setAccessor(dom, name, null, old[name], isSvgMode);
+ setAccessor(dom, name, old[name], old[name] = undefined, isSvgMode);
}
}
// new & updated
if (attrs) {
for (let name in attrs) {
- if (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name])) {
- setAccessor(dom, name, attrs[name], old[name], isSvgMode);
+ if (name!=='children' && name!=='innerHTML' && (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name]))) {
+ setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode);
}
}
}
diff --git a/thirdparty/preact/src/vdom/index.js b/thirdparty/preact/src/vdom/index.js
index 50d4ca2b9..f59fbae21 100644
--- a/thirdparty/preact/src/vdom/index.js
+++ b/thirdparty/preact/src/vdom/index.js
@@ -33,9 +33,10 @@ export function isNamedNode(node, nodeName) {
* @returns {Object} props
*/
export function getNodeProps(vnode) {
- let defaultProps = vnode.nodeName.defaultProps,
- props = clone(vnode.attributes);
+ let props = clone(vnode.attributes);
+ props.children = vnode.children;
+ let defaultProps = vnode.nodeName.defaultProps;
if (defaultProps) {
for (let i in defaultProps) {
if (props[i]===undefined) {
@@ -44,7 +45,5 @@ export function getNodeProps(vnode) {
}
}
- if (vnode.children) props.children = vnode.children;
-
return props;
}
diff --git a/thirdparty/preact/test/browser/components.js b/thirdparty/preact/test/browser/components.js
index b4649a719..9ef43cb1c 100644
--- a/thirdparty/preact/test/browser/components.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/context.js b/thirdparty/preact/test/browser/context.js
index e62a948a4..ed5f81471 100644
--- a/thirdparty/preact/test/browser/context.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/devtools.js b/thirdparty/preact/test/browser/devtools.js
new file mode 100644
index 000000000..12c0e3369
--- /dev/null
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/lifecycle.js b/thirdparty/preact/test/browser/lifecycle.js
index d6204ca8f..4deb92163 100644
--- a/thirdparty/preact/test/browser/lifecycle.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/linked-state.js b/thirdparty/preact/test/browser/linked-state.js
index 1ca84cdc6..03db2a7b8 100644
--- a/thirdparty/preact/test/browser/linked-state.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/refs.js b/thirdparty/preact/test/browser/refs.js
index 89678b76e..337a9717b 100644
--- a/thirdparty/preact/test/browser/refs.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/browser/spec.js b/thirdparty/preact/test/browser/spec.js
index eb48151f0..d33cdb93f 100644
--- a/thirdparty/preact/test/browser/spec.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/karma.conf.js b/thirdparty/preact/test/karma.conf.js
index 6ed5397fb..3236e944b 100644
--- a/thirdparty/preact/test/karma.conf.js
+++ b/thirdparty/preact/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/thirdparty/preact/test/polyfills.js b/thirdparty/preact/test/polyfills.js
new file mode 100644
index 000000000..51f256e6e
--- /dev/null
+++ b/thirdparty/preact/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/thirdparty/preact/test/shared/h.js b/thirdparty/preact/test/shared/h.js
index b0cf7f0e8..ae692e3e5 100644
--- a/thirdparty/preact/test/shared/h.js
+++ b/thirdparty/preact/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([