Squashed 'thirdparty/preact/' changes from b2d90cc..ba094e2
ba094e2 Run only local tests for pull requests (#390) e9fc3c2 Fix CI build (#386) 70a5ca3 This adds a link to preact-i18nline. (#382) 5dffd85 Merge branch 'pr-fix-build-for-windows' of https://github.com/Download/preact f14edf7 kilobits => kilobytes (#383) c193547 Test for #292 284e4aa 6.4.0 24eab2f Prevent accidental duplicate recycling of elements when swapping the base element of a component. Fixes #373. 76c5ef7 fix lint error 8008886 When swapping the base of a composed child component, update its parent's base reference. Fixes #349. fd4f21f Add an equality check prior to setting `.nodeValue` on text nodes, since Firefox (and maybe others) don't do this internally by default. Fixes #368 - thanks @zbinlin! 1555e2b Add CDNJS version badge in readme (#365) 79c8bae Disable React Developer Tools integration tests under IE (#362) 84f4eeb Refactor `linkState()` a bit to drop around 40 bytes. Coincidentally, that's the exact size of the hooks just added for DevTools... 👌 22bbfcb Little tweaks 👯 f8b326e Document how to use the React DevTools with Preact (#354) 1f4a8eb Correct "preact/devtools" type definitions (#355) 68f22eb Add React Developer Tools integration (#339) 2a7a027 Add ref and allow objects in className (#316) 4a59cca fix readme todomvc link (#345) 37ca4e0 Fixes build for Windows #343 cf93387 6.3.0 ff05818 Make `VNode.children` *always* be an Array, even when there are no children. 9b095f4 Added link to preact-layout (#342) git-subtree-dir: thirdparty/preact git-subtree-split: ba094e27b602cb16aded7dcad95f71e44b7b0476
This commit is contained in:
parent
30b577138d
commit
6e5fb04d3f
6
.gitignore
vendored
6
.gitignore
vendored
@ -4,5 +4,7 @@
|
||||
/dist
|
||||
/_dev
|
||||
/coverage
|
||||
aliases.js
|
||||
aliases.js.map
|
||||
|
||||
# Additional bundles
|
||||
/*.js
|
||||
/*.js.map
|
||||
|
@ -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:
|
||||
|
45
README.md
45
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
|
||||
|
||||
|
20
config/rollup.config.devtools.js
Normal file
20
config/rollup.config.devtools.js
Normal file
@ -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/**'
|
||||
})
|
||||
]
|
||||
}
|
427
devtools/devtools.js
Normal file
427
devtools/devtools.js
Normal file
@ -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;
|
||||
};
|
||||
}
|
4
devtools/index.js
Normal file
4
devtools/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { initDevTools } from './devtools';
|
||||
|
||||
initDevTools();
|
||||
|
36
package.json
36
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",
|
||||
|
@ -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 || '';
|
||||
|
9
src/h.js
9
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;
|
||||
}
|
||||
}
|
||||
|
@ -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]] = {});
|
||||
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]] || {});
|
||||
}
|
||||
obj[path[i]] = v;
|
||||
v = s[p0];
|
||||
}
|
||||
component.setState({ [p0]: v });
|
||||
component.setState(state);
|
||||
};
|
||||
}
|
||||
|
@ -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) { }
|
||||
};
|
||||
|
19
src/preact.d.ts
vendored
19
src/preact.d.ts
vendored
@ -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;
|
||||
|
@ -154,13 +154,13 @@ export function renderComponent(component, opts, mountAll, isChild) {
|
||||
let baseParent = initialBase.parentNode;
|
||||
if (baseParent && base!==baseParent) {
|
||||
baseParent.replaceChild(base, initialBase);
|
||||
}
|
||||
|
||||
if (!cbase && !toUnmount && component._parentComponent) {
|
||||
if (!toUnmount) {
|
||||
initialBase._component = null;
|
||||
recollectNodeTree(initialBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toUnmount) {
|
||||
unmountComponent(toUnmount, base!==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,9 +181,12 @@ export function renderComponent(component, opts, mountAll, isChild) {
|
||||
if (!isUpdate || mountAll) {
|
||||
mounts.unshift(component);
|
||||
}
|
||||
else if (!skip && component.componentDidUpdate) {
|
||||
else if (!skip) {
|
||||
if (component.componentDidUpdate) {
|
||||
component.componentDidUpdate(previousProps, previousState, previousContext);
|
||||
}
|
||||
if (options.afterUpdate) options.afterUpdate(component);
|
||||
}
|
||||
|
||||
let cb = component._renderCallbacks, fn;
|
||||
if (cb) while ( (fn = cb.pop()) ) fn.call(component);
|
||||
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
234
test/browser/devtools.js
Normal file
234
test/browser/devtools.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
@ -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() {
|
||||
|
@ -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'}}});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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: [
|
||||
|
5
test/polyfills.js
Normal file
5
test/polyfills.js
Normal file
@ -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';
|
@ -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([
|
||||
|
Loading…
Reference in New Issue
Block a user