sven36 6 лет назад
Сommit
d37e98a9de
80 измененных файлов с 172107 добавлено и 0 удалено
  1. 20 0
      .babelrc
  2. 17 0
      .gitattributes
  3. 45 0
      .gitignore
  4. 98 0
      README.md
  5. 2147 0
      dist/May.js
  6. 2574 0
      dist/ReactANU.js
  7. 2415 0
      dist/inferno.js
  8. 2151 0
      dist/nerv.js
  9. 645 0
      dist/preact.js
  10. 19116 0
      dist/react-dom.js
  11. 1357 0
      dist/react.development.js
  12. 3987 0
      dist/react.js
  13. 87 0
      karma.conf.js
  14. 28 0
      lib/ReactPropTypes.js
  15. 147 0
      lib/ReactTestUtils.js
  16. 43 0
      package.json
  17. 122855 0
      scripts/index_backup.js
  18. 10 0
      scripts/rollup.js
  19. 70 0
      src/Children.js
  20. 91 0
      src/Component.js
  21. 31 0
      src/May.js
  22. 108 0
      src/MayElement.js
  23. 22 0
      src/PropTypes.js
  24. 53 0
      src/PureComponent.js
  25. 16 0
      src/Refs.js
  26. 26 0
      src/cloneElement.js
  27. 333 0
      src/diffProps.js
  28. 288 0
      src/event.js
  29. 6 0
      src/may-dom/DOMNamespaces.js
  30. 29 0
      src/may-dom/Iteractor.js
  31. 238 0
      src/may-dom/MayDom.js
  32. 34 0
      src/may-dom/context.js
  33. 407 0
      src/may-dom/diffStrategy.js
  34. 81 0
      src/may-dom/dispose.js
  35. 74 0
      src/may-dom/instantiateComponent.js
  36. 169 0
      src/may-dom/mountStrategy.js
  37. 89 0
      src/may-dom/scheduler.js
  38. 100 0
      src/may-dom/transformChildren.js
  39. 18 0
      src/may-server/MayServer.js
  40. 14 0
      src/readme.md
  41. 50 0
      src/util.js
  42. 59 0
      test/element.test.js
  43. 315 0
      test/may-dom/CSSPropertyOperations-test.js
  44. 199 0
      test/may-dom/DOMPropertyOperations-test.js
  45. 56 0
      test/may-dom/EventPluginHub-test.jsx
  46. 151 0
      test/may-dom/ReactChildReconciler-test.js
  47. 907 0
      test/may-dom/ReactChildren-test.jsx
  48. 475 0
      test/may-dom/ReactComponent-test.jsx
  49. 532 0
      test/may-dom/ReactComponentLifeCycle-test.jsx
  50. 1317 0
      test/may-dom/ReactCompositeComponent-test.jsx
  51. 59 0
      test/may-dom/ReactCompositeComponentDOMMinimalism-test.jsx
  52. 138 0
      test/may-dom/ReactCompositeComponentNestedState-test.jsx
  53. 324 0
      test/may-dom/ReactCompositeComponentState-test.jsx
  54. 336 0
      test/may-dom/ReactContextValidator-test.jsx
  55. 92 0
      test/may-dom/ReactDOM-test.jsx
  56. 375 0
      test/may-dom/ReactES6Class-test.jsx
  57. 347 0
      test/may-dom/ReactElementClone-test.jsx
  58. 121 0
      test/may-dom/ReactEmptyComponent-test.jsx
  59. 247 0
      test/may-dom/ReactIdentity-test.jsx
  60. 364 0
      test/may-dom/ReactMultiChild-test.jsx
  61. 180 0
      test/may-dom/ReactStatelessComponent-test.jsx
  62. 402 0
      test/may-dom/ReactTestUtils-test.jsx
  63. 72 0
      test/may-dom/ReactUpdates-test.jsx
  64. 48 0
      test/may-dom/cloneElement.spec.js
  65. 646 0
      test/may-dom/component.spec.jsx
  66. 94 0
      test/may-dom/context.spec.jsx
  67. 92 0
      test/may-dom/createElement.spec.js
  68. 119 0
      test/may-dom/diffProps.spec.jsx
  69. 397 0
      test/may-dom/event.spec.jsx
  70. 57 0
      test/may-dom/findDOMNode-test.jsx
  71. 676 0
      test/may-dom/lifecycle.spec.jsx
  72. 995 0
      test/may-dom/node.spec.jsx
  73. 341 0
      test/may-dom/redux.spec.jsx
  74. 721 0
      test/may-dom/ref.spec.jsx
  75. 140 0
      test/may-dom/refs-destruction-test.jsx
  76. 180 0
      test/may-dom/refs-test.jsx
  77. 41 0
      test/may-dom/shallow.spec.js
  78. 122 0
      test/may-dom/style.spec.js
  79. 137 0
      test/may-dom/svg.spec.jsx
  80. 144 0
      test/may-dom/util.spec.js

+ 20 - 0
.babelrc

@@ -0,0 +1,20 @@
+ {
+     "presets": [
+         ["env",{ "modules": false }],"react","stage-1"
+     ],
+     "plugins": [
+        ["transform-runtime", {
+            "helpers": false,
+            "polyfill": false,
+            "regenerator": true,
+            "moduleName": "babel-runtime"
+        }]
+      ]
+ }
+     //  "plugins": [,"stage-0","stage-3"
+    //      [  "transform-class-properties",
+    //          "transform-es2015-classes", {
+    //              "loose": true
+    //          }
+    //      ]
+    //  ]

+ 17 - 0
.gitattributes

@@ -0,0 +1,17 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs     diff=csharp
+
+# Standard to msysgit
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain

+ 45 - 0
.gitignore

@@ -0,0 +1,45 @@
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+node_modules/
+
+# Windows shortcuts
+*.lnk
+
+# =========================
+# Operating System Files
+# =========================
+
+# OSX
+# =========================
+
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk

+ 98 - 0
README.md

@@ -0,0 +1,98 @@
+# MayReact
+    先介绍下MayReact   
+        MayReact是我参照React,preact,anujs等库写的一个miniReact框架;其初衷和anu很相似就是写一个更小更快,
+    不过可以拥有react的api完美兼容其生态的一个框架;(可以跑通绝大部分的官方测试用例保证其兼容性和健壮性)
+        只有2000多行;
+        已在自己的项目测试可以完美替换;
+        现在在进行性能优化,和部分代码重构;目前性能是React的一倍左右;
+        优化后期望追评anu在两倍以上;
+
+写一下过程中的思索以期能帮助也想写一写的人:React大概可以分为这几个模块:
+
+    vnode模块:
+                就是以js对象的形式表示dom,包括createElement,cloneElement,Component,PureComponent,statelessComponent
+
+    render模块:
+                最核心的模块了,包括首次render以及diff,这个过程中又使用到了
+                props模块(给dom设置css属性)
+                event模块(我们的合成事件抹平浏览器差异,统一绑定在document等)
+                ref模块(如果有ref function或string应该在哪回调)
+                context模块(context该怎样向下传递diff之后怎么区分新老context等)
+                PropTypes模块(组件取context需要先判断类型)
+                Children模块(提供操作children的方法)
+                options模块(也可叫调度模块,因为我们不可避免使用到队列来处理diff或各种回调,以及一些插件如redux也需要一些钩子函数)
+
+    首次render:
+                最重要的就是深度优先递归遍历了,难的地方在于我怎么把这个过程设计的高内聚低耦合,这样我能很方便的加一些或去掉一些处理流程
+                比如我在render过程中要设置css属性,要添加event监听事件,有ref要添加,有context要获取,
+                针对vnode,component,statelessComponent,text,我要对应如何render;我这些过程将来我想引入插件该怎么设置钩子函数等;
+                接下来要考虑生命周期了在首次render过程中有constructor,componentWillMount,render,componentDidMount;
+
+                constructor:我如果在constructor调用setState应该怎么办(不推荐写法,但是我们要做兼容处理)我在constructor里面改变props了
+                            应该怎么办
+                
+                componentWillMount:componentWillMount调用setState要合并state(不推荐,需兼容)我在componentWillMount调用了父组件的
+                                    setState放到父组件下一生命周期(不推荐,需兼容)
+                
+                render,componentDidMount:同理都放到下一生命周期处理(无论是当前组件调用还是子组件调用);
+                
+                componentDidMount:如果该组件有ref回调函数,应当先调用componentDidMount回调在调用ref回调,而且componentDidMount
+                                  应当是所有组件都render之后再顺序调用所有的componentDidMount;(如果setState传入了回调函数,
+                                  该回调函数在最后统一触发);
+                
+                还有css属性注意property与attribute的区别,backgroundColor等要转换成background-color(同理各种Webkit,Moz等);
+                event要注意一些特殊事件,focus,blur,wheel,change等;受控组件与非受控组件事件的区别等;
+                render要注意类型是vnode,component,statelessComponent,text等对应不同的render方式,还要考虑我是多个component,
+                多个statelessComponent嵌套怎么区分怎么render等;
+    
+    diff过程:
+                和render的深度优先不同,render我是每层create一个容器dom,然后再render其children不断递归向下,然后再层层把children
+                append到dom里面;
+                但是diff是不同的,diff我已经具备当前的vnode和之前的prevVnode和已经render的dom,在diff的时候如果之前为null或
+                之前的type与key和当前node不一样那我直接渲染新的vnode即可,如果type与key一样那么就需要diffProps了,这时候你或许会考虑
+                我是一个一个children diff还是先分类再统一diff?移动节点我该怎么diff?textNode的diff比较频繁我是不是可以优化?
+                diff过程中突然返回null或false该怎么办,受控组件diff之后变为非受控组件了该怎么办,等等等等;
+                尤其要注意释放不再使用的vnode,这里容易内存泄漏;
+
+                diff过程的生命周期有:componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate
+
+                setState:
+                    diff过程中的所有生命周期不论是当前组件setState还是调用父组件的setState都放到下一生命周期处理;
+                    forceUpdate和setState流程很像不过foreUpdate会忽略shouldComponentUpdate事件,也就是说它会强制走完diff流程;
+                    这部分难点基本上都在子组件调用父组件的setState,context穿透更新,ref回调,各种生命周期回调函数的执行顺序等;
+
+    调度模块:
+                再加上这部分我们的React基本上就全了,在写的过程中你肯定会发现我们有各种各样的回调,setState的回调,ref回调,生命周期回调,
+                event回调等;
+                我们diff的时候也需要一个脏组件队列方便我们排序然后顺序diff,diff过程中我还可能添加新的脏组件等等;
+                这些我们很明显都需要队列来处理,这时候或许你会想我们要一个什么样的队列,我们是把回调函数单独一个队列到时候排序然后顺序执行呢,
+                还是我把回调函数绑定在component上在遍历component的时候调用相应回调呢?
+
+
+--------------------------------------------------------------------------------------------------------------------------------------
+##接着说下MayReact;
+
+    anu我也很推荐地址:https://github.com/RubyLouvre/anu
+
+    MayReact其实May取maybe的意思,寓意无限可能吧;
+
+    在这个过程中又看了preact,anu发现司徒正美先生的anu和我目的一样,而且人家写的很好,所以MayReact有很多地方都是借(chao)鉴(xi)anu和preact的,
+    当然更多的地方是我一点点写出来的;
+    
+    其实我写这个更主要的目的还是为了帮助很多像我一样的人,这些年以来我从GitHub获益良多,看了很多框架,了解了很多思想;
+    便想着自己也写一写,写一写这过程中的坑与思索,以期能帮助更多像我一样的人;
+
+    这三个多月其实完成最初版本的只用了半个月多点吧,剩余时间都在不停的跑测试不停的重构了;其实它并不难,更多的需要你的毅力,
+    不停的思考更好的解决方法;而你写完之后也会有巨大的成就感的;借用我听到的一句话:做事情有两个最重要的品质
+    一个是热情一个是毅力,热情让你开始,毅力让你坚持;
+
+    好了鸡汤说完了,再稍微说下MayReact,为了方便你看最容易看的版本(我新建了一个分支forRead这就是刚跑完测试的版本,
+    虽然有很多地方还很low不过应该是比较容易读的版本,好多地方也都写了当时的理解等等;希望对你有帮助;)
+
+    MayReact现在是65kb 大小跟anu(75kb)差不多,等我优化之后体积应该差不多吧,最后基本上是React的1/5了;性能刚跑了一下anu是React的两倍,
+    我这才是React的一倍,等我优化之后争取比anu还快一些吧;其实写完May我觉得性能重要但是框架的健壮性也是应该保证的,
+    而且我重写一个框架让它更快还是比较简单的毕竟珠玉在前,难的是从无到有,当然这也是我们继续努力的方向;
+
+    絮絮叨叨说了这么多也希望你早日完成你的框架;路漫漫其修远兮,愿你我都能孜孜以求~
+
+ 

Разница между файлами не показана из-за своего большого размера
+ 2147 - 0
dist/May.js


Разница между файлами не показана из-за своего большого размера
+ 2574 - 0
dist/ReactANU.js


Разница между файлами не показана из-за своего большого размера
+ 2415 - 0
dist/inferno.js


Разница между файлами не показана из-за своего большого размера
+ 2151 - 0
dist/nerv.js


+ 645 - 0
dist/preact.js

@@ -0,0 +1,645 @@
+! function () {
+    "use strict";
+
+    function VNode() {}
+
+    function h(nodeName, attributes) {
+        var lastSimple, child, simple, i, children = EMPTY_CHILDREN;
+        for (i = arguments.length; i-- > 2;) {
+            stack.push(arguments[i]);
+        }
+        if (attributes && null != attributes.children) {
+            if (!stack.length) {
+                stack.push(attributes.children);
+            }
+            delete attributes.children;
+        }
+        while (stack.length) {
+            if ((child = stack.pop()) && void 0 !== child.pop)
+                for (i = child.length; i--;) stack.push(child[i]);
+            else {
+                if ('boolean' == typeof child) child = null;
+                if (simple = 'function' != typeof nodeName)
+                    if (null == child) child = '';
+                    else if ('number' == typeof child) child = String(child);
+                else if ('string' != typeof child) simple = !1;
+                if (simple && lastSimple) children[children.length - 1] += child;
+                else if (children === EMPTY_CHILDREN) children = [child];
+                else children.push(child);
+                lastSimple = simple;
+            }
+        }
+        var p = new VNode();
+        p.nodeName = nodeName;
+        p.children = children;
+        p.attributes = null == attributes ? void 0 : attributes;
+        p.key = null == attributes ? void 0 : attributes.key;
+        if (void 0 !== options.vnode) {
+            options.vnode(p);
+        }
+        return p;
+    }
+
+    function extend(obj, props) {
+        for (var i in props) {
+            obj[i] = props[i];
+        }
+        return obj;
+    }
+
+    function cloneElement(vnode, props) {
+        return h(vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children);
+    }
+
+    function enqueueRender(component) {
+        if (!component.__d && (component.__d = !0) && 1 == items.push(component)) {
+            (options.debounceRendering || defer)(rerender);
+        }
+    }
+
+    function rerender() {
+        var p, list = items;
+        items = [];
+        while (p = list.pop()) {
+            if (p.__d) renderComponent(p);
+        }
+    }
+
+    function isSameNodeType(node, vnode, hydrating) {
+        if ("string" == typeof vnode || "number" == typeof vnode) {
+            return void 0 !== node.splitText;
+        }
+        if ("string" == typeof vnode.nodeName) {
+            return !node._componentConstructor && isNamedNode(node, vnode.nodeName);
+        } else {
+            return hydrating || node._componentConstructor === vnode.nodeName;
+        }
+    }
+
+    function isNamedNode(node, nodeName) {
+        return node.__n === nodeName || node.nodeName.toLowerCase() === nodeName.toLowerCase();
+    }
+
+    function getNodeProps(vnode) {
+        var props = extend({}, vnode.attributes);
+        props.children = vnode.children;
+        var defaultProps = vnode.nodeName.defaultProps;
+        if (void 0 !== defaultProps) {
+            for (var i in defaultProps)
+                if (void 0 === props[i]) props[i] = defaultProps[i];
+        }
+        return props;
+    }
+
+    function createNode(nodeName, isSvg) {
+        var node = isSvg ? document.createElementNS("http://www.w3.org/2000/svg", nodeName) : document.createElement(nodeName);
+        node.__n = nodeName;
+        return node;
+    }
+
+    function removeNode(node) {
+        var parentNode = node.parentNode;
+        if (parentNode) {
+            parentNode.removeChild(node);
+        }
+    }
+
+    function setAccessor(node, name, old, value, isSvg) {
+        if ("className" === name) {
+            name = 'class';
+        }
+        if ("key" === name) {;
+        } else if ("ref" === name) {
+            if (old) {
+                old(null);
+            }
+            if (value) {
+                value(node);
+            }
+        } else if ("class" === name && !isSvg) {
+            node.className = value || '';
+        } else if ("style" === name) {
+            if (!value || "string" == typeof value || "string" == typeof old) {
+                node.style.cssText = value || '';
+            }
+            if (value && "object" == typeof value) {
+                if ("string" != typeof old) {
+                    for (var i in old)
+                        if (!(i in value)) node.style[i] = '';
+                }
+                for (var i in value) {
+                    node.style[i] = 'number' == typeof value[i] && !1 === IS_NON_DIMENSIONAL.test(i) ? value[i] + 'px' : value[i];
+                }
+            }
+        } else if ("dangerouslySetInnerHTML" === name) {
+            if (value) {
+                node.innerHTML = value.__html || '';
+            }
+        } else if ("o" == name[0] && "n" == name[1]) {
+            var useCapture = name !== (name = name.replace(/Capture$/, ""));
+            name = name.toLowerCase().substring(2);
+            if (value) {
+                if (!old) {
+                    node.addEventListener(name, eventProxy, useCapture);
+                }
+            } else {
+                node.removeEventListener(name, eventProxy, useCapture);
+            }
+            (node.__l || (node.__l = {}))[name] = value;
+        } else if ("list" !== name && "type" !== name && !isSvg && name in node) {
+            setProperty(node, name, null == value ? "" : value);
+            if (null == value || !1 === value) {
+                node.removeAttribute(name);
+            }
+        } else {
+            var ns = isSvg && name !== (name = name.replace(/^xlink\:?/, ""));
+            if (null == value || !1 === value) {
+                if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());
+                else node.removeAttribute(name);
+            } else if ("function" != typeof value) {
+                if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);
+                else node.setAttribute(name, value);
+            }
+        }
+    }
+
+    function setProperty(node, name, value) {
+        try {
+            node[name] = value;
+        } catch (e) {}
+    }
+
+    function eventProxy(e) {
+        return this.__l[e.type](options.event && options.event(e) || e);
+    }
+
+    function flushMounts() {
+        var c;
+        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 = null != parent && void 0 !== parent.ownerSVGElement;
+            hydrating = null != dom && !("__preactattr_" in dom);
+        }
+        var ret = idiff(dom, vnode, context, mountAll, componentRoot);
+        if (parent && ret.parentNode !== parent) {
+            parent.appendChild(ret);
+        }
+        if (!--diffLevel) {
+            hydrating = !1;
+            if (!componentRoot) {
+                flushMounts();
+            }
+        }
+        return ret;
+    }
+
+    function idiff(dom, vnode, context, mountAll, componentRoot) {
+        var out = dom,
+            prevSvgMode = isSvgMode;
+        if (null == vnode || "boolean" == typeof vnode) {
+            vnode = '';
+        }
+        if ("string" == typeof vnode || "number" == typeof vnode) {
+            if (dom && void 0 !== dom.splitText && dom.parentNode && (!dom._component || componentRoot)) {
+                if (dom.nodeValue != vnode) {
+                    dom.nodeValue = vnode;
+                }
+            } else {
+                out = document.createTextNode(vnode);
+                if (dom) {
+                    if (dom.parentNode) {
+                        dom.parentNode.replaceChild(out, dom);
+                    }
+                    recollectNodeTree(dom, !0);
+                }
+            }
+            out.__preactattr_ = !0;
+            return out;
+        }
+        var vnodeName = vnode.nodeName;
+        if ("function" == typeof vnodeName) {
+            return buildComponentFromVNode(dom, vnode, context, mountAll);
+        }
+        isSvgMode = "svg" === vnodeName ? !0 : "foreignObject" === vnodeName ? !1 : isSvgMode;
+        vnodeName = String(vnodeName);
+        if (!dom || !isNamedNode(dom, vnodeName)) {
+            out = createNode(vnodeName, isSvgMode);
+            if (dom) {
+                while (dom.firstChild) {
+                    out.appendChild(dom.firstChild);
+                }
+                if (dom.parentNode) {
+                    dom.parentNode.replaceChild(out, dom);
+                }
+                recollectNodeTree(dom, !0);
+            }
+        }
+        var fc = out.firstChild,
+            props = out.__preactattr_,
+            vchildren = vnode.children;
+        if (null == props) {
+            props = out.__preactattr_ = {};
+            for (var a = out.attributes, i = a.length; i--;) {
+                props[a[i].name] = a[i].value;
+            }
+        }
+        if (!hydrating && vchildren && 1 === vchildren.length && "string" == typeof vchildren[0] && null != fc && void 0 !== fc.splitText && null == fc.nextSibling) {
+            if (fc.nodeValue != vchildren[0]) {
+                fc.nodeValue = vchildren[0];
+            }
+        } else if (vchildren && vchildren.length || null != fc) {
+            innerDiffNode(out, vchildren, context, mountAll, hydrating || null != props.dangerouslySetInnerHTML);
+        }
+        diffAttributes(out, vnode.attributes, props);
+        isSvgMode = prevSvgMode;
+        return out;
+    }
+
+    function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {
+        var j, c, f, vchild, child, originalChildren = dom.childNodes,
+            children = [],
+            keyed = {},
+            keyedLen = 0,
+            min = 0,
+            len = originalChildren.length,
+            childrenLen = 0,
+            vlen = vchildren ? vchildren.length : 0;
+        if (0 !== len) {
+            for (var i = 0; i < len; i++) {
+                var _child = originalChildren[i],
+                    props = _child.__preactattr_,
+                    key = vlen && props ? _child._component ? _child._component.__k : props.key : null;
+                if (null != key) {
+                    keyedLen++;
+                    keyed[key] = _child;
+                } else if (props || (void 0 !== _child.splitText ? isHydrating ? _child.nodeValue.trim() : !0 : isHydrating)) children[childrenLen++] = _child;
+            }
+        }
+        if (0 !== vlen) {
+            for (var i = 0; i < vlen; i++) {
+                vchild = vchildren[i];
+                child = null;
+                var key = vchild.key;
+                if (null != key) {
+                    if (keyedLen && void 0 !== keyed[key]) {
+                        child = keyed[key];
+                        keyed[key] = void 0;
+                        keyedLen--;
+                    }
+                } else if (!child && min < childrenLen)
+                    for (j = min; j < childrenLen; j++)
+                        if (void 0 !== children[j] && isSameNodeType(c = children[j], vchild, isHydrating)) {
+                            child = c;
+                            children[j] = void 0;
+                            if (j === childrenLen - 1) childrenLen--;
+                            if (j === min) min++;
+                            break;
+                        }
+                child = idiff(child, vchild, context, mountAll);
+                f = originalChildren[i];
+                if (child && child !== dom && child !== f)
+                    if (null == f) dom.appendChild(child);
+                    else if (child === f.nextSibling) removeNode(f);
+                else dom.insertBefore(child, f);
+            }
+        }
+        if (keyedLen) {
+            for (var i in keyed)
+                if (void 0 !== keyed[i]) recollectNodeTree(keyed[i], !1);
+        }
+        while (min <= childrenLen) {
+            if (void 0 !== (child = children[childrenLen--])) recollectNodeTree(child, !1);
+        }
+    }
+
+    function recollectNodeTree(node, unmountOnly) {
+        var component = node._component;
+        if (component) {
+            unmountComponent(component);
+        } else {
+            if (null != node.__preactattr_ && node.__preactattr_.ref) {
+                node.__preactattr_.ref(null);
+            }
+            if (!1 === unmountOnly || null == node.__preactattr_) {
+                removeNode(node);
+            }
+            removeChildren(node);
+        }
+    }
+
+    function removeChildren(node) {
+        node = node.lastChild;
+        while (node) {
+            var next = node.previousSibling;
+            recollectNodeTree(node, !0);
+            node = next;
+        }
+    }
+
+    function diffAttributes(dom, attrs, old) {
+        var name;
+        for (name in old) {
+            if ((!attrs || null == attrs[name]) && null != old[name]) setAccessor(dom, name, old[name], old[name] = void 0, isSvgMode);
+        }
+        for (name in attrs) {
+            if (!('children' === name || 'innerHTML' === name || name in old && attrs[name] === ('value' === name || 'checked' === name ? dom[name] : old[name]))) setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode);
+        }
+    }
+
+    function collectComponent(component) {
+        var name = component.constructor.name;
+        (components[name] || (components[name] = [])).push(component);
+    }
+
+    function createComponent(Ctor, props, context) {
+        var inst, list = components[Ctor.name];
+        if (Ctor.prototype && Ctor.prototype.render) {
+            inst = new Ctor(props, context);
+            Component.call(inst, props, context);
+        } else {
+            inst = new Component(props, context);
+            inst.constructor = Ctor;
+            inst.render = doRender;
+        }
+        if (list) {
+            for (var i = list.length; i--;)
+                if (list[i].constructor === Ctor) {
+                    inst.__b = list[i].__b;
+                    list.splice(i, 1);
+                    break;
+                }
+        }
+        return inst;
+    }
+
+    function doRender(props, state, context) {
+        return this.constructor(props, context);
+    }
+
+    function setComponentProps(component, props, opts, context, mountAll) {
+        if (!component.__x) {
+            component.__x = !0;
+            if (component.__r = props.ref) {
+                delete props.ref;
+            }
+            if (component.__k = props.key) {
+                delete props.key;
+            }
+            if (!component.base || mountAll) {
+                if (component.componentWillMount) {
+                    component.componentWillMount();
+                }
+            } else if (component.componentWillReceiveProps) {
+                component.componentWillReceiveProps(props, context);
+            }
+            if (context && context !== component.context) {
+                if (!component.__c) {
+                    component.__c = component.context;
+                }
+                component.context = context;
+            }
+            if (!component.__p) {
+                component.__p = component.props;
+            }
+            component.props = props;
+            component.__x = !1;
+            if (0 !== opts) {
+                if (1 === opts || !1 !== options.syncComponentUpdates || !component.base) renderComponent(component, 1, mountAll);
+                else enqueueRender(component);
+            }
+            if (component.__r) {
+                component.__r(component);
+            }
+        }
+    }
+
+    function renderComponent(component, opts, mountAll, isChild) {
+        if (!component.__x) {
+            var rendered, inst, cbase, props = component.props,
+                state = component.state,
+                context = component.context,
+                previousProps = component.__p || props,
+                previousState = component.__s || state,
+                previousContext = component.__c || context,
+                isUpdate = component.base,
+                nextBase = component.__b,
+                initialBase = isUpdate || nextBase,
+                initialChildComponent = component._component,
+                skip = !1;
+            if (isUpdate) {
+                component.props = previousProps;
+                component.state = previousState;
+                component.context = previousContext;
+                if (2 !== opts && component.shouldComponentUpdate && !1 === component.shouldComponentUpdate(props, state, context)) {
+                    skip = !0;
+                } else if (component.componentWillUpdate) {
+                    component.componentWillUpdate(props, state, context);
+                }
+                component.props = props;
+                component.state = state;
+                component.context = context;
+            }
+            component.__p = component.__s = component.__c = component.__b = null;
+            component.__d = !1;
+            if (!skip) {
+                rendered = component.render(props, state, context);
+                if (component.getChildContext) {
+                    context = extend(extend({}, context), component.getChildContext());
+                }
+                var toUnmount, base, childComponent = rendered && rendered.nodeName;
+                if ("function" == typeof childComponent) {
+                    var childProps = getNodeProps(rendered);
+                    inst = initialChildComponent;
+                    if (inst && inst.constructor === childComponent && childProps.key == inst.__k) {
+                        setComponentProps(inst, childProps, 1, context, !1);
+                    } else {
+                        toUnmount = inst;
+                        component._component = inst = createComponent(childComponent, childProps, context);
+                        inst.__b = inst.__b || nextBase;
+                        inst.__u = component;
+                        setComponentProps(inst, childProps, 0, context, !1);
+                        renderComponent(inst, 1, mountAll, !0);
+                    }
+                    base = inst.base;
+                } else {
+                    cbase = initialBase;
+                    toUnmount = initialChildComponent;
+                    if (toUnmount) {
+                        cbase = component._component = null;
+                    }
+                    if (initialBase || 1 === opts) {
+                        if (cbase) {
+                            cbase._component = null;
+                        }
+                        base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, !0);
+                    }
+                }
+                if (initialBase && base !== initialBase && inst !== initialChildComponent) {
+                    var baseParent = initialBase.parentNode;
+                    if (baseParent && base !== baseParent) {
+                        baseParent.replaceChild(base, initialBase);
+                        if (!toUnmount) {
+                            initialBase._component = null;
+                            recollectNodeTree(initialBase, !1);
+                        }
+                    }
+                }
+                if (toUnmount) {
+                    unmountComponent(toUnmount);
+                }
+                component.base = base;
+                if (base && !isChild) {
+                    var componentRef = component,
+                        t = component;
+                    while (t = t.__u) {
+                        (componentRef = t).base = base;
+                    }
+                    base._component = componentRef;
+                    base._componentConstructor = componentRef.constructor;
+                }
+            }
+            if (!isUpdate || mountAll) {
+                mounts.unshift(component);
+            } else if (!skip) {
+                if (component.componentDidUpdate) {
+                    component.componentDidUpdate(previousProps, previousState, previousContext);
+                }
+                if (options.afterUpdate) {
+                    options.afterUpdate(component);
+                }
+            }
+            if (null != component.__h) {
+                while (component.__h.length) component.__h.pop().call(component);
+            }
+            if (!diffLevel && !isChild) {
+                flushMounts();
+            }
+        }
+    }
+
+    function buildComponentFromVNode(dom, vnode, context, mountAll) {
+        var c = dom && dom._component,
+            originalComponent = c,
+            oldDom = dom,
+            isDirectOwner = c && dom._componentConstructor === vnode.nodeName,
+            isOwner = isDirectOwner,
+            props = getNodeProps(vnode);
+        while (c && !isOwner && (c = c.__u)) {
+            isOwner = c.constructor === vnode.nodeName;
+        }
+        if (c && isOwner && (!mountAll || c._component)) {
+            setComponentProps(c, props, 3, context, mountAll);
+            dom = c.base;
+        } else {
+            if (originalComponent && !isDirectOwner) {
+                unmountComponent(originalComponent);
+                dom = oldDom = null;
+            }
+            c = createComponent(vnode.nodeName, props, context);
+            if (dom && !c.__b) {
+                c.__b = dom;
+                oldDom = null;
+            }
+            setComponentProps(c, props, 1, context, mountAll);
+            dom = c.base;
+            if (oldDom && dom !== oldDom) {
+                oldDom._component = null;
+                recollectNodeTree(oldDom, !1);
+            }
+        }
+        return dom;
+    }
+
+    function unmountComponent(component) {
+        if (options.beforeUnmount) {
+            options.beforeUnmount(component);
+        }
+        var base = component.base;
+        component.__x = !0;
+        if (component.componentWillUnmount) {
+            component.componentWillUnmount();
+        }
+        component.base = null;
+        var inner = component._component;
+        if (inner) {
+            unmountComponent(inner);
+        } else if (base) {
+            if (base.__preactattr_ && base.__preactattr_.ref) {
+                base.__preactattr_.ref(null);
+            }
+            component.__b = base;
+            removeNode(base);
+            collectComponent(component);
+            removeChildren(base);
+        }
+        if (component.__r) {
+            component.__r(null);
+        }
+    }
+
+    function Component(props, context) {
+        this.__d = !0;
+        this.context = context;
+        this.props = props;
+        this.state = this.state || {};
+    }
+
+    function render(vnode, parent, merge) {
+        return diff(merge, vnode, {}, !1, parent, !1);
+    }
+    var options = {};
+    var stack = [];
+    var EMPTY_CHILDREN = [];
+    var defer = "function" == typeof Promise ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
+    var IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;
+    var items = [];
+    var mounts = [];
+    var diffLevel = 0;
+    var isSvgMode = !1;
+    var hydrating = !1;
+    var components = {};
+    extend(Component.prototype, {
+        setState: function (state, callback) {
+            var s = this.state;
+            if (!this.__s) {
+                this.__s = extend({}, s);
+            }
+            extend(s, "function" == typeof state ? state(s, this.props) : state);
+            if (callback) {
+                (this.__h = this.__h || []).push(callback);
+            }
+            enqueueRender(this);
+        },
+        forceUpdate: function (callback) {
+            if (callback) {
+                (this.__h = this.__h || []).push(callback);
+            }
+            renderComponent(this, 2);
+        },
+        render: function () {}
+    });
+    var preact = {
+        h: h,
+        createElement: h,
+        cloneElement: cloneElement,
+        Component: Component,
+        render: render,
+        rerender: rerender,
+        options: options
+    };
+    if ("undefined" != typeof module) {
+        module.exports = preact;
+    } else {
+        self.preact = preact;
+    }
+}();

Разница между файлами не показана из-за своего большого размера
+ 19116 - 0
dist/react-dom.js


Разница между файлами не показана из-за своего большого размера
+ 1357 - 0
dist/react.development.js


Разница между файлами не показана из-за своего большого размера
+ 3987 - 0
dist/react.js


+ 87 - 0
karma.conf.js

@@ -0,0 +1,87 @@
+// Karma configuration
+// Generated on Thu Nov 23 2017 22:01:37 GMT+0800 (中国标准时间)
+
+var path = require("path");
+var webpack = require("webpack");
+
+module.exports = function(config) {
+  config.set({
+
+    // base path that will be used to resolve all patterns (eg. files, exclude)
+    basePath: '',
+
+
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+
+    // list of files / patterns to load in the browser
+    files: [
+      'test/*.js',
+      'test/**/*.js',
+      'test/**/*.jsx',
+      // 'test/**/ReactComponentLifeCycle-test.jsx',
+    ],
+
+
+    // list of files to exclude
+    exclude: [
+    ],
+
+
+    // preprocess matching files before serving them to the browser
+    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+    preprocessors: {
+        'test/**/*.js': ['webpack'],
+        'test/**/*.jsx': ['webpack'],
+    },
+
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+
+    // web server port
+    port: 9876,
+
+      webpack: {
+          module: {
+              /* Transpile source and test files */
+              rules: [{
+                  test: /\.jsx?$/,
+                  use: "babel-loader",
+              }]
+          }
+          
+      },
+      
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+
+    // start these browsers
+    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+    browsers: [],
+
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false,
+
+    // Concurrency level
+    // how many browser should be started simultaneous
+    concurrency: Infinity
+  })
+}

+ 28 - 0
lib/ReactPropTypes.js

@@ -0,0 +1,28 @@
+(function umd(root, factory) {
+    if (typeof exports === "object" && typeof module === "object") {module.exports = factory();}
+    else if (typeof define === "function" && define.amd) {define([], factory);}
+    else if (typeof exports === "object") {exports["ReactPropTypes"] = factory();}
+    else {root["ReactPropTypes"] = factory();}
+})(this, function () {
+    var check = function () {
+        return check;
+    };
+    check.isRequired = check;
+    return {
+        array: check,
+        bool: check,
+        func: check,
+        number: check,
+        object: check,
+        string: check,
+        any: check,
+        arrayOf: check,
+        element: check,
+        instanceOf: check,
+        node: check,
+        objectOf: check,
+        oneOf: check,
+        oneOfType: check,
+        shape: check
+    };
+});

+ 147 - 0
lib/ReactTestUtils.js

@@ -0,0 +1,147 @@
+import {
+    createElement
+} from '../src/May';
+import {
+    Component
+} from '../src/Component';
+import {
+    render
+} from '../src/may-dom/MayDom';
+import {
+    dispatchEvent
+} from '../src/event';
+
+var ReactDOM = {
+    render: render
+}
+
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+
+// import React from '../dist/ReactANU';
+// var ReactDOM = React;
+
+function findAllInRenderedTree(inst, test) {
+    var ret = [];
+    if (!inst) {
+        return ret;
+    }
+    if (inst.nodeType && inst.nodeType === 1) { //dom
+        if (test(inst)) {
+            ret.push(inst);
+        }
+        var children = [].slice.call(inst.childNodes);
+        for (var i = 0; i < children.length; i++) {
+            var el = children[i];
+            ret = ret.concat(findAllInRenderedTree(el, test));
+        }
+    }
+    if (inst.nodeType && inst.nodeType === 8) { //如果是文本,注释
+        return ret;
+    } else if (inst.mayInfo) { //如果是元素虚拟DOM
+        var dom = inst.mayInfo.hostNode;
+        if (dom && dom.nodeType === 1 && test(dom)) {
+            ret.push(dom);
+        }
+        var children = [].slice.call(dom.childNodes);
+        if (children) {
+            for (var i = 0, n = children.length; i < n; i++) {
+                var el = children[i];
+                ret = ret.concat(findAllInRenderedTree(el, test));
+            }
+        }
+
+    } else if (inst.mayInst) { //组件实例都带有refs对象
+        var rendered = inst.mayInst.rendered;
+        if (rendered) {
+            //如果是实例
+            ret = ret.concat(findAllInRenderedTree(rendered, test));
+        }
+    }
+
+    return ret;
+}
+
+var ReactTestUtils = {
+    renderIntoDocument: function (element) {
+        var div = document.createElement("div");
+        return ReactDOM.render(element, div);
+    },
+    isDOMComponent: function (inst) {
+        return !!(inst && inst instanceof HTMLElement);
+    },
+    findAllInRenderedTree: function (inst, fn) {
+        if (!inst) {
+            return [];
+        }
+        return findAllInRenderedTree(inst, fn);
+    },
+    /**
+     * 找出所有匹配指定标签的节点
+     */
+    scryRenderedDOMComponentsWithTag: function (root, tagName) {
+        return ReactTestUtils.findAllInRenderedTree(root, function (inst) {
+            return (
+                ReactTestUtils.isDOMComponent(inst) &&
+                inst.tagName.toUpperCase() === tagName.toUpperCase()
+            );
+        });
+    },
+    /**
+     * 找出所有匹配指定className的节点
+     */
+    scryRenderedDOMComponentsWithClass: function (root, classNames) {
+        return ReactTestUtils.findAllInRenderedTree(root, function (inst) {
+            if (ReactTestUtils.isDOMComponent(inst)) {
+                var className = inst.className;
+                if (typeof className !== "string") {
+                    // SVG, probably.
+                    className = inst.getAttribute("class") || "";
+                }
+                var classList = className.split(/\s+/);
+                if (!Array.isArray(classNames)) {
+                    classNames = classNames.split(/\s+/);
+                }
+                return classNames.every(function (name) {
+                    return classList.indexOf(name) !== -1;
+                });
+            }
+            return false;
+        });
+    },
+    /**
+     *与scryRenderedDOMComponentsWithClass用法相同,但只返回一个节点,如有零个或多个匹配的节点就报错
+     */
+    findRenderedDOMComponentWithClass: function (root, className) {
+        var all = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+            root,
+            className
+        );
+        if (all.length !== 1) {
+            throw new Error(
+                "Did not find exactly one match (found: " +
+                all.length +
+                ") " +
+                "for class:" +
+                className
+            );
+        }
+        return all[0];
+    },
+    Simulate: {},
+    SimulateNative: {}
+}
+"click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove,mouseover,mouseout,mouseEnter,mouseLeave,focus".replace(/\w+/g, function (name) {
+    ReactTestUtils.Simulate[name] = function (node, opts) {
+        if (!node || node.nodeType !== 1) {
+            throw "第一个参数必须为元素节点";
+        }
+        var fakeNativeEvent = opts || {};
+        fakeNativeEvent.target = node;
+        fakeNativeEvent.simulated = true;
+        fakeNativeEvent.type = name.toLowerCase();
+        dispatchEvent(fakeNativeEvent, name.toLowerCase());
+    };
+});
+export default ReactTestUtils;

+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "name": "mays",
+  "version": "1.0.0",
+  "description": "",
+  "main": "may.js",
+  "scripts": {
+    "test": "karma start",
+    "build": "rollup -c scripts/rollup.js"
+  },
+  "author": "c",
+  "license": "MIT",
+  "tips": "babel-preset-env是来替代 babel-preset(latest,es2015~)等插件包的 stage-1是箭头函数等编译 stage-3是async等编译",
+  "devDependencies": {
+    "babel-core": "^6.26.0",
+    "babel-loader": "^7.1.2",
+    "babel-plugin-transform-class-properties": "^6.24.1",
+    "babel-plugin-transform-es2015-classes": "^6.24.1",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-env": "^1.6.1",
+    "babel-preset-react": "^6.24.1",
+    "babel-preset-stage-0": "^6.24.1",
+    "babel-preset-stage-1": "^6.24.1",
+    "babel-preset-stage-3": "^6.24.1",
+    "jasmine-core": "^2.8.0",
+    "karma": "^1.7.1",
+    "karma-chrome-launcher": "^2.2.0",
+    "karma-event-driver-ext": "^0.0.13",
+    "karma-firefox-launcher": "^1.0.1",
+    "karma-jasmine": "^1.1.0",
+    "karma-webpack": "^2.0.6",
+    "react": "^16.2.0",
+    "react-dom": "^16.2.0",
+    "react-redux": "^5.0.6",
+    "redux": "^3.7.2",
+    "rollup": "^0.50.1",
+    "rollup-plugin-babel": "^3.0.2",
+    "webpack": "^3.8.1"
+  },
+  "dependencies": {
+    "babel-runtime": "^6.26.0",
+    "preact-compat": "^3.17.0"
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 122855 - 0
scripts/index_backup.js


+ 10 - 0
scripts/rollup.js

@@ -0,0 +1,10 @@
+import babel from "rollup-plugin-babel";
+
+export default {
+    input:'./src/May.js',
+    output:{
+        file:'./dist/May.js',
+        format: "cjs"
+    },
+    plugins:[babel]
+}

+ 70 - 0
src/Children.js

@@ -0,0 +1,70 @@
+import { getIteractor } from './may-dom/Iteractor';
+export const Children = {
+    only: function (child) {
+        if (child && !Array.isArray(child)) {
+            return child;
+        }
+        if (child && child.length === 1 && child[0].mtype) {
+            return child[0];
+        }
+        throw new Error("expect only one child");
+    },
+    forEach: function (children, callback, context) {
+        var ret;
+        if (!children) {
+            return null;
+        }
+        ret = toArray(children);
+
+        ret.forEach(callback, context);
+        return ret;
+
+    },
+    map: function (children, callback, context) {
+        var ret = [];
+        if (children == null) {
+            //null 或undefinded直接返回
+            return children;
+        }
+        toArray(children).forEach(function (item, index) {
+            var res = callback.call(context, item, index);
+            if (res == null) {
+                return;
+            } else {
+                ret.push(res);
+            }
+        });
+        return ret;
+
+    },
+    toArray: function (children) {
+        if (children == null) {
+            return [];
+        }
+        return toArray(children);
+    }
+}
+function toArray(children) {
+    var ret = [];
+    if (Array.isArray(children)) {
+        for (var i = 0; i < children.length; i++) {
+            var c = children[i];
+            if (c.type) {
+                ret.push(c);
+            } else { //有可能是子数组iterator
+                var iteratorFn = getIteractor(c);
+                if (iteratorFn) {
+                    var iterators = callIteractor(iteratorFn, c);
+                    for (var _i = 0; _i < iterators.length; _i++) {
+                        ret.push(iterators[_i]);
+                    }
+                } else {
+                    ret.push(c);
+                }
+            }
+        }
+    } else {
+        ret.push(children);
+    }
+    return ret;
+}

+ 91 - 0
src/Component.js

@@ -0,0 +1,91 @@
+import {
+    reRender
+} from './may-dom/MayDom';
+import {
+    mergeState
+} from './util';
+import {
+    mayQueue
+} from './may-dom/scheduler';
+
+export function Component(props, context, key, ref) {
+    this.props = props;
+    this.key = key;
+    this.ref = ref;
+    this.context = context;
+    //新建个对象存放各种信息
+    this.mayInst = {};
+}
+
+Component.prototype.setState = function (state, callback) {
+    var lifeState = this.mayInst.lifeState;
+
+    if (callback) {
+        //回调队列调用之前也许sort
+        callback = callback.bind(this);
+        callback._mountOrder = this.mayInst.mountOrder;
+        mayQueue.callbackQueue.push(callback);
+    }
+    if (this.mayInst.mergeStateQueue) {
+        this.mayInst.mergeStateQueue.push(state);
+    } else {
+        this.mayInst.mergeStateQueue = new Array(state);
+    }
+    if (mayQueue.isInEvent) {
+        //如果在绑定事件中 触发setState合并state
+        if (mayQueue.dirtyComponentsQueue.indexOf(this) === -1) {
+            this.mayInst.dirty = true;
+            mayQueue.dirtyComponentsQueue.push(this);
+        }
+        return;
+    }
+
+    switch (lifeState) {
+        //componentWillReceiveProps触发setState会合并state
+        case 1: //componentWillMount 触发setState会合并state
+            return;
+        //ComponentWillReceiveProps 中setState  3
+        //子组件在ComponentWillMount中调用父组件的setState  3
+        case 3:
+        case 2: //componentDidMount 触发setState会放到下一周期  2
+            if (mayQueue.dirtyComponentsQueue.indexOf(this) === -1) {
+                this.mayInst.dirty = true;
+                this.mayInst.needNextRender = true; //子组件componentWillReceiveProps 调用父组件的setState 触发setState会放到下一周期
+                mayQueue.dirtyComponentsQueue.push(this);
+            }
+            return;
+        default:
+            if (mayQueue.dirtyComponentsQueue.indexOf(this) === -1) {
+                this.mayInst.dirty = true;
+                mayQueue.dirtyComponentsQueue.push(this);
+            }
+            break;
+    }
+
+    mayQueue.clearQueue();
+}
+Component.prototype.forceUpdate = function (callback) {
+    if (callback) {
+        mayQueue.callbackQueue.push(callback.bind(this));
+    }
+    if (mayQueue.dirtyComponentsQueue.indexOf(this) === -1) {
+        this.mayInst.forceUpdate = true;
+        this.mayInst.dirty = true;
+        mayQueue.dirtyComponentsQueue.push(this);
+    }
+    var lifeState = this.mayInst.lifeState;
+    switch (lifeState) {
+        case 1: //ComponentWillMount
+        case 2: //componentDidMount 
+        case 3: //ComponentWillReceiveProps 
+        case 4: //ComponentWillReceiveProps 
+            return;
+        default:
+            mayQueue.clearQueue();
+            break;
+    }
+
+}
+Component.prototype.isMounted = function () {
+    return this.mayInst ? (!!(this.mayInst.rendered && this.mayInst.rendered.mayInfo.hostNode) || this.mayInst.isEmpty) : false;
+}

+ 31 - 0
src/May.js

@@ -0,0 +1,31 @@
+import { createElement } from './MayElement';
+import { Component } from './Component';
+import { PureComponent } from './PureComponent';
+import { cloneElement } from './cloneElement';
+import { Children } from './Children';
+import { PropTypes } from './PropTypes';
+import { render, findDOMNode, unmountComponentAtNode } from './may-dom/MayDom';
+
+var May = {
+    createElement: createElement,
+    Component: Component,
+    PureComponent: PureComponent,
+    cloneElement: cloneElement,
+    Children: Children,
+    render: render,
+    PropTypes: PropTypes,
+    findDOMNode: findDOMNode,
+    unmountComponentAtNode: unmountComponentAtNode,
+    isValidElement: function (vnode) {
+        return vnode && vnode.mtype;
+    },
+    createFactory: function createFactory(type) {
+        console.error("createFactory is deprecated");
+        var factory = createElement.bind(null, type);
+        factory.type = type;
+        return factory;
+    }
+}
+window.React = window.ReactDOM = May;
+
+export default May;

+ 108 - 0
src/MayElement.js

@@ -0,0 +1,108 @@
+/**
+ * 
+ * @param {*} type dom类型或func
+ * @param {*} props dom属性
+ * @param {*} children 子节点
+ */
+export function createElement(type, config, children) {
+
+    var props = {};
+    var key = null;
+    var ref = null;
+    var mtype = null;
+    var getContext = null;
+    var len = arguments.length - 2;
+    //0代表没有ref 1代表ref为func 2为string
+    var refType = 0;
+    //既然render的时候都需要判断下type 是fun或string
+    //那把这一步提前 比render循环里判断更好些;
+    var _type = typeof type;
+    switch (_type) {
+        case 'string': //HtmlElement 1  SVG 3
+            mtype = type !== 'svg' ? 1 : 3;
+            break;
+        case 'function': //component 或statelessComponent
+            mtype = 2;
+            //如果有contextTypes代表该组件可以取context
+            type.contextTypes && (getContext = true);
+            break;
+    }
+    if (config) {
+        key = config.key !== void 0 ? ('' + config.key) : null;
+        ref = config.ref || null;
+        if (typeof ref === 'number') {
+            ref = '' + ref;
+        }
+        if (ref) {
+            var _refType = typeof ref;
+            switch (_refType) {
+                case 'function':
+                    refType = 1;
+                    break;
+                case 'string':
+                    refType = 2;
+                    break;
+            }
+        }
+        for (var i in config) {
+            if (i !== 'key' && i != 'ref') {
+                props[i] = config[i];
+            }
+        }
+    }
+    var defaultProps = type.defaultProps;
+    if (defaultProps) {
+        for (var propName in defaultProps) {
+            if (props[propName] === void 666) {
+                props[propName] = defaultProps[propName];
+            }
+        }
+    }
+    if (len > 1) {
+        var array = new Array();
+        for (var i = 0; i < len; i++) {
+            var c = arguments[i + 2];
+            if (!Array.isArray(c)) {
+                array.push(c);
+            } else {
+                c.forEach(function (item) {
+                    array.push(item);
+                });
+            }
+        }
+        props.children = array;
+    } else if (len === 1) {
+        props.children = children;
+    }
+
+    return new Vnode(type, key, ref, props, mtype, getContext, refType);
+}
+
+/**
+ * 
+ * @param {*} type 
+ * @param {*} key 
+ * @param {*} ref 
+ * @param {*} self 
+ * @param {*} source 
+ * @param {*} owner 
+ * @param {*} props 
+ */
+var Vnode = function (type, key, ref, props, mtype, getContext, refType) {
+    this.type = type;
+    this.key = key;
+    this.ref = ref;
+    this.props = props;
+    this.$$typeof = 1;
+    this.mtype = mtype;
+    //之前是直接赋在vnode上 多了之后容易混 
+    //单独新建个对象存放各种信息
+    this.mayInfo = {};
+    this.getContext = getContext;
+    this.refType = refType;
+}
+
+
+// function isArray(o) {
+//     return Object.prototype.toString.call(o) == '[object Array]';
+// }

+ 22 - 0
src/PropTypes.js

@@ -0,0 +1,22 @@
+//为了兼容yo
+var check = function () {
+    return check;
+};
+check.isRequired = check;
+export var PropTypes = {
+    array: check,
+    bool: check,
+    func: check,
+    number: check,
+    object: check,
+    string: check,
+    any: check,
+    arrayOf: check,
+    element: check,
+    instanceOf: check,
+    node: check,
+    objectOf: check,
+    oneOf: check,
+    oneOfType: check,
+    shape: check
+};

+ 53 - 0
src/PureComponent.js

@@ -0,0 +1,53 @@
+import {
+    Component
+} from './Component';
+import {
+    inherits
+} from './util';
+
+export function PureComponent(props, key, ref, context) {
+    return Component.apply(this, arguments);
+}
+var fn = inherits(PureComponent, Component);
+//返回false 则不进行之后的渲染
+fn.shouldComponentUpdate = function (nextProps, nextState, context) {
+    var ret = true;;
+    var a = shallowEqual(this.props, nextProps);
+    var b = shallowEqual(this.state, nextState);
+    if (a === true && b === true) {
+        ret = false;
+    }
+    return ret;
+}
+export function shallowCompare(instance, nextProps, nextState) {
+    var ret = true;;
+    var a = shallowEqual(instance.props, nextProps);
+    var b = shallowEqual(instance.state, nextState);
+    if (a === true && b === true) {
+        ret = false;
+    }
+    return ret;
+}
+
+
+export function shallowEqual(now, next) {
+    if (Object.is(now, next)) {
+        return true;
+    }
+    //必须是对象
+    if ((now && typeof now !== 'object') || (next && typeof next !== 'object')) {
+        return false;
+    }
+    var keysA = Object.keys(now);
+    var keysB = Object.keys(next);
+    if (keysA.length !== keysB.length) {
+        return false;
+    }
+    // Test for A's keys different from B.
+    for (var i = 0; i < keysA.length; i++) {
+        if (!hasOwnProperty.call(next, keysA[i]) || !Object.is(now[keysA[i]], next[keysA[i]])) {
+            return false;
+        }
+    }
+    return true;
+}

+ 16 - 0
src/Refs.js

@@ -0,0 +1,16 @@
+import { lifeCycleQueue } from './may-dom/scheduler';
+export var Refs = {
+    //当子component含有ref的时候,需要把对应的instance或dom添加到 父component的refs属性中
+    //如果在mountComponent中做这样的操作需要每一层都要添加owner 放在外面更好些;
+    currentOwner: null,
+    //开始render时isRoot为true,方便ref定位最顶端节点
+    isRoot: false,
+    attachRef: function (vnode, hostNode) {
+        if (vnode.refType === 1) { //func
+            hostNode.mayInst && hostNode.mayInst.stateless && (hostNode = null);
+            lifeCycleQueue.push(vnode.ref.bind(vnode, hostNode));
+        } else if (vnode.refType === 2) { //string
+            this.currentOwner.refs[vnode.ref] = hostNode;
+        }
+    }
+}

+ 26 - 0
src/cloneElement.js

@@ -0,0 +1,26 @@
+import {
+    createElement
+} from './MayElement';
+
+export function cloneElement(element, additionalProps) {
+    var type = element.type;
+    var props = element.props;
+    var mergeProps = {};
+    Object.assign(mergeProps, props, additionalProps);
+
+    var config = {};
+    if (element.key) {
+        config.key = element.key;
+    }
+    if (element.ref) {
+        config.ref = element.ref;
+    }
+    for (const key in mergeProps) {
+        if (key !== 'children') {
+            config[key] = mergeProps[key];
+        }
+    }
+    var children = mergeProps.children;
+    var ret = createElement(type, config, children);
+    return ret;
+}

+ 333 - 0
src/diffProps.js

@@ -0,0 +1,333 @@
+import {
+    addEvent,
+    getBrowserName,
+    eventHooks
+} from './event';
+import {
+    Refs
+} from './Refs';
+import {
+    mayQueue
+} from './may-dom/scheduler';
+
+
+//之前是 mount的时候setDomAttr一个方法 diff的时候diffProps一个方法
+//后来发现 写着写着要修改点setDomAttr的内容 diff的时候还要在判断一遍
+//那干脆把这两个合成一个方法好了
+export function diffProps(prev, now) {
+    var props = now.props;
+    var hostNode = now.mayInfo.hostNode;
+    var prevStyle = prev && prev.props.style;
+    var nowStyle = props.style;
+    var isSVG = now.mayInfo.isSVG;
+    if (!prev) { //setDomAttr
+        for (var key in props) {
+            setDomAttr(hostNode, key, props[key]);
+        }
+        if (nowStyle) {
+            patchStyle(hostNode, prevStyle, nowStyle);
+        }
+    } else {
+        var prevProps = prev.props;
+        for (var name in props) {
+            if (name !== 'children' && !(props[name] === prevProps[name])) {
+                setDomAttr(hostNode, name, props[name]);
+            }
+        }
+        for (var prop in prevProps) {
+            if (prop !== 'children' && (props[prop] === void 666)) {
+                removeDomAttr(hostNode, prevProps, prop);
+            }
+        }
+
+        if (prevStyle !== nowStyle) {
+            patchStyle(hostNode, prevStyle, nowStyle);
+        }
+    }
+
+}
+
+export var FormElement = {
+    input: 1,
+    select: 1,
+    // option: 1,
+    textarea: 1
+}
+/**
+ * 设置DOM属性
+ * @param {*} dom 
+ * @param {*} key 
+ * @param {*} val 
+ */
+export function setDomAttr(dom, key, val) {
+    var nodeType = dom.nodeType;
+    // Don't get/set attributes on text, comment and attribute nodes
+    if (nodeType === 3 || nodeType === 8 || nodeType === 2) {
+        return;
+    }
+    if (!isEvent(key)) {
+        switch (key) {
+            case 'children':
+            case 'style':
+            case 'key':
+                break;
+            case 'dangerouslySetInnerHTML':
+                var html = val && val.__html;
+                dom.innerHTML = html;
+                break;
+            case 'defaultValue': //input等受控组件
+                key = 'value';
+            case 'className':
+                key = 'class';
+            default:
+                if (key in dom) { //property
+                    try {
+                        if (val !== null && val !== false) {
+                            dom[key] = val;
+                        }
+                    } catch (e) {
+                        dom.setAttribute(key, val + '');
+                    }
+                } else { //attribute
+                    if (val !== null && val !== false) {
+                        //attribute 永远是字符串
+                        dom.setAttribute(key, val + '');
+                    } else {
+                        //如果是null 或 false 不必添加
+                        dom.removeAttribute(key);
+                    }
+                }
+                break;
+        }
+
+    } else {
+        var e = key.substring(2).toLowerCase();
+        var eventName = getBrowserName(key);
+        var listener = dom._listener || (dom._listener = {});
+        if (!listener[e]) {
+            //添加过一次之后不必再添加;
+            addEvent(eventName);
+            var hook = eventHooks[eventName];
+            if (hook) {
+                //input的change等特殊事件需要特殊处理
+                hook(dom, eventName);
+            }
+        }
+        listener[e] = val;
+    }
+}
+export function removeDomAttr(dom, props, key) {
+    var nodeType = dom.nodeType;
+    // Don't get/set attributes on text, comment and attribute nodes
+    if (nodeType === 3 || nodeType === 8 || nodeType === 2) {
+        return;
+    }
+    if (!isEvent(key)) {
+        switch (key) {
+            case 'dangerouslySetInnerHTML':
+                dom.innerHTML = '';
+            case 'className':
+                dom.removeAttribute('class');
+                break;
+            default:
+                if (key in dom) {
+                    dom[key] = '';
+                } else {
+                    dom.removeAttribute(key);
+                }
+        }
+    } else {
+        var e = key.substring(2).toLowerCase();
+        if (dom._listener && dom._listener[e]) {
+            delete dom._listener[e];
+        }
+    }
+}
+
+function isEvent(name) {
+    return /^on[A-Z]/.test(name);
+}
+export function patchStyle(dom, prevStyle, newStyle) {
+    var _style = '';
+    for (var name in newStyle) {
+        var _type = typeof newStyle[name];
+        //backgroundColor 替换为 background-color Webkit替换为-webkit-   ms单独替换一次
+        var cssName = name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^ms-/i, '-ms-');
+        switch (_type) {
+            case 'string':
+                _style = newStyle[name].trim();
+                break;
+            case 'number':
+                _style = newStyle[name];
+                if (cssSuffix[name]) {
+                    _style += newStyle[name] !== 0 ? +cssSuffix[name] : '';
+                }
+                break;
+            case 'boolean':
+                _style = '';
+                break;
+            default:
+                _style = newStyle[name]
+                break;
+        }
+        dom.style[cssName] = _style;
+    }
+    for (var key in prevStyle) {
+        if (!newStyle || !(key in newStyle)) {
+            key = key.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^ms-/i, '-ms-');
+            dom.style[key] = '';
+        }
+    }
+}
+/**
+ input, select, textarea这几个元素如果指定了value/checked的**状态属性**,就会包装成受控组件或非受控组件
+受控组件是指,用户除了为它指定**状态属性**,还为它指定了onChange/onInput/disabled等用于控制此状态属性
+变动的属性
+反之,它就是非受控组件,非受控组件会在框架内部添加一些事件,阻止**状态属性**被用户的行为改变,只能被setState改变
+*/
+var eGroup = [{
+    'onChange': 1,
+    'onInput': 1,
+    'readOnly': 1,
+    'disabled': 1
+}, {
+    'onChange': 1,
+    'onClick': 1,
+    'readOnly': 1,
+    'disabled': 1
+}, {
+    'onChange': 1,
+    'disabled': 1
+}]
+export function getIsControlled(hostNode, vnode) {
+    //记录第一次render的值 非受控组件值不可变
+    var type = hostNode.type;
+    var hasValue, isControlled, eObj, event, ename;
+    var vprops = vnode.props;
+    switch (type) {
+        case 'text':
+        case 'textarea':
+            //非受控组件有value属性 但是没有绑定修改value的onChange等事件
+            hasValue = 'value' in vprops;
+            ename = 'oninput';
+            eObj = eGroup[0];
+            //如果是非受控组件那么我们需要阻止用户改变数据
+            event = preventInput;
+            break;
+        case 'checkbox':
+        case 'radio':
+            hasValue = 'checked' in vprops;
+            ename = 'onclick';
+            eObj = eGroup[1];
+            event = preventClick;
+            break;
+        case 'select-one':
+        case 'select-multiple':
+            hasValue = 'value' in vprops;
+            ename = 'onchange';
+            eObj = eGroup[2];
+            event = preventChange;
+            var _val = vnode.props['value'] || vnode.props['defaultValue'] || '';
+            var _optionsChilds = [].slice.call(hostNode.childNodes);
+            if (_optionsChilds) {
+                for (var k = 0; k < _optionsChilds.length; k++) {
+                    var oChild = _optionsChilds[k];
+                    if (oChild.value === _val) {
+                        oChild.selected = true;
+                        hostNode._selectIndex = k;
+                    }
+                }
+            }
+            break;
+    }
+    isControlled = hasValue && hasEventProps(vprops, eObj);
+    if (!isControlled) {
+        console.warn(vnode.type + (vprops['type'] ? ('[type=' + vprops['type'] + ']') : '') + '元素为非受控组件,用户无法通过输入改变元素的值;更多信息参见React官方文档https://reactjs.org/docs/uncontrolled-components.html');
+        setDomAttr(hostNode, ename, event);
+    }
+    return isControlled;
+}
+
+function preventInput(e) {
+    var target = e.target;
+    var name = e.type === "textarea" ? "innerHTML" : "value";
+    target[name] = target._lastValue;
+}
+
+function preventClick(e) {
+    e.preventDefault();
+}
+
+function preventChange(e) {
+    var target = e.target;
+    if (target._selectIndex) {
+        target.options[target._selectIndex].selected = true;
+    }
+}
+
+function hasEventProps(props, events) {
+    for (var key in props) {
+        if (events[key]) {
+            return true;
+        }
+    }
+}
+const cssSuffix = {
+    //需要加后缀如 px s(秒)等css属性摘出来
+    //其实用正则更简洁一些,不过可能 可读性可维护性不如key value
+    //动画属性(Animation)
+    animationDelay: 's',
+    //CSS 边框属性(Border 和 Outline)
+    borderBottomWidth: 'px',
+    borderLeftWidth: 'px',
+    borderRightWidth: 'px',
+    borderTopWidth: 'px',
+    borderWidth: 'px',
+    outlineWidth: 'px',
+    borderBottomLeftRadius: 'px',
+    borderBottomRightRadius: 'px',
+    borderRadius: 'px',
+    borderTopLeftRadius: 'px',
+    borderTopRightRadius: 'px',
+    //Box 属性
+    rotation: 'deg',
+    //CSS 尺寸属性(Dimension)
+    height: 'px',
+    maxHeight: 'px',
+    maxWidth: 'px',
+    minHeight: 'px',
+    minWidth: 'px',
+    width: 'px',
+    //CSS 字体属性(Font) font-variant:small-caps; 段落设置为小型大写字母字体
+    fontSize: 'px',
+    //CSS 外边距属性(Margin)
+    margin: 'px',
+    marginLeft: 'px',
+    marginRight: 'px',
+    marginTop: 'px',
+    marginBottom: 'px',
+    //多列属性(Multi-column)
+    columnGap: 'px',
+    WebkitColumnGap: 'px',
+    MozColumnGap: 'px',
+    columnRuleWidth: 'px',
+    WebkitColumnRuleWidth: 'px',
+    MozColumnRuleWidth: 'px',
+    columnWidth: 'px',
+    WebkitColumnWidth: 'px',
+    MozColumnWidth: 'px',
+    //CSS 内边距属性(Padding)
+    padding: 'px',
+    paddingLeft: 'px',
+    paddingRight: 'px',
+    paddingTop: 'px',
+    paddingBottom: 'px',
+    //CSS 定位属性(Positioning)
+    left: 'px',
+    right: 'px',
+    top: 'px',
+    bottom: 'px',
+    //CSS 文本属性(Text)
+    letterSpacing: 'px',
+    lineHeight: 'px'
+}

+ 288 - 0
src/event.js

@@ -0,0 +1,288 @@
+import {
+    mayQueue
+} from './may-dom/scheduler';
+
+var globalEvent = {
+
+}
+export var eventHooks = {}; //用于在元素上绑定特定的事件
+var document = window.document;
+export var isTouch = "ontouchstart" in document;
+
+export function dispatchEvent(e, type, end) {
+    e = new SyntheticEvent(e);
+    if (type) {
+        e.type = type;
+    }
+    var _type = e.type;
+    //全局的一个标识  在事件中setState应当合并
+    mayQueue.isInEvent = true;
+    //onClickCapture 在捕获阶段触发
+    var captured = _type + 'capture';
+    var eventCollect = bubbleEvent(e.target, end || document);
+
+    //先触发捕获
+    triggerEventFlow(e, eventCollect, captured);
+
+    if (!e._stopPropagation) {
+        //触发冒泡
+        triggerEventFlow(e, eventCollect.reverse(), _type);
+    }
+    mayQueue.isInEvent = false;
+    //在事件中合并state之后 触发reRender
+    mayQueue.clearQueue();
+}
+/**
+ * 自己冒泡,收集冒泡过程中的所有事件
+ * @param {*event target} from 
+ * @param {*end || document} end 
+ */
+function bubbleEvent(from, end) {
+    var collect = [];
+    do {
+        if (from === end) {
+            break;
+        }
+        var event = from._listener;
+        if (event) {
+            collect.push({
+                dom: from,
+                events: event
+            });
+        }
+    } while ((from = from.parentNode) && from.nodeType === 1);
+    // target --> parentNode --> body --> html
+    return collect;
+}
+
+function triggerEventFlow(e, collect, prop) {
+    for (var i = collect.length; i--;) {
+        var eObj = collect[i];
+        var fn = eObj.events[prop];
+        if (isFn(fn)) {
+            e.currentTarget = eObj.dom;
+            fn.call(eObj.dom, e);
+            if (e._stopPropagation) {
+                break;
+            }
+        }
+    }
+}
+
+
+export var eventLowerCache = {
+    onClick: "click",
+    onChange: "change",
+    onWheel: "wheel"
+};
+var rcapture = /Capture$/;
+export function getBrowserName(onStr) {
+    var lower = eventLowerCache[onStr];
+    if (lower) {
+        return lower;
+    }
+    var camel = onStr.slice(2).replace(rcapture, "");
+    lower = camel.toLowerCase();
+    eventLowerCache[onStr] = lower;
+    return lower;
+}
+export function addEvent(name) {
+    if (!globalEvent[name]) {
+        globalEvent[name] = true;
+        addDocumentEvent(document, name, dispatchEvent);
+    }
+}
+
+function addDocumentEvent(el, name, fn, bool) {
+    if (el.addEventListener) {
+        el.addEventListener(name, fn, bool || false);
+    } else if (el.attachEvent) {
+        el.attachEvent('on' + name, fn);
+    }
+
+}
+export function SyntheticEvent(event) {
+    if (event.nativeEvent) {
+        return event;
+    }
+    if (!this.target) {
+        this.target = event.srcElement;
+    }
+    for (var i in event) {
+        if (!eventProto[i]) {
+            this[i] = event[i];
+        }
+    }
+    this.timeStamp = new Date() - 0;
+    this.nativeEvent = event;
+}
+
+export function createHandle(name, fn) {
+    return function (e) {
+        if (fn && fn(e) === false) {
+            return;
+        }
+        dispatchEvent(e, name);
+    };
+}
+if (isTouch) {
+    eventHooks.click = noop;
+    eventHooks.clickcapture = noop;
+}
+
+var changeHandle = createHandle("change");
+var doubleClickHandle = createHandle("doubleclick");
+
+//react将text,textarea,password元素中的onChange事件当成onInput事件
+eventHooks.changecapture = eventHooks.change = function (dom) {
+    if (/text|password/.test(dom.type)) {
+        addDocumentEvent(document, "input", changeHandle);
+    }
+};
+
+eventHooks.doubleclick = eventHooks.doubleclickcapture = function () {
+    addDocumentEvent(document, "dblclick", doubleClickHandle);
+};
+var eventProto = (SyntheticEvent.prototype = {
+    preventDefault: function () {
+        var e = this.nativeEvent || {};
+        e.returnValue = this.returnValue = false;
+        if (e.preventDefault) {
+            e.preventDefault();
+        }
+    },
+    fixHooks: function () { },
+    stopPropagation: function () {
+        var e = this.nativeEvent || {};
+        e.cancleBubble = this._stopPropagation = true;
+        if (e.stopPropagation) {
+            e.stopPropagation();
+        }
+    },
+    persist: noop,
+    stopImmediatePropagation: function () {
+        this.stopPropagation();
+        this.stopImmediate = true;
+    },
+    toString: function () {
+        return "[object Event]";
+    }
+})
+Object.freeze ||
+    (Object.freeze = function (a) {
+        return a;
+    });
+
+
+function isFn(obj) {
+    return Object.prototype.toString.call(obj) === "[object Function]";
+}
+
+function noop() { }
+
+/* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
+            firefox DOMMouseScroll detail 下3 上-3
+            firefox wheel detlaY 下3 上-3
+            IE9-11 wheel deltaY 下40 上-40
+            chrome wheel deltaY 下100 上-100 */
+/* istanbul ignore next  */
+const fixWheelType = "onmousewheel" in document ? "mousewheel" : document.onwheel !== void 666 ? "wheel" : "DOMMouseScroll";
+const fixWheelDelta = fixWheelType === "mousewheel" ? "wheelDetla" : fixWheelType === "wheel" ? "deltaY" : "detail";
+eventHooks.wheel = function (dom) {
+    addDocumentEvent(dom, fixWheelType, function (e) {
+        var delta = e[fixWheelDelta] > 0 ? -120 : 120;
+        var deltaY = ~~dom.__wheel + delta;
+        dom.__wheel = deltaY;
+        e = new SyntheticEvent(e);
+        e.type = "wheel";
+        e.deltaY = deltaY;
+        dispatchEvent(e);
+    });
+};
+
+var fixFocus = {};
+"blur,focus".replace(/\w+/g, function (type) {
+    eventHooks[type] = function () {
+        if (!fixFocus[type]) {
+            fixFocus[type] = true;
+            addDocumentEvent(document, type, dispatchEvent, true);
+        }
+    };
+});
+/**
+ * 
+DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值;
+对于其他事件,这个属性的值是null。IE不支持realtedTarget属性,但提供了保存着同样信息的不同属性。
+在mouseover事件触发时,IE的fromElement属性中保存了相关元素;
+在mouseout事件出发时,IE的toElement属性中保存着相关元素。
+但fromElement与toElement可能同时都有值
+ */
+function getRelatedTarget(e) {
+    if (!e.timeStamp) {
+        e.relatedTarget = e.type === "mouseover" ? e.fromElement : e.toElement;
+    }
+    return e.relatedTarget;
+}
+
+function contains(a, b) {
+    if (b) {
+        while ((b = b.parentNode)) {
+            if (b === a) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+String("mouseenter,mouseleave").replace(/\w+/g, function (type) {
+    eventHooks[type] = function (dom, name) {
+        var mark = "__" + name;
+        if (!dom[mark]) {
+            dom[mark] = true;
+            var mask = name === "mouseenter" ? "mouseover" : "mouseout";
+            addDocumentEvent(dom, mask, function (e) {
+                let t = getRelatedTarget(e);
+                if (!t || (t !== dom && !contains(dom, t))) {
+                    var common = getLowestCommonAncestor(dom, t);
+                    //由于不冒泡,因此paths长度为1
+                    dispatchEvent(e, name, common);
+                }
+            });
+        }
+    };
+});
+
+function getLowestCommonAncestor(instA, instB) {
+    var depthA = 0;
+    for (var tempA = instA; tempA; tempA = tempA.parentNode) {
+        depthA++;
+    }
+    var depthB = 0;
+    for (var tempB = instB; tempB; tempB = tempB.parentNode) {
+        depthB++;
+    }
+
+    // If A is deeper, crawl up.
+    while (depthA - depthB > 0) {
+        instA = instA.parentNode;
+        depthA--;
+    }
+
+    // If B is deeper, crawl up.
+    while (depthB - depthA > 0) {
+        instB = instB.parentNode;
+        depthB--;
+    }
+
+    // Walk in lockstep until we find a match.
+    var depth = depthA;
+    while (depth--) {
+        if (instA === instB) {
+            return instA;
+        }
+        instA = instA.parentNode;
+        instB = instB.parentNode;
+    }
+    return null;
+}

+ 6 - 0
src/may-dom/DOMNamespaces.js

@@ -0,0 +1,6 @@
+
+export var NAMESPACE = {
+    html: 'http://www.w3.org/1999/xhtml',
+    mathml: 'http://www.w3.org/1998/Math/MathML',
+    svg: 'http://www.w3.org/2000/svg'
+};

+ 29 - 0
src/may-dom/Iteractor.js

@@ -0,0 +1,29 @@
+export var REAL_SYMBOL = typeof Symbol === "function" && Symbol.iterator;
+export var FAKE_SYMBOL = "@@iterator";
+
+export function getIteractor(a) {
+    var iteratorFn = REAL_SYMBOL && a[REAL_SYMBOL] || a[FAKE_SYMBOL];
+    if (iteratorFn && iteratorFn.call) {
+        return iteratorFn;
+    }
+}
+
+export function callIteractor(iteratorFn, children) {
+    var iterator = iteratorFn.call(children),
+        step,
+        ret = [];
+    if (iteratorFn !== children.entries) {
+        while (!(step = iterator.next()).done) {
+            ret.push(step.value);
+        }
+    } else {
+        //Map, Set
+        while (!(step = iterator.next()).done) {
+            var entry = step.value;
+            if (entry) {
+                ret.push(entry[1]);
+            }
+        }
+    }
+    return ret;
+}

+ 238 - 0
src/may-dom/MayDom.js

@@ -0,0 +1,238 @@
+import {
+	mergeState
+} from '../util';
+import {
+	mayQueue,
+	lifeCycleQueue
+} from './scheduler';
+import {
+	mountStrategy
+} from './mountStrategy'
+import {
+	updateStrategy,
+	isSameType
+} from './diffStrategy'
+import {
+	diffProps,
+	FormElement,
+	getIsControlled
+} from '../diffProps'
+import {
+	Refs
+} from '../Refs';
+import {
+	NAMESPACE
+} from './DOMNamespaces';
+import {
+	getChildContext,
+	getContextByTypes
+} from './context';
+import {
+	transformChildren,
+	genKey
+} from './transformChildren';
+import {
+	disposeVnode,
+	disposeDom,
+	emptyElement
+} from './dispose';
+
+export function render(vnode, container, callback) {
+	return renderByMay(vnode, container, callback);
+}
+/**
+ * render传入的Component都是一个function 该方法的原型对象上绑定了render方法
+ * @param {*} vnode 
+ * @param {*} container 
+ * @param {*} callback 
+ */
+var renderByMay = function (vnode, container, callback) {
+	var renderedVnode, rootDom, result;
+	var lastVnode = container._lastVnode || null;
+	if (lastVnode) { //update
+		Refs.isRoot = true;
+		rootDom = mayUpdate(lastVnode, vnode, container);
+	} else {
+		if (vnode && vnode.type) {
+			//为什么React使用var markup=renderChilden();这样的形式呢; 2018-1-13
+			//因为如果按renderComponentChildren(renderedVnode, rootDom, _isSvg);传入container这种
+			//碰上component嵌套不好处理 参见 ReactChildReconciler-test的 warns for duplicated array keys with component stack info
+			var isSVG = vnode.mtype === 3;
+			Refs.isRoot = true;
+			rootDom = mountStrategy[vnode.mtype](vnode, isSVG);
+			if (rootDom && container && container.appendChild) {
+				container.appendChild(rootDom);
+			} else {
+				throw new Error('container参数错误');
+			}
+		} else {
+			throw new Error('render参数错误');
+		}
+	}
+	var instance = vnode.mayInfo.instance;
+	result = instance && !instance.mayInst.stateless && instance || rootDom;
+	//执行render过程中的回调函数lifeCycle ref等
+	mayQueue.clearQueue();
+	if (callback) { //render的 callback
+		callback();
+	}
+	if (instance) {
+		//render之后lifeState也初始化为0;
+		instance.mayInst.lifeState = 0;
+		instance.mayInst.hostNode = rootDom;
+		//当我开始着手解决 层层嵌套的component 最底层的那个setState触发的时候lifeState并不为0
+		//因为我不能确定其是否有componentDidMount的回调,它在这个回调setState是需要放到下一周期处理的
+		//一种办法是该instance如果具备componentDidMount我把其lifeState标个值如10 setState的时候判断
+		//另一种办法就是我新建一个instance队列 instance.pendingCallback=componentDidMount
+		//然后我每次render完再遍历这个队列有回调就调用 这貌似就是ANU的逻辑吧~
+		//快写完了我才发现~惭愧~看来还是自己写写好,不如琢如磨,怎么对这流程了如指掌,不胸有成竹又如何找寻这
+		//最优方法
+	}
+	Refs.isRoot = false;
+	Refs.currentOwner = null;
+	container._lastVnode = vnode;
+	return result;
+}
+
+export function reRender(instance) {
+	var prevProps = instance.props;
+	var context = instance.context;
+	var prevState = instance.state;
+	var prevRendered = instance.mayInst.rendered;
+	var isEmpty = instance.mayInst.isEmpty;
+	var hostNode = instance.mayInst.hostNode;
+	var skip = false;
+	//lifeState为3组件开始diff
+	//WillReceive WillUpdate render DidUpdate等周期setState放到下一周期;
+	instance.mayInst.lifeState = 3;
+	var newState = mergeState(instance);
+	//forceUpdate时 忽略shouldComponentUpdate
+	if (!instance.mayInst.forceUpdate && instance.shouldComponentUpdate && instance.shouldComponentUpdate(prevProps, newState, context) === false) {
+		skip = true;
+	} else if (instance.componentWillUpdate) {
+		instance.componentWillUpdate(prevProps, newState, context);
+	}
+	instance.state = newState;
+	//getChildContext有可能setState 所以需要newState再调用
+	if (instance.getChildContext) {
+		context = getChildContext(instance, context);
+	}
+	if (skip) {
+		instance.mayInst.dirty = false;
+		return hostNode;
+	}
+	if (Refs.isRoot) {
+		Refs.currentOwner = instance;
+		Refs.isRoot = false;
+	}
+	var updated = instance.render(prevProps, context);
+	instance.mayInst.rendered = updated;
+	updated && (updated.context = context);
+	if (!updated) {
+		disposeVnode(prevRendered);
+		return;
+	}
+	if (!isEmpty && isSameType(prevRendered, updated)) {
+
+		// updated.mayInfo.instance = instance;
+		// updated.mayInfo.hostNode = hostNode;
+		hostNode = updateStrategy[updated.mtype](prevRendered, updated);
+		//mtype === 1在这在设置instance会循环引用
+		// updated.mtype === 2 && ();
+		updated.mayInfo.vChildren = transformChildren(updated, hostNode);
+		instance.mayInst.forceUpdate = null;
+		//原先这种diffProps 再diffChildren的方式并未考虑到 render返回之后还是
+		//组件怎么办,连续几个组件嵌套children都需要render怎么办 这些情况都是diffProps
+		//无法处理的,当时想的是一个组件diff diff之后再diff下一个组件;但如果组件之间发生交互的
+		//话是需要在一个处理流程的;那diff我们也需要这种递归的思想了,所以setState的时候我们设置
+		//instance的_dirty为true如果父组件 子组件都dirty,父组件向下diff的过程中也会diff子组件
+		//此时在子组件diff之后我们要把_dirty设为false 否则因为子组件也在diffQueue之中,会再进行
+		//一次diff是多余的;不晓得我说明白没有~参考测试ReactCompositeComponentNestedState-state的
+		//should provide up to date values for props
+		// diffProps(prevRenderedVnode, updatedVnode);
+	} else {
+
+		var isSVG = updated.mtype === 3;
+		var dom = mountStrategy[updated.mtype](updated, isSVG);
+		//component上的hostNode保持最新
+		// var lastVnode = hostNode.parentNode._lastVnode;
+		// lastVnode && (lastVnode.mayInfo.hostNode = dom);
+		hostNode.parentNode.replaceChild(dom, hostNode);
+		// updated.mayInfo.hostNode = dom;
+		hostNode = dom;
+		instance.mayInst.hostNode = dom;
+		updated.mayInfo.vChildren = transformChildren(updated, hostNode);
+		instance.mayInst.forceUpdate = null;
+		disposeVnode(prevRendered);
+	}
+
+	if (instance.componentDidUpdate) {
+		instance.componentDidUpdate(prevProps, prevState, instance.context);
+		// lifeCycleQueue.push(instance.componentDidUpdate.bind(instance, prevProps, prevState, instance.context));
+	} else {
+		instance.mayInst.lifeState = 0;
+	}
+	//needNextRender情况是 子组件在diff生命周期(如WillReceiveProps)调用父组件的setState
+	//这种情况下父组件需要再进行一次diff,不过本地diff完成时c.mayInst.dirty 会为false 所以需要
+	//mayInst.dirty为true;ReactCompositeComponentState-test 的should update state when called from child cWRP
+	if (!instance.mayInst.needNextRender) {
+		instance.mayInst.dirty = false;
+		instance.mayInst.needNextRender = false;
+	}
+
+}
+
+function mayUpdate(prevVnode, newVnode, parent) {
+	var dom;
+	if (isSameType(prevVnode, newVnode)) {
+		dom = updateStrategy[newVnode.mtype](prevVnode, newVnode);
+	} else {
+		var isSVG = newVnode.mtype === 3;
+		dom = mountStrategy[newVnode.mtype](newVnode, isSVG);
+		var hostNode = (prevVnode && prevVnode.mtype === 1) ? prevVnode.mayInfo.hostNode : prevVnode.mayInfo.instance.mayInst.hostNode;
+		if (!parent) {
+			parent = hostNode && hostNode.parentNode;
+		}
+		parent.replaceChild(dom, hostNode);
+		disposeVnode(prevVnode);
+		hostNode = null;
+	}
+	// newVnode.mtype === 2 && (newVnode.mayInfo.instance.mayInst.hostNode = dom);
+	// newVnode.mayInfo.hostNode = dom;
+	return dom;
+}
+
+export function unmountComponentAtNode(dom) {
+	var lastVnode = dom._lastVnode;
+	if (dom.refs) {
+		dom.refs = null;
+	}
+	if (lastVnode) {
+		disposeVnode(lastVnode);
+		emptyElement(dom);
+		//unmount之后又render
+		//参见ReactComponentLifeCycle-test
+		//should not reuse an instance when it has been unmounted
+		dom._lastVnode.mayInfo = {};
+		dom._lastVnode = null;
+	}
+}
+export function findDOMNode(ref) {
+	var ret = null;
+	if (ref) {
+		if (ref.nodeType === 1) {
+			return ref;
+		} else {
+			var c = ref.mayInst.rendered;
+			while (c) {
+				if (c.mtype === 1) {
+					return c.mayInfo.hostNode;
+				} else if (c.mayInfo.hostNode) {
+					return c.mayInfo.hostNode;
+				}
+				c = c.mayInfo.instance.mayInst.rendered;
+			}
+		}
+	}
+	return ret;
+}

+ 34 - 0
src/may-dom/context.js

@@ -0,0 +1,34 @@
+/**
+ * 如果instance具备getChildContext方法 则调用
+ * @param {component实例} instance 
+ * @param {当前上下文} context 
+ */
+export function getChildContext(instance, context) {
+    var prevProps = instance.props;
+    if (instance.nextProps) {
+        instance.props = instance.nextProps;
+    }
+    var getContext = instance.getChildContext();
+    if (instance.nextProps) {
+        instance.props = prevProps;
+    }
+    if (getContext && typeof getContext === 'object') {
+        if (!context) {
+            context = {};
+        }
+        context = Object.assign(context, getContext);
+    }
+    return context;
+}
+export function getContextByTypes(context, typeCheck) {
+    var ret = {};
+    if (!context || !typeCheck) {
+        return ret;
+    }
+    for (const key in typeCheck) {
+        if (context.hasOwnProperty(key)) {
+            ret[key] = context[key];
+        }
+    }
+    return ret;
+}

+ 407 - 0
src/may-dom/diffStrategy.js

@@ -0,0 +1,407 @@
+import {
+    disposeVnode,
+    disposeDom,
+    emptyElement
+} from './dispose';
+import {
+    diffProps,
+    FormElement,
+    getIsControlled
+} from '../diffProps'
+import {
+    getChildContext,
+    getContextByTypes
+} from './context';
+import {
+    mergeState
+} from '../util';
+import {
+    mayQueue,
+    lifeCycleQueue
+} from './scheduler';
+import {
+    transformChildren,
+    genKey
+} from './transformChildren';
+import {
+    Refs
+} from '../Refs';
+import {
+    NAMESPACE
+} from './DOMNamespaces';
+import {
+    mountStrategy
+} from './mountStrategy'
+
+//diff根据vnode的不同类型调用不同的diff方法~
+//其实写着写着就发现还是类似React 根据不同的类型生成不同的Component
+//拥有对应的diff方法;
+export var updateStrategy = {
+    1: updateDOM, //dom
+    2: updateComposite, //component
+    3: updateDOM, //svg dom
+    4: updateText //text
+}
+
+function updateDOM(prevVnode, newVnode) {
+    if (prevVnode.refType === 1) {
+        prevVnode.ref(null);
+    }
+    var hostNode = (prevVnode && prevVnode.mayInfo.hostNode) || null;
+    var vtype = newVnode.type;
+    if (!newVnode.mayInfo.hostNode) {
+        newVnode.mayInfo.hostNode = hostNode;
+    }
+    if (Refs.isRoot) {
+        Refs.currentOwner = hostNode;
+        hostNode.refs = {};
+        Refs.isRoot = false;
+    }
+    var isSVG = hostNode && hostNode.namespaceURI === NAMESPACE.svg;
+    if (isSVG) {
+        newVnode.mayInfo.isSVG = true;
+    }
+    diffProps(prevVnode, newVnode);
+    diffChildren(prevVnode, newVnode, hostNode);
+    newVnode.mayInfo.vChildren = transformChildren(newVnode, hostNode);
+    if (FormElement[vtype]) {
+        //如果是受控组件input select之类需要特殊处理下
+        if (newVnode.props) {
+            var isControlled = getIsControlled(hostNode, newVnode);
+            var _val, hasSelected;
+            if (isControlled) {
+                _val = newVnode.props['value'] || hostNode._lastValue || '';
+                switch (vtype) {
+                    case 'select':
+                        var _optionsChilds = [].slice.call(hostNode.childNodes);
+                        if (_optionsChilds) {
+                            for (var k = 0; k < _optionsChilds.length; k++) {
+                                var oChild = _optionsChilds[k];
+                                if (oChild.value === _val) {
+                                    oChild.selected = true;
+                                    hasSelected = true;
+                                } else {
+                                    oChild.selected = false;
+                                }
+                            }
+                            if (!hasSelected) { //如果给定value没有选中的  默认第一个
+                                hostNode.value = _optionsChilds[0].value;
+                            }
+                        }
+                        break;
+
+                }
+            } else {
+                //如果reRender时 dom去掉了value属性 则其变为非受控组件 value取上一次的值
+                hostNode.value = hostNode._lastValue || '';
+            }
+        }
+
+    }
+    if (newVnode.ref) {
+        Refs.attachRef(newVnode, hostNode);
+    }
+
+    return hostNode;
+}
+
+function updateComposite(prevVnode, newVnode) {
+    if (prevVnode.refType === 1) {
+        prevVnode.ref(null);
+    }
+    //如果newVnode没有定义contextTypes 在componentWillReceiveProps等生命周期方法中
+    //是不应该传入context的 那么用空的temporaryContext代替
+    var temporaryContext = {};
+    var context = newVnode.context || {};
+    if (newVnode.getContext) {
+        context = getContextByTypes(context, newVnode.type.contextTypes);
+        temporaryContext = context;
+    }
+    var instance = prevVnode.mayInfo.instance;
+    var prevRendered = instance.mayInst.rendered;
+    var hostNode = (prevRendered && prevRendered.mtype === 1) ? prevRendered.mayInfo.hostNode : instance.mayInst.hostNode;
+    //empty代表prevVnode为null
+    var isEmpty = instance.mayInst.isEmpty || (hostNode && hostNode.nodeType === 8);
+    var newDom, newRendered, prevState, prevProps;
+    var skip = false;
+    if (!instance.mayInst.stateless) {
+        //需要兼容componentWillReceiveProps直接this.state={///}的情况
+        //先保存下之前的state
+        prevState = instance.state;
+        prevProps = prevVnode.props;
+        //lifeState为3组件开始diff
+        //WillReceive WillUpdate render DidUpdate等周期setState放到下一周期;
+        instance.mayInst.lifeState = 3;
+        //用于mergeState如果setState传入一个function s.call(instance, newState, instance.nextProps || instance.props);
+        //其参数应当是newState nextProps
+        instance.nextProps = newVnode.props;
+        if (instance.getChildContext) {
+            //getChildContext 有可能用到新的props
+            context = getChildContext(instance, context);
+
+        }
+        var newState = mergeState(instance);
+        //如果context与props都没有改变,那么就不会触发组件的receive,render,update等一系列钩子
+        //但还会继续向下比较
+        var needReceive = prevVnode !== newVnode || prevVnode.context !== context;
+        if (needReceive) {
+            if (instance.componentWillReceiveProps) {
+                //componentWillReceiveProps中调用了setState 合并state
+                instance.mayInst.lifeState = 1;
+                instance.componentWillReceiveProps(newVnode.props, temporaryContext);
+                if (instance.mayInst.mergeStateQueue && instance.mayInst.mergeStateQueue.length > 0) {
+                    newState = mergeState(instance);
+                } else { //this.state={///}的情况
+                    if (instance.state !== prevState) {
+                        newState = instance.state;
+                        instance.state = prevState;
+                    }
+                }
+            }
+            instance.mayInst.lifeState = 3;
+        } else {
+            var rendered = instance.mayInst.rendered;
+            //context穿透更新问题
+            rendered.context = newVnode.temporaryContext || context;
+            hostNode = updateStrategy[rendered.mtype](rendered, rendered);
+            return hostNode;
+        }
+
+        //shouldComponentUpdate 返回false 则不进行子组件渲染
+        if (!instance.mayInst.forceUpdate && instance.shouldComponentUpdate && instance.shouldComponentUpdate(newVnode.props, newState, temporaryContext) === false) {
+            skip = true;
+        } else if (instance.componentWillUpdate) {
+            instance.componentWillUpdate(newVnode.props, newState, temporaryContext);
+        }
+        newVnode.mayInfo.instance = instance;
+        instance.props = newVnode.props;
+        if (skip) {
+            instance.state = newState;
+            instance.context = context;
+            instance.mayInst.dirty = false;
+            return hostNode;
+        }
+        instance.state = newState;
+        instance.context = context;
+        if (Refs.isRoot) {
+            Refs.currentOwner = instance;
+            Refs.currentOwner.refs = {};
+            Refs.isRoot = false;
+        }
+
+        newRendered = instance.render();
+        newRendered && (newRendered.context = context);
+        instance.mayInst.rendered = newRendered;
+        if (!isEmpty && newRendered) {
+            if (isSameType(prevRendered, newRendered)) {
+                hostNode = updateStrategy[newRendered.mtype](prevRendered, newRendered);
+                newRendered.mayInfo.hostNode = hostNode;
+            } else {
+                disposeVnode(prevRendered);
+                var isSVG = newRendered.mtype === 3;
+                newDom = mountStrategy[newRendered.mtype](newRendered, isSVG);
+                newRendered.mayInfo.hostNode = newDom;
+                hostNode.parentNode.replaceChild(newDom, hostNode);
+            }
+        } else {
+            if (isEmpty && newRendered) {
+                var isSVG = newRendered.mtype === 3;
+                newDom = mountStrategy[newRendered.mtype](newRendered, isSVG);
+                newRendered.mayInfo.hostNode = newDom;
+                if (hostNode.parentNode) {
+                    hostNode.parentNode.replaceChild(newDom, hostNode);
+                }
+            } else {
+                hostNode = document.createComment('empty');
+                instance.mayInst.hostNode = hostNode;
+                instance.mayInst.isEmpty = true;
+            }
+            //如果之前node为空 或 新render的为空 直接释放之前节点
+            disposeVnode(prevRendered);
+        }
+        if (!instance.mayInst.needNextRender) {
+            instance.mayInst.dirty = false;
+            instance.mayInst.needNextRender = false;
+        }
+        if (newDom) {
+            hostNode = newDom;
+        }
+        if (instance.componentDidUpdate) {
+            lifeCycleQueue.push(instance.componentDidUpdate.bind(instance, prevProps, prevState, instance.context));
+        } else {
+            //如果没有回调则其render生命周期结束lifeState为0
+            instance.mayInst.lifeState = 0;
+        }
+        if (newVnode.refType === 1) {
+            Refs.attachRef(newVnode, instance);
+        }
+
+    } else { //stateless component
+        var newRendered = newVnode.type.call(newVnode, newVnode.props, newVnode.context);
+        newRendered.context = newVnode.context;
+        if (prevRendered && isSameType(prevRendered, newRendered)) {
+            hostNode = updateStrategy[newRendered.mtype](prevRendered, newRendered);
+            newRendered.mayInfo.hostNode = hostNode;
+
+        } else if (newVnode) {
+            disposeVnode(prevRendered);
+            var isSVG = newVnode.mtype === 3;
+            newDom = mountStrategy[newVnode.mtype](newVnode, isSVG);
+            newVnode.mayInfo.hostNode = newDom;
+            hostNode.parentNode.replaceChild(newDom, hostNode);
+            hostNode = newDom;
+        }
+
+    }
+    return hostNode;
+}
+
+function updateText(prev, now) {
+    var hostNode = now.mayInfo.hostNode || null;
+    if (prev) { //child._prevVnode
+        if (hostNode.nodeValue !== now.value) {
+            hostNode.nodeValue = now.value;
+        }
+    } else {
+        hostNode = document.createTextNode(now.value);
+    }
+    return hostNode;
+}
+export function diffChildren(prevVnode, updatedVnode, parent) {
+    var prevChildren = prevVnode.mayInfo.vChildren || null;
+    var newRenderedChild = updatedVnode.props.children;
+    if (newRenderedChild && !Array.isArray(newRenderedChild)) {
+        newRenderedChild = [newRenderedChild];
+    }
+    //diff之前 遍历prevchildren 与newChildren 如有相同key的只对其props diff
+    var _mountChildren = [];
+    var _unMountChildren = [];
+    var k, prevK, _prevK, _tran;
+    if (newRenderedChild) {
+        var len = newRenderedChild.length;
+        for (var i = 0; i < len; i++) {
+            var c = newRenderedChild[i];
+            var t = typeof c;
+            switch (t) {
+                case 'object':
+                    k = genKey(c);
+                    //
+                    if (c.type && c.type.contextTypes) {
+                        c.context = getContextByTypes(updatedVnode.context, c.type.contextTypes);
+                    } else {
+                        c.context = {};
+                        //该组件没有定义contextTypes 无法使用context
+                        //我们还是需要保存一份context
+                        c.temporaryContext = updatedVnode.context;
+                    }
+                    break;
+                case 'boolean':
+                    k = "#text";
+                    _tran = {
+                        type: '#text',
+                        mtype: 4, //text
+                        value: '',
+                        mayInfo: {}
+                    }
+                    c = _tran;
+                    break;
+                case 'number':
+                case 'string':
+                    k = "#text";
+                    _tran = {
+                        type: '#text',
+                        mtype: 4, //text
+                        value: c,
+                        mayInfo: {}
+                    }
+                    c = _tran;
+                    //相邻简单数据类型合并
+                    if ((i + 1 < newRenderedChild.length)) {
+                        var _ntype = typeof newRenderedChild[i + 1];
+                        if (_ntype === 'string' || _ntype === 'number') {
+                            c.value += newRenderedChild[i + 1];
+                            i++;
+                        }
+                    }
+                    break;
+                case 'undefined':
+                    break;
+            }
+            prevK = prevChildren && prevChildren[k];
+            if (prevK && prevK.length > 0) { //试试=0 else
+                for (var _i = 0; _i < prevK.length; _i++) {
+                    var vnode = prevK[_i];
+                    if (c.type === vnode.type && !vnode.mayInfo.used) {
+                        vnode.mayInfo.hostNode && (c.mayInfo.hostNode = vnode.mayInfo.hostNode);
+                        c.mayInfo.instance = vnode.mayInfo.instance;
+                        c.mayInfo.prevVnode = vnode;
+                        vnode.mayInfo.used = true;
+                        c.mayInfo.reused = true;
+                        break;
+                    }
+                }
+            }
+            if (c) {
+                _mountChildren.push(c);
+            }
+        }
+    }
+    for (var name in prevChildren) {
+        var _c = prevChildren[name];
+        for (let j = 0; j < _c.length; j++) {
+            if (!_c[j].mayInfo.used) _unMountChildren.push(_c[j]);
+        }
+    }
+    flushMounts(_mountChildren, parent);
+    flushUnMounts(_unMountChildren);
+    _mountChildren.length = 0;
+}
+
+function flushMounts(newChildren, parent) {
+    for (var _i = 0; _i < newChildren.length; _i++) {
+        var child = newChildren[_i];
+        var _node = parent.childNodes[_i];
+        var newDom;
+        if (child.mayInfo.prevVnode) { //如果可以复用之前节点
+            var prevChild = child.mayInfo.prevVnode;
+            delete child.mayInfo.prevVnode;
+            newDom = updateStrategy[child.mtype](prevChild, child);
+            if (_node && _node !== newDom) { //移动dom
+                newDom = parent.removeChild(newDom);
+                parent.insertBefore(newDom, _node)
+            } else if (!_node) {
+                parent.appendChild(newDom);
+            }
+        } else { //新增节点
+            var isSVG = child.mtype === 3;
+            // var isSVG = vnode.namespaceURI === "http://www.w3.org/2000/svg";
+            newDom = mountStrategy[child.mtype](child, isSVG);
+            child.mtype === 2 && (child.mayInfo.instance.mayInst.hostNode = newDom);
+            // child.mayInfo.hostNode = newDom;
+            if (_node) {
+                parent.insertBefore(newDom, _node);
+            } else {
+                parent.appendChild(newDom);
+            }
+        }
+    }
+}
+
+function flushUnMounts(oldChildren) {
+    var c, dom;
+    while (c = oldChildren.shift()) {
+        if (c.mayInfo.hostNode) {
+            dom = c.mayInfo.hostNode;
+            c.mayInfo.hostNode = null;
+        } else if (c.mtype === 2) {
+            dom = c.mayInfo.instance.mayInst.hostNode;
+        }
+        disposeDom(dom);
+        disposeVnode(c);
+        c = null;
+    }
+}
+
+export function isSameType(prev, now) {
+    return prev.type === now.type && prev.key === now.key;
+}

+ 81 - 0
src/may-dom/dispose.js

@@ -0,0 +1,81 @@
+import {
+    recyclables
+} from '../util';
+export function disposeVnode(vnode) {
+    if (!vnode) {
+        return;
+    }
+    if (vnode.refType === 1) {
+        vnode.ref(null);
+        vnode.ref = null;
+    }
+    if (vnode.mayInfo.instance) {
+        disposeComponent(vnode, vnode.mayInfo.instance);
+    } else if (vnode.mtype === 1) {
+        disposeDomVnode(vnode);
+    }
+    vnode.mayInfo = null;
+}
+function disposeDomVnode(vnode) {
+    var children = vnode.mayInfo.vChildren;
+    if (children) {
+        for (var c in children) {
+            children[c].forEach(function (child) {
+                disposeVnode(child);
+            })
+        }
+        vnode.mayInfo.vChildren = null;
+    }
+    if (vnode.mayInfo.refOwner) {
+        vnode.mayInfo.refOwner = null;
+    }
+    vnode.mayInfo = null;
+}
+
+export function disposeComponent(vnode, instance) {
+    if (instance.setState) {
+        instance.setState = noop;
+        instance.forceUpdate = noop;
+    }
+    if (instance.componentWillUnmount) {
+        instance.componentWillUnmount();
+        instance.componentWillUnmount = noop;
+    }
+    if (instance.refs) {
+        instance.refs = null;
+    }
+    if (instance.mayInst.rendered) {
+        // vnode.mayInfo.rendered = null;
+        disposeVnode(instance.mayInst.rendered);
+    }
+    instance.mayInst.forceUpdate = instance.mayInst.dirty = vnode.mayInfo.instance = instance.mayInst = null;
+}
+var isStandard = 'textContent' in document;
+var fragment = document.createDocumentFragment();
+export function disposeDom(dom) {
+    if (dom._listener) {
+        dom._listener = null;
+    }
+    if (dom.nodeType === 1) {
+        if (isStandard) {
+            dom.textContent = '';
+        } else {
+            emptyElement(dom);
+        }
+    } else if (dom.nodeType === 3) {
+        if (recyclables['#text'].length < 100) {
+            recyclables['#text'].push(dom);
+        }
+    }
+    fragment.appendChild(dom);
+    fragment.removeChild(dom);
+}
+export function emptyElement(dom) {
+    var c;
+    while (c = dom.firstChild) {
+        emptyElement(c);
+        dom.removeChild(c);
+    }
+}
+
+function noop() { };

+ 74 - 0
src/may-dom/instantiateComponent.js

@@ -0,0 +1,74 @@
+import {
+    getChildContext,
+    getContextByTypes
+} from './context';
+import {
+    mergeState
+} from '../util';
+var mountOrder = 0;
+export function buildComponentFromVnode(vnode) {
+    var props = vnode.props;
+    var key = vnode.key;
+    var ref = vnode.ref;
+    var context = vnode.context;
+    var inst, rendered;
+    var Ctor = vnode.type;
+    //Component  PureComponent
+    if (Ctor.prototype && Ctor.prototype.render) {
+        //props, context需要放在前俩
+        inst = new Ctor(props, context, key, ref);
+        //constructor里面props不可变
+        inst.props = props;
+        inst.refType = vnode.refType;
+        inst.mayInst.mountOrder = mountOrder;
+        mountOrder++;
+        //_lifeState来控制生命周期中调用setState的作用
+        //为0代表刚创建完component实例 (diff之后也会重置为0)
+        inst.mayInst.lifeState = 0;
+
+        if (inst.componentWillMount) {
+            //此时如果在componentWillMount调用setState合并state即可
+            //为1代表componentWillMount
+            inst.mayInst.lifeState = 1;
+            inst.componentWillMount();
+        }
+        if (inst.mayInst.mergeStateQueue) {
+            inst.state = mergeState(inst);
+        }
+        //为2代表开始render
+        //children 初次render的生命周期render DidMount
+        //调用父组件的setState 都放在父组件的下一周期;
+        inst.mayInst.lifeState = 2;
+        rendered = inst.render(props, context);
+        if (inst.getChildContext) {
+            context = getChildContext(inst, context);
+        }
+        if (vnode.getContext) {
+            inst.context = getContextByTypes(context, Ctor.contextTypes);
+        }
+        rendered && (rendered.mayInfo.refOwner = inst);
+    } else {
+        //StatelessComponent 我们赋给它一个inst 省去之后判断inst是否为空等;
+        inst = {
+            mayInst: {
+                stateless: true
+            },
+            render: function (type) {
+                return type(this.props, this.context);
+            }
+        }
+        rendered = inst.render.call(vnode, Ctor);
+        //should support module pattern components
+        if (rendered && rendered.render) {
+            console.warn('不推荐使用这种module-pattern component建议换成正常的Component形式,目前只支持render暂不支持其它生命周期方法')
+            rendered = rendered.render.call(vnode, props, context);
+        }
+    }
+    if (rendered) {
+        //需要向下传递context
+        rendered.context = context;
+    }
+    vnode.mayInfo.instance = inst;
+    inst.mayInst.rendered = rendered;
+    return rendered;
+}

+ 169 - 0
src/may-dom/mountStrategy.js

@@ -0,0 +1,169 @@
+import {
+    diffProps,
+    FormElement,
+    getIsControlled
+} from '../diffProps'
+import {
+    Refs
+} from '../Refs';
+import {
+    mergeState,
+    recyclables
+} from '../util';
+import {
+    mayQueue,
+    lifeCycleQueue
+} from './scheduler';
+import {
+    NAMESPACE
+} from './DOMNamespaces';
+import {
+    getChildContext,
+    getContextByTypes
+} from './context';
+import {
+    transformChildren
+} from './transformChildren';
+import {
+    getIteractor,
+    callIteractor
+} from './Iteractor';
+import {
+    buildComponentFromVnode
+} from './instantiateComponent';
+
+
+
+//React是根据type类型分成不同的Component各自具备各自的mount与update方法
+//我们这里简化成根据type类型调用不同的方法
+//mountDOM vnode.type为string直接createElement 然后render children即可
+//mountComposite vnode.type为function 需实例化component 再render children
+export var mountStrategy = {
+    1: mountDOM, //dom
+    2: mountComposite, //component
+    3: mountDOM, //svg dom
+    4: mountText //text
+}
+
+function mountDOM(vnode, isSVG) {
+    var vtype = vnode.type;
+    vnode.mayInfo.isSVG = isSVG;
+    var hostNode = !isSVG ? document.createElement(vtype) : document.createElementNS(NAMESPACE.svg, vnode.type);
+    if (Refs.isRoot) {
+        Refs.currentOwner = hostNode;
+        hostNode.refs = {};
+        Refs.isRoot = false;
+    }
+    vnode.mayInfo.hostNode = hostNode;
+    diffProps(null, vnode);
+
+    var children = vnode.props.children;
+    if (!Array.isArray(children)) {
+        children = [children];
+    }
+    var props = vnode.props;
+    var cdom, c, parentContext;
+
+    var len = children.length;
+    for (let i = 0; i < len; i++) {
+        var c = children[i];
+        var type = typeof c;
+        switch (type) {
+            case 'number':
+            case 'string':
+                cdom = document.createTextNode(c);
+                if ((i + 1) < len && (typeof children[i + 1] === 'string')) {
+                    cdom.nodeValue += children[i + 1];
+                    i++;
+                }
+                hostNode.appendChild(cdom);
+                break;
+            case 'object': //vnode
+                if (c.type) {
+                    c.context = getContextByTypes(vnode.context, c.type.contextTypes);
+                    cdom = mountStrategy[c.mtype](c, isSVG);
+                    c.mtype === 2 && (c.mayInfo.instance.mayInst.hostNode = cdom);
+                    hostNode.appendChild(cdom);
+                } else { //有可能是子数组iterator
+                    var iteratorFn = getIteractor(c);
+                    if (iteratorFn) {
+                        var ret = callIteractor(iteratorFn, c);
+                        for (var _i = 0; _i < ret.length; _i++) {
+                            cdom = mountStrategy[ret[_i].mtype](ret[_i], isSVG);
+                            ret[_i].mayInfo.hostNode = cdom;
+                            hostNode.appendChild(cdom);
+                        }
+                    }
+                }
+        }
+    }
+    vnode.mayInfo.vChildren = transformChildren(vnode, hostNode);
+
+    if (FormElement[vtype]) {
+        //如果是受控组件input select之类需要特殊处理下
+        if (vnode.props) {
+            var _val = vnode.props['value'] || vnode.props['defaultValue'] || '';
+            getIsControlled(hostNode, vnode);
+            hostNode._lastValue = _val;
+        }
+    }
+    //本来想放在调度模块的 但是这种vnode type为dom类型的 func是要在DidMount之前调用的
+    //因为DidMount中可能用到;
+    if (vnode.ref) {
+        Refs.attachRef(vnode, hostNode);
+    }
+    return hostNode;
+}
+
+
+
+function mountComposite(vnode, isSVG) {
+    var hostNode = null;
+    var rendered = buildComponentFromVnode(vnode);
+    var inst = vnode.mayInfo.instance;
+    if (!inst.mayInst.stateless && Refs.isRoot) {
+        Refs.currentOwner = inst;
+        inst.refs = {};
+        Refs.isRoot = false;
+    }
+    if (rendered) {
+        if (!isSVG) {
+            //svg的子节点namespace也是svg
+            isSVG = rendered.mtype === 3;
+        }
+        //递归遍历 深度优先
+        hostNode = mountStrategy[rendered.mtype](rendered, isSVG);
+        //dom diff需要分类一下children以方便diff
+        rendered.mayInfo.vChildren = transformChildren(rendered, hostNode);
+        // rendered.mayInfo.hostNode = hostNode;
+    } else { //render 返回null
+        hostNode = document.createComment('empty');
+        vnode.mayInfo.hostNode = hostNode;
+        //用于isMounted 判断 即使是null
+        inst.mayInst.isEmpty = true;
+    }
+    if (inst.componentDidMount) {
+        lifeCycleQueue.push(inst.componentDidMount.bind(inst));
+    } else {
+        //如果没有回调则其render生命周期结束lifeState为0
+        inst.mayInst.lifeState = 0;
+    }
+    if (vnode.ref) {
+        Refs.attachRef(vnode, inst);
+    }
+
+    return hostNode;
+}
+
+function mountText(vnode) {
+    if (vnode) {
+        var node = recyclables['#text'].pop()
+        if (node) {
+            node.nodeValue = node.value;
+            return node;
+        }
+        return document.createTextNode(vnode.value);
+    } else {
+        return document.createComment('empty');
+    }
+}

+ 89 - 0
src/may-dom/scheduler.js

@@ -0,0 +1,89 @@
+import {
+    reRender
+} from './MayDom'
+import {
+    Refs
+} from '../Refs';
+
+//mayQueue 保存render过程中的各种事件队列 
+export var mayQueue = {
+    dirtyComponentsQueue: [], //setState 需要diff的component队列 
+    callbackQueue: [], //回调队列 setState 中的事件回调
+    lifeCycleQueue: [], //生命周期过程中的回调队列 DidUpdate DidMount ref回调
+    isInEvent: false, //是否在触发事件 回调事件中的setstate合并触发
+    clearQueue: clearQueue,
+    flushUpdates: flushUpdates,
+}
+//存放生命周期中的 DidMount DidUpdate以及ref回调
+export var lifeCycleQueue = mayQueue.lifeCycleQueue;
+/**
+ * 清空回调队列
+ * @param {*} mayQueue 
+ */
+function clearQueue() {
+    //ComponentDidMount
+    clearLifeCycleQueue();
+
+    //如有有dirty Component diff
+    flushUpdates();
+    //setState传入的回调函数
+    clearCallbackQueue();
+}
+
+function flushUpdates() {
+    var instance;
+    var i = 0;
+    //如果在当前生命周期的DidMount调用setState 放到下一生命周期处理
+    mayQueue.dirtyComponentsQueue = mayQueue.dirtyComponentsQueue.sort(sortComponent);
+    while (instance = mayQueue.dirtyComponentsQueue.shift()) {
+        if (i++ === 0) {
+            Refs.isRoot = true;
+        }
+        if (instance.mayInst.dirty) {
+            //如果C是脏组件diff 如果其在diff过程中子组件也需要diff diff之后
+            //子组件_dirty会为false 没必要再diff一次;
+            reRender(instance);
+        }
+
+        if (instance) {
+            //diff之后组件的状态返回0
+            instance.mayInst.lifeState = 0;
+        }
+    }
+    //ComponentDidUpdate
+    clearLifeCycleQueue();
+    //防止setState currentOwner混乱
+    Refs.currentOwner = null;
+}
+
+function clearLifeCycleQueue() {
+    //先清空 生命周期 ref 的回调函数
+    if (mayQueue.lifeCycleQueue && mayQueue.lifeCycleQueue.length > 0) {
+        var callback;
+        //其实ref很像生命周期函数,它比较特殊的地方在于vnode.type='div'之类的vnode
+        //其string ref要指向其真实dom func ref也是回调真实的dom 其它的都是回调instance
+        //vnode.type='div'之类的ref特殊处理;剩余的就和生命周期很像了;
+        while (callback = mayQueue.lifeCycleQueue.shift()) {
+            callback();
+        }
+    }
+}
+
+function clearCallbackQueue() {
+    //再清空 setState传入的回调函数
+    if (mayQueue.callbackQueue && mayQueue.callbackQueue.length > 0) {
+        var callback;
+        mayQueue.callbackQueue = mayQueue.callbackQueue.sort(sortCallback);
+        while (callback = mayQueue.callbackQueue.shift()) {
+            callback();
+        }
+    }
+}
+
+function sortCallback(a, b) {
+    return a._mountOrder - b._mountOrder;
+}
+
+function sortComponent(a, b) {
+    return a.mayInst.mountOrder - b.mayInst.mountOrder;
+}

+ 100 - 0
src/may-dom/transformChildren.js

@@ -0,0 +1,100 @@
+import { getIteractor, callIteractor } from './Iteractor';
+
+//https://segmentfault.com/a/1190000010336457  司徒正美先生写的分析
+//hydrate是最早出现于inferno(另一个著名的react-like框架),并相邻的简单数据类型合并成一个字符串。
+//因为在react的虚拟DOM体系中,字符串相当于一个文本节点。减少children中的个数,
+//就相当减少实际生成的文本节点的数量,也减少了以后diff的数量,能有效提高性能。
+
+//render过程中有Key的 是最有可能变动的,无Key的很可能不会变(绝大部分情况)
+//把children带Key的放一起  不带Key的放一起(因为他们很可能不变化,顺序也不变减少diff寻找)
+export function transformChildren(renderedVnode, parent) {
+    var children = renderedVnode.props.children || null;
+    if (children && !Array.isArray(children)) {
+        children = [children];
+    }
+    var len = children ? children.length : 0;
+    var childList = [].slice.call(parent.childNodes);
+    var result = children ? {} : null;
+    //如有undefined null 简单数据类型合并 noCount++;
+    var noCount = 0;
+    for (var i = 0; i < len; i++) {
+        var c = children[i];
+        var __type = typeof c;
+        switch (__type) {
+            case 'object':
+                if (c.type) {
+                    if (c.mayInfo.reused) {
+                        //如果该组件 diff 两次 第一次vnode重用之后_reused为true
+                        //生成vchildren时需要其为false 否则第二次diff
+                        c.mayInfo.reused = false;
+                    }
+                    var _key = genKey(c);
+                    if (!result[_key]) {
+                        result[_key] = [c];
+                    } else {
+                        result[_key].push(c);
+                    }
+                    if (c.ref) { //如果子dom有ref 标识一下 
+                        var owner = renderedVnode.mayInfo.refOwner;
+                        if (owner) {
+                            if (owner.refs) {
+                                owner.refs[c.ref] = c.mayInfo.instance || c.mayInfo.hostNode || null;
+                            } else {
+                                owner.refs = {};
+                                owner.refs[c.ref] = c.mayInfo.instance || c.mayInfo.hostNode || null;
+                            }
+                        }
+                    }
+                } else {
+                    var iteratorFn = getIteractor(c);
+                    if (iteratorFn) {
+                        var ret = callIteractor(iteratorFn, c);
+                        for (var _i = 0; _i < ret.length; _i++) {
+                            var _key = genKey(ret[_i]);
+                            if (!result[_key]) {
+                                result[_key] = [ret[_i]];
+                            } else {
+                                result[_key].push(ret[_i]);
+                            }
+                        }
+                    }
+                }
+
+                break;
+            case 'number':
+            case 'string':
+                //相邻的简单数据类型合并成一个字符串
+                var tran = {
+                    type: '#text',
+                    mtype: 4,
+                    value: c,
+                    mayInfo: {}
+                }
+                if (childList[i - noCount]) {
+                    tran.mayInfo.hostNode = childList[i - noCount];
+                }
+                if ((i + 1 < len)) {
+                    var _ntype = typeof children[i + 1 - noCount];
+                    if (_ntype === 'string' || _ntype === 'number') {
+                        tran.value += children[i + 1 - noCount];
+                        noCount++;
+                        i++;
+                    }
+                }
+                var _k = '#text';
+                if (!result[_k]) {
+                    result[_k] = [tran];
+                } else {
+                    result[_k].push(tran);
+                }
+                break;
+            default:
+                noCount++;
+                break;
+        }
+    }
+    return result;
+}
+export function genKey(child) {
+    return !child.key ? (child.type.name || child.type) : ('_$' + child.key);
+}

+ 18 - 0
src/may-server/MayServer.js

@@ -0,0 +1,18 @@
+import {
+    render
+} from '../may-dom/MayDom';
+
+var MayServer = {
+    renderToString: renderToString
+}
+export function renderToString(vnode) {
+    var rootDom;
+    if (vnode.type && typeof vnode.type === 'string') {
+        rootDom = document.createElement(vnode.type);
+        render(vnode, rootDom);
+    }
+    if(rootDom){
+        return rootDom.innerHTML;
+    }
+}
+export default MayServer;

+ 14 - 0
src/readme.md

@@ -0,0 +1,14 @@
+#React大概可以分为这几个模块:
+
+    vnode模块:
+                就是以js对象的形式表示dom,包括createElement,cloneElement,Component,PureComponent,statelessComponent
+
+    render模块:
+                最核心的模块了,包括首次render以及diff,这个过程中又使用到了
+                props模块(给dom设置css属性)
+                event模块(我们的合成事件抹平浏览器差异,统一绑定在document等)
+                ref模块(如果有ref function或string应该在哪回调)
+                context模块(context该怎样向下传递diff之后怎么区分新老context等)
+                PropTypes模块(组件取context需要先判断类型)
+                Children模块(提供操作children的方法)
+                options模块(也可叫调度模块,因为我们不可避免使用到队列来处理diff或各种回调,以及一些插件如redux也需要一些钩子函数)

+ 50 - 0
src/util.js

@@ -0,0 +1,50 @@
+
+
+//文本节点重复利用
+export var recyclables={
+    '#text':[]
+}
+export function mergeState(instance) {
+    var newState;
+    var prevState = instance.state;
+    if (instance.mayInst.mergeStateQueue && instance.mayInst.mergeStateQueue.length > 0) {
+        var queue = instance.mayInst.mergeStateQueue;
+        var newState = extend({}, prevState);
+        for (var i = 0; i < queue.length; i++) {
+            var s = queue[i];
+            if (s && s.call) {
+                s = s.call(instance, newState, instance.nextProps || instance.props);
+            }
+            newState = extend(newState, s);
+        }
+        instance.mayInst.mergeStateQueue.length = 0;
+    } else {
+        newState = prevState;
+    }
+    return newState;
+}
+
+
+// export function eventProxy(e) {
+//     return this._listener[e.type](e);
+// }
+export function extend(target, src) {
+    for (var key in src) {
+        if (src.hasOwnProperty(key)) {
+            target[key] = src[key];
+        }
+    }
+    return target;
+}
+/**
+ * 寄生组合式继承
+ * @param {*} target 
+ * @param {*} superClass 
+ */
+export function inherits(target, superClass) {
+    function b() { };
+    b.prototype = superClass.prototype;
+    var fn = target.prototype = new b();
+    fn.constructor = target;
+    return fn;
+}

+ 59 - 0
test/element.test.js

@@ -0,0 +1,59 @@
+
+import React from '../src/May'
+// import React from "../dist/ReactANU";
+
+describe('may.js', () => {
+
+	it('mayRender', () => {
+		spyOn(console, 'error');
+		var container = document.createElement('div');
+		class Child extends React.Component {
+
+
+			render() {
+				return (
+					<div>
+						{this.props.val}
+					</div>);
+			}
+		}
+		class Parent extends React.Component {
+			constructor() {
+				super();
+				this.state = { val: 'I wonder' };
+			}
+			Change = () => {
+				this.setState({ val: 'I see' });
+			}
+			render() {
+				return (
+					<div className="mystyle" style={{ width: '40%', marginLeft: '30px', backgroundColor: 'blue' }} onClick={this.Change}>
+						{this.state.val === 'I wonder' ? <Child key="1" val="1" /> : <Child key="1" val="1" />}
+						{this.state.val === 'I wonder' ? <Child key="2" val="2" /> : <Child key="3" val="3" />}
+					</div>
+				);
+				// if (this.state.val === 'I wonder') {
+				// 	return (
+				// 		<div className="mystyle" style={{ width: '40%', marginLeft: '30px', backgroundColor: 'blue' }} onClick={this.Change}>
+				// 			<Child key="1" val="1" />
+				// 			<Child key="2" val="2" />
+				// 		</div>
+				// 	);
+				// } else {
+				// 	return (
+				// 		<div className="mystyle" style={{ width: '40%', marginLeft: '30px', backgroundColor: 'blue' }} onClick={this.Change}>
+				// 			<Child key="2" val="2" />
+				// 			<Child key="1" val="1" />
+				// 		</div>
+				// 	);
+				// }
+
+			}
+		}
+		React.render(<Parent />, container);
+		document.body.appendChild(container);
+		expect(console.error.calls.count()).toBe(0);
+	});
+
+
+})

+ 315 - 0
test/may-dom/CSSPropertyOperations-test.js

@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+import React from '../../src/May';
+import {
+	render
+} from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render
+}
+
+import ReactDOMServer from '../../src/may-server/MayServer'
+
+// import React from '../../dist/ReactANU';
+// var ReactDOM = React;
+
+function normalizeCodeLocInfo(str) {
+	return str && str.replace(/at .+?:\d+/g, 'at **');
+}
+
+describe('CSSPropertyOperations', () => {
+	it('should automatically append `px` to relevant styles', () => {
+		var styles = {
+			left: 0,
+			margin: 16,
+			opacity: 0.5,
+			// columnGap: 60,
+			// transform: 'translate(10,20)',
+			// WebkitFlex: 1,
+			// MozColumnGap: 60,
+			padding: '4px',
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var html = ReactDOMServer.renderToString(div);
+		expect(/style=".*"/.test(html)).toBe(true);
+		//   expect(html).toContain('"left:0;margin:16px;opacity:0.5;padding:4px;"');
+	});
+
+	it('should trim values', () => {
+		var styles = {
+			left: '16 ',
+			opacity: 0.5,
+			right: ' 4 ',
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var html = ReactDOMServer.renderToString(div);
+		expect(/style=".*"/.test(html)).toBe(true);
+		//   expect(html).toContain('"left:16;opacity:0.5;right:4;"');
+	});
+
+	it('should not append `px` to styles that might need a number', () => {
+		var styles = {
+			flex: 0,
+			opacity: 0.5,
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var html = ReactDOMServer.renderToString(div);
+		expect(/style=".*"/.test(html)).toBe(true);
+		//   expect(html).toContain('"flex:0;opacity:0.5;"');
+	});
+
+	it('should create vendor-prefixed markup correctly', () => {
+		var styles = {
+			msTransition: 'none',
+			MozTransition: 'none',
+			WebkitTransition: 'none',
+			transition: 'none'
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var html = ReactDOMServer.renderToString(div);
+		expect(/style=".*"/.test(html)).toBe(true);
+		//   expect(html).toContain('"-ms-transition:none;-moz-transition:none;"');
+	});
+
+	it('should set style attribute when styles exist', () => {
+		var styles = {
+			backgroundColor: '#000',
+			display: 'none',
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var root = document.createElement('div');
+		div = ReactDOM.render(div, root);
+		expect(/style=".*"/.test(root.innerHTML)).toBe(true);
+	});
+
+	it('should not set style attribute when no styles exist', () => {
+		var styles = {
+			backgroundColor: null,
+			display: null,
+		};
+		var div = < div style = {
+			styles
+		}
+		/>;
+		var html = ReactDOMServer.renderToString(div);
+		expect(/style=/.test(html)).toBe(false);
+	});
+
+	it('should warn when using hyphenated style names', () => {
+		class Comp extends React.Component {
+			static displayName = 'Comp';
+
+			render() {
+				return <div style = {
+					{
+						'background-color': 'crimson'
+					}
+				}
+				/>;
+			}
+		}
+
+		spyOn(console, 'error');
+		var root = document.createElement('div');
+		ReactDOM.render( < Comp /> , root);
+		// expect(console.error.calls.count()).toBe(1);
+		// expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+		// 	'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
+		// 	'\n    in div (at **)' +
+		// 	'\n    in Comp (at **)',
+		// );
+	});
+
+	it('should warn when updating hyphenated style names', () => {
+	   class Comp extends React.Component {
+		 static displayName = 'Comp';
+   
+		 render() {
+		   return <div style={this.props.style} />;
+		 }
+	   }
+   
+	   spyOn(console, 'error');
+	   var styles = {
+		 '-ms-transform': 'translate3d(0, 0, 0)',
+		 '-webkit-transform': 'translate3d(0, 0, 0)',
+	   };
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+	   ReactDOM.render(<Comp style={styles} />, root);
+   
+	//    expect(console.error.calls.count()).toBe(2);
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+	// 	 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
+	// 	 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	 });
+   
+	 it('warns when miscapitalizing vendored style names', () => {
+	   class Comp extends React.Component {
+		 static displayName = 'Comp';
+   
+		 render() {
+		   return (
+			 <div
+			   style={{
+				 msTransform: 'translate3d(0, 0, 0)',
+				 oTransform: 'translate3d(0, 0, 0)',
+				 webkitTransform: 'translate3d(0, 0, 0)',
+			   }}
+			 />
+		   );
+		 }
+	   }
+   
+	   spyOn(console, 'error');
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+	   // msTransform is correct already and shouldn't warn
+	//    expect(console.error.calls.count()).toBe(2);
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+	// 	 'Warning: Unsupported vendor-prefixed style property oTransform. ' +
+	// 	   'Did you mean OTransform?' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
+	// 	 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' +
+	// 	   'Did you mean WebkitTransform?' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	 });
+   
+	 it('should warn about style having a trailing semicolon', () => {
+	   class Comp extends React.Component {
+		 static displayName = 'Comp';
+   
+		 render() {
+		   return (
+			 <div
+			   style={{
+				 fontFamily: 'Helvetica, arial',
+				 backgroundImage: 'url(foo;bar)',
+				 backgroundColor: 'blue;',
+				 color: 'red;   ',
+			   }}
+			 />
+		   );
+		 }
+	   }
+   
+	   spyOn(console, 'error');
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+	//    expect(console.error.calls.count()).toBe(2);
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+	// 	 "Warning: Style property values shouldn't contain a semicolon. " +
+	// 	   'Try "backgroundColor: blue" instead.' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
+	// 	 "Warning: Style property values shouldn't contain a semicolon. " +
+	// 	   'Try "color: red" instead.' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	 });
+   
+	 it('should warn about style containing a NaN value', () => {
+	   class Comp extends React.Component {
+		 static displayName = 'Comp';
+   
+		 render() {
+		   return <div style={{fontSize: NaN}} />;
+		 }
+	   }
+   
+	   spyOn(console, 'error');
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+   
+	//    expect(console.error.calls.count()).toBe(1);
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+	// 	 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	 });
+   
+	it('should not warn when setting CSS custom properties', () => {
+	   class Comp extends React.Component {
+		 render() {
+		   return <div style={{'--foo-primary': 'red', backgroundColor: 'red'}} />;
+		 }
+	   }
+   
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+	 });
+   
+	 it('should warn about style containing a Infinity value', () => {
+	   class Comp extends React.Component {
+		 static displayName = 'Comp';
+   
+		 render() {
+		   return <div style={{fontSize: 1 / 0}} />;
+		 }
+	   }
+   
+	   spyOn(console, 'error');
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+   
+	//    expect(console.error.calls.count()).toBe(1);
+	//    expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
+	// 	 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
+	// 	   '\n    in div (at **)' +
+	// 	   '\n    in Comp (at **)',
+	//    );
+	 });
+   
+	 it('should not add units to CSS custom properties', () => {
+	   class Comp extends React.Component {
+		 render() {
+		   return <div style={{'--foo': 5}} />;
+		 }
+	   }
+   
+	   var root = document.createElement('div');
+	   ReactDOM.render(<Comp />, root);
+   
+	//    expect(root.children[0].style.Foo).toEqual('5');
+	 });
+});

+ 199 - 0
test/may-dom/DOMPropertyOperations-test.js

@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+import React from '../../src/May';
+import { render } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render
+}
+
+import ReactDOMServer from '../../src/may-server/MayServer'
+
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = {
+// 	render: React.render
+// }
+
+describe('DOMPropertyOperations', () => {
+//   var React;
+//   var ReactDOM;
+
+//   beforeEach(() => {
+//     // jest.resetModules();
+//     React = require('react');
+//     ReactDOM = require('react-dom');
+//   });
+
+  describe('setValueForProperty', () => {
+    it('should set values as properties by default', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div title="Tip!" />, container);
+      expect(container.firstChild.title).toBe('Tip!');
+    });
+
+    it('should set values as attributes if necessary', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div role="#" />, container);
+      expect(container.firstChild.getAttribute('role')).toBe('#');
+      expect(container.firstChild.role).toBeUndefined();
+    });
+
+    /*it('should set values as namespace attributes if necessary', () => {
+      var container = document.createElement('svg');
+    //   var svg = <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
+    //     <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
+	//   </svg>;//
+	  var svg=<image xlinkHref="about:blank" />;
+      ReactDOM.render(svg, container);
+      expect(
+        container.firstChild.getAttributeNS(
+          'http://www.w3.org/1999/xlink',
+          'href',
+        ),
+      ).toBe('about:blank');
+    });*/
+
+    it('should set values as boolean properties', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div disabled="disabled" />, container);
+    //   expect(container.firstChild.getAttribute('disabled')).toBe('');
+      ReactDOM.render(<div disabled={true} />, container);
+    //   expect(container.firstChild.getAttribute('disabled')).toBe('');
+      ReactDOM.render(<div disabled={false} />, container);
+      expect(container.firstChild.getAttribute('disabled')).toBe(null);
+      ReactDOM.render(<div disabled={true} />, container);
+      ReactDOM.render(<div disabled={null} />, container);
+      expect(container.firstChild.getAttribute('disabled')).toBe(null);
+      ReactDOM.render(<div disabled={true} />, container);
+      ReactDOM.render(<div disabled={undefined} />, container);
+      expect(container.firstChild.getAttribute('disabled')).toBe(null);
+    });
+
+    it('should convert attribute values to string first', () => {
+      // Browsers default to this behavior, but some test environments do not.
+      // This ensures that we have consistent behavior.
+      var obj = {
+        toString: function() {
+          return 'css-class';
+        },
+      };
+
+      var container = document.createElement('div');
+      ReactDOM.render(<div className={obj} />, container);
+      expect(container.firstChild.getAttribute('class')).toBe('css-class');
+    });
+
+    it('should not remove empty attributes for special properties', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<input value="" />, container);
+      expect(container.firstChild.getAttribute('value')).toBe(null);
+      expect(container.firstChild.value).toBe('');
+    });
+
+    it('should remove for falsey boolean properties', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div allowFullScreen={false} />, container);
+      expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false);
+    });
+
+    it('should remove when setting custom attr to null', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div data-foo="bar" />, container);
+      expect(container.firstChild.hasAttribute('data-foo')).toBe(true);
+      ReactDOM.render(<div data-foo={null} />, container);
+      expect(container.firstChild.hasAttribute('data-foo')).toBe(false);
+    });
+
+    it('should set className to empty string instead of null', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div className="selected" />, container);
+      expect(container.firstChild.className).toBe('selected');
+      ReactDOM.render(<div className={null} />, container);
+      // className should be '', not 'null' or null (which becomes 'null' in
+      // some browsers)
+      expect(container.firstChild.className).toBe('');
+      expect(container.firstChild.getAttribute('class')).toBe(null);
+    });
+
+    it('should remove property properly for boolean properties', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div hidden={true} />, container);
+      expect(container.firstChild.hasAttribute('hidden')).toBe(true);
+	  ReactDOM.render(<div hidden={false} />, container);
+	  //hidden是property
+      //expect(container.firstChild.hasAttribute('hidden')).toBe(false);
+    });
+  });
+
+ describe('value mutation method', function() {
+    it('should update an empty attribute to zero', function() {
+      var container = document.createElement('div');
+      ReactDOM.render(
+        <input type="radio" value="" onChange={function() {}} />,
+        container,
+      );
+      spyOn(container.firstChild, 'setAttribute');
+      ReactDOM.render(
+        <input type="radio" value={0} onChange={function() {}} />,
+        container,
+	  );
+	  //之前为toBe(1)  不过input的value为property直接赋值最好
+	  //不需要setAttribute
+      expect(container.firstChild.setAttribute.calls.count()).toBe(0);
+    });
+
+    it('should always assign the value attribute for non-inputs', function() {
+      var container = document.createElement('div');
+      ReactDOM.render(<progress />, container);
+      spyOn(container.firstChild, 'setAttribute');
+      ReactDOM.render(<progress value={30} />, container);
+	  ReactDOM.render(<progress value="30" />, container);
+	  //同上
+      expect(container.firstChild.setAttribute.calls.count()).toBe(0);
+    });
+  });
+
+  describe('deleteValueForProperty', () => {
+    it('should remove attributes for normal properties', () => {
+      var container = document.createElement('div');
+      ReactDOM.render(<div title="foo" />, container);
+      expect(container.firstChild.getAttribute('title')).toBe('foo');
+	  ReactDOM.render(<div />, container);
+	  //title 是div的property
+      expect(container.firstChild.getAttribute('title')).toBe('');
+    });
+
+    it('should not remove attributes for special properties', () => {
+      var container = document.createElement('div');
+      spyOn(console, 'warn');
+      ReactDOM.render(
+        <input type="text" value="foo" onChange={function() {}} />,
+        container,
+      );
+    //   expect(container.firstChild.getAttribute('value')).toBe('foo');
+      expect(container.firstChild.value).toBe('foo');
+      ReactDOM.render(
+        <input type="text" onChange={function() {}} />,
+        container,
+      );
+    //   expect(container.firstChild.getAttribute('value')).toBe('foo');
+      expect(container.firstChild.value).toBe('foo');
+    //   expect(console.warn.calls.count()).toBe(1);
+    //   expect(console.error.calls.argsFor(0)[0]).toContain(
+    //     'A component is changing a controlled input of type text to be uncontrolled',
+    //   );
+    });
+  });
+});

+ 56 - 0
test/may-dom/EventPluginHub-test.jsx

@@ -0,0 +1,56 @@
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+import {
+    dispatchEvent, SyntheticEvent, addEvent
+} from '../../src/event';
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = { Simulate: {} };
+// "click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove".replace(/\w+/g, function (name) {
+//     ReactTestUtils.Simulate[name] = function (node, opts) {
+//         if (!node || node.nodeType !== 1) {
+//             throw "第一个参数必须为元素节点";
+//         }
+//         var fakeNativeEvent = opts || {};
+//         fakeNativeEvent.target = node;
+//         fakeNativeEvent.simulated = true;
+//         fakeNativeEvent.type = name.toLowerCase();
+//         React.eventSystem.dispatchEvent(fakeNativeEvent, name.toLowerCase());
+//     };
+// });
+// https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
+
+
+// 已测试
+describe("isEventSupported", function() {
+    // this.timeout(200000);
+
+
+    /*it('should prevent non-function listeners, at dispatch', () => {
+      spyOn(console, 'error');
+      var node = ReactTestUtils.renderIntoDocument(
+        <div onClick="not a function" />,
+      );
+      expect(function() {
+        ReactTestUtils.Simulate.click(node);
+      }).toThrowError(
+        'Expected `onClick` listener to be a function, instead got a value of `string` type.',
+      );
+    });
+
+    it('should not prevent null listeners, at dispatch', () => {
+    var node = ReactTestUtils.renderIntoDocument(<div onClick={null} />);
+    expect(function() {
+      ReactTestUtils.Simulate.click(node);
+    }).not.toThrow('Expected `onClick` listener to be a function, instead got a value of `null` type.');
+  });*/
+
+
+});

+ 151 - 0
test/may-dom/ReactChildReconciler-test.js

@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+// NOTE: We're explicitly not using JSX here. This is intended to test
+// the current stack addendum without having source location added by babel.
+
+'use strict';
+
+import React from '../../src/May';
+import { render } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render
+}
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = {
+// 	render: React.render
+// }
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+var ReactTestUtils = {};
+ReactTestUtils.renderIntoDocument = function (component) {
+	var div = document.createElement('div');
+	ReactDOM.render(component, div);
+}
+
+describe('ReactChildReconciler', () => {
+  function normalizeCodeLocInfo(str) {
+    return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+  }
+
+  // beforeEach(() => {
+  //   // jest.resetModules();
+
+  //   React = require('react');
+  //   ReactTestUtils = require('react-dom/test-utils');
+  // });
+
+  function createIterable(array) {
+    return {
+      '@@iterator': function() {
+        var i = 0;
+        return {
+          next() {
+            const next = {
+              value: i < array.length ? array[i] : undefined,
+              done: i === array.length,
+            };
+            i++;
+            return next;
+          },
+        };
+      },
+    };
+  }
+
+  it('warns for duplicated array keys', () => {
+    spyOn(console, 'error');
+
+    class Component extends React.Component {
+      render() {
+        return <div>{[<div key="1" />, <div key="1" />]}</div>;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<Component />);
+
+  });
+
+  it('warns for duplicated array keys with component stack info', () => {
+    spyOn(console, 'error');
+
+    class Component extends React.Component {
+      render() {
+        return <p>{[<span key="1" />, <span key="1" />]}</p>;
+      }
+    }
+
+    class Parent extends React.Component {
+        componentWillMount(){
+            console.log('Parent Will Mount');
+        }
+        componentDidMount(){
+            console.log('Parent Did Mount');
+        }
+      render() {
+        return React.cloneElement(this.props.child);
+      }
+    }
+
+    class GrandParent extends React.Component {
+        componentWillMount(){
+            console.log('GrandParent Will Mount');
+        }
+        componentDidMount(){
+            console.log('GrandParent Did Mount');
+        }
+      render() {
+        return <Parent child={<Component />} />;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<GrandParent />);
+
+  });
+
+  it('warns for duplicated iterable keys', () => {
+    spyOn(console, 'error');
+
+    class Component extends React.Component {
+      render() {
+        return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<Component />);
+
+  });
+
+  it('warns for duplicated iterable keys with component stack info', () => {
+    spyOn(console, 'error');
+
+    class Component extends React.Component {
+      render() {
+        return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
+      }
+    }
+
+    class Parent extends React.Component {
+      render() {
+        return React.cloneElement(this.props.child);
+      }
+    }
+
+    class GrandParent extends React.Component {
+      render() {
+        return <Parent child={<Component />} />;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<GrandParent />);
+
+  });
+});

+ 907 - 0
test/may-dom/ReactChildren-test.jsx

@@ -0,0 +1,907 @@
+
+// import ReactTestUtils from "../../lib/ReactTestUtils";
+// import React from '../../src/May';
+// import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+// var ReactDOM = {
+// 	render: render,
+// 	unmountComponentAtNode: unmountComponentAtNode,
+// 	findDOMNode: findDOMNode
+// }
+// React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+
+var React = require('react');
+var ReactDOM = require('react-dom');
+
+
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+// var ReactDOM = window.ReactDOM || React;
+
+describe("ReactChildren", function () {
+	// this.timeout(200000);
+	it('testjasmime', () => {
+		var foo;
+		foo = {
+			setBar: function (value) {
+				bar = value;
+			}
+		};
+
+		spyOn(foo, 'setBar');
+
+		foo.setBar(123);
+		expect(foo.setBar).toHaveBeenCalled();
+	})
+	it('should support identity for simple', () => {
+		var context = {};
+		// var callback = {
+		var	foo= function (kid, index) {
+				// expect(this).toBe(context);
+				// expect(index).toBe(0);
+				// if (index > 0) {
+					return kid;
+				// }
+			}
+		// };
+
+		// spyOn(callback, 'foo');
+		var simpleKid = <span key="simple" />;
+		var simpleKid2 = <span key="simple2" />;
+
+		// First pass children into a component to fully simulate what happens when
+		// using structures that arrive from transforms.
+
+		var instance = <div>{simpleKid}</div>;
+		// React.Children.forEach(instance.props.children, callback.foo, context);
+		// expect(callback.foo).toHaveBeenCalled();
+		// callback.foo.calls.reset();
+		var mappedChildren = React.Children.map(
+			instance.props.children,
+			foo,
+			context,
+		);
+		// expect(callback.foo).toHaveBeenCalled();
+		expect(mappedChildren[0]).toEqual(<span key=".$simple" />);
+	});
+
+	/*it('should treat single arrayless child as being in array', () => {
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		expect(index).toBe(0);
+		return kid;
+	  });
+  
+	  var simpleKid = <span />;
+	  var instance = <div>{simpleKid}</div>;
+	  React.Children.forEach(instance.props.children, callback, context);
+	  expect(callback).toHaveBeenCalledWith(simpleKid, 0);
+	  callback.calls.reset();
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  expect(callback).toHaveBeenCalledWith(simpleKid, 0);
+	  expect(mappedChildren[0]).toEqual(<span key=".0" />);
+	});
+  
+	it('should treat single child in array as expected', () => {
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  var simpleKid = <span key="simple" />;
+	  var instance = <div>{[simpleKid]}</div>;
+	  React.Children.forEach(instance.props.children, callback, context);
+	  expect(callback).toHaveBeenCalledWith(simpleKid, 0);
+	  callback.calls.reset();
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  expect(callback).toHaveBeenCalledWith(simpleKid, 0);
+	  expect(mappedChildren[0]).toEqual(<span key=".$simple" />);
+	});
+  
+	it('should be called for each child', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+	  var context = {};
+  
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  var instance = (
+		<div>
+		  {zero}
+		  {one}
+		  {two}
+		  {three}
+		  {four}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback).toHaveBeenCalledWith(zero, 0);
+		expect(callback).toHaveBeenCalledWith(one, 1);
+		expect(callback).toHaveBeenCalledWith(two, 2);
+		expect(callback).toHaveBeenCalledWith(three, 3);
+		expect(callback).toHaveBeenCalledWith(four, 4);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual([
+		<div key=".$keyZero" />,
+		<div key=".$keyTwo" />,
+		<div key=".$keyFour" />,
+	  ]);
+	});
+	it('React.Children.forEach不处理null void 0', () => {
+	  var i = 0
+	  React.Children.forEach(null, function(){
+		i++
+	  })
+	  React.Children.forEach(void 0, function(){
+		i++
+	  })
+	  expect(i).toBe(0)
+	})
+	it('should traverse children of different kinds', () => {
+	  var div = <div key="divNode" />;
+	  var span = <span key="spanNode" />;
+	  var a = <a key="aNode" />;
+  
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+	  var instance = (
+		<div>
+		  {div}
+		  {[[span]]}
+		  {[a]}
+		  {'string'}
+		  {1234}
+		  {true}
+		  {false}
+		  {null}
+		  {undefined}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback.calls.count()).toBe(9);
+		expect(callback).toHaveBeenCalledWith(div, 0);
+		expect(callback).toHaveBeenCalledWith(span, 1);
+		expect(callback).toHaveBeenCalledWith(a, 2);
+		expect(callback).toHaveBeenCalledWith('string', 3);
+		expect(callback).toHaveBeenCalledWith(1234, 4);
+		expect(callback).toHaveBeenCalledWith(null, 5);
+		expect(callback).toHaveBeenCalledWith(null, 6);
+		expect(callback).toHaveBeenCalledWith(null, 7);
+		expect(callback).toHaveBeenCalledWith(null, 8);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual([
+		<div key=".$divNode" />,
+		<span key=".1:0:$spanNode" />,
+		<a key=".2:$aNode" />,
+		'string',
+		1234,
+	  ]);
+	});
+  
+	it('should be called for each child in nested structure', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+	  var five = <div key="keyFive" />;
+  
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  var instance = (
+		<div>
+		  {[[zero, one, two], [three, four], five]}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback.calls.count()).toBe(6);
+		expect(callback).toHaveBeenCalledWith(zero, 0);
+		expect(callback).toHaveBeenCalledWith(one, 1);
+		expect(callback).toHaveBeenCalledWith(two, 2);
+		expect(callback).toHaveBeenCalledWith(three, 3);
+		expect(callback).toHaveBeenCalledWith(four, 4);
+		expect(callback).toHaveBeenCalledWith(five, 5);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual([
+		<div key=".0:$keyZero" />,
+		<div key=".0:$keyTwo" />,
+		<div key=".1:$keyFour" />,
+		<div key=".$keyFive" />,
+	  ]);
+	});
+  
+  
+	it('should retain key across two mappings', () => {
+	  var zeroForceKey = <div key="keyZero" />;
+	  var oneForceKey = <div key="keyOne" />;
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+	  var forcedKeys = (
+		<div>
+		  {zeroForceKey}
+		  {oneForceKey}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback).toHaveBeenCalledWith(zeroForceKey, 0);
+		expect(callback).toHaveBeenCalledWith(oneForceKey, 1);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(forcedKeys.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		forcedKeys.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual([
+		<div key=".$keyZero" />,
+		<div key=".$keyOne" />,
+	  ]);
+	});
+  
+	it('should be called for each child in an iterable without keys', () => {
+	  spyOn(console, 'error');
+	  var threeDivIterable = {
+		'@@iterator': function() {
+		  var i = 0;
+		  return {
+			next: function() {
+			  if (i++ < 3) {
+				return {value: <div />, done: false};
+			  } else {
+				return {value: undefined, done: true};
+			  }
+			},
+		  };
+		},
+	  };
+  
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  var instance = (
+		<div>
+		  {threeDivIterable}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback.calls.count()).toBe(3);
+		expect(callback).toHaveBeenCalledWith(<div />, 0);
+		expect(callback).toHaveBeenCalledWith(<div />, 1);
+		expect(callback).toHaveBeenCalledWith(<div />, 2);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+	  
+	  console.error.calls.reset();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(console.error.calls.count()).toBe(0);
+	  expect(mappedChildren).toEqual([
+		<div key=".0" />,
+		<div key=".1" />,
+		<div key=".2" />,
+	  ]);
+	});
+  
+  
+  
+	it('should be called for each child in an iterable with keys', () => {
+	  var threeDivIterable = {
+		'@@iterator': function() {
+		  var i = 0;
+		  return {
+			next: function() {
+			  if (i++ < 3) {
+				return {value: <div key={'#' + i} />, done: false};
+			  } else {
+				return {value: undefined, done: true};
+			  }
+			},
+		  };
+		},
+	  };
+  
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  var instance = (
+		<div>
+		  {threeDivIterable}
+		</div>
+	  );
+  
+	  function assertCalls() {
+		expect(callback.calls.count()).toBe(3);
+		expect(callback).toHaveBeenCalledWith(<div key="#1" />, 0);
+		expect(callback).toHaveBeenCalledWith(<div key="#2" />, 1);
+		expect(callback).toHaveBeenCalledWith(<div key="#3" />, 2);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual([
+		<div key=".$#1" />,
+		<div key=".$#2" />,
+		<div key=".$#3" />,
+	  ]);
+	});
+  
+	it('should not enumerate enumerable numbers (#4776)', () => {
+	  Number.prototype['@@iterator'] = function() {
+		throw new Error('number iterator called');
+	  };
+  
+	  try {
+		var instance = (
+		  <div>
+			{5}
+			{12}
+			{13}
+		  </div>
+		);
+  
+		var context = {};
+		var callback = spyOn.createSpy(function(kid, index) {
+		  expect(this).toBe(context);
+		  return kid;
+		});
+  
+		var assertCalls = function() {
+		  expect(callback.calls.count()).toBe(3);
+		  expect(callback).toHaveBeenCalledWith(5, 0);
+		  expect(callback).toHaveBeenCalledWith(12, 1);
+		  expect(callback).toHaveBeenCalledWith(13, 2);
+		  callback.calls.reset();
+		};
+  
+		React.Children.forEach(instance.props.children, callback, context);
+		assertCalls();
+  
+		var mappedChildren = React.Children.map(
+		  instance.props.children,
+		  callback,
+		  context,
+		);
+		assertCalls();
+		expect(mappedChildren).toEqual([5, 12, 13]);
+	  } finally {
+		delete Number.prototype['@@iterator'];
+	  }
+	});
+  
+	it('should allow extension of native prototypes', () => {
+	  String.prototype.key = 'react';
+	  Number.prototype.key = 'rocks';
+  
+	  var instance = (
+		<div>
+		  {'a'}
+		  {13}
+		</div>
+	  );
+  
+	  var context = {};
+	  var callback = spyOn.createSpy(function(kid, index) {
+		expect(this).toBe(context);
+		return kid;
+	  });
+  
+	  function assertCalls() {
+		expect(callback.calls.count()).toBe(2, 0);
+		expect(callback).toHaveBeenCalledWith('a', 0);
+		expect(callback).toHaveBeenCalledWith(13, 1);
+		callback.calls.reset();
+	  }
+  
+	  React.Children.forEach(instance.props.children, callback, context);
+	  assertCalls();
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		context,
+	  );
+	  assertCalls();
+	  expect(mappedChildren).toEqual(['a', 13]);
+  
+	  delete String.prototype.key;
+	  delete Number.prototype.key;
+	});
+  
+	it('should pass key to returned component', () => {
+	  var mapFn = function(kid, index) {
+		return <div>{kid}</div>;
+	  };
+  
+	  var simpleKid = <span key="simple" />;
+  
+	  var instance = <div>{simpleKid}</div>;
+	  var mappedChildren = React.Children.map(instance.props.children, mapFn);
+  
+	  expect(React.Children.count(mappedChildren)).toBe(1);
+	  expect(mappedChildren[0]).not.toBe(simpleKid);
+	  expect(mappedChildren[0].props.children).toBe(simpleKid);
+	  expect(mappedChildren[0].key).toBe('.$simple');
+	});
+  
+	it('should invoke callback with the right context', () => {
+	  var lastContext;
+	  var callback = function(kid, index) {
+		lastContext = this;
+		return this;
+	  };
+  
+	  // TODO: Use an object to test, after non-object fragments has fully landed.
+	  var scopeTester = 'scope tester';
+  
+	  var simpleKid = <span key="simple" />;
+	  var instance = <div>{simpleKid}</div>;
+	  React.Children.forEach(instance.props.children, callback, scopeTester);
+	  expect(lastContext).toBe(scopeTester);
+  
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		callback,
+		scopeTester,
+	  );
+  
+	  expect(React.Children.count(mappedChildren)).toBe(1);
+	  expect(mappedChildren[0]).toBe(scopeTester);
+	});
+  
+	it('should be called for each child', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+  
+	  var mapped = [
+		<div key="giraffe" />, // Key should be joined to obj key
+		null, // Key should be added even if we don't supply it!
+		<div />, // Key should be added even if not supplied!
+		<span />, // Map from null to something.
+		<div key="keyFour" />,
+	  ];
+	  var callback = spyOn.createSpy(function(kid, index) {
+		return mapped[index];
+	  });
+  
+	  var instance = (
+		<div>
+		  {zero}
+		  {one}
+		  {two}
+		  {three}
+		  {four}
+		</div>
+	  );
+  
+	  React.Children.forEach(instance.props.children, callback);
+	  expect(callback).toHaveBeenCalledWith(zero, 0);
+	  expect(callback).toHaveBeenCalledWith(one, 1);
+	  expect(callback).toHaveBeenCalledWith(two, 2);
+	  expect(callback).toHaveBeenCalledWith(three, 3);
+	  expect(callback).toHaveBeenCalledWith(four, 4);
+	  callback.calls.reset();
+  
+	  var mappedChildren = React.Children.map(instance.props.children, callback);
+	  expect(callback.calls.count()).toBe(5);
+	  expect(React.Children.count(mappedChildren)).toBe(4);
+	  // Keys default to indices.
+	 
+	  expect([
+		mappedChildren[0].key,
+		mappedChildren[1].key,
+		mappedChildren[2].key,
+		mappedChildren[3].key,
+	  ]).toEqual(['giraffe/.$keyZero', '.$keyTwo', '.3', '.$keyFour']);
+  
+	  expect(callback).toHaveBeenCalledWith(zero, 0);
+	  expect(callback).toHaveBeenCalledWith(one, 1);
+	  expect(callback).toHaveBeenCalledWith(two, 2);
+	  expect(callback).toHaveBeenCalledWith(three, 3);
+	  expect(callback).toHaveBeenCalledWith(four, 4);
+	  expect(mappedChildren[0]).toEqual(<div key="giraffe/.$keyZero" />);
+	  expect(mappedChildren[1]).toEqual(<div key=".$keyTwo" />);
+	  expect(mappedChildren[2]).toEqual(<span key=".3" />);
+	  expect(mappedChildren[3]).toEqual(<div key=".$keyFour" />);
+	  
+	});
+  
+  
+	it('should be called for each child in nested structure', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+	  var five = <div key="keyFive" />;
+  
+	  var zeroMapped = <div key="giraffe" />; // Key should be overridden
+	  var twoMapped = <div />; // Key should be added even if not supplied!
+	  var fourMapped = <div key="keyFour" />;
+	  var fiveMapped = <div />;
+  
+	  var callback = spyOn.createSpy(function(kid) {
+		switch (kid) {
+		  case zero:
+			return zeroMapped;
+		  case two:
+			return twoMapped;
+		  case four:
+			return fourMapped;
+		  case five:
+			return fiveMapped;
+		  default:
+			return kid;
+		}
+	  });
+  
+	  var frag = [[zero, one, two], [three, four], five];
+	  var instance = <div>{[frag]}</div>;
+  
+	  React.Children.forEach(instance.props.children, callback);
+	  expect(callback.calls.count()).toBe(6);
+	  expect(callback).toHaveBeenCalledWith(zero, 0);
+	  expect(callback).toHaveBeenCalledWith(one, 1);
+	  expect(callback).toHaveBeenCalledWith(two, 2);
+	  expect(callback).toHaveBeenCalledWith(three, 3);
+	  expect(callback).toHaveBeenCalledWith(four, 4);
+	  expect(callback).toHaveBeenCalledWith(five, 5);
+	  callback.calls.reset();
+  
+	  var mappedChildren = React.Children.map(instance.props.children, callback);
+	  expect(callback.calls.count()).toBe(6);
+	  expect(callback).toHaveBeenCalledWith(zero, 0);
+	  expect(callback).toHaveBeenCalledWith(one, 1);
+	  expect(callback).toHaveBeenCalledWith(two, 2);
+	  expect(callback).toHaveBeenCalledWith(three, 3);
+	  expect(callback).toHaveBeenCalledWith(four, 4);
+	  expect(callback).toHaveBeenCalledWith(five, 5);
+  
+	  expect(React.Children.count(mappedChildren)).toBe(4);
+	  // Keys default to indices.
+	  expect([
+		mappedChildren[0].key,
+		mappedChildren[1].key,
+		mappedChildren[2].key,
+		mappedChildren[3].key,
+	  ]).toEqual([
+		'giraffe/.0:0:$keyZero',
+		'.0:0:$keyTwo',
+		'.0:1:$keyFour',
+		'.0:$keyFive',
+	  ]);
+  
+	  expect(mappedChildren[0]).toEqual(<div key="giraffe/.0:0:$keyZero" />);
+	  expect(mappedChildren[1]).toEqual(<div key=".0:0:$keyTwo" />);
+	  expect(mappedChildren[2]).toEqual(<div key=".0:1:$keyFour" />);
+	  expect(mappedChildren[3]).toEqual(<div key=".0:$keyFive" />);
+	});
+	it('should retain key across two mappings', () => {
+	  var zeroForceKey = <div key="keyZero" />;
+	  var oneForceKey = <div key="keyOne" />;
+  
+	  // Key should be joined to object key
+	  var zeroForceKeyMapped = <div key="giraffe" />;
+	  // Key should be added even if we don't supply it!
+	  var oneForceKeyMapped = <div />;
+  
+	  var mapFn = function(kid, index) {
+		return index === 0 ? zeroForceKeyMapped : oneForceKeyMapped;
+	  };
+  
+	  var forcedKeys = (
+		<div>
+		  {zeroForceKey}
+		  {oneForceKey}
+		</div>
+	  );
+  
+	  var expectedForcedKeys = ['giraffe/.$keyZero', '.$keyOne'];
+	  var mappedChildrenForcedKeys = React.Children.map(
+		forcedKeys.props.children,
+		mapFn,
+	  );
+	  var mappedForcedKeys = mappedChildrenForcedKeys.map(c => c.key);
+	  expect(mappedForcedKeys).toEqual(expectedForcedKeys);
+  
+	  var expectedRemappedForcedKeys = [
+		'giraffe/.$giraffe/.$keyZero',
+		'.$.$keyOne',
+	  ];
+	  var remappedChildrenForcedKeys = React.Children.map(
+		mappedChildrenForcedKeys,
+		mapFn,
+	  );
+	  expect(remappedChildrenForcedKeys.map(c => c.key)).toEqual(
+		expectedRemappedForcedKeys,
+	  );
+	});
+  
+  
+	it('should not throw if key provided is a dupe with array key', () => {
+	  var zero = <div />;
+	  var one = <div key="0" />;
+  
+	  var mapFn = function() {
+		return null;
+	  };
+  
+	  var instance = (
+		<div>
+		  {zero}
+		  {one}
+		</div>
+	  );
+  
+	  expect(function() {
+		React.Children.map(instance.props.children, mapFn);
+	  }).not.toThrow();
+	});
+  
+	it('should use the same key for a cloned element', () => {
+	  var instance = (
+		<div>
+		  <div />
+		</div>
+	  );
+  
+	  var mapped = React.Children.map(
+		instance.props.children,
+		element => element,
+	  );
+  
+	  var mappedWithClone = React.Children.map(instance.props.children, element =>
+		React.cloneElement(element),
+	  );
+  
+	  expect(mapped[0].key).toBe(mappedWithClone[0].key);
+	});
+  
+	it('should use the same key for a cloned element with key', () => {
+	  var instance = (
+		<div>
+		  <div key="unique" />
+		</div>
+	  );
+  
+	  var mapped = React.Children.map(
+		instance.props.children,
+		element => element,
+	  );
+  
+	  var mappedWithClone = React.Children.map(instance.props.children, element =>
+		React.cloneElement(element, {key: 'unique'}),
+	  );
+  
+	  expect(mapped[0].key).toBe(mappedWithClone[0].key);
+	});
+  
+	it('should return 0 for null children', () => {
+	  var numberOfChildren = React.Children.count(null);
+	  expect(numberOfChildren).toBe(0);
+	});
+  
+	it('should return 0 for undefined children', () => {
+	  var numberOfChildren = React.Children.count(undefined);
+	  expect(numberOfChildren).toBe(0);
+	});
+  
+	it('should return 1 for single child', () => {
+	  var simpleKid = <span key="simple" />;
+	  var instance = <div>{simpleKid}</div>;
+	  var numberOfChildren = React.Children.count(instance.props.children);
+	  expect(numberOfChildren).toBe(1);
+	});
+  
+	it('should count the number of children in flat structure', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+  
+	  var instance = (
+		<div>
+		  {zero}
+		  {one}
+		  {two}
+		  {three}
+		  {four}
+		</div>
+	  );
+	  var numberOfChildren = React.Children.count(instance.props.children);
+	  expect(numberOfChildren).toBe(5);
+	});
+  
+  
+	it('should count the number of children in nested structure', () => {
+	  var zero = <div key="keyZero" />;
+	  var one = null;
+	  var two = <div key="keyTwo" />;
+	  var three = null;
+	  var four = <div key="keyFour" />;
+	  var five = <div key="keyFive" />;
+  
+	  var instance = (
+		<div>
+		  {[[[zero, one, two], [three, four], five], null]}
+		</div>
+	  );
+	  var numberOfChildren = React.Children.count(instance.props.children);
+	  expect(numberOfChildren).toBe(7);
+	});
+  
+	it('should flatten children to an array', () => {
+	  expect(React.Children.toArray(undefined)).toEqual([]);
+	  expect(React.Children.toArray(null)).toEqual([]);
+  
+	  expect(React.Children.toArray(<div />).length).toBe(1);
+	  expect(React.Children.toArray([<div />]).length).toBe(1);
+	  expect(React.Children.toArray(<div />)[0].key).toBe(
+		React.Children.toArray([<div />])[0].key,
+	  );
+  
+	  var flattened = React.Children.toArray([
+		[<div key="apple" />, <div key="banana" />, <div key="camel" />],
+		[<div key="banana" />, <div key="camel" />, <div key="deli" />],
+	  ]);
+	  expect(flattened.length).toBe(6);
+	  expect(flattened[1].key).toContain('banana');
+	  expect(flattened[3].key).toContain('banana');
+	 
+	  expect(flattened[1].key).not.toBe(flattened[3].key);
+  
+	  var reversed = React.Children.toArray([
+		[<div key="camel" />, <div key="banana" />, <div key="apple" />],
+		[<div key="deli" />, <div key="camel" />, <div key="banana" />],
+	  ]);
+	  expect(flattened[0].key).toBe(reversed[2].key);
+	  expect(flattened[1].key).toBe(reversed[1].key);
+	  expect(flattened[2].key).toBe(reversed[0].key);
+	  expect(flattened[3].key).toBe(reversed[5].key);
+	  expect(flattened[4].key).toBe(reversed[4].key);
+	  expect(flattened[5].key).toBe(reversed[3].key);
+  
+	  // null/undefined/bool are all omitted
+	  expect(React.Children.toArray([1, 'two', null, undefined, true])).toEqual([
+		1,
+		'two',
+	  ]);
+  
+	});
+  
+	it('should escape keys', () => {
+	  var zero = <div key="1" />;
+	  var one = <div key="1=::=2" />;
+	  var instance = (
+		<div>
+		  {zero}
+		  {one}
+		</div>
+	  );
+	  var mappedChildren = React.Children.map(
+		instance.props.children,
+		kid => kid,
+	  );
+	  expect(mappedChildren).toEqual([
+		<div key=".$1" />,
+		<div key=".$1=0=2=2=02" />,
+	  ]);
+	});
+  
+	it('should throw on object', () => {
+	  expect(function() {
+		React.Children.forEach({a: 1, b: 2}, function() {}, null);
+	  }).toThrowError(
+		'Objects are not valid as a React child (found: object with keys ' +
+		  '{a, b}). If you meant to render a collection of children, use an ' +
+		  'array instead.',
+	  );
+	});
+  
+	it('should throw on regex', () => {
+	  // Really, we care about dates (#4840) but those have nondeterministic
+	  // serialization (timezones) so let's test a regex instead:
+	  expect(function() {
+		React.Children.forEach(/abc/, function() {}, null);
+	  }).toThrowError(
+		'Objects are not valid as a React child (found: /abc/). If you meant ' +
+		  'to render a collection of children, use an array instead.',
+	  );
+	});*/
+
+})

+ 475 - 0
test/may-dom/ReactComponent-test.jsx

@@ -0,0 +1,475 @@
+
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+import ReactDOMServer from '../../src/may-server/MayServer'
+// import React from "../dist/ReactANU";
+// var ReactDOM = React;
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+describe("ReactComponent", function () {
+    // this.timeout(200000);
+
+    it("should throw on invalid render targets", () => {
+        var container = document.createElement("div");
+        // jQuery objects are basically arrays; people often pass them in by mistake
+        expect(function() {
+            ReactDOM.render(<div />, [container]);
+        }).toThrowError(/container参数错误/);
+
+    });
+
+    it("should throw when supplying a ref outside of render method", () => {
+        var instance = <div ref="badDiv" />;
+            instance = ReactTestUtils.renderIntoDocument(instance);
+    });
+    it('mayRender', () => {
+        spyOn(console, 'error');
+        var container = document.createElement('div');
+        class Child extends React.Component {
+            //   constructor(props){
+            // 	  super(props);
+            // 	  this.state={val2:'I wonder'};
+            //   }
+
+            render() {
+                return (
+                    <div>
+                        {this.props.key}
+                        {this.props.val}
+                    </div>);
+            }
+        }
+        class Parent extends React.Component {
+            constructor() {
+                super();
+                this.state = { val: 'I wonder' };
+                this.Change = this.Change.bind(this);
+            }
+            Change() {
+                this.setState({ val: 'I see' });
+            }
+            onFocus() {
+                this.refs.inputRef.focus()
+            }
+            render() {
+                var item = [];
+                return (
+                    <div className="mystyle" style={{ width: '40%', marginLeft: '30px' }}>
+                        {this.state.val === 'I wonder' ? '' : <Child key="1" val="2" />}
+                        <input onChange={this.Change} type="text" value={this.state.val} />
+                        666&nbsp; {this.state.val}
+                        {this.state.val !== 'I wonder' ? <Child key="1" val="2" /> : <Child key="3" val="4" />}
+                        <Child key="0" val={this.state.val} />
+                    </div>
+                )
+            }
+        }
+        //this._updateDOMChildren
+        React.render(<Parent />, container);
+        expect(console.error.calls.count()).toBe(0);
+    });
+     it("should warn when children are mutated during render", () => {
+        function Wrapper(props) {
+            props.children[1] = <p key={1} />; // Mutation is illegal
+            return <div>{props.children}</div>;
+        }
+
+        var instance = ReactTestUtils.renderIntoDocument(
+            <Wrapper>
+                <span key={0} />
+                <span key={1} />
+                <span key={2} />
+            </Wrapper> 
+        );
+        expect(ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, "p").length).toBe(1);
+    });
+
+    it("should warn when children are mutated during update", () => {
+        class Wrapper extends React.Component {
+            componentDidMount() {
+                this.props.children[1] = <p key={1} />; // Mutation is illegal
+                this.forceUpdate();
+            }
+
+            render() {
+                return <div>{this.props.children}</div>;
+            }
+        }
+
+        var instance = ReactTestUtils.renderIntoDocument(
+            <Wrapper>
+                <span key={0} />
+                <span key={1} />
+                <span key={2} />
+            </Wrapper>
+        );
+        expect(ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, "p").length).toBe(1);
+    });
+
+     it("should support refs on owned components", () => {
+        var innerObj = {};
+        var outerObj = {};
+ 
+        class Wrapper extends React.Component {
+            getObject = () => {
+                return this.props.object;
+            };
+ 
+            render() {
+                return <div>{this.props.children}</div>;
+            }
+        }
+ 
+        class Component extends React.Component {
+            render() {
+                var inner = <Wrapper object={innerObj} ref="inner" />;
+                var outer = (
+                    <Wrapper object={outerObj} ref="outer">
+                        {inner}
+                    </Wrapper>
+                );
+                return outer;
+            }
+ 
+            componentDidMount() {
+                expect(this.refs.inner.getObject()).toEqual(innerObj);
+                expect(this.refs.outer.getObject()).toEqual(outerObj);
+            }
+        }
+ 
+        ReactTestUtils.renderIntoDocument(<Component />);
+    });
+
+    it("should not have refs on unmounted components", () => {
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <Child>
+                        <div ref="test" />
+                    </Child>
+                );
+            }
+
+            componentDidMount() {
+                expect(this.refs && this.refs.test).toEqual(null);
+            }
+        }
+
+        class Child extends React.Component {
+            render() {
+                return <div />;
+            }
+        }
+
+        ReactTestUtils.renderIntoDocument(<Parent child={<span />} />);
+    });
+
+    it("should support new-style refs", () => {
+        var innerObj = {};
+        var outerObj = {};
+ 
+        class Wrapper extends React.Component {
+            getObject = () => {
+                return this.props.object;
+            };
+ 
+            render() {
+                return <div>{this.props.children}</div>;
+            }
+        }
+ 
+        var mounted = false;
+ 
+        class Component extends React.Component {
+            render() {
+                var inner = <Wrapper object={innerObj} ref={c => (this.innerRef = c)} />;
+                var outer = (
+                    <Wrapper object={outerObj} ref={c => (this.outerRef = c)}>
+                        {inner}
+                    </Wrapper>
+                );
+                return outer;
+            }
+ 
+            componentDidMount() {
+                expect(this.innerRef.getObject()).toEqual(innerObj);
+                expect(this.outerRef.getObject()).toEqual(outerObj);
+                mounted = true;
+            }
+        }
+ 
+        ReactTestUtils.renderIntoDocument(<Component />);
+        expect(mounted).toBe(true);
+    });
+
+    it("should support new-style refs with mixed-up owners", () => {
+        class Wrapper extends React.Component {
+            getTitle = () => {
+                return this.props.title;
+            };
+
+            render() {
+                return this.props.getContent();
+            }
+        }
+
+        var mounted = false;
+
+        class Component extends React.Component {
+            getInner = () => {
+                // (With old-style refs, it's impossible to get a ref to this div
+                // because Wrapper is the current owner when this function is called.)
+                return <div className="inner" ref={c => (this.innerRef = c)} />;
+            };
+
+            render() {
+                return <Wrapper title="wrapper" ref={c => (this.wrapperRef = c)} getContent={this.getInner} />;
+            }
+
+            componentDidMount() {
+                // Check .props.title to make sure we got the right elements back
+                expect(this.wrapperRef.getTitle()).toBe("wrapper");
+                expect(ReactDOM.findDOMNode(this.innerRef).className).toBe("inner");
+                mounted = true;
+            }
+        }
+
+        ReactTestUtils.renderIntoDocument(<Component />);
+        expect(mounted).toBe(true);
+    });
+
+    /**
+ * ------------------ The Life-Cycle of a Composite Component ------------------
+ *
+ * - constructor: Initialization of state. The instance is now retained.
+ *   - componentWillMount
+ *   - render
+ *   - [children's constructors]
+ *     - [children's componentWillMount and render]
+ *     - [children's componentDidMount]
+ *     - componentDidMount
+ *
+ *       Update Phases:
+ *       - componentWillReceiveProps (only called if parent updated)
+ *       - shouldComponentUpdate
+ *         - componentWillUpdate
+ *           - render
+ *           - [children's constructors or receive props phases]
+ *         - componentDidUpdate
+ *
+ *     - componentWillUnmount
+ *     - [children's componentWillUnmount]
+ *   - [children destroyed]
+ * - (destroyed): The instance is now blank, released by React and ready for GC.
+ *
+ * -----------------------------------------------------------------------------
+ */
+
+    it("should call refs at the correct time", () => {
+        var log = [];
+
+        class Inner extends React.Component {
+            render() {
+                log.push(`inner ${this.props.id} render`);
+                return <div />;
+            }
+
+            componentDidMount() {
+                log.push(`inner ${this.props.id} componentDidMount`);
+            }
+
+            componentDidUpdate() {
+                log.push(`inner ${this.props.id} componentDidUpdate`);
+            }
+
+            componentWillUnmount() {
+                log.push(`inner ${this.props.id} componentWillUnmount`);
+            }
+        }
+
+        class Outer extends React.Component {
+            render() {
+                return (
+                    <div>
+                        <Inner
+                            id={1}
+                            ref={c => {
+                                log.push(`ref 1 got ${c ? `instance ${c.props.id}` : "null"}`);
+                            }}
+                        />
+                        <Inner
+                            id={2}
+                            ref={c => {
+                                log.push(`ref 2 got ${c ? `instance ${c.props.id}` : "null"}`);
+                            }}
+                        />
+                    </div>
+                );
+            }
+
+            componentDidMount() {
+                log.push("outer componentDidMount");
+            }
+
+            componentDidUpdate() {
+                log.push("outer componentDidUpdate");
+            }
+
+            componentWillUnmount() {
+                log.push("outer componentWillUnmount");
+            }
+        }
+
+        // mount, update, unmount
+        var container = document.createElement("div");
+        log.push("start mount");
+        ReactDOM.render(<Outer />, container);
+        log.push("start update");
+        ReactDOM.render(<Outer />, container);
+        log.push("start unmount");
+        ReactDOM.unmountComponentAtNode(container);
+
+        //eslint-disable indent 
+        expect(log).toEqual([
+            "start mount",
+            "inner 1 render",
+            "inner 2 render",
+            "inner 1 componentDidMount",
+            "ref 1 got instance 1",
+            "inner 2 componentDidMount",
+            "ref 2 got instance 2",
+            "outer componentDidMount",
+            "start update",
+
+            // Stack resets refs before rendering
+            "ref 1 got null",
+            "inner 1 render",
+            "ref 2 got null",
+            "inner 2 render",
+
+            "inner 1 componentDidUpdate",
+            "ref 1 got instance 1",
+            "inner 2 componentDidUpdate",
+            "ref 2 got instance 2",
+            "outer componentDidUpdate",
+            "start unmount",
+            "outer componentWillUnmount",
+            "ref 1 got null",
+            "inner 1 componentWillUnmount",
+            "ref 2 got null",
+            "inner 2 componentWillUnmount"
+        ]);
+    });
+
+
+ 
+    it("includes owner name in the error about badly-typed elements", () => {
+        spyOn(console, "error");
+ 
+        var X = 'undefined';
+ 
+        function Indirection(props) {
+            return <div>{props.children}</div>;
+        }
+ 
+        function Bar() {
+            return (
+                <Indirection>
+                    <X />
+                </Indirection>
+            );
+        }
+ 
+        function Foo() {
+            return <Bar />;
+        }
+ 
+        ReactTestUtils.renderIntoDocument(<Foo />)
+    });
+ 
+    it("throws if a plain object is used as a child", () => {
+        var children = {
+            x: <span />,
+            y: <span />,
+            z: <span />
+        };
+        var element = <div>{[children]}</div>;
+        var container = document.createElement("div");
+        var ex;
+        try {
+            ReactDOM.render(element, container);
+        } catch (e) {
+            ex = e;
+        }
+        // expect(ex).toBeDefined();
+    });
+    it("throws if a plain object even if it is in an owner", () => {
+        class Foo extends React.Component {
+            render() {
+                var children = {
+                    a: <span />,
+                    b: <span />,
+                    c: <span />
+                };
+                return <div>{[children]}</div>;
+            }
+        }
+        var container = document.createElement("div");
+        var ex;
+        try {
+            ReactDOM.render(<Foo />, container);
+        } catch (e) {
+            ex = e;
+        }
+        // expect(ex).toBeDefined();
+    });
+ 
+    it("throws if a plain object is used as a child when using SSR", async () => {
+        var children = {
+            x: <span />,
+            y: <span />,
+            z: <span />
+        };
+        var element = <div>{[children]}</div>;
+        var ex;
+        try {
+            ReactDOMServer.renderToString(element);
+        } catch (e) {
+            ex = e;
+            console.warn(e);
+        }
+        // expect(ex).toBeDefined();
+    });
+ 
+    it("throws if a plain object even if it is in an owner when using SSR", async () => {
+        class Foo extends React.Component {
+            render() {
+                var children = {
+                    a: <span />,
+                    b: <span />,
+                    c: <span />
+                };
+                return <div>{[children]}</div>;
+            }
+        }
+        var container = document.createElement("div");
+        var ex;
+        try {
+            ReactDOMServer.renderToString(<Foo />, container);
+        } catch (e) {
+            ex = e;
+            console.warn(e);
+        }
+        // expect(ex).toBeDefined();
+    });
+});

+ 532 - 0
test/may-dom/ReactComponentLifeCycle-test.jsx

@@ -0,0 +1,532 @@
+
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+
+describe("ReactComponentLifeCycle-test", function() {
+    // this.timeout(200000);
+
+    it('should not reuse an instance when it has been unmounted', () => {
+    var container = document.createElement('div');
+
+    class StatefulComponent extends React.Component {
+      state = {};
+
+      render() {
+        return <div />;
+      }
+    }
+
+    var element = <StatefulComponent />;
+    var firstInstance = ReactDOM.render(element, container);
+    ReactDOM.unmountComponentAtNode(container);
+    var secondInstance = ReactDOM.render(element, container);
+    expect(firstInstance).not.toBe(secondInstance);
+  });
+
+  /**
+   * If a state update triggers rerendering that in turn fires an onDOMReady,
+   * that second onDOMReady should not fail.
+   */
+  it('it should fire onDOMReady when already in onDOMReady', () => {
+    var _testJournal = [];
+
+    class Child extends React.Component {
+      componentDidMount() {
+        _testJournal.push('Child:onDOMReady');
+      }
+
+      render() {
+        return <div />;
+      }
+    }
+
+    class SwitcherParent extends React.Component {
+      constructor(props) {
+        super(props);
+        _testJournal.push('SwitcherParent:getInitialState');
+        this.state = {showHasOnDOMReadyComponent: false};
+      }
+
+      componentDidMount() {
+        _testJournal.push('SwitcherParent:onDOMReady');
+        this.switchIt();
+      }
+
+      switchIt = () => {
+        this.setState({showHasOnDOMReadyComponent: true});
+      };
+
+      render() {
+        return (
+          <div>
+            {this.state.showHasOnDOMReadyComponent ? <Child /> : <div />}
+          </div>
+        );
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<SwitcherParent />);
+    expect(_testJournal).toEqual([
+      'SwitcherParent:getInitialState',
+      'SwitcherParent:onDOMReady',
+      'Child:onDOMReady',
+    ]);
+  });
+
+  // You could assign state here, but not access members of it, unless you
+  // had provided a getInitialState method.
+  it('throws when accessing state in componentWillMount', () => {
+    class StatefulComponent extends React.Component {
+      componentWillMount() {
+        void this.state.yada;
+      }
+
+      render() {
+        return <div />;
+      }
+    }
+
+    var instance = <StatefulComponent />;
+    expect(function() {
+      instance = ReactTestUtils.renderIntoDocument(instance);
+    }).toThrow();
+  });
+
+  it('should allow update state inside of componentWillMount', () => {
+    class StatefulComponent extends React.Component {
+      componentWillMount() {
+        this.setState({stateField: 'something'});
+      }
+
+      render() {
+        return <div />;
+      }
+    }
+
+    var instance = <StatefulComponent />;
+    expect(function() {
+      instance = ReactTestUtils.renderIntoDocument(instance);
+    }).not.toThrow();
+  });
+
+  it('should not allow update state inside of getInitialState', () => {
+  
+    class StatefulComponent extends React.Component {
+      constructor(props, context) {
+        super(props, context);
+       
+        this.state = {stateField: 'somethingelse'};
+        // this.setState({stateField: 'something'});
+
+      }
+
+      render() {
+        return <div>{this.state.stateField}</div>;
+      }
+    }
+    var container = document.createElement('div');
+    ReactDOM.render(<StatefulComponent />, container);
+    expect(container.textContent).toBe("somethingelse");
+
+  });
+
+  it('should correctly determine if a component is mounted', () => {
+    class Component extends React.Component {
+ 
+      componentWillMount() {
+        expect(this.isMounted()).toBeFalsy();
+      }
+      componentDidMount() {
+        expect(this.isMounted()).toBeTruthy();
+      }
+      render() {
+        expect(this.isMounted()).toBeFalsy();
+        return <div />;
+      }
+    }
+
+    var element = <Component />;
+
+    var instance = ReactTestUtils.renderIntoDocument(element);
+    expect(instance.isMounted()).toBeTruthy();
+
+
+  });
+
+  it('should correctly determine if a null component is mounted', () => {
+    class Component extends React.Component {
+  
+      componentWillMount() {
+        expect(this.isMounted()).toBeFalsy();
+      }
+      componentDidMount() {
+        expect(this.isMounted()).toBeTruthy();
+      }
+      render() {
+        expect(this.isMounted()).toBeFalsy();
+        return null;
+      }
+    }
+
+    var element = <Component />;
+
+    var instance = ReactTestUtils.renderIntoDocument(element);
+    expect(instance.isMounted()).toBeTruthy();
+  })
+  it('isMounted should return false when unmounted', () => {
+    class Component extends React.Component {
+      render() {
+        return <div />;
+      }
+    }
+
+    var container = document.createElement('div');
+    var instance = ReactDOM.render(<Component />, container);
+
+    // No longer a public API, but we can test that it works internally by
+    // reaching into the updater.
+    expect(instance.isMounted()).toBe(true);
+
+    ReactDOM.unmountComponentAtNode(container);
+
+    // expect(instance.isMounted()).toBe(false);
+  });
+  it('warns if findDOMNode is used inside render', () => {
+  
+    class Component extends React.Component {
+      state = {isMounted: false};
+      componentDidMount() {
+        this.setState({isMounted: true});
+      }
+      render() {
+        if (this.state.isMounted) {
+          expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
+        }
+        return <div />;
+      }
+    }
+    ReactTestUtils.renderIntoDocument(<Component />);
+
+  });
+
+
+  it('should carry through each of the phases of setup', () => {
+    var clone = function(o) {
+  return JSON.parse(JSON.stringify(o));
+};
+
+var GET_INIT_STATE_RETURN_VAL = {
+  hasWillMountCompleted: false,
+  hasRenderCompleted: false,
+  hasDidMountCompleted: false,
+  hasWillUnmountCompleted: false,
+};
+
+var INIT_RENDER_STATE = {
+  hasWillMountCompleted: true,
+  hasRenderCompleted: false,
+  hasDidMountCompleted: false,
+  hasWillUnmountCompleted: false,
+};
+
+var DID_MOUNT_STATE = {
+  hasWillMountCompleted: true,
+  hasRenderCompleted: true,
+  hasDidMountCompleted: false,
+  hasWillUnmountCompleted: false,
+};
+
+var NEXT_RENDER_STATE = {
+  hasWillMountCompleted: true,
+  hasRenderCompleted: true,
+  hasDidMountCompleted: true,
+  hasWillUnmountCompleted: false,
+};
+
+var WILL_UNMOUNT_STATE = {
+  hasWillMountCompleted: true,
+  hasDidMountCompleted: true,
+  hasRenderCompleted: true,
+  hasWillUnmountCompleted: false,
+};
+
+var POST_WILL_UNMOUNT_STATE = {
+  hasWillMountCompleted: true,
+  hasDidMountCompleted: true,
+  hasRenderCompleted: true,
+  hasWillUnmountCompleted: true,
+};
+function getLifeCycleState(instance) {
+  return instance.isMounted() ? 'MOUNTED' : 'UNMOUNTED';
+}
+    spyOn(console, 'error');
+
+    class LifeCycleComponent extends React.Component {
+      constructor(props, context) {
+        super(props, context);
+        this._testJournal = {};
+        var initState = {
+          hasWillMountCompleted: false,
+          hasDidMountCompleted: false,
+          hasRenderCompleted: false,
+          hasWillUnmountCompleted: false,
+        };
+        this._testJournal.returnedFromGetInitialState = clone(initState);
+        this._testJournal.lifeCycleAtStartOfGetInitialState = getLifeCycleState(
+          this,
+        );
+        this.state = initState;
+      }
+
+      componentWillMount() {
+        this._testJournal.stateAtStartOfWillMount = clone(this.state);
+        this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
+        this.state.hasWillMountCompleted = true;
+      }
+
+      componentDidMount() {
+        this._testJournal.stateAtStartOfDidMount = clone(this.state);
+        this._testJournal.lifeCycleAtStartOfDidMount = getLifeCycleState(this);
+        this.setState({hasDidMountCompleted: true});
+      }
+
+      render() {
+        var isInitialRender = !this.state.hasRenderCompleted;
+        if (isInitialRender) {
+          this._testJournal.stateInInitialRender = clone(this.state);
+          this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
+        } else {
+          this._testJournal.stateInLaterRender = clone(this.state);
+          this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
+        }
+        // you would *NEVER* do anything like this in real code!
+        this.state.hasRenderCompleted = true;
+        return (
+          <div ref="theDiv">
+            I am the inner DIV
+          </div>
+        );
+      }
+
+      componentWillUnmount() {
+        this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
+        this._testJournal.lifeCycleAtStartOfWillUnmount = getLifeCycleState(
+          this,
+        );
+        this.state.hasWillUnmountCompleted = true;
+      }
+    }
+
+    // A component that is merely "constructed" (as in "constructor") but not
+    // yet initialized, or rendered.
+    //
+    var container = document.createElement('div');
+    var instance = ReactDOM.render(<LifeCycleComponent />, container);
+
+    // getInitialState
+    expect(instance._testJournal.returnedFromGetInitialState).toEqual(
+      GET_INIT_STATE_RETURN_VAL,
+    );
+    expect(instance._testJournal.lifeCycleAtStartOfGetInitialState).toBe(
+      'UNMOUNTED',
+    );
+
+    // componentWillMount
+    expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
+      instance._testJournal.returnedFromGetInitialState,
+    );
+    expect(instance._testJournal.lifeCycleAtStartOfWillMount).toBe('UNMOUNTED');
+
+    // componentDidMount
+    expect(instance._testJournal.stateAtStartOfDidMount).toEqual(
+      DID_MOUNT_STATE,
+    );
+    expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe('MOUNTED');
+
+    // initial render
+    expect(instance._testJournal.stateInInitialRender).toEqual(
+      INIT_RENDER_STATE,
+    );
+    expect(instance._testJournal.lifeCycleInInitialRender).toBe('UNMOUNTED');
+
+    expect(getLifeCycleState(instance)).toBe('MOUNTED');
+
+    // Now *update the component*
+    instance.forceUpdate();
+
+    // render 2nd time
+    expect(instance._testJournal.stateInLaterRender).toEqual(NEXT_RENDER_STATE);
+    expect(instance._testJournal.lifeCycleInLaterRender).toBe('MOUNTED');
+
+    expect(getLifeCycleState(instance)).toBe('MOUNTED');
+
+    ReactDOM.unmountComponentAtNode(container);
+
+    expect(instance._testJournal.stateAtStartOfWillUnmount).toEqual(
+      WILL_UNMOUNT_STATE,
+    );
+    // componentWillUnmount called right before unmount.
+    expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe('MOUNTED');
+
+    // But the current lifecycle of the component is unmounted.
+    /*expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
+    expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);*/
+
+ 
+  });
+
+  it('should allow state updates in componentDidMount', () => {
+    /**
+     * calls setState in an componentDidMount.
+     */
+    class SetStateInComponentDidMount extends React.Component {
+      state = {
+        stateField: this.props.valueToUseInitially,
+      };
+
+      componentDidMount() {
+        this.setState({stateField: this.props.valueToUseInOnDOMReady});
+      }
+
+      render() {
+        return <div />;
+      }
+    }
+
+    var instance = (
+      <SetStateInComponentDidMount
+        valueToUseInitially="hello"
+        valueToUseInOnDOMReady="goodbye"
+      />
+    );
+    instance = ReactTestUtils.renderIntoDocument(instance);
+    expect(instance.state.stateField).toBe('goodbye');
+  });
+
+  it('should call nested lifecycle methods in the right order', () => {
+    var log;
+    var logger = function(msg) {
+      return function() {
+        // return true for shouldComponentUpdate
+        log.push(msg);
+        return true;
+      };
+    };
+    class Outer extends React.Component {
+      componentWillMount = logger('outer componentWillMount');
+      componentDidMount = logger('outer componentDidMount');
+      componentWillReceiveProps = logger('outer componentWillReceiveProps');
+      shouldComponentUpdate = logger('outer shouldComponentUpdate');
+      componentWillUpdate = logger('outer componentWillUpdate');
+      componentDidUpdate = logger('outer componentDidUpdate');
+      componentWillUnmount = logger('outer componentWillUnmount');
+      render() {
+        return <div><Inner x={this.props.x} /></div>;
+      }
+    }
+
+    class Inner extends React.Component {
+      componentWillMount = logger('inner componentWillMount');
+      componentDidMount = logger('inner componentDidMount');
+      componentWillReceiveProps = logger('inner componentWillReceiveProps');
+      shouldComponentUpdate = logger('inner shouldComponentUpdate');
+      componentWillUpdate = logger('inner componentWillUpdate');
+      componentDidUpdate = logger('inner componentDidUpdate');
+      componentWillUnmount = logger('inner componentWillUnmount');
+      render() {
+        return <span>{this.props.x}</span>;
+      }
+    }
+
+    var container = document.createElement('div');
+    log = [];
+    ReactDOM.render(<Outer x={17} />, container);
+    expect(log).toEqual([
+      'outer componentWillMount',
+      'inner componentWillMount',
+      'inner componentDidMount',
+      'outer componentDidMount',
+    ]);
+
+    log = [];
+    ReactDOM.render(<Outer x={42} />, container);
+    expect(log).toEqual([
+      'outer componentWillReceiveProps',
+      'outer shouldComponentUpdate',
+      'outer componentWillUpdate',
+      'inner componentWillReceiveProps',
+      'inner shouldComponentUpdate',
+      'inner componentWillUpdate',
+      'inner componentDidUpdate',
+      'outer componentDidUpdate',
+    ]);
+
+    log = [];
+    ReactDOM.unmountComponentAtNode(container);
+    expect(log).toEqual([
+      'outer componentWillUnmount',
+      'inner componentWillUnmount',
+    ]);
+  });
+	//暂不支持这种形式的component
+ /*it('calls effects on module-pattern component', function() {
+    const log = [];
+    function Parent() {
+      return {
+        render() {
+          expect(typeof this.props).toBe('object');
+          log.push('render');
+          return <Child />;
+        },
+        componentWillMount() {
+          log.push('will mount');
+        },
+        componentDidMount() {
+          log.push('did mount');
+        },
+        componentDidUpdate() {
+          log.push('did update');
+        },
+        getChildContext() {
+          return {x: 2};
+        },
+      };
+    }
+    Parent.childContextTypes = {
+      x: PropTypes.number,
+    };
+    function Child(props, context) {
+      expect(context.x).toBe(2);
+      return <div />;
+    }
+    Child.contextTypes = {
+      x: PropTypes.number,
+    };
+
+    const div = document.createElement('div');
+    ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
+    ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
+    expect(log).toEqual([
+      'will mount',
+      'render',
+      'did mount',
+      'ref',
+
+      'render',
+      'did update',
+      'ref',
+    ]);
+  });*/
+});

Разница между файлами не показана из-за своего большого размера
+ 1317 - 0
test/may-dom/ReactCompositeComponent-test.jsx


+ 59 - 0
test/may-dom/ReactCompositeComponentDOMMinimalism-test.jsx

@@ -0,0 +1,59 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+describe("ReactCompositeComponentDOMMinimalism",function() {
+    // this.timeout(200000);
+
+    var LowerLevelComposite = class extends React.Component {
+        render() {
+            return <div>{this.props.children}</div>;
+        }
+    };
+
+    var MyCompositeComponent = class extends React.Component {
+        render() {
+            return <LowerLevelComposite>{this.props.children}</LowerLevelComposite>;
+        }
+    };
+
+    var expectSingleChildlessDiv = function(instance) {
+        var el = ReactDOM.findDOMNode(instance);
+        expect(el.tagName).toBe("DIV");
+        expect(el.children.length).toBe(0);
+    };
+
+    it("should not render extra nodes for non-interpolated text", () => {
+        var instance = <MyCompositeComponent>A string child</MyCompositeComponent>;
+        instance = ReactTestUtils.renderIntoDocument(instance);
+        expectSingleChildlessDiv(instance);
+    });
+
+    it("should not render extra nodes for non-interpolated text", () => {
+        var instance = <MyCompositeComponent>{"Interpolated String Child"}</MyCompositeComponent>;
+        instance = ReactTestUtils.renderIntoDocument(instance);
+        expectSingleChildlessDiv(instance);
+    });
+
+    it("should not render extra nodes for non-interpolated text", () => {
+        var instance = (
+            <MyCompositeComponent>
+                <ul>This text causes no children in ul, just innerHTML</ul>
+            </MyCompositeComponent>
+        );
+        instance = ReactTestUtils.renderIntoDocument(instance);
+        var el = ReactDOM.findDOMNode(instance);
+        expect(el.tagName).toBe("DIV");
+        expect(el.children.length).toBe(1);
+        expect(el.children[0].tagName).toBe("UL");
+        expect(el.children[0].children.length).toBe(0);
+    });
+});

+ 138 - 0
test/may-dom/ReactCompositeComponentNestedState-test.jsx

@@ -0,0 +1,138 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = { Simulate: {} };
+// "click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove".replace(/\w+/g, function (name) {
+//     ReactTestUtils.Simulate[name] = function (node, opts) {
+//         if (!node || node.nodeType !== 1) {
+//             throw "第一个参数必须为元素节点";
+//         }
+//         var fakeNativeEvent = opts || {};
+//         fakeNativeEvent.target = node;
+//         fakeNativeEvent.simulated = true;
+//         fakeNativeEvent.type = name.toLowerCase();
+//         React.eventSystem.dispatchEvent(fakeNativeEvent, name.toLowerCase());
+//     };
+// });
+
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+
+describe("ReactCompositeComponentNestedState-state", function() {
+    //this.timeout(200000);
+
+
+    it('should provide up to date values for props', () => {
+    class ParentComponent extends React.Component {
+      state = {color: 'blue'};
+
+      handleColor = color => {
+        this.props.logger('parent-handleColor', this.state.color);
+        this.setState({color: color}, function() {
+          this.props.logger('parent-after-setState', this.state.color);
+        });
+      };
+
+      render() {
+        this.props.logger('parent-render', this.state.color);
+        return (
+          <ChildComponent
+            logger={this.props.logger}
+            color={this.state.color}
+            onSelectColor={this.handleColor}
+          />
+        );
+      }
+    }
+
+    class ChildComponent extends React.Component {
+      constructor(props) {
+        super(props);
+        props.logger('getInitialState', props.color);
+        this.state = {hue: 'dark ' + props.color};
+      }
+
+      handleHue = (shade, color) => {
+        this.props.logger('handleHue', this.state.hue, this.props.color);
+        this.props.onSelectColor(color);
+        this.setState(
+          function(state, props) {
+            this.props.logger(
+              'setState-this',
+              this.state.hue,
+              this.props.color,
+            );
+            this.props.logger('setState-args', state.hue, props.color);
+            return {hue: shade + ' ' + props.color};
+          },
+          function() {
+            this.props.logger(
+              'after-setState',
+              this.state.hue,
+              this.props.color,
+            );
+          },
+        );
+      };
+
+      render() {
+        this.props.logger('render', this.state.hue, this.props.color);
+        return (
+          <div>
+            <button onClick={this.handleHue.bind(this, 'dark', 'blue')}>
+              Dark Blue
+            </button>
+            <button onClick={this.handleHue.bind(this, 'light', 'blue')}>
+              Light Blue
+            </button>
+            <button onClick={this.handleHue.bind(this, 'dark', 'green')}>
+              Dark Green
+            </button>
+            <button onClick={this.handleHue.bind(this, 'light', 'green')}>
+              Light Green
+            </button>
+          </div>
+        );
+      }
+    }
+
+    var container = document.createElement('div');
+    document.body.appendChild(container);
+    var list = []
+    function logger(){
+      list.push([].slice.call(arguments, 0))
+    }
+
+    void ReactDOM.render(<ParentComponent logger={logger} />, container);
+
+    // click "light green"
+    ReactTestUtils.Simulate.click(container.childNodes[0].childNodes[3]);
+    //console.log(JSON.stringify(logger.calls))
+    expect(list).toEqual([
+      ['parent-render', 'blue'],
+      ['getInitialState', 'blue'],
+      ['render', 'dark blue', 'blue'],
+      ['handleHue', 'dark blue', 'blue'],
+      ['parent-handleColor', 'blue'],
+      ['parent-render', 'green'],
+      ['setState-this', 'dark blue', 'blue'],
+      ['setState-args', 'dark blue', 'green'],
+      ['render', 'light green', 'green'],
+      ['parent-after-setState', 'green'],
+      ['after-setState', 'light green', 'green']
+     
+    ]);
+  });
+
+
+});

+ 324 - 0
test/may-dom/ReactCompositeComponentState-test.jsx

@@ -0,0 +1,324 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
+
+describe("ReactCompositeComponentDOMMinimalism", function() {
+    // this.timeout(200000);
+
+    var TestComponent = class extends React.Component {
+        constructor(props) {
+            super(props);
+            this.peekAtState("getInitialState", undefined, props);
+            this.state = { color: "red" };
+        }
+
+        peekAtState = (from, state = this.state, props = this.props) => {
+            props.stateListener(from, state && state.color);
+        };
+
+        peekAtCallback = from => {
+            return () => this.peekAtState(from);
+        };
+
+        setFavoriteColor(nextColor) {
+            this.setState({ color: nextColor }, this.peekAtCallback("setFavoriteColor"));
+        }
+
+        render() {
+            this.peekAtState("render");
+            return <div>{this.state.color}</div>;
+        }
+
+        componentWillMount() {
+            this.peekAtState("componentWillMount-start");
+            this.setState(function(state) {
+                this.peekAtState("before-setState-sunrise", state);
+            });
+            this.setState({ color: "sunrise" }, this.peekAtCallback("setState-sunrise"));
+            this.setState(function(state) {
+                this.peekAtState("after-setState-sunrise", state);
+            });
+            this.peekAtState("componentWillMount-after-sunrise");
+            this.setState({ color: "orange" }, this.peekAtCallback("setState-orange"));
+            this.setState(function(state) {
+                this.peekAtState("after-setState-orange", state);
+            });
+            this.peekAtState("componentWillMount-end");
+        }
+
+        componentDidMount() {
+            this.peekAtState("componentDidMount-start");
+            this.setState({ color: "yellow" }, this.peekAtCallback("setState-yellow"));
+            this.peekAtState("componentDidMount-end");
+        }
+
+        componentWillReceiveProps(newProps) {
+            this.peekAtState("componentWillReceiveProps-start");
+            if (newProps.nextColor) {
+                this.setState(function(state) {
+                    this.peekAtState("before-setState-receiveProps", state);
+                    return { color: newProps.nextColor };
+                });
+                // No longer a public API, but we can test that it works internally by
+                // reaching into the updater.
+                this.setState({ color: undefined });
+                this.setState(function(state) {
+                    this.peekAtState("before-setState-again-receiveProps", state);
+                    return { color: newProps.nextColor };
+                }, this.peekAtCallback("setState-receiveProps"));
+                this.setState(function(state) {
+                    this.peekAtState("after-setState-receiveProps", state);
+                });
+            }
+            this.peekAtState("componentWillReceiveProps-end");
+        }
+
+        shouldComponentUpdate(nextProps, nextState) {
+            this.peekAtState("shouldComponentUpdate-currentState");
+            this.peekAtState("shouldComponentUpdate-nextState", nextState);
+            return true;
+        }
+
+        componentWillUpdate(nextProps, nextState) {
+            this.peekAtState("componentWillUpdate-currentState");
+            this.peekAtState("componentWillUpdate-nextState", nextState);
+        }
+
+        componentDidUpdate(prevProps, prevState) {
+            this.peekAtState("componentDidUpdate-currentState");
+            this.peekAtState("componentDidUpdate-prevState", prevState);
+        }
+
+        componentWillUnmount() {
+            this.peekAtState("componentWillUnmount");
+        }
+    };
+
+    /*it("should support setting state", () => {
+        var container = document.createElement("div");
+        document.body.appendChild(container);
+        var stateListener = spyOn.createSpy();
+
+        var instance = ReactDOM.render(<TestComponent stateListener={stateListener} />, container, function peekAtInitialCallback() {
+            this.peekAtState("initial-callback");
+        });
+        ReactDOM.render(<TestComponent stateListener={stateListener} nextColor="green" />, container, instance.peekAtCallback("setProps"));
+        instance.setFavoriteColor("blue");
+        instance.forceUpdate(instance.peekAtCallback("forceUpdate"));
+
+        ReactDOM.unmountComponentAtNode(container);
+        let expected = [
+           // there is no state when getInitialState() is called
+            ["getInitialState", null],
+            ["componentWillMount-start", "red"],
+              // setState()'s only enqueue pending states.
+            ["componentWillMount-after-sunrise", "red"],
+            ["componentWillMount-end", "red"],
+             // pending state queue is processed
+            ["before-setState-sunrise", "red"],
+            ["after-setState-sunrise", "sunrise"],
+            ["after-setState-orange", "orange"],
+             // pending state has been applied
+            ["render", "orange"],
+            ["componentDidMount-start", "orange"],
+              // setState-sunrise and setState-orange should be called here,
+            // after the bug in #1740
+            // componentDidMount() called setState({color:'yellow'}), which is async.
+            // The update doesn't happen until the next flush.
+           
+            ["componentDidMount-end", "orange"],
+            ["shouldComponentUpdate-currentState", "orange"],
+            ["shouldComponentUpdate-nextState", "yellow"],
+            ["componentWillUpdate-currentState", "orange"],
+            ["componentWillUpdate-nextState", "yellow"],
+            ["render", "yellow"],
+            ["componentDidUpdate-currentState", "yellow"],
+            ["componentDidUpdate-prevState", "orange"],
+            ["setState-sunrise", "yellow"],
+            ["setState-orange", "yellow"],
+            ["setState-yellow", "yellow"],
+            ["initial-callback", "yellow"],
+            ["componentWillReceiveProps-start", "yellow"],
+             // setState({color:'green'}) only enqueues a pending state.
+            ["componentWillReceiveProps-end", "yellow"],
+             // pending state queue is processed
+            // We keep updates in the queue to support
+            // replaceState(prevState => newState).
+            ["before-setState-receiveProps", "yellow"],
+            ["before-setState-again-receiveProps", void 666],
+            ["after-setState-receiveProps", "green"],
+            ["shouldComponentUpdate-currentState", "yellow"],
+            ["shouldComponentUpdate-nextState", "green"],
+            ["componentWillUpdate-currentState", "yellow"],
+            ["componentWillUpdate-nextState", "green"],
+             // setFavoriteColor('blue')
+            ["render", "green"],
+            ["componentDidUpdate-currentState", "green"],
+            ["componentDidUpdate-prevState", "yellow"],
+            ["setState-receiveProps", "green"],
+            ["setProps", "green"],
+            // setFavoriteColor('blue')
+            ["shouldComponentUpdate-currentState", "green"],
+            ["shouldComponentUpdate-nextState", "blue"],
+            ["componentWillUpdate-currentState", "green"],
+            ["componentWillUpdate-nextState", "blue"],
+            ["render", "blue"],
+            ["componentDidUpdate-currentState", "blue"],
+            ["componentDidUpdate-prevState", "green"],
+            ["setFavoriteColor", "blue"],
+            // forceUpdate()
+            ["componentWillUpdate-currentState", "blue"],
+            ["componentWillUpdate-nextState", "blue"],
+            ["render", "blue"],
+            ["componentDidUpdate-currentState", "blue"],
+            ["componentDidUpdate-prevState", "blue"],
+            ["forceUpdate", "blue"],
+             // unmountComponent()
+            // state is available within `componentWillUnmount()`
+            ["componentWillUnmount", "blue"]
+        ];
+
+        expect(stateListener.calls.join("\n")).toEqual(expected.join("\n"));
+    });*/
+
+    it("should call componentDidUpdate of children first", () => {});
+
+    it("should batch unmounts", () => {
+        var outer;
+
+        class Inner extends React.Component {
+            render() {
+                return <div />;
+            }
+
+            componentWillUnmount() {
+                // This should get silently ignored (maybe with a warning), but it
+                // shouldn't break React.
+                outer.setState({ showInner: false });
+            }
+        }
+
+        class Outer extends React.Component {
+            state = { showInner: true };
+
+            render() {
+                return <div>{this.state.showInner && <Inner />}</div>;
+            }
+        }
+
+        var container = document.createElement("div");
+        outer = ReactDOM.render(<Outer />, container);
+        expect(() => {
+            ReactDOM.unmountComponentAtNode(container);
+        }).not.toThrow();
+    });
+
+    it("should update state when called from child cWRP", function() {
+        const log = [];
+        class Parent extends React.Component {
+            state = { value: "one" };
+            render() {
+                log.push("parent render " + this.state.value);
+                return <Child parent={this} value={this.state.value} />;
+            }
+        }
+        let updated = false;
+        class Child extends React.Component {
+            componentWillReceiveProps() {
+                if (updated) {
+                    return;
+                }
+                log.push("child componentWillReceiveProps " + this.props.value);
+                this.props.parent.setState({ value: "two" });
+                log.push("child componentWillReceiveProps done " + this.props.value);
+                updated = true;
+            }
+            render() {
+                log.push("child render " + this.props.value);
+                return <div>{this.props.value}</div>;
+            }
+        }
+        var container = document.createElement("div");
+        ReactDOM.render(<Parent />, container);
+        ReactDOM.render(<Parent />, container);
+        expect(log).toEqual([
+            "parent render one",
+            "child render one",
+            "parent render one",
+            "child componentWillReceiveProps one",
+            "child componentWillReceiveProps done one",
+            "child render one",
+            "parent render two",
+            "child render two"
+        ]);
+    });
+
+    it("should merge state when sCU returns false", function() {
+        const log = [];
+        class Test extends React.Component {
+            state = { a: 0 };
+            render() {
+                return null;
+            }
+            shouldComponentUpdate(nextProps, nextState) {
+                log.push("scu from " + Object.keys(this.state) + " to " + Object.keys(nextState));
+                return false;
+            }
+        }
+
+        const container = document.createElement("div");
+        const test = ReactDOM.render(<Test />, container);
+        test.setState({ b: 0 });
+        expect(log.length).toBe(1);
+        test.setState({ c: 0 });
+        expect(log.length).toBe(2);
+        expect(log).toEqual(["scu from a to a,b", "scu from a,b to a,b,c"]);
+    });
+
+    it("should treat assigning to this.state inside cWRP as a replaceState, with a warning", () => {
+        spyOn(console, "error");
+
+        let ops = [];
+        class Test extends React.Component {
+            state = { step: 1, extra: true };
+            componentWillReceiveProps() {
+                this.setState({ step: 2 }, () => {
+                    // Tests that earlier setState callbacks are not dropped
+                    ops.push(`callback -- step: ${this.state.step}, extra: ${!!this.state.extra}`);
+                });
+                // Treat like replaceState
+                this.state = { step: 3 };
+            }
+            render() {
+                ops.push(`render -- step: ${this.state.step}, extra: ${!!this.state.extra}`);
+                return null;
+            }
+        }
+
+        // Mount
+        const container = document.createElement("div");
+        ReactDOM.render(<Test />, container);
+        // Update
+        ReactDOM.render(<Test />, container);
+
+        expect(ops).toEqual(["render -- step: 1, extra: true", "render -- step: 2, extra: false", "callback -- step: 2, extra: false"]);
+        /*
+      expect(console.error.calls.count()).toEqual(1);
+      expect(console.error.calls.argsFor(0)[0]).toEqual(
+        'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
+          "this.state is deprecated (except inside a component's constructor). " +
+          'Use setState instead.',
+      );/**/
+   });
+});

+ 336 - 0
test/may-dom/ReactContextValidator-test.jsx

@@ -0,0 +1,336 @@
+import React from "../../src/May";
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import PropTypes from '../../lib/ReactPropTypes';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render,
+	unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+
+// https://github.com/facebook/react/blob/master/src/isomorphic/classic/__tests__/ReactContextValidator-test.js
+
+describe('ReactContextValidator', function () {
+	// this.timeout(200000);
+	function normalizeCodeLocInfo(str) {
+		return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+	}
+
+	// TODO: This behavior creates a runtime dependency on propTypes. We should
+	// ensure that this is not required for ES6 classes with Flow.
+
+	it('should filter out context not in contextTypes', () => {
+		class Component extends React.Component {
+			render() {
+				return <div />;
+			}
+		}
+		Component.contextTypes = {
+			foo: PropTypes.string,
+		};
+
+		class ComponentInFooBarContext extends React.Component {
+			getChildContext() {
+				return {
+					foo: 'abc',
+					bar: 123,
+				};
+			}
+
+			render() {
+				return <Component ref="child" />;
+			}
+		}
+		ComponentInFooBarContext.childContextTypes = {
+			foo: PropTypes.string,
+			bar: PropTypes.number,
+		};
+
+		// var instance = ReactTestUtils.renderIntoDocument(
+		// 	<ComponentInFooBarContext />,
+		// );
+		// expect(instance.refs.child.context).toEqual({ foo: 'abc' });
+	});
+
+	it('should pass next context to lifecycles', () => {
+		var actualComponentWillReceiveProps;
+		var actualShouldComponentUpdate;
+		var actualComponentWillUpdate;
+
+		class Parent extends React.Component {
+			getChildContext() {
+				return {
+					foo: this.props.foo,
+					bar: 'bar',
+				};
+			}
+
+			render() {
+				return <Component />;
+			}
+		}
+		Parent.childContextTypes = {
+			foo: PropTypes.string.isRequired,
+			bar: PropTypes.string.isRequired,
+		};
+
+		class Component extends React.Component {
+			componentWillReceiveProps(nextProps, nextContext) {
+				actualComponentWillReceiveProps = nextContext;
+				return true;
+			}
+
+			shouldComponentUpdate(nextProps, nextState, nextContext) {
+				actualShouldComponentUpdate = nextContext;
+				return true;
+			}
+
+			componentWillUpdate(nextProps, nextState, nextContext) {
+				actualComponentWillUpdate = nextContext;
+			}
+
+			render() {
+				return <div />;
+			}
+		}
+		Component.contextTypes = {
+			foo: PropTypes.string,
+		};
+
+		var container = document.createElement('div');
+		ReactDOM.render(<Parent foo="abc" />, container);
+		ReactDOM.render(<Parent foo="def" />, container);
+		expect(actualComponentWillReceiveProps).toEqual({ foo: 'def' });
+		expect(actualShouldComponentUpdate).toEqual({ foo: 'def' });
+		expect(actualComponentWillUpdate).toEqual({ foo: 'def' });
+	});
+
+	it('should not pass previous context to lifecycles', () => {
+		var actualComponentDidUpdate;
+
+		class Parent extends React.Component {
+			getChildContext() {
+				return {
+					foo: this.props.foo,
+				};
+			}
+
+			render() {
+				return <Component />;
+			}
+		}
+		Parent.childContextTypes = {
+			foo: PropTypes.string.isRequired,
+		};
+
+		class Component extends React.Component {
+			componentDidUpdate(...args) {
+				actualComponentDidUpdate = args;
+			}
+
+			render() {
+				return <div />;
+			}
+		}
+		Component.contextTypes = {
+			foo: PropTypes.string,
+		};
+
+		var container = document.createElement('div');
+		ReactDOM.render(<Parent foo="abc" />, container);
+		ReactDOM.render(<Parent foo="def" />, container);
+		expect(actualComponentDidUpdate.length).toBe(3);
+	});
+
+	it('should check context types', () => {
+	  spyOn(console, 'error')
+  
+	  class Component extends React.Component {
+		render() {
+		  return <div />;
+		}
+	  }
+	  Component.contextTypes = {
+		foo: PropTypes.string.isRequired,
+	  };
+  
+	  ReactTestUtils.renderIntoDocument(<Component />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+	  class ComponentInFooStringContext extends React.Component {
+		getChildContext() {
+		  return {
+			foo: this.props.fooValue,
+		  };
+		}
+  
+		render() {
+		  return <Component />;
+		}
+	  }
+	  ComponentInFooStringContext.childContextTypes = {
+		foo: PropTypes.string,
+	  };
+  
+	  ReactTestUtils.renderIntoDocument(
+		<ComponentInFooStringContext fooValue={'bar'} />,
+	  );
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+	  class ComponentInFooNumberContext extends React.Component {
+		getChildContext() {
+		  return {
+			foo: this.props.fooValue,
+		  };
+		}
+  
+		render() {
+		  return <Component />;
+		}
+	  }
+	  ComponentInFooNumberContext.childContextTypes = {
+		foo: PropTypes.number,
+	  };
+  
+	  ReactTestUtils.renderIntoDocument(
+		<ComponentInFooNumberContext fooValue={123} />,
+	  );
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+	});
+  
+	it('should check child context types', () => {
+	  spyOn(console, 'error')
+  
+	  class Component extends React.Component {
+		getChildContext() {
+		  return this.props.testContext;
+		}
+  
+		render() {
+		  return <div />;
+		}
+	  }
+	  Component.childContextTypes = {
+		foo: PropTypes.string.isRequired,
+		bar: PropTypes.number,
+	  };
+  
+	  ReactTestUtils.renderIntoDocument(<Component testContext={{ bar: 123 }} />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+	  ReactTestUtils.renderIntoDocument(<Component testContext={{ foo: 123 }} />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+  
+	  ReactTestUtils.renderIntoDocument(
+		<Component testContext={{ foo: 'foo', bar: 123 }} />,
+	  );
+  
+	  ReactTestUtils.renderIntoDocument(<Component testContext={{ foo: 'foo' }} />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+	});
+  
+	// TODO (bvaughn) Remove this test and the associated behavior in the future.
+	// It has only been added in Fiber to match the (unintentional) behavior in Stack.
+	it('should warn (but not error) if getChildContext method is missing', () => {
+	  spyOn(console, 'error')
+  
+	  class ComponentA extends React.Component {
+		static childContextTypes = {
+		  foo: PropTypes.string.isRequired,
+		};
+		render() {
+		  return <div />;
+		}
+	  }
+	  class ComponentB extends React.Component {
+		static childContextTypes = {
+		  foo: PropTypes.string.isRequired,
+		};
+		render() {
+		  return <div />;
+		}
+	  }
+  
+	  ReactTestUtils.renderIntoDocument(<ComponentA />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+  
+	  // Warnings should be deduped by component type
+	  ReactTestUtils.renderIntoDocument(<ComponentA />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+  
+	  // PropTypes 为空实现所以没有报错
+	  ReactTestUtils.renderIntoDocument(<ComponentB />);
+  
+	  // PropTypes 为空实现所以没有报错
+	  expect(console.error.calls.count()).toBe(0);
+  
+	});
+  
+	// TODO (bvaughn) Remove this test and the associated behavior in the future.
+	// It has only been added in Fiber to match the (unintentional) behavior in Stack.
+	it('should pass parent context if getChildContext method is missing', () => {
+  
+	  class ParentContextProvider extends React.Component {
+		static childContextTypes = {
+		  foo: PropTypes.number,
+		};
+		getChildContext() {
+		  return {
+			foo: 'FOO',
+		  };
+		}
+		render() {
+		  return <MiddleMissingContext />;
+		}
+	  }
+  
+	  class MiddleMissingContext extends React.Component {
+		static childContextTypes = {
+		  bar: PropTypes.string.isRequired,
+		};
+		render() {
+		  return <ChildContextConsumer />;
+		}
+	  }
+  
+	  var childContext;
+	  class ChildContextConsumer extends React.Component {
+		render() {
+		  childContext = this.context;
+		  return <div />;
+		}
+	  }
+	  ChildContextConsumer.contextTypes = {
+		bar: PropTypes.string.isRequired,
+		foo: PropTypes.string.isRequired,
+	  };
+  
+	  ReactTestUtils.renderIntoDocument(<ParentContextProvider />);
+	  expect(childContext.bar).toBeUndefined();
+	  expect(childContext.foo).toBe('FOO');
+	});
+});

+ 92 - 0
test/may-dom/ReactDOM-test.jsx

@@ -0,0 +1,92 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import {shallowCompare} from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+// https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
+
+describe("ReactDOM", function () {
+  // this.timeout(200000);
+
+  it('allows a DOM element to be used with a string', () => {
+    var element = React.createElement('div', { className: 'foo' });
+    var instance = ReactTestUtils.renderIntoDocument(element);
+    expect(ReactDOM.findDOMNode(instance).tagName).toBe('DIV');
+  });
+
+  it('should allow children to be passed as an argument', () => {
+    var argDiv = ReactTestUtils.renderIntoDocument(
+      React.createElement('div', null, 'child'),
+    );
+    var argNode = ReactDOM.findDOMNode(argDiv);
+    expect(argNode.innerHTML).toBe('child');
+  });
+
+  it('should overwrite props.children with children argument', () => {
+    var conflictDiv = ReactTestUtils.renderIntoDocument(
+      React.createElement('div', { children: 'fakechild' }, 'child'),
+    );
+    var conflictNode = ReactDOM.findDOMNode(conflictDiv);
+    expect(conflictNode.innerHTML).toBe('child');
+  });
+
+  /**
+   * We need to make sure that updates occur to the actual node that's in the
+   * DOM, instead of a stale cache.
+   */
+  it('should purge the DOM cache when removing nodes', () => {
+    var myDiv = ReactTestUtils.renderIntoDocument(
+      <div>
+        <div key="theDog" className="dog" />,
+        <div key="theBird" className="bird" />
+      </div>,
+    );
+    // Warm the cache with theDog
+    myDiv = ReactTestUtils.renderIntoDocument(
+      <div>
+        <div key="theDog" className="dogbeforedelete" />,
+        <div key="theBird" className="bird" />,
+      </div>,
+    );
+    // Remove theDog - this should purge the cache
+    myDiv = ReactTestUtils.renderIntoDocument(
+      <div>
+        <div key="theBird" className="bird" />,
+      </div>,
+    );
+    // Now, put theDog back. It's now a different DOM node.
+    myDiv = ReactTestUtils.renderIntoDocument(
+      <div>
+        <div key="theDog" className="dog" />,
+        <div key="theBird" className="bird" />,
+      </div>,
+    );
+    // Change the className of theDog. It will use the same element
+    myDiv = ReactTestUtils.renderIntoDocument(
+      <div>
+        <div key="theDog" className="bigdog" />,
+        <div key="theBird" className="bird" />,
+      </div>,
+    );
+    var root = ReactDOM.findDOMNode(myDiv);
+    var dog = root.childNodes[0];
+    expect(dog.className).toBe('bigdog');
+  });
+})

+ 375 - 0
test/may-dom/ReactES6Class-test.jsx

@@ -0,0 +1,375 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import {shallowCompare} from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+
+describe("ReactES6Class", function() {
+    // this.timeout(200000);
+    var container;
+    var freeze = function(expectation) {
+        Object.freeze(expectation);
+        return expectation;
+    };
+    var Inner;
+    var attachedListener = null;
+    var renderedName = null;
+    beforeEach(() => {
+
+        container = document.createElement("div");
+        attachedListener = null;
+        renderedName = null;
+        Inner = class extends React.Component {
+            getName() {
+                return this.props.name;
+            }
+            render() {
+                attachedListener = this.props.onClick;
+                renderedName = this.props.name;
+                return <div className={this.props.name} />;
+            }
+        };
+    });
+    function test(element, expectedTag, expectedClassName) {
+        var instance = ReactDOM.render(element, container);
+        expect(container.firstChild).not.toBeNull();
+        expect(container.firstChild.tagName).toBe(expectedTag);
+        expect(container.firstChild.className).toBe(expectedClassName);
+        return instance;
+    }
+
+    it("preserves the name of the class for use in error messages", () => {
+        class Foo extends React.Component {}
+        expect(Foo.name).toBe("Foo");
+    });
+    it("renders a simple stateless component with prop", () => {
+        class Foo extends React.Component {
+            render() {
+                return <Inner name={this.props.bar} />;
+            }
+        }
+        test(<Foo bar="foo" />, "DIV", "foo");
+        test(<Foo bar="bar" />, "DIV", "bar");
+    });
+    it("renders based on state using initial values in this.props", () => {
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {bar: this.props.initialValue};
+            }
+            render() {
+                return <span className={this.state.bar} />;
+            }
+        }
+        test(<Foo initialValue="foo" />, "SPAN", "foo");
+    });
+    it("renders based on state using props in the constructor", () => {
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {bar: props.initialValue};
+            }
+            changeState() {
+                this.setState({bar: "bar"});
+            }
+            render() {
+                if (this.state.bar === "foo") {
+                    return <div className="foo" />;
+                }
+                return <span className={this.state.bar} />;
+            }
+        }
+        var instance = test(<Foo initialValue="foo" />, "DIV", "foo");
+        instance.changeState();
+        test(<Foo />, "SPAN", "bar");
+    });
+    it("renders based on context in the constructor", () => {
+        class Foo extends React.Component {
+            constructor(props, context) {
+                super(props, context);
+                this.state = {tag: context.tag, className: this.context.className};
+            }
+            render() {
+                var Tag = this.state.tag;
+                return <Tag className={this.state.className} />;
+            }
+        }
+        Foo.contextTypes = {
+            tag: PropTypes.string,
+            className: PropTypes.string,
+        };
+
+        class Outer extends React.Component {
+            getChildContext() {
+                return {tag: "span", className: "foo"};
+            }
+            render() {
+                return <Foo />;
+            }
+        }
+        Outer.childContextTypes = {
+            tag: PropTypes.string,
+            className: PropTypes.string,
+        };
+        test(<Outer />, "SPAN", "foo");
+    });
+
+    it("renders only once when setting state in componentWillMount", () => {
+        var renderCount = 0;
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {bar: props.initialValue};
+            }
+            componentWillMount() {
+                this.setState({bar: "bar"});
+            }
+            render() {
+                renderCount++;
+                return <span className={this.state.bar} />;
+            }
+        }
+        test(<Foo initialValue="foo" />, "SPAN", "bar");
+        expect(renderCount).toBe(1);
+    });
+    it("should render with null in the initial state property", () => {
+        class Foo extends React.Component {
+            constructor() {
+                super();
+                this.state = null;
+            }
+            render() {
+                return <span />;
+            }
+        }
+        test(<Foo />, "SPAN", "");
+    });
+    it("setState through an event handler", () => {
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {bar: props.initialValue};
+            }
+            handleClick() {
+                this.setState({bar: "bar"});
+            }
+            render() {
+                return (
+                    <Inner name={this.state.bar} onClick={this.handleClick.bind(this)} />
+                );
+            }
+        }
+        test(<Foo initialValue="foo" />, "DIV", "foo");
+        attachedListener();
+        expect(renderedName).toBe("bar");
+    });
+    it("should not implicitly bind event handlers", () => {
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {bar: props.initialValue};
+            }
+            handleClick() {
+                this.setState({bar: "bar"});
+            }
+            render() {
+                return <Inner name={this.state.bar} onClick={this.handleClick} />;
+            }
+        }
+        test(<Foo initialValue="foo" />, "DIV", "foo");
+        expect(attachedListener).toThrow();
+    });
+    it("renders using forceUpdate even when there is no state", () => {
+        class Foo extends React.Component {
+            constructor(props) {
+                super(props);
+                this.mutativeValue = props.initialValue;
+            }
+            handleClick() {
+                this.mutativeValue = "bar";
+                this.forceUpdate();
+            }
+            render() {
+                return (
+                    <Inner
+                        name={this.mutativeValue}
+                        onClick={this.handleClick.bind(this)}
+                    />
+                );
+            }
+        }
+        test(<Foo initialValue="foo" />, "DIV", "foo");
+        attachedListener();
+        expect(renderedName).toBe("bar");
+    });
+    it("will call all the normal life cycle methods", () => {
+        var lifeCycles = [];
+        class Foo extends React.Component {
+            constructor() {
+                super();
+                this.state = {};
+            }
+            componentWillMount() {
+                lifeCycles.push("will-mount");
+            }
+            componentDidMount() {
+                lifeCycles.push("did-mount");
+            }
+            componentWillReceiveProps(nextProps) {
+                lifeCycles.push("receive-props", nextProps);
+            }
+            shouldComponentUpdate(nextProps, nextState) {
+                lifeCycles.push("should-update", nextProps, nextState);
+                return true;
+            }
+            componentWillUpdate(nextProps, nextState) {
+                lifeCycles.push("will-update", nextProps, nextState);
+            }
+            componentDidUpdate(prevProps, prevState) {
+                lifeCycles.push("did-update", prevProps, prevState);
+            }
+            componentWillUnmount() {
+                lifeCycles.push("will-unmount");
+            }
+            render() {
+                return <span className={this.props.value} />;
+            }
+        }
+        test(<Foo value="foo" />, "SPAN", "foo");
+        expect(lifeCycles).toEqual(["will-mount", "did-mount"]);
+        lifeCycles = []; // reset
+        test(<Foo value="bar" />, "SPAN", "bar");
+        // prettier-ignore
+        expect(lifeCycles).toEqual([
+            "receive-props", freeze({value: "bar"}),
+            "should-update", freeze({value: "bar"}), {},
+            "will-update", freeze({value: "bar"}), {},
+            "did-update", freeze({value: "foo"}), {},
+        ]);
+        lifeCycles = []; // reset
+        ReactDOM.unmountComponentAtNode(container);
+        expect(lifeCycles).toEqual(["will-unmount"]);
+    });
+    it("warns when classic properties are defined on the instance, but does not invoke them.", () => {
+      
+        var getDefaultPropsWasCalled = false;
+        var getInitialStateWasCalled = false;
+        class Foo extends React.Component {
+            constructor() {
+                super();
+                this.contextTypes = {};
+                this.propTypes = {};
+            }
+            getInitialState() {
+                getInitialStateWasCalled = true;
+                return {};
+            }
+            getDefaultProps() {
+                getDefaultPropsWasCalled = true;
+                return {};
+            }
+            render() {
+                return <span className="foo" />;
+            }
+        }
+        test(<Foo />, "SPAN", "foo");
+        expect(getInitialStateWasCalled).toBe(false);
+        expect(getDefaultPropsWasCalled).toBe(false);
+    });
+
+   it('does not warn about getInitialState() on class components if state is also defined.', () => {
+    spyOn(console, 'error');
+    class Foo extends React.Component {
+      state = this.getInitialState();
+      getInitialState() {
+        return {};
+      }
+      render() {
+        return <span className="foo" />;
+      }
+    }
+    test(<Foo />, 'SPAN', 'foo');
+    expect(console.error.calls.count()).toBe(0);
+  });
+  it('should warn when misspelling shouldComponentUpdate', () => {
+   
+
+    class NamedComponent extends React.Component {
+      componentShouldUpdate() {
+        return false;
+      }
+      render() {
+        return <span className="foo" />;
+      }
+    }
+    test(<NamedComponent />, 'SPAN', 'foo');
+
+  });
+   it('should warn when misspelling componentWillReceiveProps', () => {
+
+    class NamedComponent extends React.Component {
+      componentWillRecieveProps() {
+        return false;
+      }
+      render() {
+        return <span className="foo" />;
+      }
+    }
+    test(<NamedComponent />, 'SPAN', 'foo');
+
+  });
+  it('supports this.context passed via getChildContext', () => {
+    class Bar extends React.Component {
+      render() {
+        return <div className={this.context.bar} />;
+      }
+    }
+    Bar.contextTypes = {bar: PropTypes.string};
+    class Foo extends React.Component {
+      getChildContext() {
+        return {bar: 'bar-through-context'};
+      }
+      render() {
+        return <Bar />;
+      }
+    }
+    Foo.childContextTypes = {bar: PropTypes.string};
+    test(<Foo />, 'DIV', 'bar-through-context');
+  });
+
+  it('supports classic refs', () => {
+    class Foo extends React.Component {
+      render() {
+        return <Inner name="foo" ref="inner" />;
+      }
+    }
+    var instance = test(<Foo />, 'DIV', 'foo');
+    expect(instance.refs.inner.getName()).toBe('foo');
+  });
+
+  it('supports drilling through to the DOM using findDOMNode', () => {
+    var instance = test(<Inner name="foo" />, 'DIV', 'foo');
+    var node = ReactDOM.findDOMNode(instance);
+    expect(node).toBe(container.firstChild);
+  });
+
+
+});

+ 347 - 0
test/may-dom/ReactElementClone-test.jsx

@@ -0,0 +1,347 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import {shallowCompare} from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+// https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
+
+describe("ReactElementClone", function() {
+    // this.timeout(200000);
+
+    // NOTE: We're explicitly not using JSX here. This is intended to test
+    // classic JS without JSX.
+    var ComponentClass = class extends React.Component {
+        render() {
+            return React.createElement("div");
+        }
+    };
+
+
+    it("should clone a DOM component with new props", () => {
+        class Grandparent extends React.Component {
+            render() {
+                return <Parent child={<div className="child" />} />;
+            }
+        }
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <div className="parent">
+                        {React.cloneElement(this.props.child, {className: "xyz"})}
+                    </div>
+                );
+            }
+        }
+        var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
+        expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe("xyz");
+    });
+
+    it("should clone a composite component with new props", () => {
+        class Child extends React.Component {
+            render() {
+                return <div className={this.props.className} />;
+            }
+        }
+        class Grandparent extends React.Component {
+            render() {
+                return <Parent child={<Child className="child" />} />;
+            }
+        }
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <div className="parent">
+                        {React.cloneElement(this.props.child, {className: "xyz"})}
+                    </div>
+                );
+            }
+        }
+        var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
+        expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe("xyz");
+    });
+
+    it("does not fail if config has no prototype", () => {
+        var config = Object.create(null, {foo: {value: 1, enumerable: true}});
+        React.cloneElement(<div />, config);
+    });
+
+    it("should keep the original ref if it is not overridden", () => {
+        class Grandparent extends React.Component {
+            render() {
+                return <Parent child={<div ref="yolo" />} />;
+            }
+        }
+
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {React.cloneElement(this.props.child, {className: "xyz"})}
+                    </div>
+                );
+            }
+        }
+
+        var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
+        expect(component.refs.yolo.tagName).toBe("DIV");
+    });
+
+    it('should transfer the key property', () => {
+    class Component extends React.Component {
+      render() {
+        return null;
+      }
+    }
+    var clone = React.cloneElement(<Component />, {key: 'xyz'});
+    expect(clone.key).toBe('xyz');
+  });
+
+  it('should transfer children', () => {
+    class Component extends React.Component {
+      render() {
+        expect(this.props.children).toBe('xyz');
+        return <div />;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(
+      React.cloneElement(<Component />, {children: 'xyz'}),
+    );
+  });
+  it('should shallow clone children', () => {
+    class Component extends React.Component {
+      render() {
+        expect(this.props.children).toBe('xyz');
+        return <div />;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(
+      React.cloneElement(<Component>xyz</Component>, {}),
+    );
+  });
+
+//   it('should accept children as rest arguments', () => {
+//     class Component extends React.Component {
+//       render() {
+//         return null;
+//       }
+//     }
+
+//     var clone = React.cloneElement(
+//       <Component>xyz</Component>,
+//       {children: <Component />},
+//       <div />,
+//       <span />,
+//     );
+
+//     expect(clone.props.children).toEqual([<div />, <span />]);
+//   });
+//   it('should override children if undefined is provided as an argument', () => {
+//     var element = React.createElement(
+//       ComponentClass,
+//       {
+//         children: 'text',
+//       },
+//       undefined,
+//     );
+//     expect(element.props.children).toBe(undefined);
+
+//     var element2 = React.cloneElement(
+//       React.createElement(ComponentClass, {
+//         children: 'text',
+//       }),
+//       {},
+//       undefined,
+//     );
+//     expect(element2.props.children).toBe(undefined);
+//   });
+  it('should support keys and refs', () => {
+    class Parent extends React.Component {
+      render() {
+        var clone = React.cloneElement(this.props.children, {
+          key: 'xyz',
+          ref: 'xyz',
+        });
+        expect(clone.key).toBe('xyz');
+       // expect(clone.ref).toBe('xyz');
+        return <div>{clone}</div>;
+      }
+    }
+
+    class Grandparent extends React.Component {
+      render() {
+        return <Parent ref="parent"><span key="abc" /></Parent>;
+      }
+    }
+
+    var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
+    expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN');
+  });
+
+  it('should steal the ref if a new ref is specified', () => {
+    class Parent extends React.Component {
+      render() {
+        var clone = React.cloneElement(this.props.children, {ref: 'xyz'});
+        return <div>{clone}</div>;
+      }
+    }
+
+    class Grandparent extends React.Component {
+      render() {
+        return <Parent ref="parent"><span ref="child" /></Parent>;
+      }
+    }
+
+    var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
+    expect(component.refs.child).toBe(null);
+    expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN');
+  });
+  it('should overwrite props', () => {
+    class Component extends React.Component {
+      render() {
+        expect(this.props.myprop).toBe('xyz');
+        return <div />;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(
+      React.cloneElement(<Component myprop="abc" />, {myprop: 'xyz'}),
+    );
+  });
+
+  it('should normalize props with default values', () => {
+    class Component extends React.Component {
+      render() {
+        return <span />;
+      }
+    }
+    Component.defaultProps = {prop: 'testKey'};
+
+    var instance = React.createElement(Component);
+    var clonedInstance = React.cloneElement(instance, {prop: undefined});
+    expect(clonedInstance.props.prop).toBe('testKey');
+    var clonedInstance2 = React.cloneElement(instance, {prop: null});
+    expect(clonedInstance2.props.prop).toBe(null);
+
+    var instance2 = React.createElement(Component, {prop: 'newTestKey'});
+    var cloneInstance3 = React.cloneElement(instance2, {prop: undefined});
+    expect(cloneInstance3.props.prop).toBe('testKey');
+    var cloneInstance4 = React.cloneElement(instance2, {});
+    expect(cloneInstance4.props.prop).toBe('newTestKey');
+  });
+  it('does not warns for arrays of elements with keys', () => {
+    spyOn(console, 'error');
+
+    React.cloneElement(<div />, null, [<div key="#1" />, <div key="#2" />]);
+
+    expect(console.error.calls.count()).toBe(0);
+  });
+
+  it('does not warn when the element is directly in rest args', () => {
+    spyOn(console, 'error');
+
+    React.cloneElement(<div />, null, <div />, <div />);
+
+    expect(console.error.calls.count()).toBe(0);
+  });
+
+  it('does not warn when the array contains a non-element', () => {
+    spyOn(console, 'error');
+
+    React.cloneElement(<div />, null, [{}, {}]);
+
+    expect(console.error.calls.count()).toBe(0);
+  });
+
+  it('should ignore key and ref warning getters', () => {
+    var elementA = React.createElement('div');
+    var elementB = React.cloneElement(elementA, elementA.props);
+    expect(!!elementB.key).toBe(false);
+    expect(!!elementB.ref).toBe(false);
+  });
+
+  it('should ignore undefined key and ref', () => {
+    var element = React.createFactory(ComponentClass)({
+      key: '12',
+      ref: '34',
+      foo: '56',
+    });
+    var props = {
+      key: undefined,
+      ref: undefined,
+      foo: 'ef',
+    };
+    var clone = React.cloneElement(element, props);
+    expect(clone.type).toBe(ComponentClass);
+    expect(clone.key).toBe(null);
+    expect(clone.ref).toBe(null);
+  //  expect(Object.isFrozen(element)).toBe(true);
+  //  expect(Object.isFrozen(element.props)).toBe(true);
+    // expect(clone.props).toEqual({foo: 'ef'});
+  });
+
+  it('should extract null key and ref', () => {
+    var element = React.createFactory(ComponentClass)({
+      key: '12',
+      ref: '34',
+      foo: '56',
+    });
+    var props = {
+      key: null,
+      ref: null,
+      foo: 'ef',
+    };
+    var clone = React.cloneElement(element, props);
+    expect(clone.type).toBe(ComponentClass);
+    expect(clone.key).toBe('null');
+    expect(!!clone.ref).toBe(false);
+   // expect(Object.isFrozen(element)).toBe(true);
+   // expect(Object.isFrozen(element.props)).toBe(true);
+    // expect(clone.props).toEqual({foo: 'ef'});
+  });
+  it("子元素被克隆", function(){
+    function Bar(props) {
+        return React.cloneElement(props.children, {className: props.className})
+      }
+    var container = document.createElement('div');
+  
+    var myNodeA = ReactDOM.render(<Bar className="a"><span /></Bar>, container);
+    expect(myNodeA.className).toBe("a")
+  
+     myNodeA = ReactDOM.render(<Bar className="kk"><span /></Bar>, container);
+    expect(myNodeA.className).toBe("kk")
+  })
+  it("子元素被克隆2", function(){
+    function Bar(props) {
+      return React.cloneElement(props.children, {className: props.className})
+    }
+    function Foo(props) {
+      return props.className === "a" ?  <span {...props} />:<p {...props} />
+    }
+    var container = document.createElement('div');
+  
+    var myNodeA = ReactDOM.render(<Bar className="a"><Foo /></Bar>, container);
+    expect(myNodeA.className).toBe("a")
+  
+     myNodeA = ReactDOM.render(<Bar className="kk"><Foo /></Bar>, container);
+    expect(myNodeA.className).toBe("kk")
+  })/**/
+});

+ 121 - 0
test/may-dom/ReactEmptyComponent-test.jsx

@@ -0,0 +1,121 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import {shallowCompare} from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+
+describe("ReactComponent", function() {
+    // this.timeout(200000);
+
+
+    it("should not produce child DOM nodes for null and false", function() {
+        class Component1 extends React.Component {
+            render() {
+                return null;
+            }
+        }
+
+        class Component2 extends React.Component {
+            render() {
+                return false;
+            }
+        }
+
+        var container1 = document.createElement("div");
+        ReactDOM.render(<Component1 />, container1);
+        expect(container1.children.length).toBe(0);
+
+        var container2 = document.createElement("div");
+        ReactDOM.render(<Component2 />, container2);
+        expect(container2.children.length).toBe(0);
+    });
+
+    it("works when switching components", () => {
+        var assertions = 0;
+
+        class Inner extends React.Component {
+            render() {
+                return <span />;
+            }
+
+            componentDidMount() {
+                // Make sure the DOM node resolves properly even if we're replacing a
+                // `null` component
+                expect(ReactDOM.findDOMNode(this)).not.toBe(null);
+                assertions++;
+            }
+
+            componentWillUnmount() {
+                // Even though we're getting replaced by `null`, we haven't been
+                // replaced yet!
+                expect(ReactDOM.findDOMNode(this)).not.toBe(null);
+                assertions++;
+            }
+        }
+
+        class Wrapper extends React.Component {
+            render() {
+                return this.props.showInner ? <Inner /> : null;
+            }
+        }
+
+        var el = document.createElement("div");
+        var component;
+
+        // Render the <Inner /> component...
+        component = ReactDOM.render(<Wrapper showInner={true} />, el);
+        expect(ReactDOM.findDOMNode(component)).not.toBe(null);
+
+        // Switch to null...
+        component = ReactDOM.render(<Wrapper showInner={false} />, el);
+        // expect(ReactDOM.findDOMNode(component).nodeName).toBe("#comment");
+        // ...then switch back.
+        component = ReactDOM.render(<Wrapper showInner={true} />, el);
+        expect(ReactDOM.findDOMNode(component)).not.toBe(null);
+
+        expect(assertions).toBe(3);
+       
+    });
+    it("preserves the dom node during updates", () => {
+        class Empty extends React.Component {
+            render() {
+                return null;
+            }
+        }
+
+        var container = document.createElement("div");
+
+        ReactDOM.render(<Empty />, container);
+        var noscript1 = container.firstChild;
+       
+        expect(noscript1.nodeName).toBe("#comment");
+       
+
+        // This update shouldn't create a DOM node
+        ReactDOM.render(<Empty />, container);
+        var noscript2 = container.firstChild;
+       
+        expect(noscript2.nodeName).toBe("#comment");
+       
+
+        expect(noscript1).toBe(noscript2);
+    });
+});

+ 247 - 0
test/may-dom/ReactIdentity-test.jsx

@@ -0,0 +1,247 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import { shallowCompare } from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+
+describe("ReactIdentity", function () {
+    // this.timeout(200000);
+
+    it("should allow key property to express identity", () => {
+        var node;
+        var Component = props => (
+            <div ref={c => (node = c)}>
+                <div key={props.swap ? "banana" : "apple"} id={props.swap ? "banana" : "apple"} />
+                <div key={props.swap ? "apple" : "banana"} id={props.swap ? "apple" : "banana"} />
+            </div>
+        );
+
+        var container = document.createElement("div");
+        ReactDOM.render(<Component />, container);
+        var origChildren = Array.from(node.childNodes);
+        ReactDOM.render(<Component swap={true} />, container);
+        var newChildren = Array.from(node.childNodes);
+        expect(origChildren[0] === newChildren[1]).toBe(true);
+        expect(origChildren[1] === newChildren[0]).toBe(true);
+    });
+
+    it("should use composite identity", () => {
+        class Wrapper extends React.Component {
+            render() {
+                return <a>{this.props.children}</a>;
+            }
+        }
+
+        var container = document.createElement("div");
+        var node1;
+        var node2;
+        ReactDOM.render(
+            <Wrapper key="wrap1"><span ref={c => (node1 = c)} /></Wrapper>,
+            container
+        );
+        ReactDOM.render(
+            <Wrapper key="wrap2"><span ref={c => (node2 = c)} /></Wrapper>,
+            container
+        );
+
+        expect(node1).not.toBe(node2);
+    });
+
+
+    function renderAComponentWithKeyIntoContainer(key, container) {
+        class Wrapper extends React.Component {
+            render() {
+                return <div><span ref="span" key={key} /></div>;
+            }
+        }
+
+        var instance = ReactDOM.render(<Wrapper />, container);
+        var span = instance.refs.span;
+        expect(ReactDOM.findDOMNode(span)).not.toBe(null);
+    }
+
+    it("should allow any character as a key, in a detached parent", () => {
+        var detachedContainer = document.createElement("div");
+        renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer);
+    });
+
+    it("should allow any character as a key, in an attached parent", () => {
+    // This test exists to protect against implementation details that
+    // incorrectly query escaped IDs using DOM tools like getElementById.
+        var attachedContainer = document.createElement("div");
+        document.body.appendChild(attachedContainer);
+
+        renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer);
+
+        document.body.removeChild(attachedContainer);
+    });
+
+    it("should not allow scripts in keys to execute", () => {
+        var h4x0rKey =
+      "\"><script>window['YOUVEBEENH4X0RED']=true;</script><div id=\"";
+
+        var attachedContainer = document.createElement("div");
+        document.body.appendChild(attachedContainer);
+
+        renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer);
+
+        document.body.removeChild(attachedContainer);
+
+        // If we get this far, make sure we haven't executed the code
+        expect(window.YOUVEBEENH4X0RED).toBe(undefined);
+    });
+    it("should let restructured components retain their uniqueness", () => {
+        var instance0 = <span />;
+        var instance1 = <span />;
+        var instance2 = <span />;
+
+        class TestComponent extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {instance2}
+                        {this.props.children[0]}
+                        {this.props.children[1]}
+                    </div>
+                );
+            }
+        }
+
+        class TestContainer extends React.Component {
+            render() {
+                return <TestComponent>{instance0}{instance1}</TestComponent>;
+            }
+        }
+
+        expect(function() {
+            ReactTestUtils.renderIntoDocument(<TestContainer />);
+        }).not.toThrow();
+    });
+
+    it("should let nested restructures retain their uniqueness", () => {
+        var instance0 = <span />;
+        var instance1 = <span />;
+        var instance2 = <span />;
+
+        class TestComponent extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {instance2}
+                        {this.props.children[0]}
+                        {this.props.children[1]}
+                    </div>
+                );
+            }
+        }
+
+        class TestContainer extends React.Component {
+            render() {
+                return (
+                    <div>
+                        <TestComponent>{instance0}{instance1}</TestComponent>
+            </div>
+                );
+            }
+        }
+
+        expect(function() {
+            ReactTestUtils.renderIntoDocument(<TestContainer />);
+        }).not.toThrow();
+    });
+
+    it("should let text nodes retain their uniqueness", () => {
+        class TestComponent extends React.Component {
+            render() {
+                return <div>{this.props.children}<span /></div>;
+            }
+        }
+
+        class TestContainer extends React.Component {
+            render() {
+                return (
+                    <TestComponent>
+                        <div />
+                        {"second"}
+                    </TestComponent>
+                );
+            }
+        }
+
+        expect(function() {
+            ReactTestUtils.renderIntoDocument(<TestContainer />);
+        }).not.toThrow();
+    });
+    it('should retain key during updates in composite components', () => {
+    class TestComponent extends React.Component {
+      render() {
+        return <div>{this.props.children}</div>;
+      }
+    }
+
+    class TestContainer extends React.Component {
+      state = {swapped: false};
+
+      swap = () => {
+        this.setState({swapped: true});
+      };
+
+      render() {
+        return (
+          <TestComponent>
+            {this.state.swapped ? this.props.second : this.props.first}
+            {this.state.swapped ? this.props.first : this.props.second}
+          </TestComponent>
+        );
+      }
+    }
+
+    var instance0 = <span key="A" />;
+    var instance1 = <span key="B" />;
+
+    var wrapped = <TestContainer first={instance0} second={instance1} />;
+
+    wrapped = ReactDOM.render(wrapped, document.createElement('div'));
+    var div = ReactDOM.findDOMNode(wrapped);
+
+    var beforeA = div.childNodes[0];
+    var beforeB = div.childNodes[1];
+    wrapped.swap();
+    var afterA = div.childNodes[1];
+    var afterB = div.childNodes[0];
+
+    expect(beforeA).toBe(afterA);
+    expect(beforeB).toBe(afterB);
+  });
+
+  it('should not allow implicit and explicit keys to collide', () => {
+    var component = (
+      <div>
+        <span />
+        <span key="0" />
+      </div>
+    );
+
+    expect(function() {
+      ReactTestUtils.renderIntoDocument(component);
+    }).not.toThrow();
+  });
+});

+ 364 - 0
test/may-dom/ReactMultiChild-test.jsx

@@ -0,0 +1,364 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import { shallowCompare } from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+
+describe("ReactMultiChild", function () {
+    // this.timeout(200000);
+    it("should update children when possible", () => {
+        var container = document.createElement("div");
+
+        var mockMount = jasmine.createSpy('mockMount');
+        var mockUpdate = jasmine.createSpy('mockUpdate');
+        var mockUnmount = jasmine.createSpy('mockUnmount');
+
+        class MockComponent extends React.Component {
+            componentDidMount = mockMount;
+            componentDidUpdate = mockUpdate;
+            componentWillUnmount = mockUnmount;
+            render() {
+                return <span />;
+            }
+        }
+        expect(mockMount.calls.count()).toBe(0);
+        expect(mockUpdate.calls.count()).toBe(0);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <MockComponent />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUpdate.calls.count()).toBe(0);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <MockComponent />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUpdate.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(0);
+    });
+    var LetterInner = class extends React.Component {
+        render() {
+            return <div>{this.props.char}</div>;
+        }
+    };
+
+    var Letter = class extends React.Component {
+        render() {
+            return <LetterInner char={this.props.char} />;
+        }
+        shouldComponentUpdate() {
+            return false;
+        }
+    };
+
+    var Letters = class extends React.Component {
+        render() {
+            const letters = this.props.letters.split("");
+            return <div>{letters.map(c => <Letter key={c} char={c} />)}</div>;
+        }
+    };
+
+    it("should replace children with different constructors", () => {
+        var container = document.createElement("div");
+
+        var mockMount = jasmine.createSpy('mockMount');
+        var mockUnmount = jasmine.createSpy('mockUnmount');
+
+        class MockComponent extends React.Component {
+            componentDidMount = mockMount;
+            componentWillUnmount = mockUnmount;
+            render() {
+                return <span />;
+            }
+        }
+
+        expect(mockMount.calls.count()).toBe(0);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <MockComponent />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <span />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(1);
+    });
+
+    it("should NOT replace children with different owners", () => {
+        var container = document.createElement("div");
+
+        var mockMount = jasmine.createSpy('mockMount');
+        var mockUnmount = jasmine.createSpy('mockUnmount');
+
+        class MockComponent extends React.Component {
+            componentDidMount = mockMount;
+            componentWillUnmount = mockUnmount;
+            render() {
+                return <span />;
+            }
+        }
+
+        class WrapperComponent extends React.Component {
+            render() {
+                return this.props.children || <MockComponent />;
+            }
+        }
+
+        expect(mockMount.calls.count()).toBe(0);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(<WrapperComponent />, container);
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <WrapperComponent>
+                <MockComponent />
+            </WrapperComponent>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(0);
+    });
+    it("should replace children with different keys", () => {
+        var container = document.createElement("div");
+
+        var mockMount = jasmine.createSpy('mockMount');
+        var mockUnmount = jasmine.createSpy('mockUnmount');
+
+        class MockComponent extends React.Component {
+            componentDidMount = mockMount;
+            componentWillUnmount = mockUnmount;
+            render() {
+                return <span />;
+            }
+        }
+
+        expect(mockMount.calls.count()).toBe(0);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <MockComponent key="A" />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(1);
+        expect(mockUnmount.calls.count()).toBe(0);
+
+        ReactDOM.render(
+            <div>
+                <MockComponent key="B" />
+            </div>,
+            container
+        );
+
+        expect(mockMount.calls.count()).toBe(2);
+        expect(mockUnmount.calls.count()).toBe(1);
+    });
+
+    it("should warn for duplicated array keys with component stack info", () => {
+        class WrapperComponent extends React.Component {
+            render() {
+                return <div>{this.props.children}</div>;
+            }
+        }
+
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <div>
+                        <WrapperComponent>{this.props.children}</WrapperComponent>
+                    </div>
+                );
+            }
+        }
+
+        var instance = ReactTestUtils.renderIntoDocument(<Parent>{[<div className="child" />]}</Parent>);
+        var array = ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, "child");
+        expect(array.length).toBe(1);
+
+        var instance2 = ReactTestUtils.renderIntoDocument(<Parent>{[<div className="aaa" />, <div className="aaa" />]}</Parent>);
+        var array2 = ReactTestUtils.scryRenderedDOMComponentsWithClass(instance2, "aaa");
+        expect(array2.length).toBe(2);
+    });
+
+    it("should warn for duplicated iterable keys with component stack info", () => {
+        class WrapperComponent extends React.Component {
+            render() {
+                return <div>{this.props.children}</div>;
+            }
+        }
+
+        class Parent extends React.Component {
+            render() {
+                return (
+                    <div>
+                        <WrapperComponent>{this.props.children}</WrapperComponent>
+                    </div>
+                );
+            }
+        }
+
+        function createIterable(array) {
+            return {
+                "@@iterator": function() {
+                    var i = 0;
+                    return {
+                        next() {
+                            const next = {
+                                value: i < array.length ? array[i] : undefined,
+                                done: i === array.length
+                            };
+                            i++;
+                            return next;
+                        }
+                    };
+                }
+            };
+        }
+
+        var instance = ReactTestUtils.renderIntoDocument(<Parent>{createIterable([<div className="aaa" />])}</Parent>);
+        var array = ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, "aaa");
+        expect(array.length).toBe(1);
+        var instance = ReactTestUtils.renderIntoDocument(<Parent>{createIterable([<div className="aaa" />, <div className="aaa" />])}</Parent>);
+        var array = ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, "aaa");
+        expect(array.length).toBe(2);
+    });
+    //暂不处理 maps children
+    // it("should warn for using maps as children with owner info", () => {
+    //     if (typeof Map === "function") {
+    //         class Parent extends React.Component {
+    //             render() {
+    //                 return <div>{new Map([["foo", 0], ["bar", 1]])}</div>;
+    //             }
+    //         }
+    //         var container = document.createElement("div");
+    //         ReactDOM.render(<Parent />, container);
+    //         expect(container.innerText || container.textContent).toBe("01");
+    //     }
+    // });
+
+    it("should reorder bailed-out children", () => {
+        var container = document.createElement("div");
+
+        // Two random strings -- some additions, some removals, some moves
+        ReactDOM.render(<Letters letters="XKwHomsNjIkBcQWFbiZU" />, container);
+        expect(container.textContent).toBe("XKwHomsNjIkBcQWFbiZU");
+        ReactDOM.render(<Letters letters="EHCjpdTUuiybDvhRJwZt" />, container);
+        expect(container.textContent).toBe("EHCjpdTUuiybDvhRJwZt");
+    });
+
+    it("添加增删改", () => {
+        var container = document.createElement("div");
+
+        // Two random strings -- some additions, some removals, some moves
+        ReactDOM.render(<Letters letters="8ABC4D5EFGH" />, container);
+        expect(container.textContent).toBe("8ABC4D5EFGH");
+        ReactDOM.render(<Letters letters="GFE9DBACH" />, container);
+        expect(container.textContent).toBe("GFE9DBACH");
+    });
+    //暂不处理 需要逐步diff
+   /*it("prepares new children before unmounting old", () => {
+        var list = [];
+
+        class Spy extends React.Component {
+            componentWillMount() {
+                list.push(this.props.name + " componentWillMount");
+            }
+            render() {
+                list.push(this.props.name + " render");
+                return <div />;
+            }
+            componentDidMount() {
+                list.push(this.props.name + " componentDidMount");
+            }
+            componentWillUnmount() {
+                list.push(this.props.name + " componentWillUnmount");
+            }
+        }
+
+        // These are reference-unequal so they will be swapped even if they have
+        // matching keys
+        var SpyA = props => <Spy {...props} />;
+        var SpyB = props => <Spy {...props} />;
+
+        var container = document.createElement("div");
+        ReactDOM.render(
+            <div>
+                <SpyA key="one" name="oneA" />
+                <SpyA key="two" name="twoA" />
+            </div>,
+            container
+        );
+        ReactDOM.render(
+            <div>
+                <SpyB key="one" name="oneB" />
+                <SpyB key="two" name="twoB" />
+            </div>,
+            container
+        );
+        expect(list).toEqual([
+            "oneA componentWillMount",
+            "oneA render",
+            "twoA componentWillMount",
+            "twoA render",
+            "oneA componentDidMount",
+            "twoA componentDidMount",
+
+            "oneA componentWillUnmount",
+            "oneB componentWillMount",
+            "oneB render",
+            "twoA componentWillUnmount",
+            "twoB componentWillMount",
+            "twoB render",
+
+            "oneB componentDidMount",
+            "twoB componentDidMount"
+        ]);
+    }); */
+});

+ 180 - 0
test/may-dom/ReactStatelessComponent-test.jsx

@@ -0,0 +1,180 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import { shallowCompare } from '../../src/PureComponent';
+
+var ReactDOM = {
+	render: render,
+	unmountComponentAtNode: unmountComponentAtNode,
+	findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/renderers/dom/test/__tests__/ReactTestUtils-test.js
+
+
+
+describe("ReactStatelessComponent", function () {
+	// this.timeout(200000);
+	// before(async () => {
+	//   await beforeHook();
+	// });
+	// after(async () => {
+	//   await afterHook(false);
+	// });
+	function StatelessComponent(props) {
+		return <div>{props.name}</div>;
+	}
+	it('should render stateless component', () => {
+		var el = document.createElement('div');
+		ReactDOM.render(<StatelessComponent name="A" />, el);
+
+		expect(el.textContent).toBe('A');
+	});
+
+	it('should update stateless component', () => {
+		class Parent extends React.Component {
+			render() {
+				return <StatelessComponent {...this.props} />;
+			}
+		}
+
+		var el = document.createElement('div');
+		ReactDOM.render(<Parent name="A" />, el);
+		expect(el.textContent).toBe('A');
+
+		ReactDOM.render(<Parent name="B" />, el);
+		expect(el.textContent).toBe('B');
+	});
+
+	it('should unmount stateless component', () => {
+		var container = document.createElement('div');
+
+		ReactDOM.render(<StatelessComponent name="A" />, container);
+		expect(container.textContent).toBe('A');
+
+		ReactDOM.unmountComponentAtNode(container);
+		expect(container.textContent).toBe('');
+	});
+
+	it('should pass context thru stateless component', () => {
+		class Child extends React.Component {
+			static contextTypes = {
+				test: PropTypes.string,
+			};
+
+			render() {
+				return <div>{this.context.test}</div>;
+			}
+		}
+
+		function Parent() {
+			return <Child />;
+		}
+
+		class GrandParent extends React.Component {
+			static childContextTypes = {
+				test: PropTypes.string,
+			};
+
+			getChildContext() {
+				return { test: this.props.test };
+			}
+
+			render() {
+				return <Parent />;
+			}
+		}
+
+		var el = document.createElement('div');
+		ReactDOM.render(<GrandParent test="test" />, el);
+
+		expect(el.textContent).toBe('test');
+
+		ReactDOM.render(<GrandParent test="mest" />, el);
+
+		expect(el.textContent).toBe('mest');
+	});
+
+	it('should use correct name in key warning', () => {
+	  function Child() {
+		return <div>{[<span>3</span>]}</div>;
+	  }
+  
+  
+	  var s = ReactTestUtils.renderIntoDocument(<Child />);
+	  expect(s.textContent).toBe("3")
+  
+	});
+	it('should support default props and prop types', () => {
+	  function Child(props) {
+		return <div>{props.test}</div>;
+	  }
+	  Child.defaultProps = {test: 2};
+	  Child.propTypes = {test: PropTypes.string};
+  
+	  spyOn(console, 'error');
+	  var s = ReactTestUtils.renderIntoDocument(<Child />);
+	  expect(s.textContent).toBe("2")
+  
+	});
+	it('should receive context', () => {
+	  class Parent extends React.Component {
+		static childContextTypes = {
+		  lang: PropTypes.string,
+		};
+  
+		getChildContext() {
+		  return {lang: 'en'};
+		}
+  
+		render() {
+		  return <Child />;
+		}
+	  }
+  
+	  function Child(props, context) {
+		return <div>{context.lang}</div>;
+	  }
+	  Child.contextTypes = {lang:PropTypes.string};
+  
+	  var el = document.createElement('div');
+	  ReactDOM.render(<Parent />, el);
+	  expect(el.textContent).toBe('en');
+	});
+	it('should work with arrow functions', () => {
+	  var Child = function() {
+		return <div />;
+	  };
+	  // Will create a new bound function without a prototype, much like a native
+	  // arrow function.
+	  Child = Child.bind(this);
+  
+	  expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
+	});
+  
+	it('should allow simple functions to return null', () => {
+	  var Child = function() {
+		return null;
+	  };
+	  expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
+	});
+  
+	it('should allow simple functions to return false', () => {
+	  function Child() {
+		return false;
+	  }
+	  expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
+	});
+
+})

+ 402 - 0
test/may-dom/ReactTestUtils-test.jsx

@@ -0,0 +1,402 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import { shallowCompare } from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/renderers/dom/test/__tests__/ReactTestUtils-test.js
+
+describe("ReactTestUtils", function() {
+  // this.timeout(200000);
+  // before(async () => {
+  //   await beforeHook();
+  // });
+  // after(async () => {
+  //   await afterHook(false);
+  // });
+
+  var body = document.body,
+    div;
+  beforeEach(function() {
+    div = document.createElement("div");
+    body.appendChild(div);
+  });
+  afterEach(function() {
+    body.removeChild(div);
+  });
+  it("can scryRenderedDOMComponentsWithClass with TextComponent", () => {
+    class Wrapper extends React.Component {
+      render() {
+        return (
+          <div>
+            Hello <span>Jim</span>
+          </div>
+        );
+      }
+    }
+
+    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      "NonExistentClass"
+    );
+    expect(scryResults.length).toBe(0);
+  });
+
+  it("can scryRenderedDOMComponentsWithClass with className contains \\n", () => {
+    class Wrapper extends React.Component {
+      render() {
+        return (
+          <div>
+            Hello <span className={"x\ny"}>Jim</span>
+          </div>
+        );
+      }
+    }
+
+    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      "x"
+    );
+    expect(scryResults.length).toBe(1);
+  });
+
+  it("can scryRenderedDOMComponentsWithClass with multiple classes", () => {
+    class Wrapper extends React.Component {
+      render() {
+        return (
+          <div>
+            Hello <span className={"x y z"}>Jim</span>
+          </div>
+        );
+      }
+    }
+
+    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      "x y"
+    );
+    expect(scryResults1.length).toBe(1);
+
+    const scryResults2 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      "x z"
+    );
+    expect(scryResults2.length).toBe(1);
+
+    const scryResults3 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      ["x", "y"]
+    );
+    expect(scryResults3.length).toBe(1);
+
+    expect(scryResults1[0]).toBe(scryResults2[0]);
+    expect(scryResults1[0]).toBe(scryResults3[0]);
+
+    const scryResults4 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      ["x", "a"]
+    );
+    expect(scryResults4.length).toBe(0);
+
+    const scryResults5 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+      renderedComponent,
+      ["x a"]
+    );
+    expect(scryResults5.length).toBe(0);
+  });
+
+  /*it("traverses children in the correct order", () => {
+    class Wrapper extends React.Component {
+      render() {
+        return <div>{this.props.children}</div>;
+      }
+    }
+
+    const container = document.createElement("div");
+    ReactDOM.render(
+      <Wrapper>
+        {null}
+        <div>purple</div>
+      </Wrapper>,
+      container
+    );
+    const tree = ReactDOM.render(
+      <Wrapper>
+        <div>orange</div>
+        <div>purple</div>
+      </Wrapper>,
+      container
+    );
+
+    const log = [];
+    ReactTestUtils.findAllInRenderedTree(tree, function(child) {
+      if (ReactTestUtils.isDOMComponent(child)) {
+        log.push(ReactDOM.findDOMNode(child).textContent);
+      }
+    });
+
+    // Should be document order, not mount order (which would be purple, orange)
+    expect(log).toEqual(["orangepurple", "orange", "purple"]);
+  });
+
+  it("should support injected wrapper components as DOM components", () => {
+    const injectedDOMComponents = [
+      "button",
+      "form",
+      "iframe",
+      "img",
+      "input",
+      "option",
+      "select",
+      "textarea"
+    ];
+
+    injectedDOMComponents.forEach(function(type) {
+      const testComponent = ReactTestUtils.renderIntoDocument(
+        React.createElement(type)
+      );
+
+      expect(testComponent.tagName).toBe(type.toUpperCase());
+      expect(ReactTestUtils.isDOMComponent(testComponent)).toBe(true);
+    });
+    // Full-page components (html, head, body) can't be rendered into a div
+    // directly...
+    class Root extends React.Component {
+      render() {
+        return (
+          <html ref="html">
+            <head ref="head">
+              <title>hello</title>
+            </head>
+            <body ref="body">hello, world</body>
+          </html>
+        );
+      }
+    }
+    // const markup = ReactDOMServer.renderToString(<Root />);
+    // const testDocument = getTestDocument(markup);
+    // const component = ReactDOM.render(<Root />, testDocument.body);
+    // expect(component.refs.html.tagName).toBe("HTML");
+    // expect(component.refs.head.tagName).toBe("HEAD");
+    // expect(component.refs.body.tagName).toBe("BODY");
+    // expect(ReactTestUtils.isDOMComponent(component.refs.html)).toBe(true);
+    // expect(ReactTestUtils.isDOMComponent(component.refs.head)).toBe(true);
+    // expect(ReactTestUtils.isDOMComponent(component.refs.body)).toBe(true);
+  });
+  it("can scry with stateless components involved", () => {
+    const Stateless = () => (
+      <div>
+        <hr />
+      </div>
+    );
+
+    class SomeComponent extends React.Component {
+      render() {
+        return (
+          <div>
+            <Stateless />
+            <hr />
+          </div>
+        );
+      }
+    }
+
+    const inst = ReactTestUtils.renderIntoDocument(<SomeComponent />);
+    const hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, "hr");
+    expect(hrs.length).toBe(2);
+  });
+
+  it("should change the value of an input field", () => {
+    const obj = {
+      handler: function(e) {
+        e.persist();
+      }
+    };
+    spyOn(obj, "handler").and.callThrough();
+    const container = document.createElement("div");
+    const instance = ReactDOM.render(
+      <input type="text" onChange={obj.handler} />,
+      container
+    );
+
+    const node = ReactDOM.findDOMNode(instance);
+    node.value = "giraffe";
+    ReactTestUtils.Simulate.change(node);
+
+    expect(obj.handler).toHaveBeenCalledWith({ target: node });
+  });
+
+  it("should change the value of an input field in a component", () => {
+    class SomeComponent extends React.Component {
+      render() {
+        return (
+          <div>
+            <input type="text" ref="input" onChange={this.props.handleChange} />
+          </div>
+        );
+      }
+    }
+
+    const obj = {
+      handler: function(e) {
+        e.persist();
+      }
+    };
+    spyOn(obj, "handler").and.callThrough();
+    const container = document.createElement("div");
+    const instance = ReactDOM.render(
+      <SomeComponent handleChange={obj.handler} />,
+      container
+    );
+
+    const node = ReactDOM.findDOMNode(instance.refs.input);
+    node.value = "zebra";
+    ReactTestUtils.Simulate.change(node);
+
+    expect(obj.handler).toHaveBeenCalledWith({ target: node });
+  });
+
+  it("should throw when attempting to use a React element", () => {
+    class SomeComponent extends React.Component {
+      render() {
+        return <div onClick={this.props.handleClick}>hello, world.</div>;
+      }
+    }
+
+    const handler = spyOn.createSpy();
+
+    const shallowRenderer = ReactShallowRenderer();
+    const result = shallowRenderer.render(
+      <SomeComponent handleClick={handler} />
+    );
+
+    expect(() => ReactTestUtils.Simulate.click(result)).toThrowError(
+      "TestUtils.Simulate expected a DOM node as the first argument but received " +
+        "a React element. Pass the DOM node you wish to simulate the event on instead. " +
+        "Note that TestUtils.Simulate will not work if you are using shallow rendering."
+    );
+    expect(handler).toNotHaveBeenCalled();
+  });
+
+  it("should throw when attempting to use a component instance", () => {
+    class SomeComponent extends React.Component {
+      render() {
+        return <div onClick={this.props.handleClick}>hello, world.</div>;
+      }
+    }
+
+    let handler = spyOn.createSpy("spy");
+    let container = document.createElement("div");
+    let instance = ReactDOM.render(
+      <SomeComponent handleClick={handler} />,
+      container
+    );
+
+    expect(() => ReactTestUtils.Simulate.click(instance)).toThrowError(
+      "TestUtils.Simulate expected a DOM node as the first argument but received " +
+        "a component instance. Pass the DOM node you wish to simulate the event on instead."
+    );
+
+    expect(handler).toNotHaveBeenCalled();
+  });
+
+  it("should not warn when used with extra properties", () => {
+    spyOn(console, "error");
+
+    const CLIENT_X = 100;
+
+    class Component extends React.Component {
+      handleClick(e) {
+        expect(e.clientX).toBe(CLIENT_X);
+      }
+
+      render() {
+        return <div onClick={this.handleClick} />;
+      }
+    }
+
+    const element = document.createElement("div");
+    const instance = ReactDOM.render(<Component />, element);
+    ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance), {
+      clientX: CLIENT_X
+    });
+    console.log(!!console.error.spyArgs);
+
+    expect(console.error.spyArgs).toBe(undefined);
+  });
+  it("should set the type of the event", () => {
+    let event;
+    const stub = function(e) {
+      e.persist();
+      event = e;
+    };
+
+    const container = document.createElement("div");
+    const instance = ReactDOM.render(<div onKeyDown={stub} />, container);
+    const node = ReactDOM.findDOMNode(instance);
+
+    ReactTestUtils.Simulate.keyDown(node);
+
+    expect(event.type).toBe("keydown");
+    expect(event.nativeEvent.type).toBe("keydown");
+  });
+
+  it("should work with renderIntoDocument", () => {
+    var target;
+    var i = 0;
+    const onChange = spyOn.createSpy();
+
+    class MyComponent extends React.Component {
+      render() {
+        return (
+          <div>
+            <input type="text" onChange={onChange} />
+          </div>
+        );
+      }
+    }
+
+    const instance = ReactTestUtils.renderIntoDocument(<MyComponent />);
+    const input = ReactTestUtils.findRenderedDOMComponentWithTag(
+      instance,
+      "input"
+    );
+    input.value = "giraffe";
+    ReactTestUtils.Simulate.change(input);
+    expect(onChange).toHaveBeenCalledWith({ target: input });
+  });
+
+  it("should call setState callback with no arguments", () => {
+    let mockArgs;
+    class Component extends React.Component {
+      componentDidMount() {
+        this.setState({}, (...args) => (mockArgs = args));
+      }
+      render() {
+        return false;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<Component />);
+    expect(mockArgs.length).toEqual(0);
+  });*/
+});

+ 72 - 0
test/may-dom/ReactUpdates-test.jsx

@@ -0,0 +1,72 @@
+import PropTypes from '../../lib/ReactPropTypes';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom';
+import { shallowCompare } from '../../src/PureComponent';
+
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+//https://github.com/facebook/react/blob/master/src/renderers/dom/test/__tests__/ReactTestUtils-test.js
+
+describe("ReactUpdates", function() {
+  // this.timeout(200000);
+  // before(async () => {
+  //   await beforeHook();
+  // });
+  // after(async () => {
+  //   await afterHook(false);
+  // });
+
+  /**
+ * Counts clicks and has a renders an item for each click. Each item rendered
+ * has a ref of the form "clickLogN".
+ */
+it('should not reconcile children passed via props', () => {
+    var numMiddleRenders = 0;
+    var numBottomRenders = 0;
+
+    class Top extends React.Component {
+      render() {
+        return <Middle><Bottom /></Middle>;
+      }
+    }
+
+    class Middle extends React.Component {
+      componentDidMount() {
+        this.forceUpdate();
+      }
+
+      render() {
+        numMiddleRenders++;
+        return React.Children.only(this.props.children);
+      }
+    }
+
+    class Bottom extends React.Component {
+      render() {
+        numBottomRenders++;
+        return null;
+      }
+    }
+
+    ReactTestUtils.renderIntoDocument(<Top />);
+    expect(numMiddleRenders).toBe(2);
+    // expect(numBottomRenders).toBe(1);
+    expect(numBottomRenders).toBe(2);
+  });
+
+})

+ 48 - 0
test/may-dom/cloneElement.spec.js

@@ -0,0 +1,48 @@
+import React from "../../src/May";
+var cloneElement=React.cloneElement;
+describe("cloneElement", function () {
+    it("test", () => {
+        var a = {
+            type: "div",
+            props: {
+                v: 1,
+                children: []
+            }
+        };
+        expect(cloneElement(a).props.v).toBe(1);
+    });
+
+    it("array", () => {
+        var a =  {
+            type: "div",
+            props: {
+                v: 2,
+                children: []
+            }
+        };
+ 
+        expect(cloneElement(a).props.v).toBe(2);
+    });
+    it("should transfer the key property", ()=> {
+        class Component extends React.Component{
+            render() {
+                return null;
+            }
+        }
+        var clone = cloneElement(<Component />, {key: "xyz"});
+        expect(clone.key).toBe("xyz");
+    });
+    it("children", () => {
+        function A() { }
+        var b = React.cloneElement({
+            type: A,
+            vtype: 2,
+            props: {}
+        }, {
+            children: [111, 222],
+            onChange: function () { },
+            key: "tabContent"
+        });
+        expect(b.props.children.length).toBe(2);
+    });
+});

+ 646 - 0
test/may-dom/component.spec.jsx

@@ -0,0 +1,646 @@
+
+// import PureComponent from "src/PureComponent";
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom';
+var ReactDOM = {
+	render: render,
+	unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM=React;
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+describe("组件相关", function() {
+    // this.timeout(200000);
+    // before(async () => {
+    //     await beforeHook();
+    // });
+    // after(async () => {
+    //     await afterHook(false);
+    // });
+    var body = document.body,
+        div;
+    beforeEach(function() {
+        div = document.createElement("div");
+        body.appendChild(div);
+    });
+    afterEach(function() {
+        body.removeChild(div);
+    });
+    it("stateless", function() {
+        function HelloComponent(
+            props
+            //context
+        ) {
+            return <div onClick={() => (props.name = 11)}>Hello {props.name}</div>;
+        }
+        var vnode = <HelloComponent name="Sebastian" />
+        React.render(vnode, div);
+
+        expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("Hello Sebastian");
+    });
+   
+    it("shouldComponentUpdate什么也不返回", function() {
+        var a = 1;
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 1
+                };
+            }
+            shouldComponentUpdate() {
+                //这里相当于返回false
+            }
+            click() {
+                this.setState(
+                    function(a) {
+                        a.aaa++;
+                    },
+                    function() {
+                        a++;
+                    }
+                );
+
+                this.setState(
+                    function(a) {
+                        a.aaa++;
+                    },
+                    function() {
+                        a++;
+                    }
+                );
+            }
+            render() {
+                return <div onClick={this.click.bind(this)}>{this.state.aaa}</div>;
+            }
+        }
+        var vnode = <App />
+        ReactDOM.render(vnode, div);
+        expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("1");
+        // ReactTestUtils.Simulate.click(vnode._hostNode)
+        // expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("1");
+
+        // expect(a).toBe(3);
+    });
+    it("shouldComponentUpdate返回false", function() {
+        var a = 1;
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 1
+                };
+            }
+            shouldComponentUpdate() {
+                return false;
+            }
+            click() {
+                this.setState(
+                    function(a) {
+                        a.aaa++;
+                    },
+                    function() {
+                        a++;
+                    }
+                );
+
+                this.setState(
+                    function(a) {
+                        a.aaa++;
+                    },
+                    function() {
+                        a++;
+                    }
+                );
+            }
+            render() {
+                return <div onClick={this.click.bind(this)}>{this.state.aaa}</div>;
+            }
+        }
+
+        var vnode = <App />
+        ReactDOM.render(vnode, div);
+        expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("1");
+        // ReactTestUtils.Simulate.click(vnode.mayInfo.hostNode)
+        // expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("1");
+        // expect(a).toBe(3);
+    });
+    it("PureComponent", function() {
+        var a = 1;
+        class App extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            click() {
+                this.setState(function(state) {
+                    state.aaa.a = 8;
+                });
+            }
+            render() {
+                return <div onClick={this.click.bind(this)}>{this.state.aaa.a}</div>;
+            }
+        }
+
+        var vnode = <App />
+        ReactDOM.render(vnode, div);
+        expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("7");
+        // ReactTestUtils.Simulate.click(vnode._hostNode)
+        // expect(vnode._hostNode.innerHTML).toBe("7");
+    });
+
+    it("PureComponent2", function() {
+        class App extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            click() {
+                var aaa = this.state.aaa;
+                aaa.a = 9;
+                this.setState({
+                    aaa: aaa,
+                    ccc: 222
+                });
+            }
+            render() {
+                return <div onClick={this.click.bind(this)}>{this.state.aaa.a}</div>;
+            }
+        }
+
+        var vnode = <App />
+        ReactDOM.render(vnode, div);
+        expect(vnode.mayInfo.instance.mayInst.hostNode.innerHTML).toBe("7");
+        // ReactTestUtils.Simulate.click(vnode._hostNode)
+        // expect(vnode._hostNode.innerHTML).toBe("9");
+
+    });
+    it("子组件是无状态组件",() => {// async 
+        function Select(props) {
+            return <strong>{props.value}</strong>;
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    value: "南京"
+                };
+            }
+
+            onChange(e) {
+                this.setState({
+                    value: e.target.value
+                });
+            }
+            render() {
+                return (
+                    <div>
+                        <Select value={this.state.value} />
+                        <input ref="a" value={this.state.value} onInput={this.onChange.bind(this)} />
+                    </div>
+                );
+            }
+        }
+
+        var s = React.render(<App />, div);
+        expect(s.refs.a.value).toBe("南京");//await
+        //  browser
+        //     .setValue(s.refs.a, "南京22")
+        //     .pause(200)
+        //     .$apply();
+        expect(s.refs.a.value).toBe("南京");
+        expect(div.getElementsByTagName("strong")[0].innerHTML).toBe("南京");
+    });
+    it("多选下拉框",() => {// async 
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    value: ["aaa", "ccc"]
+                };
+            }
+
+            onChange(e) {
+                var values = [];
+                var elems = e.target.getElementsByTagName("option");
+                for (var i = 0, el; (el = elems[i++]); ) {
+                    if (el.selected) {
+                        if (el.getAttribute("value") != null) {
+                            values.push(el.getAttribute("value"));
+                        } else {
+                            values.push(el.text);
+                        }
+                    }
+                }
+                this.setState({
+                    values: values
+                });
+            }
+            render() {
+                return (
+                    <select value={this.state.value} multiple="true" onChange={this.onChange.bind(this)}>
+                        <optgroup>
+                            <option ref="a">aaa</option>
+                            <option ref="b">bbb</option>
+                        </optgroup>
+                        <optgroup>
+                            <option ref="c">ccc</option>
+                            <option ref="d">ddd</option>
+                        </optgroup>
+                    </select>
+                );
+            }
+        }
+
+        // var s = React.render(<App />, div);
+        // // await browser.pause(100).$apply();
+        // expect(s.refs.a.selected).toBe(true);
+        // expect(s.refs.b.selected).toBe(false);
+        // expect(s.refs.c.selected).toBe(true);
+        // expect(s.refs.d.selected).toBe(false);
+        // s.setState({
+        //     value: ["bbb", "ddd"]
+        // });
+        // // await browser.pause(100).$apply();
+        // expect(s.refs.a.selected).toBe(false);
+        // expect(s.refs.b.selected).toBe(true);
+        // expect(s.refs.c.selected).toBe(false);
+        // expect(s.refs.d.selected).toBe(true);
+    });
+
+    /*it("多选下拉框defaultValue", function() {
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    value: "ccc"
+                };
+            }
+
+            render() {
+                return (
+                    <select defaultValue={this.state.value}>
+                        <option ref="a">aaa</option>
+                        <option ref="b">bbb</option>
+                        <option ref="c">ccc</option>
+                        <option ref="d">ddd</option>
+                    </select>
+                );
+            }
+        }
+
+        var s = React.render(<App />, div);
+      
+        expect(s.refs.c.selected).toBe(true);
+    });
+
+    it("多选下拉框没有defaultValue与ReactDOM.render的回调this指向问题", function() {
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {};
+            }
+
+            render() {
+                return (
+                    <select>
+                        <option ref="a">aaa</option>
+                        <option ref="b">bbb</option>
+                        <option ref="c">ccc</option>
+                        <option ref="d">ddd</option>
+                    </select>
+                );
+            }
+        }
+
+        var s = React.render(<App />, div, function() {
+            expect(this.constructor.name).toBe("App");
+        });
+        expect(s.refs.a.selected).toBe(true);
+    });
+
+    it("一个组件由元素节点变注释节点再回元素节点,不触发componentWillUnmount", function() {
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    path: "111"
+                };
+            }
+            change(path) {
+                this.setState({
+                    path: path || "333"
+                });
+            }
+            render() {
+                return (
+                    <div>
+                        <span>xx</span>
+                        <Route path={this.state.path} />
+                    </div>
+                );
+            }
+        }
+        var updateCount = 0;
+        var receiveCount = 0;
+        var destroyCount = 0;
+        class Route extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    path: props.path
+                };
+            }
+            componentWillReceiveProps(props) {
+                receiveCount++;
+                console.log("receiveCount", receiveCount);
+                this.setState(function(nextState, props) {
+                    nextState.path = props.path;
+                    return nextState;
+                });
+            }
+            componentWillUpdate() {
+                updateCount++;
+                console.log("updateCount", updateCount);
+            }
+            componentWillUnmount() {
+                destroyCount++;
+                console.log("destroyCount", destroyCount);
+            }
+            render() {
+                return this.state.path == "111" ? <p>{this.state.path}</p> : null;
+            }
+        }
+        var s = React.render(<App />, div);
+      
+        expect(updateCount).toBe(0);
+        expect(receiveCount).toBe(0);
+        s.change("111");
+       
+        expect(updateCount).toBe(1);
+        expect(receiveCount).toBe(1);
+        s.change("111x");
+      
+        expect(updateCount).toBe(2);
+        expect(receiveCount).toBe(2);
+        expect(div.firstChild.childNodes[1].nodeType).toBe(8);
+        expect(destroyCount).toBe(0);
+        s.change("111");
+      
+        expect(updateCount).toBe(3);
+        expect(receiveCount).toBe(3);
+        expect(div.firstChild.childNodes[1].nodeType).toBe(1);
+        expect(destroyCount).toBe(0);
+    });
+
+    it("select的准确匹配", function() {
+        var dom = ReactDOM.render(
+            <select value={222}>
+                <option value={111}>aaa</option>
+                <option value={"222"}>xxx</option>
+                <option value={222}>bbb</option>
+                <option value={333}>ccc</option>
+            </select>,
+            div
+        );
+        expect(dom.options[2].selected).toBe(true);
+    });
+    it("确保ref执行在componentDidMount之前", function() {
+        var str = "";
+        class Test extends React.Component {
+            componentDidMount() {
+                expect(typeof this.refs.wrapper).toBe("object");
+                str += "111";
+            }
+            render() {
+                return (
+                    <div ref="wrapper" id="aaa">
+                        xxx<B />
+                    </div>
+                );
+            }
+        }
+        class B extends React.Component {
+            componentDidMount() {
+                expect(typeof this.refs.wrapper2).toBe("object");
+                str += "222";
+            }
+            render() {
+                return <p ref="wrapper2">son</p>;
+            }
+        }
+        var s = React.render(<Test />, div);
+       
+        expect(str).toBe("222111");
+        expect(React.findDOMNode(s.refs.wrapper)).toBe(div.querySelector("#aaa"));
+    });
+    it("确保componentDidUpdate的第一个参数是prevProps", function() {
+        class HasChild extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {};
+                this.onClick = this.onClick.bind(this);
+                this.a = 0;
+            }
+            onClick() {
+                this.a = 1;
+                this.forceUpdate();
+            }
+            render() {
+                return (
+                    <div onClick={this.onClick} ref="componentDidUpdate">
+                        {this.a == 0 ? <A title="xxx" ref="a" /> : <A ddd="ddd" ref="a" />}
+                    </div>
+                );
+            }
+        }
+        var title = 0;
+        class A extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {};
+            }
+            componentDidUpdate(a) {
+                title = 1;
+            }
+            render() {
+                return <span>{this.props.title}</span>;
+            }
+        }
+        A.defaultProps = {
+            title: "默认值"
+        };
+
+        var s = React.render(<HasChild />, div);
+       
+        expect(s.refs.a.props.title).toBe("xxx");
+        eactTestUtils.Simulate.click(s.refs.componentDidUpdate)
+       
+        expect(s.refs.a.props.title).toBe("默认值");
+        expect(s.refs.a.props.ddd).toBe("ddd");
+        expect(title).toBe(1);
+    });
+
+    it("defaultProps的处理", function() {
+        class HasChild extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {};
+                this.onClick = this.onClick.bind(this);
+                this.a = 0;
+            }
+            onClick() {
+                this.a = 1;
+                this.forceUpdate();
+            }
+            render() {
+                return (
+                    <div onClick={this.onClick} ref="componentDidUpdate2">
+                        {this.a == 0 ? <A title="xxx" ref="a" /> : <A ddd="ddd" ref="a" />}
+                    </div>
+                );
+            }
+        }
+        function A(props) {
+            return <span>{props.title}</span>;
+        }
+
+        A.defaultProps = {
+            title: "默认值"
+        };
+
+
+        var s = React.render(<HasChild />, div);
+       
+        var span = div.getElementsByTagName("span")[0];
+        expect(span.innerHTML).toBe("xxx");
+        eactTestUtils.Simulate.click(s.refs.componentDidUpdate2)
+      
+        expect(span.innerHTML).toBe("默认值");
+    });
+
+    it("新旧两种无状态组件的ref传参", function(){
+        var list = []
+        function Old (props){
+           return  <span>{props.aaa}</span>
+        }
+        ReactDOM.render(<Old ref={(a)=>{ list.push(!!a)}} aaa={111} />, div);
+        ReactDOM.render(<Old ref={(a)=>{ list.push(!!a)}} aaa={222} />, div);
+        expect(list).toEqual([false, false, false])
+        var list2 = []
+        function New (props){
+           return {
+                componentWillMount(){
+                    list2.push("mount")
+                },
+                componentWillUpdate(){
+                    list2.push("update")
+                },
+               render(){
+                   return <span>{props.aaa}</span>
+               }
+           } 
+        }
+        ReactDOM.render(<New ref={(a)=>{ list2.push(!!a)}} aaa={111} />, div);
+        ReactDOM.render(<New ref={(a)=>{ list2.push(!!a)}} aaa={222} />, div);
+        expect(list2).toEqual(["mount", true, false, "update",true])
+
+    })
+
+    it("componentWillUnmount钩子中调用ReactDOM.findDOMNode 应该还能找到元素", () => {
+        var assertions = 0;
+
+        class Inner extends React.Component {
+            render() {
+                return <span />;
+            }
+
+            componentDidMount() {
+                expect(!!ReactDOM.findDOMNode(this)).toBe(true);
+                assertions++;
+            }
+
+            componentWillUnmount() {
+                expect(!!ReactDOM.findDOMNode(this)).toBe(true);
+                assertions++;
+            }
+        }
+
+        class Wrapper extends React.Component {
+            render() {
+                return this.props.showInner ? <Inner /> : null;
+            }
+        }
+
+        var el = document.createElement("div");
+        var component;
+
+        component = React.render(<Wrapper showInner={true} />, el);
+        expect(!!ReactDOM.findDOMNode(component)).toBe(true);
+
+        component = React.render(<Wrapper showInner={false} />, el);
+        expect(ReactDOM.findDOMNode(component).tagName).toBe(undefined);
+
+        component = React.render(<Wrapper showInner={true} />, el);
+        expect(!!ReactDOM.findDOMNode(component)).toBe(true);
+
+        expect(assertions).toBe(3);
+    });
+
+    it("虚拟DOM的_owner必须在render中加上", function() {
+        class B extends React.Component {
+            render() {
+                return <div className="xxx">{this.props.children}</div>;
+            }
+        }
+        var b, c;
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    value: "南京"
+                };
+            }
+            _renderPopup(a) {
+                return (
+                    <B>
+                        <p {...a} />
+                    </B>
+                );
+            }
+            onChange(e) {
+                this.setState({
+                    value: e.target.value
+                });
+            }
+            render() {
+                return (
+                    <div>
+                        {this._renderPopup({ ref: "xxxx", children: [(b = <span>333</span>)] })}
+                        {this._renderPopup({ ref: "yyyy", children: [(c = <strong>444</strong>)] })}
+                    </div>
+                );
+            }
+        }
+        var s = React.render(<App />, div);
+        
+        expect(b._owner.constructor).toBe(App);
+        expect(c._owner.constructor).toBe(App);
+    });*/
+});

+ 94 - 0
test/may-dom/context.spec.jsx

@@ -0,0 +1,94 @@
+import PropTypes from "../../lib/ReactPropTypes";
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM=React;
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+
+describe('context', function () {
+    // this.timeout(200000);
+
+    var body = document.body, div
+    beforeEach(function () {
+        div = document.createElement('div')
+        body.appendChild(div)
+    })
+    afterEach(function () {
+        body.removeChild(div)
+    })
+
+
+    it('getChildContext', async () => {
+
+        var arr = ['111', '222', '333']
+        class App extends React.Component {
+
+            getChildContext() {
+                return {
+                    name: "Jonas",
+                    fruit: "Banana"
+                };
+            }
+            handleClick() {
+                this.getChildContext = function () {
+                    return {
+                        name: "Jonas",
+                        fruit: arr.shift()
+                    };
+                }
+                this.forceUpdate()
+
+            }
+            render() {
+                return <div ref='a' onClick={this.handleClick.bind(this)}><h4>{new Date - 0}</h4><B /></div>;
+            }
+        }
+        App.childContextTypes = {
+            name: PropTypes.string,
+            fruit: PropTypes.string
+        }
+
+        class B extends React.Component {
+
+            render() {
+                return <div><C /><strong>{this.context.fruit}</strong></div>;
+            }
+        }
+        B.contextTypes = {
+            fruit: PropTypes.string
+        }
+        class C extends React.Component {
+            render() {
+                return <strong>{this.context.fruit}</strong>;
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div)
+
+        var strongs = div.getElementsByTagName('strong')
+        expect(strongs[0].innerHTML).toBe('')
+        expect(strongs[1].innerHTML).toBe('Banana')
+        ReactTestUtils.Simulate.click(s.refs.a)
+
+        strongs = div.getElementsByTagName('strong')
+        expect(strongs[0].innerHTML).toBe('')
+        expect(strongs[1].innerHTML).toBe('111')
+        ReactTestUtils.Simulate.click(s.refs.a)
+        strongs = div.getElementsByTagName('strong')
+        expect(strongs[0].innerHTML).toBe('')
+        expect(strongs[1].innerHTML).toBe('222')
+    })
+
+
+
+})

+ 92 - 0
test/may-dom/createElement.spec.js

@@ -0,0 +1,92 @@
+import React from '../../src/May';
+
+
+describe("createElement", function () {
+    it("type", () => {
+        var el = React.createElement("p", null, "aaa");
+        expect(el.type).toBe("p");
+        expect(el.mtype).toBe(1);
+        expect(el.props.children).toBe("aaa");
+        expect(el.props.children.length).toBe(3);
+    });
+    it("children", () => {
+        var el = React.createElement("p", null, "aaa", "bbb", "ccc");
+        expect(el.props.children).toEqual(["aaa", "bbb", "ccc"]);
+
+        el = React.createElement("p", null, null);
+        expect(el.props.children).toEqual(null);
+        el = React.createElement("div", { key: "xxx" });
+
+        expect(el.key).toBe("xxx");
+
+        el = React.createElement("p", null, []);
+        expect(el.props.children.length).toBe(0);
+
+        el = React.createElement("p", { children: ["aaa", "bbb"] });
+        expect(el.props.children.length).toBe(2);
+
+        el = React.createElement("p", null);
+        expect(el.props.children).toBe(void 666);
+    });
+    it("Children.only", () => {
+        var el = React.createElement("p", null, "aaa", "bbb", "ccc");
+        var a = React.Children.only(el);
+        expect(a.type).toBe("p");
+        expect(a.props.children).toEqual(["aaa", "bbb", "ccc"]);
+
+        el = React.createElement("p", null, null);
+        expect(el.props.children).toEqual(null);
+
+        el = React.createElement("p", null, []);
+        expect(el.props.children.length).toBe(0);
+        expect(el.mtype).toBe(1);
+        el = React.createElement("p", { children: ["aaa", "bbb"] });
+        expect(el.props.children.length).toBe(2);
+    });
+
+    it("flatChildren", () => {
+        var el = React.createElement("p", null, "aaa", false, "ccc");
+        expect(el.props.children).toEqual(["aaa", false, "ccc"]);
+
+        var el = React.createElement("p", null, "aaa", true, "ccc");
+        expect(el.props.children).toEqual(["aaa", true, "ccc"]);
+
+        var el = React.createElement("p", null, "aaa", 111, "ccc");
+        expect(el.props.children).toEqual(["aaa", 111, "ccc"]);
+
+        var el = React.createElement("p", null, "aaa", "", "ccc");
+        expect(el.props.children).toEqual(["aaa", "", "ccc"]);
+
+        var el = React.createElement("p", null, "aaa", "ccc", "");
+        expect(el.props.children).toEqual(["aaa", "ccc", ""]);
+
+        var el = React.createElement("p", null, "aaa", "", "ccc");
+        expect(el.props.children).toEqual(["aaa", "", "ccc"]);
+
+        var el = React.createElement("p", null, 111, 222, 333);
+        expect(el.props.children).toEqual([111, 222, 333]);
+
+        var el = React.createElement("p", null, 111, "ddd", 333);
+        expect(el.props.children).toEqual([111, "ddd", 333]);
+    });
+    it("class render", () => {
+        class A extends React.Component {
+            render() {
+                return <div id="aaa" />;
+            }
+        }
+        var el = React.createElement(A, {});
+        expect(el.mtype).toBe(2);
+        el = React.createElement(function () { }, {});
+        expect(el.mtype).toBe(2);
+        var obj = new A().render();
+        expect(obj.props.children).toEqual(void 666);
+        expect(obj.props.id).toBe("aaa");
+        expect(obj.props).toEqual({
+            id: "aaa"
+        });
+        expect(obj.type).toEqual("div");
+        expect(obj.key == null).toBe(true);
+        // expect(typeof obj._owner).toBe("object");
+    });
+});

+ 119 - 0
test/may-dom/diffProps.spec.jsx

@@ -0,0 +1,119 @@
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+
+describe('diffProps', function () {
+    // this.timeout(200000);
+    // before(async() => {
+    //     await beforeHook();
+    // });
+    // after(async() => {
+    //     await afterHook(false);
+    // })
+    var body = document.body, div
+    beforeEach(function () {
+        div = document.createElement('div')
+        body.appendChild(div)
+    })
+    afterEach(function () {
+        body.removeChild(div)
+
+    })
+    it('使用对象解构', async () => {
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    title: 'xxx',
+                    className: 'aaa'
+                }
+            }
+            render() {
+                return <div ref='a' {...this.state}>
+                    xxx
+                </div>
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div)
+        var dom = s.refs.a
+        expect(dom.title).toBe('xxx')
+        expect(dom.className).toBe('aaa')
+        s.setState({ title: '123', id: 'uuuu' })
+
+        expect(dom.title).toBe('123')
+        expect(dom.className).toBe('aaa')
+        expect(dom.id).toBe('uuuu')
+
+    })
+
+    it('改变属性', async () => {
+        var index = 1
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    title: 'xxx',
+                    className: 'aaa'
+                }
+            }
+            onClick() {
+                index = 0
+                this.forceUpdate()
+            }
+            render() {
+                return index
+                    ? <div
+                        ref='a'
+                        title='xxx'
+                        className='ddd'
+                        id='h33'
+                        onClick={this
+                            .onClick
+                            .bind(this)}
+                        dangerouslySetInnerHTML={{
+                            __html: '<b>xxx</b>'
+                        }}></div>
+                    : <div ref='a' title='yyy' id='h44' data-bbb='sss'>
+                        xxx{new Date - 0}
+                    </div>
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div)
+        var dom = s.refs.a
+        expect(dom.title).toBe('xxx')
+        expect(dom.className).toBe('ddd')
+        expect(dom.id).toBe('h33')
+        var events = dom._listener || {}
+        var t;
+        if (typeof events.onClick === 'function' || typeof events.click === 'function') {
+            t = 'function';
+        }
+        expect(t).toBe('function')
+        expect(dom.getElementsByTagName('b').length).toBe(1)
+        index = 0
+
+        ReactTestUtils.Simulate.click(document.getElementById('h33'));
+        dom = s.refs.a
+        expect(dom.title).toBe('yyy')
+        expect(dom.className).toBe('')
+        expect(dom.id).toBe('h44')
+        expect(dom.getAttribute('data-bbb')).toBe('sss')
+        var events = dom._listener || {}
+        if (typeof events.onClick === 'undefined' && typeof events.click === 'undefined') {
+            t = 'undefined';
+        }
+        expect(t).toBe('undefined')
+        expect(dom.getElementsByTagName('b').length).toBe(0)
+
+    })
+
+
+})

+ 397 - 0
test/may-dom/event.spec.jsx

@@ -0,0 +1,397 @@
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+import {
+    dispatchEvent, SyntheticEvent, addEvent
+} from '../../src/event';
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = { Simulate: {} };
+// "click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove".replace(/\w+/g, function (name) {
+//     ReactTestUtils.Simulate[name] = function (node, opts) {
+//         if (!node || node.nodeType !== 1) {
+//             throw "第一个参数必须为元素节点";
+//         }
+//         var fakeNativeEvent = opts || {};
+//         fakeNativeEvent.target = node;
+//         fakeNativeEvent.simulated = true;
+//         fakeNativeEvent.type = name.toLowerCase();
+//         React.eventSystem.dispatchEvent(fakeNativeEvent, name.toLowerCase());
+//     };
+// });
+
+describe("事件系统模块", function () {
+    // this.timeout(200000);
+    // before(async () => {
+    //     await beforeHook();
+    // });
+    // after(async () => {
+    //     await afterHook(false);
+    // });
+    var body = document.body,
+        div;
+    beforeEach(function () {
+        div = document.createElement("div");
+        body.appendChild(div);
+    });
+    afterEach(function () {
+        body.removeChild(div);
+    });
+    it("事件与样式", async () => {
+        class App extends React.Component {
+            constructor() {
+                super();
+                this.state = {
+                    aaa: 111
+                };
+                this.click = this.click.bind(this)
+            }
+            click(e) {
+                e.preventDefault();
+                console.log('clicked' + this.state.aaa);
+                // expect(e.currentTarget.nodeType).toBe(1);
+                this.setState({
+                    aaa: this.state.aaa + 1
+                });
+            }
+
+            render() {
+                return (
+                    <div
+                        id="aaa3"
+                        style={{
+                            height: this.state.aaa,
+                            color: 'blue'
+                        }}
+                        onClick={this.click}
+                    >
+                        {this.state.aaa}
+                    </div>
+                );
+            }
+        }
+        var s = ReactDOM.render(<App />, div);
+        //记一个坑 如果是在页面上点击该dom this.state.aaa会增加多次,那是因为
+        //karma在跑完当前test之后 还会接着跑 ref.spec.jsx的test 跑ref-test的test等
+        //故而会多次触发点击; 故测试用代码触发即可;ReactTestUtils.Simulate.click
+        expect(s.state.aaa).toBe(111);
+        ReactTestUtils.Simulate.click(s.mayInst.hostNode);
+        expect(s.state.aaa).toBe(112);
+        ReactTestUtils.Simulate.click(s.mayInst.hostNode);
+        expect(s.state.aaa).toBe(113);
+        //确保存在eventSystem对象
+        // expect(React.eventSystem).toA("object");
+    });
+
+    it("冒泡", async () => {
+        var aaa = "";
+        class App extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            click() {
+                aaa += "aaa ";
+            }
+            click2(e) {
+                aaa += "bbb ";
+                e.stopPropagation();
+            }
+            click3(e) {
+                aaa += "ccc ";
+            }
+            render() {
+                return (
+                    <div onClick={this.click}>
+                        <p>=========</p>
+                        <div onClick={this.click2}>
+                            <p>=====</p>
+                            <div id="bubble" onClick={this.click3}>
+                                {this.state.aaa.a}
+                            </div>
+                        </div>
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.click(document.getElementById("bubble"));
+        expect(aaa.trim()).toBe("ccc bbb");
+    });
+
+    it("模拟mouseover,mouseout", async () => {
+        var aaa = "";
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            mouseover() {
+                aaa += "aaa ";
+            }
+            mouseout(e) {
+                console.log(aaa);
+                aaa += "bbb ";
+            }
+
+            render() {
+                return (
+                    <div>
+                        <div
+                            id="mouse1"
+                            onMouseOver={this.mouseover}
+                            onMouseOut={this.mouseout}
+                            style={{
+                                width: 200,
+                                height: 200
+                            }}
+                        >67</div>
+                        <div id="mouse2" />
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.mouseover(document.getElementById('mouse1'));
+        ReactTestUtils.Simulate.mouseout(document.getElementById('mouse1'));
+        expect(aaa.trim()).toBe("aaa bbb");
+    });
+    it("1.1.2checkbox绑定onChange事件会触发两次", async () => {
+        var logIndex = 0;
+        function refFn(e) {
+            logIndex++;
+        }
+
+        var el = ReactDOM.render(<input id="ci" type="checkbox" onChange={refFn} />, div);
+        ReactTestUtils.Simulate.change(document.getElementById('ci'));
+        expect(logIndex).toBe(1);
+    });
+    it("模拟mouseenter,mouseleave", async () => {
+        var aaa = "";
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            mouseover() {
+                aaa += "aaa ";
+            }
+            mouseout(e) {
+                aaa += "bbb ";
+            }
+
+            render() {
+                return (
+                    <div>
+                        <div
+                            id="mouse3"
+                            onMouseEnter={this.mouseover}
+                            onMouseLeave={this.mouseout}
+                            style={{
+                                width: 200,
+                                height: 200
+                            }}
+                        />
+                        <div id="mouse4" />
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.mouseEnter(document.getElementById('mouse3'));
+        ReactTestUtils.Simulate.mouseLeave(document.getElementById('mouse3'));
+        expect(aaa.trim()).toBe("aaa bbb");
+    });
+    it("捕获", async () => {
+        var aaa = "";
+        class App extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            click() {
+                aaa += "aaa ";
+            }
+            click2(e) {
+                aaa += "bbb ";
+                e.preventDefault();
+                e.stopPropagation();
+            }
+            click3(e) {
+                aaa += "ccc ";
+            }
+            render() {
+                return (
+                    <div onClickCapture={this.click}>
+                        <p>=========</p>
+                        <div onClickCapture={this.click2}>
+                            <p>=====</p>
+                            <div id="capture" onClickCapture={this.click3}>
+                                {this.state.aaa.a}
+                            </div>
+                        </div>
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.click(document.getElementById("capture"));
+
+        expect(aaa.trim()).toBe("aaa bbb");
+    });
+    it("让focus能冒泡", async () => {
+        var aaa = "";
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: {
+                        a: 7
+                    }
+                };
+            }
+
+            onFocus1() {
+                aaa += "aaa ";
+            }
+            onFocus2(e) {
+                aaa += "bbb ";
+                console.log('fff ' + aaa);
+            }
+
+            render() {
+                return (
+                    <div
+                        onFocus={this.onFocus2}
+                        style={{
+                            width: 200,
+                            height: 200
+                        }}
+                    >
+                        <div
+                            id="focus2"
+                            tabIndex={-1}
+                            onFocus={this.onFocus1}
+                            style={{
+                                width: 100,
+                                height: 100
+                            }}
+                        >
+                            focus2
+                        </div>
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.focus(document.getElementById("focus2"));
+
+        expect(aaa.trim()).toBe("aaa bbb");
+    });
+    it("测试事件对象的属性", function () {
+        var obj = {
+            type: "change",
+            srcElement: 1
+        };
+        var e = new SyntheticEvent(obj);
+        expect(e.type).toBe("change");
+        expect(typeof e.timeStamp).toBe("number");
+        expect(e.target).toBe(1);
+        expect(e.nativeEvent).toBe(obj);
+        e.stopImmediatePropagation();
+        expect(e._stopPropagation).toBe(true);
+        expect(e.toString()).toBe("[object Event]");
+        var e2 = new SyntheticEvent(e);
+        expect(e2).toBe(e);
+
+        // var p = new DOMElement();
+        // p.addEventListener = false;
+        // addEvent(p, "type", "xxx");
+    });
+
+    it("合并点击事件中的setState", async () => {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    path: "111"
+                };
+            }
+
+            render() {
+                list.push("render " + this.state.path);
+                return (
+                    <div>
+                        <span id="click2time" onClick={this.onClick.bind(this)}>
+                            {this.state.path}
+                        </span>
+                    </div>
+                );
+            }
+
+            onClick() {
+                this.setState(
+                    {
+                        path: "click"
+                    },
+                    function () {
+                        list.push("click....");
+                    }
+                );
+                this.setState(
+                    {
+                        path: "click2"
+                    },
+                    function () {
+                        list.push("click2....");
+                    }
+                );
+            }
+            componentWillUpdate() {
+                list.push("will update");
+            }
+            componentDidUpdate() {
+                list.push("did update");
+            }
+        }
+
+        ReactDOM.render(<App />, div, function () {
+            list.push("ReactDOM cb");
+        });
+        ReactTestUtils.Simulate.click(document.getElementById("click2time"));
+
+        expect(list).toEqual(["render 111", "ReactDOM cb", "will update", "render click2", "did update", "click....", "click2...."]);
+    });
+});

+ 57 - 0
test/may-dom/findDOMNode-test.jsx

@@ -0,0 +1,57 @@
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode,findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode:findDOMNode
+}
+React.render = render;
+// https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
+
+describe("findDOMNode", function() {
+    // this.timeout(200000);
+
+    it("findDOMNode should find dom element", () => {
+        class MyNode extends React.Component {
+            render() {
+                return <div><span>Noise</span></div>;
+            }
+        }
+
+        var myNode = ReactTestUtils.renderIntoDocument(<MyNode />);
+        var myDiv = ReactDOM.findDOMNode(myNode);
+        var mySameDiv = ReactDOM.findDOMNode(myDiv);
+        expect(myDiv.tagName).toBe("DIV");
+        expect(mySameDiv).toBe(myDiv);
+    });
+
+    it("findDOMNode should find dom element after an update from null", () => {
+        function Bar({flag}) {
+            if (flag) {
+                return <span>A</span>;
+            }
+            return null;
+        }
+        class MyNode extends React.Component {
+            render() {
+                return <Bar flag={this.props.flag} />;
+            }
+        }
+
+        var container = document.createElement("div");
+
+        var myNodeA = ReactDOM.render(<MyNode />, container);
+        var a = ReactDOM.findDOMNode(myNodeA);
+        expect(a && a.nodeType).toBe(8);
+
+        var myNodeB = ReactDOM.render(<MyNode flag={true} />, container);
+        expect(myNodeA === myNodeB).toBe(true);
+
+        var b = ReactDOM.findDOMNode(myNodeB);
+        expect(b.tagName).toBe("SPAN");
+    });
+
+
+});

+ 676 - 0
test/may-dom/lifecycle.spec.jsx

@@ -0,0 +1,676 @@
+
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = { Simulate: {} };
+// "click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove".replace(/\w+/g, function (name) {
+//     ReactTestUtils.Simulate[name] = function (node, opts) {
+//         if (!node || node.nodeType !== 1) {
+//             throw "第一个参数必须为元素节点";
+//         }
+//         var fakeNativeEvent = opts || {};
+//         fakeNativeEvent.target = node;
+//         fakeNativeEvent.simulated = true;
+//         fakeNativeEvent.type = name.toLowerCase();
+//         React.eventSystem.dispatchEvent(fakeNativeEvent, name.toLowerCase());
+//     };
+// });
+
+describe("生命周期例子", function () {
+    // this.timeout(200000);
+
+    var body = document.body,
+        div;
+    beforeEach(function () {
+        div = document.createElement("div");
+        body.appendChild(div);
+    });
+    afterEach(function () {
+        body.removeChild(div);
+    });
+    it("如果在componentDidMount中调用setState方法\n那么setState的所有回调,\n都会延迟到componentDidUpdate中执行", function() {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: "aaa"
+                };
+            }
+            componentWillMount() {
+                this.setState(
+                    {
+                        aaa: "bbb"
+                    },
+                    function() {
+                        list.push("1111");
+                    }
+                );
+            }
+            componentDidMount() {
+                this.setState(
+                    {
+                        aaa: "cccc"
+                    },
+                    function() {
+                        list.push("2222");
+                    }
+                );
+                this.setState(
+                    {
+                        aaa: "dddd"
+                    },
+                    function() {
+                        list.push("3333");
+                    }
+                );
+                list.push("did mount");
+            }
+
+            componentWillUpdate() {
+                list.push("will update");
+            }
+            componentDidUpdate() {
+                list.push("did update");
+            }
+            render() {
+                list.push(this.state.aaa);
+                return <div>{this.state.aaa}</div>;
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+        expect(list.join("-")).toBe(
+            "bbb-did mount-will update-dddd-did update-1111-2222-3333"
+        );
+    });
+    it("父组件没有DidMount之时被子组件在willMount钩子里调用其setState", function () {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: "app render"
+                };
+            }
+            componentWillMount() {
+                list.push("app will mount");
+            }
+            componentDidMount() {
+                list.push("app did mount");
+            }
+
+            componentWillUpdate() {
+                list.push("app will update");
+            }
+            componentDidUpdate() {
+                list.push("app did update");
+            }
+            render() {
+                list.push(this.state.aaa);
+                return (
+                    <div>
+                        <A parent={this} />
+                        {this.state.aaa}
+                    </div>
+                );
+            }
+        }
+        class A extends React.Component {
+            componentWillMount() {
+                this.props.parent.setState({
+                    aaa: "app new render"
+                });
+                this.props.parent.setState({
+                    aaa: "app new render2"
+                });
+            }
+            componentWillReceiveProps() {
+                list.push("child receive");
+            }
+            render() {
+                return <p>A</p>;
+            }
+        }
+        var s = ReactDOM.render(<App />, div);
+        expect(list.join("-")).toBe(
+            "app will mount-app render-app did mount-app will update-app new render2-child receive-app did update"
+        );
+    });
+
+    it("父组件DidMount之时被子组件在componentWillReceiveProps钩子里调用其setState\n父组件的再次render会待到这次render完才调起", function () {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: "app render"
+                };
+            }
+            componentWillMount() {
+                list.push("app will mount");
+            }
+            componentDidMount() {
+                this.setState({
+                    aaa: "app render1"
+                });
+                list.push("app did mount");
+            }
+            componentDidUpdate() {
+                list.push("app did update");
+            }
+            render() {
+                list.push(this.state.aaa);
+                return (
+                    <div>
+                        <A parent={this} />
+                        {this.state.aaa}
+                        <C />
+                    </div>
+                );
+            }
+        }
+        var a = 1;
+        class C extends React.Component {
+            render() {
+                list.push("C render");
+                return <p>C</p>;
+            }
+        }
+        class A extends React.Component {
+            componentWillReceiveProps() {
+                if (a < 2) {
+                    this.props.parent.setState(
+                        {
+                            aaa: "child call app render " + ++a
+                        },
+                        function () {
+                            list.push("componentWillReceiveProps 1");
+                        }
+                    );
+                    this.props.parent.setState(
+                        {
+                            aaa: "child call app render " + ++a
+                        },
+                        function () {
+                            list.push("componentWillReceiveProps 2");
+                        }
+                    );
+                }
+            }
+            componentWillUpdate() {
+                list.push("child will update");
+            }
+            render() {
+                list.push("child render");
+                return <p>A</p>;
+            }
+        }
+        React.render(<App />, div);
+        var list2 = [
+            "app will mount",
+            "app render",
+            "child render",
+            "C render",
+            "app did mount",
+            "app render1",
+            "child will update",
+            "child render",
+            "C render",
+            "app did update",
+            "child call app render 3",
+            "child will update",
+            "child render",
+            "C render",
+            "app did update",
+            "componentWillReceiveProps 1",
+            "componentWillReceiveProps 2"
+        ];
+        expect(list.join("-")).toBe(list2.join("-"));
+    });
+
+    it("第一次渲染时不会触发componentWillUpdate", function() {
+        var a = 1;
+        class ReceivePropsComponent extends React.Component {
+            componentWillUpdate() {
+                a = 2;
+            }
+            render() {
+                return <div />;
+            }
+        }
+
+        React.render(<ReceivePropsComponent />, div);
+        expect(a).toBe(1);
+    });
+
+    it("先执行子组件的mount钩子再到父组件的mount钩子", function() {
+        let log = [];
+
+        class Inner extends React.Component {
+            componentDidMount() {
+                log.push("inner");
+            }
+
+            render() {
+                return <div id="inner" />;
+            }
+        }
+
+        class Outer extends React.Component {
+            componentDidMount() {
+                log.push("outer");
+            }
+
+            render(props) {
+                return <Inner />;
+            }
+        }
+
+        React.render(<Outer />, div);
+        expect(log.join("-")).toBe("inner-outer");
+    });
+    it("在componentWillMount中使用setState", function() {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 111
+                };
+            }
+            componentWillMount() {
+                this.setState(
+                    {
+                        aaa: 222
+                    },
+                    function() {
+                        list.push("555");
+                    }
+                );
+                this.setState(
+                    {
+                        aaa: 333
+                    },
+                    function() {
+                        list.push("666");
+                    }
+                );
+            }
+            render() {
+                list.push(this.state.aaa);
+                return <p>{this.state.aaa}</p>;
+            }
+        }
+
+        var s = React.render(<App />, div);
+   
+        expect(list.join("-")).toBe("333-555-666");
+        expect(div.textContent || div.innerText).toBe("333");
+    });
+    it("在componentWillMount中使用setState", function() {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 111
+                };
+            }
+            componentWillMount() {
+                this.setState(
+                    {
+                        aaa: 222
+                    },
+                    function() {
+                        list.push("555");
+                    }
+                );
+                this.setState(
+                    {
+                        aaa: 333
+                    },
+                    function() {
+                        list.push("666");
+                    }
+                );
+            }
+            render() {
+                list.push(this.state.aaa);
+                return <p>{this.state.aaa}</p>;
+            }
+        }
+
+        var s = React.render(<App />, div);
+        expect(list.join("-")).toBe("333-555-666");
+        expect(div.textContent || div.innerText).toBe("333");
+    });
+    it("在componentDidMount中使用setState,会导致willMount, DidMout中的回调都延后",function() {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 111
+                };
+            }
+
+            componentWillMount() {
+                this.setState(
+                    {
+                        aaa: 222
+                    },
+                    function() {
+                        list.push("555");
+                    }
+                );
+                this.setState(
+                    {
+                        aaa: 333
+                    },
+                    function() {
+                        list.push("666");
+                    }
+                );
+            }
+            componentDidMount() {
+                this.setState(
+                    {
+                        aaa: 444
+                    },
+                    function() {
+                        list.push("777");
+                    }
+                );
+            }
+
+            render() {
+                list.push(this.state.aaa);
+                return <p>{this.state.aaa}</p>;
+            }
+        }
+
+        var s = React.render(<App />, div);
+        expect(list.join("-")).toBe("333-444-555-666-777");
+        expect(div.textContent || div.innerText).toBe("444");
+    });
+
+    it("ReactDOM的回调总在最后",function() {
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    path: "111"
+                };
+            }
+            componentWillMount() {
+                this.setState(
+                    {
+                        path: "222"
+                    },
+                    function() {
+                        list.push("componentWillMount cb");
+                    }
+                );
+                this.setState(
+                    {
+                        path: "2222"
+                    },
+                    function() {
+                        list.push("componentWillMount cb2");
+                    }
+                );
+            }
+            render() {
+                list.push("render " + this.state.path);
+                return (
+                    <div>
+                        <span>
+                            {this.state.path}
+                            <Child parent={this} />
+                        </span>
+                    </div>
+                );
+            }
+            componentDidMount() {
+                this.setState(
+                    {
+                        path: "eeee"
+                    },
+                    function() {
+                        list.push("componentDidMount cb");
+                    }
+                );
+            }
+            componentWillUpdate() {
+                list.push("will update");
+            }
+            componentDidUpdate() {
+                list.push("did update");
+            }
+        }
+        class Child extends React.Component {
+            componentWillMount() {
+                this.props.parent.setState(
+                    {
+                        path: "child"
+                    },
+                    function() {
+                        list.push("child setState");
+                    }
+                );
+            }
+            render() {
+                list.push("child render");
+                return <p>33333</p>;
+            }
+        }
+        ReactDOM.render(<App />, div, function() {
+            list.push("ReactDOM cb");
+        });
+
+        expect(list).toEqual([
+            "render 2222",
+            "child render",
+            "will update",
+            "render eeee",
+            "child render",
+            "did update",
+            "componentWillMount cb",
+            "componentWillMount cb2",
+            "child setState",
+            "componentDidMount cb",
+            "ReactDOM cb"
+        ]);
+    });
+
+
+    it("在componentWillUnmount中setState应该不起作用", function() {
+        class Issue extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    status: "normal"
+                };
+            }
+
+            componentWillUnmount() {
+                this.setState({ status: "unmount" });
+            }
+
+            render() {
+                const { status } = this.state;
+
+                if (status === "unmount") {
+                    throw new Error("Issue unmounted");
+                }
+
+                return <span>Issue status: {status}</span>;
+            }
+        }
+
+        class App extends React.PureComponent {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    showIssue: true
+                };
+            }
+
+            render() {
+                const { showIssue } = this.state;
+
+                return (
+                    <div>
+                        {showIssue && <Issue />}
+                        <button
+                            type="button"
+                            ref="button"
+                            onClick={() => this.setState({ showIssue: !showIssue })}
+                        >
+              Click
+                        </button>
+                    </div>
+                );
+            }
+        }
+        var s = React.render(<App />, div);
+   
+        expect(div.getElementsByTagName("span").length).toBe(1);
+        ReactTestUtils.Simulate.click(s.refs.button);
+     
+        expect(div.getElementsByTagName("span").length).toBe(0);
+    });
+
+    it("forceUpdate在componentDidMount中使用",function(){
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: "aaa"
+                };
+            }
+            componentWillMount() {
+                this.setState(
+                    {
+                        aaa: "bbb"
+                    },
+                    function() {
+                        list.push("1111");
+                    }
+                );
+            }
+            componentDidMount() {
+                this.state.aaa = "cccc";
+                this.forceUpdate(function() {
+                    list.push("2222");
+                });
+                this.state.aaa = "dddd";
+                this.forceUpdate(function() {
+                    list.push("3333");
+                });
+                list.push("did mount");
+            }
+            componentWillUpdate() {
+                list.push("app will update");
+            }
+            componentDidUpdate() {
+                list.push("app did update");
+            }
+
+            render() {
+                list.push("render " + this.state.aaa);
+                return <div>{this.state.aaa}</div>;
+            }
+        }
+        ReactDOM.render(<App />, div, function() {
+            list.push("ReactDOM cb");
+        });
+        expect(list).toEqual([
+            "render bbb",
+            "did mount",
+            "app will update",
+            "render dddd",
+            "app did update",
+            "1111",
+            "2222",
+            "3333",
+            "ReactDOM cb"
+        ]);
+    });
+    /*it("事件回调里执行多个组件的setState,不会按触发时的顺序执行,而是按文档顺序执行",function(){
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.handleClick = this.handleClick.bind(this);
+            }
+            handleClick() {
+                this.refs.c.setState({
+                    text:"第1"
+                },function(){
+                    list.push("c的回调");
+                });
+                this.refs.a.setState({
+                    text:"第2"
+                }, function(){
+                    list.push("a的回调");
+                });
+                this.refs.b.setState({
+                    text:"第3"
+                },function(){
+                    list.push("b的回调");
+                });
+            }
+            render() {
+                return <div ref="kk" onClick={this.handleClick.bind(this)}>
+                    <Child name="a" ref="a" >aaa</Child> 
+                    <Child name="b" ref="b" >bbb</Child> 
+                    <Child name="c" ref="c" >ccc</Child> 
+                </div>;
+            }
+        }
+        class Child extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    text: props.children
+                };
+            }
+            componentWillUpdate(){
+                list.push(this.props.name+" will update");
+            }
+            componentDidUpdate(){
+                list.push(this.props.name+" did update");
+            }
+            render(){
+                return <span className={this.props.name}>{this.state.text}</span>;
+            }
+        }
+        var s = ReactDOM.render(<App />, div);
+        ReactTestUtils.Simulate.click(s.refs.kk);
+        expect(list).toEqual([
+            "a will update",
+            "b will update",
+            "c will update",
+            "a did update",
+            "b did update",
+            "c did update",
+            "a的回调",
+            "b的回调",
+            "c的回调"
+        ]);
+    });*/
+});

+ 995 - 0
test/may-dom/node.spec.jsx

@@ -0,0 +1,995 @@
+import ReactTestUtils from "../../lib/ReactTestUtils";
+import React from '../../src/May';
+import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render,
+    unmountComponentAtNode: unmountComponentAtNode,
+    findDOMNode: findDOMNode
+}
+React.render = render;
+
+// var React = require('react');
+// var ReactDOM = require('react-dom');
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = { Simulate: {} };
+// "click,change,keyDown,keyUp,KeyPress,mouseDown,mouseUp,mouseMove".replace(/\w+/g, function (name) {
+//     ReactTestUtils.Simulate[name] = function (node, opts) {
+//         if (!node || node.nodeType !== 1) {
+//             throw "第一个参数必须为元素节点";
+//         }
+//         var fakeNativeEvent = opts || {};
+//         fakeNativeEvent.target = node;
+//         fakeNativeEvent.simulated = true;
+//         fakeNativeEvent.type = name.toLowerCase();
+//         React.eventSystem.dispatchEvent(fakeNativeEvent, name.toLowerCase());
+//     };
+// });
+
+describe('node模块', function () {
+
+
+    var body = document.body, div
+    beforeEach(function () {
+        div = document.createElement('div')
+        body.appendChild(div)
+    })
+    afterEach(function () {
+        body.removeChild(div)
+    })
+    // it('连续点击一个DIV', async () => {
+    //     div.innerHTML = '看到我吗?'
+    //     var a = 1
+    //     div.onclick = function () {
+    //         a++
+    //     }
+    //     await browser.click(div).pause(100).$apply()
+    //     expect(a).toBe(2)
+    //     await browser.click(div).pause(100).$apply()
+    //     expect(a).toBe(3)
+    // });
+    it('输出简单的元素', async () => {
+
+        var s = React.render(<div>222</div>, div)
+        //组件直接返回元素节点
+        expect(s.nodeName).toBe('DIV');
+
+    });
+    it('InputControlES6', async () => {
+
+        class InputControlES6 extends React.Component {
+            constructor(props) {
+                super(props);
+
+                // 设置 initial state
+                this.state = {
+                    text: props.initialValue || 'placeholder'
+                }
+
+                // ES6 类中函数必须手动绑定
+                this.handleChange = this
+                    .handleChange
+                    .bind(this);
+            }
+
+            handleChange(event) {
+                this.setState({ text: event.target.value });
+            }
+
+            render() {
+                return (
+                    <div>
+                        Type something:
+                        <input ref="input" onChange={this.handleChange} value={this.state.text} />
+                    </div>
+                )
+            }
+        }
+
+        InputControlES6.defaultProps = {
+            initialValue: '请输入内容'
+        }
+
+        var s = React.render(<InputControlES6 />, div)
+
+        var input = s.refs.input
+        expect(input.value).toBe('请输入内容')
+        //input已经是dom 暂不做这个兼容了
+        // expect(input.getDOMNode()).toBe(input)
+
+    })
+    it('forceUpdate', async () => {
+
+
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+
+                // 设置 initial state
+                this.state = {
+                    text: 'xxx'
+                };
+            }
+
+            shouldComponentUpdate() {
+                return false
+            }
+
+            render() {
+                return (
+                    <div>
+                        Type something:
+                        <input ref="input" value={new Date - 0} />
+                    </div>
+                );
+            }
+        }
+
+        App.defaultProps = {
+            initialValue: '请输入内容'
+        };
+        div.innerHTML = '<span>remove</span>'
+
+        var s = React.render(<App />, div)
+
+        var index = 0
+        expect(s.mayInst.hostNode.nodeName).toBe('DIV')
+        s.forceUpdate(function () {
+            index++
+        })
+        expect(index).toBe(1)
+
+        s.forceUpdate(function () {
+            index++
+        })
+        expect(index).toBe(2)
+
+    })
+    it('下拉菜单的选择', async () => {
+
+        class Select extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    city: "bj"
+                }
+            }
+            handleCity(e) {
+                // expect(e.type).toBe('change')
+                console.warn(e.type);
+                var value = e.target.value;
+                this.setState({ city: value })
+            }
+            render() {
+                return <select
+                    name='city'
+                    id="node2"
+                    value={this.state.city}
+                    onChange={this
+                        .handleCity
+                        .bind(this)}>
+                    <option value='hz'>杭州</option>
+                    <option value='bj'>北京</option>
+                    <option value='sh'>上海</option>
+                </select>
+            }
+        }
+
+
+        var s = React.render(<Select />, div)
+
+        expect(div.firstChild.children[1].selected).toBe(true)
+        // await browser.selectByVisibleText('#node2', '上海').pause(100).$apply()
+
+        // expect(div.firstChild.children[2].selected).toBe(true)
+        // await browser.selectByVisibleText('#node2', '杭州').pause(100).$apply()
+
+        // expect(div.firstChild.children[0].selected).toBe(true)
+
+
+    })
+
+    it('下拉菜单的options重排后确保selected正确', async () => {
+
+        class Select extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    city: "bj",
+                    cities: [
+                        {
+                            value: 'bj',
+                            text: '北京'
+                        }, {
+                            value: 'hj',
+                            text: '杭州'
+                        }, {
+                            value: 'nj',
+                            text: '南京'
+                        }
+                    ]
+                }
+            }
+            change() {
+                this.setState({
+                    cities: [
+                        {
+                            value: 'hj',
+                            text: '杭州'
+                        }, {
+                            value: 'nj',
+                            text: '南京'
+                        }, {
+                            value: 'bj',
+                            text: '北京'
+                        }
+                    ]
+                })
+            }
+            handleCity(e) {
+                var value = e.target.value;
+                this.setState({ city: value })
+            }
+            render() {
+                return <select
+                    name='city'
+                    id="node3"
+                    value={this.state.city}
+                    onChange={this
+                        .handleCity
+                        .bind(this)}>
+                    {this
+                        .state
+                        .cities
+                        .map(function (el) {
+                            return <option value={el.value}>{el.text}</option>
+                        })
+                    }
+                </select>
+            }
+        }
+        ;
+        var s = React.render(<Select />, div)
+
+        expect(s.mayInst.hostNode.children[0].text).toBe('北京')
+        expect(s.mayInst.hostNode.children[1].text).toBe('杭州')
+        expect(s.mayInst.hostNode.children[2].text).toBe('南京')
+        s.change()
+
+        expect(s.mayInst.hostNode.children[0].text).toBe('杭州')
+        expect(s.mayInst.hostNode.children[1].text).toBe('南京')
+        expect(s.mayInst.hostNode.children[2].text).toBe('北京')
+
+
+    })
+
+    it('测试radio的onchange事件', async () => {
+
+        class Radio extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    checkedIndex: 2
+                }
+            }
+            handleChange(index) {
+                this.setState({ checkedIndex: index })
+            }
+            // webdriver.io不支持触发
+            // checkbox的onchange事件,只能browsers.click它,然后在一个onClick回调中手动调用onChange回调
+            onClick(index) {
+                var me = this
+                me.handleChange(index);
+                // setTimeout(function () {
+                //     me.handleChange(index)
+                // })
+            }
+
+            render() {
+
+                return <div>
+                    {[1, 2, 3]
+                        .map(function (el) {
+                            return <input
+                                type='radio'
+                                id={'radio' + el}
+                                name='xxx'
+                                key={el}
+                                value={el}
+                                checked={this.state.checkedIndex === el}
+                                onClick={this
+                                    .onClick
+                                    .bind(this, el)}
+                                onChange={this
+                                    .handleChange
+                                    .bind(this, el)} />
+
+                        }, this)
+                    }
+                </div>
+            }
+        }
+
+
+        var s = React.render(<Radio />, div)
+
+        expect(s.mayInst.hostNode.children[0].checked).toBe(false)
+        expect(s.mayInst.hostNode.children[1].checked).toBe(true)
+        expect(s.mayInst.hostNode.children[2].checked).toBe(false)
+        ReactTestUtils.Simulate.click(document.getElementById('radio3'));
+        expect(s.mayInst.hostNode.children[0].checked).toBe(false)
+        expect(s.mayInst.hostNode.children[1].checked).toBe(false)
+        expect(s.mayInst.hostNode.children[2].checked).toBe(true)
+
+
+    })
+
+    it('测试input元素的oninput事件', async () => {
+
+        var values = ['x', 'xx', 'xxx', 'xxxx']
+        var el = ''
+        class Input extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: 2
+                }
+            }
+            onInput(e) {
+                console.log('oninput', e.type, e.target.value)
+                el = values.shift()
+                this.setState({ value: e.target.value })
+            }
+
+
+            componentDidUpdate() {
+                // console.warn(s.mayInst.hostNode.children[0].value);
+                // expect(s.mayInst.hostNode.children[0].value).toBe(el)
+            }
+            render() {
+                return <div>
+                    <input
+                        id='node4'
+                        value={this.state.value}
+                        onInput={this
+                            .onInput
+                            .bind(this)} />{this.state.value}
+                    <input type='input' id="node3" />
+                    <input type='button' value='提交' />
+                </div>
+            }
+        }
+
+
+        var s = React.render(<Input />, div)
+
+        expect(s.mayInst.hostNode.children[0].value).toBe('2')
+
+
+    })
+    it('测试textarea元素的oninput事件', async () => {
+
+        var values = ['y', 'yy', 'yyy', 'yyyy']
+        var el = ''
+        class TextArea extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: 4
+                }
+            }
+            onInput(e) {
+                el = values.shift()
+                this.setState({ value: e.target.value })
+            }
+
+            componentDidUpdate() {
+                // expect(s._renderedVnode._hostNode.children[0].value).toBe(el)
+            }
+            render() {
+                return <div>
+                    <textarea
+                        id='node5'
+                        onInput={this
+                            .onInput
+                            .bind(this)}>{this.state.value}</textarea>{this.state.value}
+                </div>
+            }
+        }
+
+
+        var s = React.render(<TextArea />, div)
+
+
+        expect(s.mayInst.hostNode.children[0].value).toBe('4')
+
+        // await browser
+        //     .setValue('#node5', 'yyyy').pause(300).$apply()
+
+
+    })
+    it('非受控组件textarea的value不可变', async () => {
+
+        class TextArea extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: 5
+                }
+            }
+            render() {
+                return <div>
+                    <textarea id='node6' value={this.state.value}></textarea>{this.state.value}
+                </div>
+            }
+        }
+
+
+        var s = React.render(<TextArea />, div)
+
+        // expect(s.mayInst.hostNode.children[0].value).toBe('5')
+
+
+    })
+    it('非受控组件checkbox的checked不可变', async () => {
+
+        class Checkbox extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: true
+                }
+            }
+            render() {
+                return <div>
+                    <input id='node7' type='checkbox' name='xxx' checked={this.state.value} />
+
+                </div>
+            }
+        }
+
+
+        var s = React.render(<Checkbox />, div)
+  
+        expect(s.mayInst.hostNode.children[0].checked).toBe(true)
+
+    })
+    it('非受控组件radio的checked不可变', async () => {
+
+        class Radio extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: false
+                }
+            }
+            render() {
+                return <div>
+                    <input id='radio7' type='checkbox' name='xxx' checked={this.state.value} />
+
+                </div>
+            }
+        }
+
+
+        var s = React.render(<Radio />, div)
+
+        expect(s.mayInst.hostNode.children[0].checked).toBe(false)
+
+        ReactTestUtils.Simulate.click(document.getElementById('radio7'));
+
+        expect(s.mayInst.hostNode.children[0].checked).toBe(false)
+
+
+    })
+   it('元素节点存在dangerouslySetInnerHTML', async () => {
+        class App extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    aaa: 0
+                }
+            }
+            change(s) {
+                this.setState({
+                    aaa: 1
+                })
+            }
+            render() {
+                return <div>{this.state.aaa === 1 ? <p dangerouslySetInnerHTML={{ __html: "<span>111</span" }}  >222</p> :
+                    <p><strong>222</strong></p>
+                }</div>
+            }
+        }
+        var s = React.render(<App />, div)
+
+        expect(div.getElementsByTagName('strong').length).toBe(1)
+        s.change(1)
+        expect(div.getElementsByTagName('span').length).toBe(1)
+
+
+    })
+     it('非受控组件select的value不可变', async () => {
+        class Com extends React.Component {
+            constructor() {
+                super()
+                this.state = {
+                    value: 'bbb'
+                }
+            }
+            render() {
+                return <select id='node8' value={this.state.value}>
+                    <option value='aaa'>aaa</option>
+                    <option value='bbb'>bbb</option>
+                    <option value='ccc'>ccc</option>
+                </select>
+            }
+        }
+
+        var s = React.render(<Com />, div)
+
+        // expect(s.mayInst.hostNode.children[1].selected).toBe(true)
+
+        // expect(s.mayInst.hostNode.children[2].selected).toBe(false)
+        // expect(s.mayInst.hostNode.children[1].selected).toBe(true)
+
+
+    })
+
+    it('父子组件间的通信', async () => {
+        class Select extends React.Component {
+            constructor(props) {
+                super(props)
+
+                this.state = {
+                    value: props.value
+                }
+                this.onUpdate = props.onUpdate
+                this.onChange = this.onChange.bind(this)
+            }
+            componentWillReceiveProps(props) {
+                this.state = { //更新自己
+                    value: props.value
+                }
+            }
+            onChange(e) {//让父组件更新自己
+                this.onUpdate(e.target.value)
+            }
+            render() {
+                return <select id="communicate" value={this.state.value} onChange={this.onChange}>
+                    <option>北京</option>
+                    <option>南京</option>
+                    <option>东京</option>
+                </select>
+            }
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    value: '南京'
+                }
+            }
+            onUpdate(value) { //让子组件调用这个父组件的方法
+                this.setState({
+                    value: value
+                })
+            }
+            onChange(e) {
+                this.onUpdate(e.target.value)
+
+            }
+            render() {
+                return <div><Select onUpdate={this.onUpdate.bind(this)} value={this.state.value} /><input ref='sss' value={this.state.value} onChange={this.onChange.bind(this)} /></div>
+            }
+
+        }
+
+        var s = React.render(<App />, div)
+
+        expect(s.refs.sss.value).toBe('南京')
+
+
+    })
+    it('empty Component', async () => {
+        class Empty extends React.Component {
+            render() {
+                return null
+            }
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    value: '南京'
+                }
+            }
+            onUpdate(value) { //让子组件调用这个父组件的方法
+                this.setState({
+                    value: value
+                })
+            }
+            onChange(e) {//让父组件更新自己
+                this.onUpdate(e.target.value)
+            }
+            render() {
+                return <div><Empty />
+                    <input ref="a" value={this.state.value} onInput={this.onChange.bind(this)} /></div>
+            }
+        }
+
+
+        var s = React.render(<App />, div)
+
+        expect(s.refs.a.value).toBe('南京')
+        // await browser.setValue(s.refs.a, '北京').pause(100)
+        //     .$apply()
+        // expect(s.refs.a.value).toBe('北京')
+
+    })
+    it('移除组件', async () => {
+        var str = ''
+        class Component1 extends React.Component {
+            componentWillUnmount() {
+                str += 'xxxx'
+            }
+            render() {
+                return <div className="component1">{this.props.children}</div>
+            }
+        }
+        class Component2 extends React.Component {
+            componentWillUnmount() {
+                str += ' yyyy'
+            }
+            render() {
+                return <div className="component2">xxx</div>
+            }
+        }
+        var index = 1
+        function detect(a) {
+            console.log('detect 方法', index, a)
+            if (index === 1) {
+                expect(typeof a).toBe('object')
+            } else {
+                expect(a).toBeNull()
+            }
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.handleClick = this.handleClick.bind(this)
+            }
+            handleClick() {
+                index = 0
+                this.forceUpdate()
+                setTimeout(function () {
+                    console.warn('应该输出', str)
+                })
+            }
+            render() {
+                return index ?
+                    <div ref='a' onClick={this.handleClick.bind(this)}>
+                        <Component1>
+                            <p ref={detect}>这是子节点(移除节点测试1)</p>
+                            <Component2 />
+                        </Component1>
+                    </div> : <div>文本节点</div>
+
+            }
+        };
+
+        var s = React.render(<App />, div)
+        ReactTestUtils.Simulate.click(s.refs.a);
+        // await browser.pause(100).click(s.refs.a).pause(100)
+        //     .$apply()
+        expect(str).toBe('xxxx yyyy')
+
+    })
+    it('移除组件2', async () => {
+        var index = 1
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.handleClick = this.handleClick.bind(this)
+            }
+            handleClick() {
+                index = 0
+                this.forceUpdate()
+            }
+            render() {
+                return index ?
+                    <div ref='a' onClick={this.handleClick.bind(this)}>
+                        <div ref='b'><span>这是点击前</span></div>
+                    </div> : <div><p><strong>这是点击后</strong></p></div>
+
+            }
+        };
+
+        var s = React.render(<App />, div)
+        ReactTestUtils.Simulate.click(s.refs.a);
+
+        // await browser.pause(100).click(s.refs.a).pause(100)
+        //     .$apply()
+        expect(div.getElementsByTagName('p').length).toBe(1)
+
+    })
+    it('removedChildren', async () => {
+        var index = 1
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.handleClick = this.handleClick.bind(this)
+            }
+            handleClick() {
+                index = 0
+                this.forceUpdate()
+            }
+            render() {
+                return index ?
+                    <div ref='a' onClick={this.handleClick.bind(this)}>
+                        <p><strong>111</strong></p><p>2</p><p>3</p><p>4</p>
+                    </div> : <div><p>11</p></div>
+
+            }
+        };
+
+        var s = React.render(<App />, div)
+
+        expect(div.getElementsByTagName('p').length).toBe(4)
+        ReactTestUtils.Simulate.click(s.refs.a);
+        expect(div.getElementsByTagName('p').length).toBe(1)
+    })
+
+    it('一个元素拥有多个实例', async () => {
+        var arr = ['111', '222', '333']
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    title: 111
+                }
+            }
+
+            handleClick() {
+
+                this.setState({
+                    title: arr.shift() || new Date - 0
+                })
+
+            }
+            render() {
+                return <B title={this.state.title} onClick={this.handleClick.bind(this)} />
+            }
+        }
+
+        class B extends React.Component {
+            componentWillReceiveProps() {
+                this.forceUpdate()
+            }
+            render() {
+                return <div title={this.props.title} onClick={this.props.onClick}  >{new Date - 0}<C /></div>;
+            }
+        }
+
+        class C extends React.Component {
+            render() {
+                return <strong >{new Date - 0}</strong>;
+            }
+        }
+        var s = React.render(<App />, div)
+
+        expect(div.getElementsByTagName('strong').length).toBe(1)
+        expect(s instanceof App).toBe(true)
+        //expect(div.getElementsByTagName('p').length).toBe(1)
+    })
+    it('一个元素拥有多个实例2', async () => {
+        var arr = ['111', '222', '333']
+        class App extends React.Component {
+            render() {
+                return <div><A /></div>
+            }
+        }
+        class A extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    title: 111
+                }
+            }
+
+            handleClick() {
+
+                this.setState({
+                    title: arr.shift() || new Date - 0
+                })
+
+            }
+            render() {
+                return <B title={this.state.title} onClick={this.handleClick.bind(this)} />
+            }
+        }
+
+        class B extends React.Component {
+            componentWillReceiveProps() {
+                this.forceUpdate()
+            }
+            render() {
+                return <div title={this.props.title} onClick={this.props.onClick}  >{new Date - 0}<C /></div>;
+            }
+        }
+
+        class C extends React.Component {
+            render() {
+                return <strong >{new Date - 0}</strong>;
+            }
+        }
+        var s = React.render(<App />, div)
+
+        expect(div.getElementsByTagName('strong').length).toBe(1)
+        s.forceUpdate()
+        expect(div.getElementsByTagName('strong').length).toBe(1)
+        //expect(div.getElementsByTagName('p').length).toBe(1)
+    })
+
+    it('用一个新组件替换另一个组件', async () => {
+        var index = 1
+        class App extends React.Component {
+
+            handleClick() {
+                index = 0
+                this.forceUpdate()
+
+            }
+            render() {
+                return <div onClick={this.handleClick.bind(this)}>
+                    {index ? <A /> : <B />}</div>
+            }
+        }
+        class A extends React.Component {
+
+            render() {
+                return <strong>111</strong>
+            }
+        }
+
+        class B extends React.Component {
+            render() {
+                return <em>111</em>
+            }
+        }
+        var s = React.render(<App />, div)
+
+        expect(div.getElementsByTagName('strong').length).toBe(1)
+        s.handleClick()
+        expect(div.getElementsByTagName('em').length).toBe(1)
+        //expect(div.getElementsByTagName('p').length).toBe(1)
+    })
+
+   it('复杂的孩子转换', async () => {
+        var index = 0
+        var map = [
+            <div >1111<p>ddd</p><span>333</span><Link /></div>,
+            <div><em>新的</em><span>111</span>222<span>333</span><b>444</b><Link /></div>,
+            <div><span>33</span></div>
+        ]
+        function Link() {
+            return index == 1 ? <strong>ddd</strong> : <i>ddd</i>
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props)
+                this.state = {
+                    aaa: 'aaa'
+                }
+            }
+            change(a) {
+                this.setState({
+                    aaa: a
+                })
+            }
+            componentDidMount() {
+                console.log('App componentDidMount')
+            }
+            componentWillUpdate() {
+                console.log('App componentWillUpdate')
+            }
+            render() {
+                return map[index++]
+
+            }
+        }
+        var s = React.render(<App />, div)
+
+        function getString(nodes) {
+            var str = []
+            for (var i = 0, node; node = nodes[i++];) {
+                str.push(node.nodeName.toLowerCase())
+            }
+            return str.join(' ')
+        }
+        expect(getString(div.firstChild.childNodes)).toBe('#text p span strong')
+        s.change(100)
+        expect(getString(div.firstChild.childNodes)).toBe('em span #text span b i')
+        s.change(100)
+        expect(getString(div.firstChild.childNodes)).toBe('span')
+    })
+
+     it('对一个容器节点反复渲染组件或元素 ', async () => {
+        class Comp extends React.Component {
+            render() {
+                return <span>span in a component</span>;
+            }
+        }
+        let root;
+        function test(content) {
+            root = React.render(content, div);
+        }
+
+        test(<Comp />);
+        test(<div>just a div</div>);
+        test(<Comp />);
+
+        expect(div.firstChild.innerHTML).toBe('span in a component');
+    });
+
+    it('切换style对象', async () => {
+        var index = 1
+        class Comp extends React.Component {
+            render() {
+                return <span style={index ? { color: 'red' } : null}>span in a component</span>;
+            }
+        }
+        let root;
+        function test(content) {
+            root = React.render(content, div);
+        }
+
+        test(<Comp />);
+        expect(div.firstChild.style.color).toBe('red');
+        index = 0
+
+        test(<Comp />);
+        expect(div.firstChild.style.color).toBe('');
+    });
+
+    it('子组件的DOM节点改变了,会同步父节点的DOM', async () => {
+        var s, s2
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+            }
+            render() {
+                return <A />
+            }
+        }
+        class A extends React.Component {
+            constructor(props) {
+                super(props);
+            }
+            render() {
+                return <B />
+            }
+        }
+        class B extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    value: '3333'
+                };
+            }
+            componentDidMount() {
+                s2 = this
+            }
+            render() {
+                return this.state.value ? <div>111</div> : <strong>3333</strong>
+            }
+        }
+        var s = React.render(<App />, div);
+        expect(s.mayInst.hostNode ).toBe(s2.mayInst.rendered.mayInfo.hostNode);
+        s2.setState({value: 0});//子组件改变后 父组件的ref跟着变动
+        // expect(s.updater._hostNode ).toBe(s2.updater._hostNode);
+        // expect(s.updater._hostNode.nodeName).toBe('STRONG');
+    })
+
+})

+ 341 - 0
test/may-dom/redux.spec.jsx

@@ -0,0 +1,341 @@
+
+import React from '../../src/May';
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM = React;
+// var ReactTestUtils = {
+//   renderIntoDocument: function (element) {
+//     var div = document.createElement("div");
+//     return React.render(element, div);
+//   }
+// };
+var Redux = require("redux");
+var ReactRedux = require("react-redux");
+
+describe("Redux", function () {
+	// this.timeout(200000);
+	// before(async () => {
+	//   await beforeHook();
+	// });
+	// after(async () => {
+	//   await afterHook(false);
+	// });
+	var body = document.body,
+		div;
+	beforeEach(function () {
+		div = document.createElement("div");
+		body.appendChild(div);
+	});
+	afterEach(function () {
+		body.removeChild(div);
+	});
+	it("Counter", async () => {
+		class Counter extends React.Component {
+			render() {
+				return (
+					<div>
+						<h1 ref="value">{this.props.value}</h1>
+						<button ref="a" onClick={this.props.onIncrement}>
+							+
+            </button>&nbsp;
+            <button ref="b" onClick={this.props.onDecrement}>
+							-
+            </button>
+					</div>
+				);
+			}
+		}
+
+		const reducer = (state = 0, action) => {
+			switch (action.type) {
+				case "INCREMENT":
+					return state + 1;
+				case "DECREMENT":
+					return state - 1;
+				default:
+					return state;
+			}
+		};
+
+		const store = Redux.createStore(reducer);
+		function onIncrement() {
+			store.dispatch({ type: "INCREMENT" });
+		}
+		function onDecrement() {
+			store.dispatch({ type: "DECREMENT" });
+		}
+
+		const render = () => {
+			return ReactDOM.render(
+				<Counter
+					value={store.getState()}
+					onIncrement={onIncrement}
+					onDecrement={onDecrement}
+				/>,
+				div
+			);
+		};
+
+		var s = render();
+		store.subscribe(render);
+		expect(s.refs.value.innerHTML).toBe("0");
+		expect(s.refs.a.tagName).toBe("BUTTON");
+		ReactTestUtils.Simulate.click(s.refs.a);
+
+		expect(s.refs.value.innerHTML).toBe("10");//1
+		// await browser
+		// 	.click(s.refs.a)
+		// 	.pause(100)
+		// 	.$apply();
+		// expect(s.refs.value.innerHTML).toBe("2");
+		// await browser
+		// 	.click(s.refs.b)
+		// 	.pause(100)
+		// 	.$apply();
+		// expect(s.refs.value.innerHTML).toBe("1");
+	});
+	/*it("TreeView", async () => {
+	 
+	  var idArr = ["tree1", "tree2", "tree3", "tree4", "tree5", "tree6", "tree7"];
+	  var combineReducers = Redux.combineReducers;
+	  var Provider = ReactRedux.Provider;
+	  var connect = ReactRedux.connect;
+	  var createStore = Redux.createStore;
+	  function generateTree() {
+		let tree = {
+		  0: {
+			id: 0,
+			counter: 0,
+			childIds: []
+		  }
+		};
+  
+		for (let i = 1; i < 3; i++) {
+		  let parentId = 0; //Math.floor(Math.pow(Math.random(), 2) * i)
+		  tree[i] = {
+			id: i,
+			counter: 0,
+			childIds: []
+		  };
+		  tree[parentId].childIds.push(i);
+		}
+  
+		return tree;
+	  }
+	  const INCREMENT = "INCREMENT";
+	  const CREATE_NODE = "CREATE_NODE";
+	  const DELETE_NODE = "DELETE_NODE";
+	  const ADD_CHILD = "ADD_CHILD";
+	  const REMOVE_CHILD = "REMOVE_CHILD";
+  
+	  const increment = nodeId => ({
+		type: INCREMENT,
+		nodeId
+	  });
+  
+	  let nextId = 0;
+	  const createNode = () => ({
+		type: CREATE_NODE,
+		nodeId: `new_${nextId++}`
+	  });
+  
+	  const deleteNode = nodeId => ({
+		type: DELETE_NODE,
+		nodeId
+	  });
+  
+	  const addChild = (nodeId, childId) => ({
+		type: ADD_CHILD,
+		nodeId,
+		childId
+	  });
+  
+	  const removeChild = (nodeId, childId) => ({
+		type: REMOVE_CHILD,
+		nodeId,
+		childId
+	  });
+	  var actions = {
+		increment,
+		createNode,
+		deleteNode,
+		addChild,
+		removeChild
+	  };
+	  class Node extends React.Component {
+		constructor(props) {
+		  super(props);
+		  this.handleIncrementClick = this.handleIncrementClick.bind(this);
+		  this.handleAddChildClick = this.handleAddChildClick.bind(this);
+		  this.handleRemoveClick = this.handleRemoveClick.bind(this);
+		  this.renderChild = this.renderChild.bind(this);
+		}
+		handleIncrementClick() {
+		  const { increment, id } = this.props;
+		  increment(id);
+		}
+  
+		handleAddChildClick(e) {
+		  e.preventDefault();
+  
+		  const { addChild, createNode, id } = this.props;
+		  const childId = createNode().nodeId;
+		  addChild(id, childId);
+		}
+  
+		handleRemoveClick(e) {
+		  e.preventDefault();
+  
+		  const { removeChild, deleteNode, parentId, id } = this.props;
+		  removeChild(parentId, id);
+		  deleteNode(id);
+		}
+  
+		renderChild(childId) {
+		  const { id } = this.props;
+		  return (
+			<li key={childId}>
+			  <ConnectedNode id={childId} parentId={id} />
+			</li>
+		  );
+		}
+  
+		render() {
+		  const { counter, parentId, childIds } = this.props;
+		  return (
+			<div>
+			  Counter: {counter}{" "}
+			  <button onClick={this.handleIncrementClick}>+</button>{" "}
+			  {typeof parentId !== "undefined" && (
+				<a
+				  href="#"
+				  id={idArr.shift()}
+				  onClick={this.handleRemoveClick}
+				  className="remove"
+				  style={{ color: "lightgray", textDecoration: "none" }}
+				>
+				  ×
+				</a>
+			  )}
+			  <ul>
+				{childIds.map(this.renderChild)}
+				<li key="add">
+				  <a
+					href="#"
+					id={idArr.shift()}
+					onClick={this.handleAddChildClick}
+					className="add"
+				  >
+					Add child
+				  </a>
+				</li>
+			  </ul>
+			</div>
+		  );
+		}
+	  }
+  
+	  function mapStateToProps(state, ownProps) {
+		return state[ownProps.id];
+	  }
+  
+	  const ConnectedNode = connect(mapStateToProps, actions)(Node);
+  
+	  //import { INCREMENT, ADD_CHILD, REMOVE_CHILD, CREATE_NODE, DELETE_NODE } from '../actions'
+  
+	  const childIds = (state, action) => {
+		switch (action.type) {
+		  case ADD_CHILD:
+			return [...state, action.childId];
+		  case REMOVE_CHILD:
+			return state.filter(id => id !== action.childId);
+		  default:
+			return state;
+		}
+	  };
+  
+	  const node = (state, action) => {
+		switch (action.type) {
+		  case CREATE_NODE:
+			return {
+			  id: action.nodeId,
+			  counter: 0,
+			  childIds: []
+			};
+		  case INCREMENT:
+			return {
+			  ...state,
+			  counter: state.counter + 1
+			};
+		  case ADD_CHILD:
+		  case REMOVE_CHILD:
+			return {
+			  ...state,
+			  childIds: childIds(state.childIds, action)
+			};
+		  default:
+			return state;
+		}
+	  };
+  
+	  const getAllDescendantIds = (state, nodeId) =>
+		state[nodeId].childIds.reduce(
+		  (acc, childId) => [
+			...acc,
+			childId,
+			...getAllDescendantIds(state, childId)
+		  ],
+		  []
+		);
+  
+	  const deleteMany = (state, ids) => {
+		state = { ...state };
+		ids.forEach(id => delete state[id]);
+		return state;
+	  };
+  
+	  function reducers(state = {}, action) {
+		const { nodeId } = action;
+		if (typeof nodeId === "undefined") {
+		  return state;
+		}
+  
+		if (action.type === DELETE_NODE) {
+		  const descendantIds = getAllDescendantIds(state, nodeId);
+		  return deleteMany(state, [nodeId, ...descendantIds]);
+		}
+  
+		return {
+		  ...state,
+		  [nodeId]: node(state[nodeId], action)
+		};
+	  }
+  
+	  const tree = generateTree();
+  
+	  const store = createStore(reducers, tree);
+	  var s = ReactDOM.render(
+		<Provider store={store}>
+		  <ConnectedNode id={0} />
+		</Provider>,
+		div
+	  );
+	//   var ass = div.getElementsByTagName("a");
+	//   expect(ass.length).toBe(5);
+	//   var el = ass[1];
+  
+	//   await browser
+	// 	.click(el)
+	// 	.pause(200)
+	// 	.$apply();
+  
+	//   expect(ass.length).toBe(el.className === "add" ? 7 : 5);
+	//   await browser
+	// 	.click(ass[4])
+	// 	.pause(100)
+	// 	.$apply();
+  
+	//   expect(ass.length).toBe(5);
+	});*/
+});

+ 721 - 0
test/may-dom/ref.spec.jsx

@@ -0,0 +1,721 @@
+
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+    render: render
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+//https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
+// var ReactDOM = window.ReactDOM || React;
+
+describe("ref", function () {
+    // this.timeout(200000);
+
+    var body = document.body,
+        div;
+    beforeEach(function () {
+        div = document.createElement("div");
+        body.appendChild(div);
+    });
+    afterEach(function () {
+        body.removeChild(div);
+    });
+    it("patchRef", function () {
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.handleClick = this
+                    .handleClick
+                    .bind(this);
+
+            }
+            handleClick() {
+                // Explicitly focus the text input using the raw DOM API.
+                if (this.myTextInput !== null) {
+                    this
+                        .myTextInput
+                        .focus();
+                }
+            }
+            render() {
+                return (
+                    <div>
+                        <input type="text" ref={(ref) => this.myTextInput = ref} />
+                        <input
+                            ref='a'
+                            type="button"
+                            value="Focus the text input"
+                            onClick={this.handleClick} />
+                    </div>
+                );
+            }
+        }
+
+        var s = ReactDOM.render(<App />, div);
+
+        var dom = s.refs.a;
+        ReactTestUtils.Simulate.click(dom);
+        expect(document.activeElement).toBe(s.myTextInput);
+        expect(s.myTextInput).toBeDefined();
+
+    });
+
+    it("patchRef Component", function() {
+
+        class App extends React.Component {
+            render() {
+                return <div title='1'><A ref='a' /></div>;
+            }
+        }
+        var index = 1;
+        class A extends React.Component {
+            componentWillReceiveProps() {
+                index = 0;
+                this.forceUpdate();
+            }
+            render() {
+                return index
+                    ? <strong>111</strong>
+                    : <em>111</em>;
+            }
+        }
+
+        var s = ReactTestUtils.renderIntoDocument(<App />);
+     
+        expect(s.refs.a instanceof A).toBe(true);
+
+    });
+
+    it("没有组件的情况", function() {
+        var index = 0;
+        function ref(a) {
+            index ++;
+            expect(a.tagName).toBe("DIV");
+        }
+
+        var s = ReactTestUtils.renderIntoDocument(<div ref={ref}></div>);
+        expect(index).toBe(1);
+
+    });
+    it("should invoke refs in Component.render()", function() {
+        var i = 0;
+        let outer = function (a) {
+            expect(a).toBe(div.firstChild);
+            i++;
+        };
+        let inner = function (a) {
+            expect(a).toBe(div.firstChild.firstChild);
+            i++;
+        };
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div ref={outer}>
+                        <span ref={inner} />
+                    </div>
+                );
+            }
+        }
+        ReactDOM.render(<Foo />, div);
+      
+
+        expect(i).toBe(2);
+    });
+    it("rener方法在存在其他组件,那么组件以innerHTML方式引用子节点,子节点有ref", function() {
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    tip: "g-up-tips",
+                    text: "xxxx"
+                };
+
+            }
+
+            render() {
+                return <div ref='parent' className='parent'>
+                    <Child ref='child'>
+                        <span ref="inner" className="inner">child</span>
+                    </Child>
+                </div>;
+            }
+        }
+        class Child extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {};
+            }
+
+            render() {
+                return <div className='child'>{this.props.children}</div>;
+            }
+        }
+        var s = ReactTestUtils.renderIntoDocument(<App />);
+      
+        expect(Object.keys(s.refs).sort()).toEqual(["child", "inner", "parent"]);
+
+    });
+    it("用户在构造器里生成虚拟DOM", function() {
+        var a;
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.sliderLeftJSX = this.renderSlider("btnLeft");
+                this.state = {};
+            }
+            renderSlider(which = "btnLeft") {
+                return (<span ref={dom => {
+                    this[which] = dom;
+                }} />);
+            }
+            componentDidMount() {
+                a = !!this.btnLeft;
+            }
+
+            render() {
+                return (
+                    <div >
+                        <div className="track" ref="track">
+                            {this.sliderLeftJSX}
+                        </div>
+                    </div>
+                );
+            }
+        }
+        var s = ReactTestUtils.renderIntoDocument(<App/>);
+    
+
+        expect(a).toBe(true);
+
+    });
+
+    it("Stateless组件也会被执行", function() {
+        var b;
+        function App() {
+            return (
+                <div >StateLess</div>
+            );
+        }
+        function refFn(a){
+            b = a;
+        }
+        ReactTestUtils.renderIntoDocument(<App ref={refFn} />);
+     
+        expect(b).toBe(null);
+
+    });
+    it("ReactDOM.render中的元素也会被执行", function() {
+        var b;
+        function refFn(a){
+            b = a;
+        }
+        ReactTestUtils.renderIntoDocument(<h1 ref={refFn}/>);
+
+
+        expect(b && b.tagName).toBe("H1");
+
+    });
+    it("带ref的组件被子组件cloneElement", function() {
+        class Select extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 1
+                };
+            }
+            render() {
+                return React.createElement(SelectTrigger, {
+                    ref: "trigger"
+                }, React.createElement(
+                    "div",
+                    {
+                        ref: "root"
+                    }, "xxxx"));
+            }
+        }
+        class SelectTrigger extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 2
+                };
+            }
+            render() {
+                return React.createElement(Trigger, Object.assign({ title: "xxx" }, this.props), this.props.children);
+            }
+        }
+        class Trigger extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    aaa: 2
+                };
+            }
+            render() {
+                var props = this.props;
+                var children = props.children;
+                var child = React.Children.only(children);
+                return React.cloneElement(child, { className: "5555" });
+            }
+        }
+        var s = React.render(<Select />, div);
+     
+        expect(typeof s.refs.root).toBe("object");
+        expect(typeof s.refs.trigger).toBe("object");
+    });
+    it("相同位置上的元素节点的string ref值不一样", function() {
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {this.props.a ? <span ref="aaa" className="aaa"/>: <span ref="bbb" className="bbb" />}
+                    </div>
+                );
+            }
+        }
+        var s = ReactDOM.render(<Foo a={1}/>,  div);
+        expect(s.refs.aaa.className).toBe("aaa");
+        ReactDOM.render(<Foo a={0}/>, div);
+        expect(s.refs.aaa).toBe(undefined);
+        expect(s.refs.bbb.className).toBe("bbb");
+    });
+    it("相同位置上的元素节点的ref类型不一样", function() {
+        var a = 1;
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {this.props.a ? <span ref="aaa" className="aaa"/>: <span ref={function(b){
+                            a = b;
+                        }} className="bbb" />}
+                    </div>
+                );
+            }
+        }
+        var s = ReactDOM.render(<Foo a={1}/>,  div);
+        expect(s.refs.aaa.className).toBe("aaa");
+        ReactDOM.render(<Foo a={0}/>, div);
+        expect(s.refs.aaa).toBe(undefined);
+        expect(typeof a).toBe("object");
+    });
+    it("为元素添加ref", function() {
+       
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {this.props.a ? <span  className="aaa"/>: <span ref="aaa" className="bbb" />}
+                    </div>
+                );
+            }
+        }
+        var s = ReactDOM.render(<Foo a={1}/>,  div);
+        expect(s.refs).toEqual({});
+        ReactDOM.render(<Foo a={0}/>, div);
+        expect(typeof s.refs.aaa).toBe("object");
+    });
+    it("相同位置上的元素节点的ref函数不一样", function() {
+        var log = [];
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {this.props.a ? <span ref={function(a){
+                            log.push(a);
+                        }} className="aaa"/>: <b ref={function(a){
+                            log.push(a);
+                        }} className="bbb" />}
+                    </div>
+                );
+            }
+        }
+        ReactDOM.render(<Foo a={1}/>,  div);
+     
+        ReactDOM.render(<Foo a={0}/>, div);
+     
+        expect(log.length).toBe(3);
+        expect(log[1]).toBe(null);
+        expect(log[0].nodeName).toBe("SPAN");
+        expect(log[2].nodeName).toBe("B");
+    });
+    it("相同位置上的元素节点的ref函数一样", function() {
+        var log = [];
+        function refFn(a){
+            log.push(a);
+        }
+        class Foo extends React.Component {
+            render() {
+                return (
+                    <div>
+                        {this.props.a ? <span ref={refFn} className="aaa"/>: <b ref={refFn} className="bbb" />}
+                    </div>
+                );
+            }
+        }
+        ReactDOM.render(<Foo a={1}/>,  div);
+     
+        ReactDOM.render(<Foo a={0}/>, div);
+     
+        expect(log.length).toBe(3);
+        expect(log[1]).toBe(null);
+        expect(log[0].nodeName).toBe("SPAN");
+        expect(log[2].nodeName).toBe("B");
+    });
+    it("组件虚拟DOM的ref总在componentDidMount/Update后才执行", function() {
+        var list = [];
+        class Static extends React.Component {
+            componentDidMount(){
+                list.push("static did mount");
+            }
+            componentWillUpdate(){
+                list.push("static will update");
+            }
+            componentDidUpdate(){
+                list.push("static did update");
+            }
+            render() {
+                list.push("static render");
+                return <div>{this.props.children}</div>;
+            }
+        }
+
+        class Component extends React.Component {
+            render() {
+                if (this.props.flipped) {
+                    return (
+                        <div>
+                            <Static ref={function(b){
+                                list.push(b === null ? "null 222":"instance 222");
+                            }}>
+                           B
+                            </Static>
+                      
+                        </div>
+                    );
+                } else {
+                    return (
+                        <div>
+                            <Static ref={function(a){
+                                list.push(a === null ? "null 111":"instance 111");
+                            }} >
+                            A
+                            </Static>
+                        </div>
+                    );
+                }
+            }
+        }
+
+        var container = document.createElement("div");
+        ReactDOM.render(<Component flipped={false} />, container);
+   
+        ReactDOM.render(<Component flipped={true} />, container);
+        expect(list).toEqual([
+            "static render",
+            "static did mount",
+            "instance 111",
+            "null 111",
+            "static will update",
+            "static render",
+            "static did update",
+            "instance 222",
+        ]);
+
+    });
+    it("先执行匹配元素的detach ref,然后卸载组件,最后attach ref",function(){
+        var list = [];
+        class A extends React.Component{
+            componentWillUnmount(){
+                list.push("remove");
+            }
+            render(){
+                return <span>A</span>;
+            }
+        }
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    a: 1
+                };
+            }
+            render() {
+                return <div>
+                    {
+                        this.state.a ? 
+                            [<A />,<A />,<A />,<span key="a" ref={(a)=>{
+                                list.push(111+  (a ? "instance": "null"));
+                            }}>a</span>,<span key="b" ref={(a)=>{
+                                list.push(222 + (a ? "instance": "null"));
+                                
+                            }}>b</span>]:
+                            [<span key="b" ref={(a)=>{
+                                list.push(333 + (a ? "instance": "null"));
+                                
+                            }}>b</span>,<span key="a" ref={(a)=>{
+                                list.push(444 + (a ? "instance": "null"));
+                            }}>a</span>]
+                    }
+                </div>;
+            }
+        }
+        var s = ReactDOM.render(<App />, div);
+        s.setState({a: 0});
+        expect(list).toEqual([
+            "111instance",
+            "222instance",
+            "222null",
+            "111null",
+            "remove",
+            "remove",
+            "remove",
+            "333instance",
+            "444instance"
+        ]);
+    
+    });
+    it("先执行匹配元素的detach ref,然后更新卸载组件,最后attach ref",function(){
+        var list = [];
+        class A extends React.Component{
+            componentWillUnmount(){
+                list.push("A remove");
+            }
+            componentWillUpdate(){
+                list.push("A update");
+            }
+            render(){
+                return <span>A</span>;
+            }
+        }
+        class B extends React.Component{
+            componentDidMount(){
+                list.push("B mount");
+            }
+            render(){
+                return <span>B</span>;
+            }
+        }
+        var list = [];
+        class App extends React.Component {
+            constructor(props) {
+                super(props);
+                this.state = {
+                    a: 1
+                };
+            }
+            render() {
+                return <div>
+                    {
+                        this.state.a ? 
+                            [<A />,<A />,<A />,
+                                <span key="a" ref={(a)=>{
+                                    list.push(111+(a?"instance":"null"));
+                                }}>a</span>,
+                                <span key="b" ref={(a)=>{
+                                    list.push(222+(a?"instance":"null"));    
+                                }}>b</span>,
+                                <span key="c" ref={(a)=>{
+                                    list.push(333+(a?"instance":"null"));
+                                }}>c</span>,
+                            ]:
+                            [
+                                <span key="b" ref={(a)=>{
+                                    list.push(444+(a?"instance":"null"));
+                                }}>b</span>,
+                                <span key="a" ref={(a)=>{
+                                    list.push(555+(a?"instance":"null"));
+                                }}>a</span>,<A />, <B />]
+                    }
+                </div>;
+            }
+        }
+        var s = ReactDOM.render(<App />, div);
+        s.setState({a: 0});
+        expect(list).toEqual([
+            "111instance",
+            "222instance",
+            "333instance",
+            "222null",
+            "111null",
+            "A update",
+            "A remove",
+            "A remove",
+            "333null",
+            "444instance",
+            "555instance",
+            "B mount"
+        ]);
+    
+    });
+    it("ref与生命周期的执行顺序,更新后没有key",function(){
+        var list = [];
+            
+        var A = class extends React.Component {
+            componentWillMount() {
+                list.push(this.props.name + " componentWillMount");
+            }
+            render() {
+                return <div />;
+            }
+            componentDidMount() {
+                list.push(this.props.name + " componentDidMount");
+            }
+            componentDidUpdate() {
+                list.push(this.props.name + " componentDidUpdate");
+            }
+            componentWillUnmount() {
+                list.push(this.props.name + " componentWillUnmount");
+            }
+        };
+        var B = class extends React.Component {
+            componentWillMount() {
+                list.push(this.props.name + " componentWillMount");
+            }
+            render() {
+                return <strong />;
+            }
+            componentDidMount() {
+                list.push(this.props.name + " componentDidMount");
+            }
+            componentWillUnmount() {
+                list.push(this.props.name + " componentWillUnmount");
+            }
+        };
+        ReactDOM.render(
+            <div>
+                <A key="aa" name="a" ref={(a)=>{
+                    list.push("a "+!!a)
+                    ;
+                }}></A>
+                <A key="bb" name="b" ref={(a)=>{
+                    list.push("b "+!!a)
+                    ;
+                }}></A>
+            </div>,
+            div
+        );
+        list.push("update...");
+        ReactDOM.render(
+            <div>
+                <B  name="c" ref={(a)=>{
+                    list.push("c "+!!a);
+                }}></B>
+                <B  name="d" ref={(a)=>{
+                    list.push("d "+!!a)
+                    ;
+                }}></B>
+            </div>,
+            div
+        );
+        expect(list).toEqual([
+            "a componentWillMount",
+            "b componentWillMount",
+            "a componentDidMount",
+            "a true",
+            "b componentDidMount",
+            "b true",
+            "update...",
+            "c componentWillMount",
+            "d componentWillMount",
+            "a false",
+            "a componentWillUnmount",
+            "b false",
+            "b componentWillUnmount",
+            "c componentDidMount",
+            "c true",
+            "d componentDidMount",
+            "d true"
+        ]);
+    });
+    it("ref与生命周期的执行顺序,更新后有key",function(){
+        var list = [];
+            
+        var A = class extends React.Component {
+            componentWillMount() {
+                list.push(this.props.name + " componentWillMount");
+            }
+            render() {
+                return <div />;
+            }
+            componentDidMount() {
+                list.push(this.props.name + " componentDidMount");
+            }
+            componentDidUpdate() {
+                list.push(this.props.name + " componentDidUpdate");
+            }
+            componentWillUnmount() {
+                list.push(this.props.name + " componentWillUnmount");
+            }
+        };
+        var B = class extends React.Component {
+            componentWillMount() {
+                list.push(this.props.name + " componentWillMount");
+            }
+            render() {
+                return <strong />;
+            }
+            componentDidMount() {
+                list.push(this.props.name + " componentDidMount");
+            }
+            componentWillUnmount() {
+                list.push(this.props.name + " componentWillUnmount");
+            }
+        };
+        ReactDOM.render(
+            <div>
+                <A key="aa" name="a" ref={(a)=>{
+                    list.push("a "+!!a)
+                    ;
+                }}></A>
+                <A key="bb" name="b" ref={(a)=>{
+                    list.push("b "+!!a)
+                    ;
+                }}></A>
+            </div>,
+            div
+        );
+        list.push("update...");
+        ReactDOM.render(
+            <div>
+                <B key="aa" name="c" ref={(a)=>{
+                    list.push("c "+!!a);
+                }}></B>
+                <B key="bb" name="d" ref={(a)=>{
+                    list.push("d "+!!a)
+                    ;
+                }}></B>
+            </div>,
+            div
+        );
+        // "update...", "c componentWillMount", "d componentWillMount", "a false", 
+        //"a componentWillUnmount", "b false", "b componentWillUnmount", 
+        // expect(list).toEqual([
+        //     "a componentWillMount",
+        //     "b componentWillMount",
+        //     "a componentDidMount",
+        //     "a true",
+        //     "b componentDidMount",
+        //     "b true",
+        //     "update...",
+        //     "a false",
+        //     "a componentWillUnmount",
+        //     "c componentWillMount",
+        //     "b false",
+        //     "b componentWillUnmount",
+        //     "d componentWillMount",
+        //     "c componentDidMount",
+        //     "c true",
+        //     "d componentDidMount",
+        //     "d true"
+        // ]);
+    });
+});

+ 140 - 0
test/may-dom/refs-destruction-test.jsx

@@ -0,0 +1,140 @@
+// import {
+//   beforeHook,
+//   afterHook,
+//   browser
+// } from "karma-event-driver-ext/cjs/event-driver-hooks";
+// import getTestDocument from "./getTestDocument";
+// import ReactTestUtils from "lib/ReactTestUtils";
+
+
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render,unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render,
+	unmountComponentAtNode:unmountComponentAtNode
+}
+React.render = render;
+
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM=React;
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+//https://github.com/facebook/react/blob/master/src/renderers/dom/test/__tests__/ReactTestUtils-test.js
+
+describe("refs-destruction", function() {
+  // this.timeout(200000);
+  // before(async () => {
+  //   await beforeHook();
+  // });
+  // after(async () => {
+  //   await afterHook(false);
+  // });
+
+  /**
+ * Counts clicks and has a renders an item for each click. Each item rendered
+ * has a ref of the form "clickLogN".
+ */
+ var TestComponent = class extends React.Component {
+    render() {
+      return (
+        <div>
+          {this.props.destroy
+            ? null
+            : <div ref="theInnerDiv">
+                Lets try to destroy this.
+              </div>}
+        </div>
+      );
+    }
+  };
+
+  it('should remove refs when destroying the parent', () => {
+    var container = document.createElement('div');
+    var testInstance = ReactDOM.render(<TestComponent />, container);
+    expect(ReactTestUtils.isDOMComponent(testInstance.refs.theInnerDiv)).toBe(
+      true,
+    );
+    expect(
+      Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
+        .length,
+    ).toEqual(1);
+    ReactDOM.unmountComponentAtNode(container);
+    expect(
+      Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
+        .length,
+    ).toEqual(0);
+  });
+
+  it('should remove refs when destroying the child', () => {
+    var container = document.createElement('div');
+    var testInstance = ReactDOM.render(<TestComponent />, container);
+    expect(testInstance.refs.theInnerDiv.tagName).toBe(
+      "DIV",
+    );
+    expect(
+      Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
+        .length,
+    ).toBe(1);
+    ReactDOM.render(<TestComponent destroy={true} />, container);
+    expect(
+      Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
+        .length,
+    ).toBe(0);
+  });
+
+  it('should not error when destroying child with ref asynchronously', () => {
+    class Modal extends React.Component {
+      componentDidMount() {
+        this.div = document.createElement('div');
+        document.body.appendChild(this.div);
+        this.componentDidUpdate();
+      }
+
+      componentDidUpdate() {
+        ReactDOM.render(<div>{this.props.children}</div>, this.div);
+      }
+
+      componentWillUnmount() {
+        var self = this;
+        // some async animation
+        setTimeout(function() {
+          expect(function() {
+            ReactDOM.unmountComponentAtNode(self.div);
+          }).not.toThrow();
+          document.body.removeChild(self.div);
+        }, 0);
+      }
+
+      render() {
+        return null;
+      }
+    }
+
+    class AppModal extends React.Component {
+      render() {
+        return (
+          <Modal>
+            <a ref="ref" />
+          </Modal>
+        );
+      }
+    }
+
+    class App extends React.Component {
+      render() {
+        return this.props.hidden ? null : <AppModal onClose={this.close} />;
+      }
+    }
+
+    var container = document.createElement('div');
+    ReactDOM.render(<App />, container);
+    ReactDOM.render(<App hidden={true} />, container);
+  });
+
+ 
+
+})

+ 180 - 0
test/may-dom/refs-test.jsx

@@ -0,0 +1,180 @@
+
+import ReactTestUtils from "../../lib/ReactTestUtils";
+
+import React from '../../src/May';
+import { render, unmountComponentAtNode } from '../../src/may-dom/MayDom'
+var ReactDOM = {
+	render: render,
+	unmountComponentAtNode: unmountComponentAtNode
+}
+React.render = render;
+
+// import React from "../../dist/ReactANU";
+// var ReactDOM=React;
+// var React = require('react');//hyphenate
+// var ReactDOM = require('react-dom');
+
+//https://github.com/facebook/react/blob/master/src/renderers/dom/test/__tests__/ReactTestUtils-test.js
+// var ReactDOM = window.ReactDOM || React;
+
+
+
+describe("reactiverefs", function () {
+	// this.timeout(200000);
+	// before(async () => {
+	//   await beforeHook();
+	// });
+	// after(async () => {
+	//   await afterHook(false);
+	// });
+	var body = document.body,
+		div;
+	beforeEach(function () {
+		div = document.createElement("div");
+		body.appendChild(div);
+	});
+	afterEach(function () {
+		body.removeChild(div);
+	});
+	/**
+   * Counts clicks and has a renders an item for each click. Each item rendered
+   * has a ref of the form "clickLogN".
+   */
+	class ClickCounter extends React.Component {
+		state = { count: this.props.initialCount };
+
+		triggerReset = () => {
+			this.setState({ count: this.props.initialCount });
+		};
+
+		handleClick = () => {
+			this.setState({ count: this.state.count + 1 });
+		};
+
+		render() {
+			var children = [];
+			var i;
+			for (i = 0; i < this.state.count; i++) {
+				children.push(
+					<div
+						className="clickLogDiv"
+						key={'clickLog' + i}
+						ref={'clickLog' + i}
+					/>,
+				);
+			}
+			return (
+				<span className="clickIncrementer" onClick={this.handleClick}>
+					{children}
+				</span>
+			);
+		}
+	}
+
+	/**
+	 * Only purpose is to test that refs are tracked even when applied to a
+	 * component that is injected down several layers. Ref systems are difficult to
+	 * build in such a way that ownership is maintained in an airtight manner.
+	 */
+	class GeneralContainerComponent extends React.Component {
+		render() {
+			return <div>{this.props.children}</div>;
+		}
+	}
+
+	/**
+	 * Notice how refs ownership is maintained even when injecting a component
+	 * into a different parent.
+	 */
+	class TestRefsComponent extends React.Component {
+		doReset = () => {
+			this.refs.myCounter.triggerReset();
+		};
+
+		render() {
+			return (
+				<div>
+					<div ref="resetDiv" onClick={this.doReset}>
+						Reset Me By Clicking This.
+        </div>
+					<GeneralContainerComponent ref="myContainer">
+						<ClickCounter ref="myCounter" initialCount={1} />
+					</GeneralContainerComponent>
+				</div>
+			);
+		}
+	}
+
+	/**
+	 * Render a TestRefsComponent and ensure that the main refs are wired up.
+	 */
+	var renderTestRefsComponent = function () {
+		var testRefsComponent = ReactDOM.render(
+			<TestRefsComponent />,div
+		);
+		expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
+
+		var generalContainer = testRefsComponent.refs.myContainer;
+		expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
+
+		var counter = testRefsComponent.refs.myCounter;
+		expect(counter instanceof ClickCounter).toBe(true);
+
+		return testRefsComponent;
+	};
+
+	var expectClickLogsLengthToBe = function (instance, length) {
+		var clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+			instance,
+			'clickLogDiv',
+		);
+		expect(clickLogs.length).toBe(length);
+		expect(Object.keys(instance.refs.myCounter.refs).length).toBe(length);
+	};
+
+	it('Should increase refs with an increase in divs', () => {
+		var testRefsComponent = renderTestRefsComponent();
+		var clickIncrementer = ReactTestUtils.findRenderedDOMComponentWithClass(
+			testRefsComponent,
+			'clickIncrementer',
+		);
+
+		expectClickLogsLengthToBe(testRefsComponent, 1);
+
+		// // After clicking the reset, there should still only be one click log ref.
+		// ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
+		// expectClickLogsLengthToBe(testRefsComponent, 1);
+
+		// // Begin incrementing clicks (and therefore refs).
+		// ReactTestUtils.Simulate.click(clickIncrementer);
+		// expectClickLogsLengthToBe(testRefsComponent, 2);
+
+		// ReactTestUtils.Simulate.click(clickIncrementer);
+		// expectClickLogsLengthToBe(testRefsComponent, 3);
+
+		// // Now reset again
+		// ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
+		// expectClickLogsLengthToBe(testRefsComponent, 1);
+	});
+
+	describe('factory components', () => {
+		it('Should correctly get the ref', () => {
+			function Comp() {
+				return <div ref="elemRef" />;
+			}
+
+			const inst = ReactTestUtils.renderIntoDocument(<Comp />);
+			expect(inst.refs.elemRef.tagName).toBe('DIV');
+		});
+	});
+
+	it('coerces numbers to strings', () => {
+		class A extends React.Component {
+			render() {
+				return <div ref={1} />;
+			}
+		}
+		const a = ReactTestUtils.renderIntoDocument(<A />);
+		expect(a.refs[1].nodeName).toBe('DIV');
+	});
+})

+ 41 - 0
test/may-dom/shallow.spec.js

@@ -0,0 +1,41 @@
+import {
+    shallowEqual
+} from '../../src/PureComponent'
+
+describe('shallowEqual', function () {
+
+    it('shallowEqual', async () => {
+        expect(shallowEqual(1, 1)).toBe(true)
+        // expect(shallowEqual(-0, +0)).toBe(false)
+        expect(shallowEqual(NaN, NaN)).toBe(true)
+        expect(shallowEqual({}, {})).toBe(true)
+        // expect(shallowEqual({
+        //     a: {}
+        // }, {
+        //     a: {}
+        // })).toBe(false)
+        expect(shallowEqual({
+            a: 1,
+            b: 2
+        }, {
+            a: 1,
+            b: 2,
+            c: 3,
+            d: 4
+        })).toBe(false)
+        var b = {}
+        expect(shallowEqual({
+            a: b
+        }, {
+            a: b
+        })).toBe(true)
+
+        expect(shallowEqual([
+            1, 2, 3
+        ], [4, 5, 6])).toBe(false)
+
+        expect(shallowEqual([
+            1, 2, 3
+        ], [1, 2, 3])).toBe(true)
+    })
+})

+ 122 - 0
test/may-dom/style.spec.js

@@ -0,0 +1,122 @@
+/*const cssSuffix = {
+    //需要加后缀如 px s(秒)等css属性摘出来
+    //其实用正则更简洁一些,不过可能 可读性可维护性不如key value
+    //动画属性(Animation)
+    animationDelay: 's',
+    //CSS 边框属性(Border 和 Outline)
+    borderBottomWidth: 'px',
+    borderLeftWidth: 'px',
+    borderRightWidth: 'px',
+    borderTopWidth: 'px',
+    borderWidth: 'px',
+    outlineWidth: 'px',
+    borderBottomLeftRadius: 'px',
+    borderBottomRightRadius: 'px',
+    borderRadius: 'px',
+    borderTopLeftRadius: 'px',
+    borderTopRightRadius: 'px',
+    //Box 属性
+    rotation: 'deg',
+    //CSS 尺寸属性(Dimension)
+    height: 'px',
+    maxHeight: 'px',
+    maxWidth: 'px',
+    minHeight: 'px',
+    minWidth: 'px',
+    width: 'px',
+    //CSS 字体属性(Font) font-variant:small-caps; 段落设置为小型大写字母字体
+    fontSize: 'px',
+    //CSS 外边距属性(Margin)
+    margin: 'px',
+    marginLeft: 'px',
+    marginRight: 'px',
+    marginTop: 'px',
+    marginBottom: 'px',
+    //多列属性(Multi-column)
+    columnGap: 'px',
+    WebkitColumnGap: 'px',
+    MozColumnGap: 'px',
+    columnRuleWidth: 'px',
+    WebkitColumnRuleWidth: 'px',
+    MozColumnRuleWidth: 'px',
+    columnWidth: 'px',
+    WebkitColumnWidth: 'px',
+    MozColumnWidth: 'px',
+    //CSS 内边距属性(Padding)
+    padding: 'px',
+    paddingLeft: 'px',
+    paddingRight: 'px',
+    paddingTop: 'px',
+    paddingBottom: 'px',
+    //CSS 定位属性(Positioning)
+    left: 'px',
+    right: 'px',
+    top: 'px',
+    bottom: 'px',
+    //CSS 文本属性(Text)
+    letterSpacing: 'px',
+    lineHeight: 'px'
+}
+function patchStyle(dom, prevStyle, newStyle) {
+    var _style = '';
+    for (var name in newStyle) {
+        var _type = typeof newStyle[name];
+        //backgroundColor 替换为 background-color Webkit替换为-webkit-   ms单独替换一次
+        var cssName = name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^ms-/i, '-ms-');
+        switch (_type) {
+            case 'string':
+                _style = newStyle[name].trim();
+                break;
+            case 'number':
+                _style = newStyle[name];
+                if (cssSuffix[name]) {
+                    _style += newStyle[name] !== 0 ? +cssSuffix[name] : '';
+                }
+                break;
+            case 'boolean':
+                _style = '';
+                break;
+            default:
+                _style = newStyle[name]
+                break;
+        }
+        dom.style[cssName] = _style;
+    }
+    for (var key in prevStyle) {
+        if (!(key in newStyle)) {
+            key = name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^ms-/i, '-ms-');
+            dom.style[key] = '';
+        }
+    }
+}
+describe("style", function () {
+    it("patchStyle", function () {
+        var dom = document.createElement('div');
+        var sameStyle = {};
+        patchStyle(dom, sameStyle, sameStyle);
+        expect(dom.style).toEqual({});
+
+        dom.style.color = "red";
+        patchStyle(dom, {
+            color: "red"
+        }, { color: "green" });
+        expect(dom.style).toEqual({ color: "green" });
+
+        patchStyle(dom, {
+            color: "red"
+        }, { color: null });
+        expect(dom.style).toEqual({ color: "" });
+
+        patchStyle(dom, {
+            color: "red"
+        }, { color: false });
+        expect(dom.style).toEqual({ color: "" });
+        dom.style.backgroundColor = "black";
+        patchStyle(dom, dom.style, {});
+        expect(dom.style).toEqual({ backgroundColor: "", color: "" });
+    });
+    // it("cssName", function () {
+    //     expect(cssName("xxx")).toBe(null);
+    // });
+
+});*/

+ 137 - 0
test/may-dom/svg.spec.jsx

@@ -0,0 +1,137 @@
+
+import React from '../../src/May'
+var ReactDOM = React;
+
+describe('SVG元素', function () {
+    // this.timeout(200000);
+    // before(async () => {
+    //     await beforeHook()
+    // })
+    // after(async () => {
+    //     await afterHook(false)
+    // })
+    var body = document.body,
+        div
+    beforeEach(function () {
+        div = document.createElement('div')
+        body.appendChild(div)
+    })
+    afterEach(function () {
+        body.removeChild(div)
+    })
+    var rsvg = /^\[object SVG\w*Element\]$/
+    it('circle', async () => {
+
+        var s = ReactDOM.render(
+            <svg><circle cx='25' cy='25' r='20' fill='green' /></svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+    it('ellipse', async() => {
+
+        var s = ReactDOM.render(
+            <svg><ellipse cx='25' cy='25' rx='20' ry='10' fill='green'/></svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+
+    it('line', async() => {
+
+        var s = ReactDOM.render(
+            <svg><line x1='5' y1='5' x2='45' y2='45' stroke='green'/></svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+
+    it('path', async() => {
+
+        var s = ReactDOM.render(
+            <svg><path d='M5,5 C5,45 45,45 45,5' fill="none" stroke='red'/></svg>, div)
+
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+
+    it('polygon', async() => {
+
+        var s = ReactDOM.render(
+            <svg><polygon points='5,5 45,45 5,45 45,5' fill="none" stroke='red'/></svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+
+    it('polyline', async() => {
+
+        var s = ReactDOM.render(
+            <svg><polyline points='5,5 45,45 5,45 45,5' fill="none" stroke='red'/></svg>, div)
+  
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+    it('rect', async() => {
+
+        var s = ReactDOM.render(
+            <svg><rect
+            x='5'
+            y='5'
+            rx='5'
+            ry='5'
+            width='40'
+            height='40'
+            fill="green"
+            stroke='red'/></svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+    it('defs', async() => {
+
+        var s = ReactDOM.render(
+            <svg>
+            <defs>
+                <rect id='rect' style='fill:green' width='15' height='15'/>
+            </defs>
+            <use x='5' y='5' xlinkHref='#rect'/>
+            <use x='30' y='30' xlinkHref='#rect'/>
+        </svg>, div)
+
+        expect(rsvg.test(s.firstChild)).toBe(true)
+
+    })
+    //svg一般用的不会太复杂,先支持比较简单的
+    /*it('attribute throw error', async() => {
+
+        var a = {}
+        a.toString = function () {
+            throw "xxx"
+        }
+        var s = ReactDOM.render(React.createElement('div', {aaa: a}), div)
+
+        expect(s.getAttribute('aaa')).toBeNull()
+    })
+
+    it('use元素的xlinkHref', async() => {
+        function Test() {
+            return (
+                <svg className="icon-twitter" width="16px" height="16px">
+                    <use xlinkHref="#twitter" xlinkRole="#aaa" id='aaa'/>
+                </svg>
+            )
+        }
+
+        ReactDOM.render(
+            <Test/>, div)
+
+
+        var el = div.getElementsByTagName('use')
+        expect(el.length).toBe(1)
+        expect(el[0].getAttribute('xlink:href')).toBe('#twitter')
+        expect(el[0].getAttribute('xlink:role')).toBe('#aaa')
+    })*/
+
+})

+ 144 - 0
test/may-dom/util.spec.js

@@ -0,0 +1,144 @@
+ /*import {
+    extend,
+    inherit
+
+} from "../../src/util";
+
+describe("util", function () {
+
+   it('inherit', () => {
+        function A() {}
+        A.prototype = {
+            render: function () {
+                console.log('111')
+            },
+            setState: function(){}
+        }
+
+        function B() {
+
+        }
+        inherit(B, A)
+        var b = new B
+        expect(b).toInstanceOf(B)
+        expect(b).toInstanceOf(A)
+        expect(b.render).toBe(A.prototype.render)
+        expect(b.setState).toA('function')
+    })
+
+    it("oneObject", function () {
+
+        expect(oneObject("aa,bb,cc")).toEqual({
+            aa: 1,
+            bb: 1,
+            cc: 1
+        });
+        expect(oneObject("")).toEqual({});
+        expect(oneObject([1, 2, 3], false)).toEqual({
+            1: false,
+            2: false,
+            3: false
+        });
+    });
+    it("extend", function () {
+
+        expect(extend({}, {
+            a: 1,
+            b: 2
+        })).toEqual({
+            a: 1,
+            b: 2
+        });
+        expect(extend({
+            a: 1
+        }, null)).toEqual({
+            a: 1
+        });
+
+    });
+
+
+    it("isFn", function () {
+        expect(isFn("sss")).toBe(false);
+        expect(isFn(function a() {})).toBe(true);
+
+    });
+
+    it("isEventName", () => {
+        expect(isEventName("onaaa")).toBe(false);
+        expect(isEventName("onAaa")).toBe(true);
+        expect(isEventName("xxx")).toBe(false);
+    });
+
+    it("toLowerCase", () => {
+        expect(toLowerCase("onaaa")).toBe("onaaa");
+        expect(toLowerCase("onA")).toBe("ona");
+        expect(toLowerCase("onA")).toBe("ona");
+    });
+    it("inherit", () => {
+        function A() {}
+
+        function B() {}
+        inherit(A, B);
+        var a = new A;
+
+        expect(a instanceof A).toBe(true);
+        expect(a instanceof B).toBe(true);
+    });
+
+    it("camelize", function () {
+
+        expect(typeof camelize).toBe("function");
+        expect(camelize("aaa-bbb-ccc")).toBe("aaaBbbCcc");
+        expect(camelize("aaa_bbb_ccc")).toBe("aaaBbbCcc");
+        expect(camelize("-aaa-bbb-ccc")).toBe("aaaBbbCcc");
+        expect(camelize("_aaa_bbb_ccc")).toBe("aaaBbbCcc");
+        expect(camelize("")).toBe("");
+    });
+
+    it("firstLetterLower", function(){
+        expect(typeof firstLetterLower).toBe("function");
+        expect(firstLetterLower("WebkitBorderStart")).toBe("webkitBorderStart");
+        expect(firstLetterLower("")).toBe("");
+    });
+
+    it("toArray", () => {
+        var dom = {
+            childNodes: [{}, {}, {}]
+        };
+        expect(toArray(dom.childNodes).length).toBe(3);
+    });
+    it("getChildContext", () => {
+        var instance = {
+            getChildContext: function () {
+                return {
+                    a: 1
+                };
+            }
+        };
+        var b = getChildContext(instance, {
+            b: 4
+        });
+        expect(b).toEqual({
+            a: 1,
+            b: 4
+        });
+    });
+  
+
+    it("typeNumber", () => {
+        var A = function() {};
+        var a = new A;
+         
+        expect(typeNumber(void 2)).toBe(0);
+        expect(typeNumber(null)).toBe(1);
+        expect(typeNumber(false)).toBe(2);
+        expect(typeNumber(true)).toBe(2);
+        expect(typeNumber(1)).toBe(3);
+        expect(typeNumber("333")).toBe(4);
+        expect(typeNumber(A)).toBe(5);
+        expect(typeNumber([])).toBe(7);
+        expect(typeNumber(a)).toBe(8);
+    });
+      
+});*/