Match-id-6457906eabfeed181e7563bd49efe9db3c404a5c
This commit is contained in:
commit
77747e72ee
|
@ -1,3 +1,6 @@
|
|||
## 0.0.51 (2023-05-29)
|
||||
- **core**: 增加mouseenter和mouseleave事件代理机制
|
||||
|
||||
## 0.0.50 (2023-05-23)
|
||||
- **core**: 解决IE11不兼容Symbol问题
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"keywords": [
|
||||
"horizon"
|
||||
],
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.51",
|
||||
"homepage": "",
|
||||
"bugs": "",
|
||||
"main": "index.js",
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { Container } from './DOMOperator';
|
|||
import { isElement } from './utils/Common';
|
||||
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
||||
import { Callback } from '../renderer/UpdateHandler';
|
||||
import { listenSimulatedDelegatedEvents } from '../event/EventBinding';
|
||||
|
||||
function createRoot(children: any, container: Container, callback?: Callback) {
|
||||
// 清空容器
|
||||
|
@ -31,6 +32,7 @@ function createRoot(children: any, container: Container, callback?: Callback) {
|
|||
// 调度器创建根节点,并给容器dom赋vNode结构体
|
||||
const treeRoot = createTreeRootVNode(container);
|
||||
container._treeRoot = treeRoot;
|
||||
listenSimulatedDelegatedEvents(treeRoot);
|
||||
|
||||
// 执行回调
|
||||
if (typeof callback === 'function') {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
/**
|
||||
* 事件绑定实现,分为绑定委托事件和非委托事件
|
||||
*/
|
||||
import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventHub';
|
||||
import { allDelegatedHorizonEvents, simulatedDelegatedEvents } from './EventHub';
|
||||
import { isDocument } from '../dom/utils/Common';
|
||||
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
||||
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
||||
|
@ -24,8 +24,6 @@ import { handleEventMain } from './HorizonEventMain';
|
|||
import { decorateNativeEvent } from './EventWrapper';
|
||||
import { VNode } from '../renderer/vnode/VNode';
|
||||
|
||||
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
|
||||
|
||||
// 触发委托事件
|
||||
function triggerDelegatedEvent(
|
||||
nativeEvtName: string,
|
||||
|
@ -64,6 +62,13 @@ function isCaptureEvent(horizonEventName) {
|
|||
return horizonEventName.slice(-7) === 'Capture';
|
||||
}
|
||||
|
||||
// 利用冒泡事件模拟不冒泡事件,需要直接在根节点绑定
|
||||
export function listenSimulatedDelegatedEvents(root: VNode) {
|
||||
for (let i = 0; i < simulatedDelegatedEvents.length; i++) {
|
||||
lazyDelegateOnRoot(root, simulatedDelegatedEvents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||
currentRoot.delegatedEvents.add(eventName);
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
// 需要委托的horizon事件和原生事件对应关系
|
||||
export const allDelegatedHorizonEvents = new Map();
|
||||
|
||||
// 模拟委托事件,不冒泡事件需要利用其他事件来触发冒泡过程
|
||||
export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave'];
|
||||
// 所有委托的原生事件集合
|
||||
export const allDelegatedNativeEvents = new Set();
|
||||
|
||||
|
@ -49,6 +52,8 @@ export const horizonEventToNativeMap = new Map([
|
|||
['onCompositionUpdate', ['compositionupdate']],
|
||||
['onChange', ['change', 'click', 'focusout', 'input']],
|
||||
['onSelect', ['select']],
|
||||
['onMouseEnter', ['mouseout', 'mouseover']],
|
||||
['onMouseLeave', ['mouseout', 'mouseover']],
|
||||
|
||||
['onAnimationEnd', ['animationend']],
|
||||
['onAnimationIteration', ['animationiteration']],
|
||||
|
|
|
@ -37,6 +37,9 @@ export class WrappedEvent {
|
|||
key: string;
|
||||
currentTarget: EventTarget | null = null;
|
||||
|
||||
target: HTMLElement;
|
||||
relatedTarget: HTMLElement;
|
||||
|
||||
stopPropagation: () => void;
|
||||
preventDefault: () => void;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import { getDomTag } from '../dom/utils/Common';
|
|||
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
||||
import { getDom } from '../dom/DOMInternalKeys';
|
||||
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
|
||||
import { getMouseEnterListeners } from './MouseEvent';
|
||||
|
||||
// web规范,鼠标右键key值
|
||||
const RIGHT_MOUSE_BUTTON = 2;
|
||||
|
@ -141,18 +142,26 @@ function triggerHorizonEvents(
|
|||
const target = nativeEvent.target || nativeEvent.srcElement!;
|
||||
|
||||
// 触发普通委托事件
|
||||
let listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
|
||||
const listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
|
||||
|
||||
let mouseEnterListeners: ListenerUnitList = [];
|
||||
if (horizonEventToNativeMap.get('onMouseEnter')!.includes(nativeEvtName)) {
|
||||
mouseEnterListeners = getMouseEnterListeners(
|
||||
nativeEvtName,
|
||||
vNode,
|
||||
nativeEvent,
|
||||
target,
|
||||
);
|
||||
}
|
||||
|
||||
let changeEvents: ListenerUnitList = [];
|
||||
// 触发特殊handler委托事件
|
||||
if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
||||
const changeListeners = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
||||
if (changeListeners.length) {
|
||||
listenerList = listenerList.concat(changeListeners);
|
||||
}
|
||||
changeEvents = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
||||
}
|
||||
|
||||
// 处理触发的事件队列
|
||||
processListeners(listenerList);
|
||||
processListeners([...listenerList, ...mouseEnterListeners, ...changeEvents]);
|
||||
}
|
||||
|
||||
// 其他事件正在执行中标记
|
||||
|
|
|
@ -86,3 +86,90 @@ export function getListenersFromTree(
|
|||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
// 获取enter和leave事件队列
|
||||
export function collectMouseListeners(
|
||||
leaveEvent: null | WrappedEvent,
|
||||
enterEvent: null | WrappedEvent,
|
||||
from: VNode | null,
|
||||
to: VNode | null,
|
||||
): ListenerUnitList {
|
||||
// 确定公共父节点,作为在树上遍历的终点
|
||||
const commonParent = from && to ? getCommonAncestor(from, to) : null;
|
||||
let leaveEventList: ListenerUnitList = [];
|
||||
if (from && leaveEvent) {
|
||||
// 遍历树,获取绑定的leave事件
|
||||
leaveEventList = getMouseListenersFromTree(
|
||||
leaveEvent,
|
||||
from,
|
||||
commonParent,
|
||||
);
|
||||
}
|
||||
let enterEventList: ListenerUnitList = [];
|
||||
if (to && enterEvent) {
|
||||
// 先触发父节点enter事件,所以需要逆序
|
||||
enterEventList = getMouseListenersFromTree(
|
||||
enterEvent,
|
||||
to,
|
||||
commonParent,
|
||||
).reverse();
|
||||
}
|
||||
return [...leaveEventList, ...enterEventList];
|
||||
}
|
||||
|
||||
function getMouseListenersFromTree(
|
||||
event: WrappedEvent,
|
||||
target: VNode,
|
||||
commonParent: VNode | null,
|
||||
): ListenerUnitList {
|
||||
const registrationName = event.customEventName;
|
||||
const listeners: ListenerUnitList = [];
|
||||
|
||||
let vNode = target;
|
||||
while (vNode !== null) {
|
||||
// commonParent作为终点
|
||||
if (vNode === commonParent) {
|
||||
break;
|
||||
}
|
||||
const {realNode, tag} = vNode;
|
||||
if (tag === DomComponent && realNode !== null) {
|
||||
const currentTarget = realNode;
|
||||
const listener = getListenerFromVNode(vNode, registrationName);
|
||||
if (listener) {
|
||||
listeners.push({
|
||||
vNode,
|
||||
listener,
|
||||
currentTarget,
|
||||
event,
|
||||
});
|
||||
}
|
||||
}
|
||||
vNode = vNode.parent;
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
|
||||
// 寻找两个节点的共同最近祖先,如果没有则返回null
|
||||
function getCommonAncestor(instA: VNode, instB: VNode): VNode | null {
|
||||
const parentsSet = new Set<VNode>();
|
||||
for (let tempA: VNode | null = instA; tempA; tempA = getParent(tempA)) {
|
||||
parentsSet.add(tempA);
|
||||
}
|
||||
for (let tempB: VNode | null = instB; tempB; tempB = getParent(tempB)) {
|
||||
if (parentsSet.has(tempB)) {
|
||||
return tempB;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取父节点
|
||||
function getParent(inst: VNode | null): VNode | null {
|
||||
if (inst === null) {
|
||||
return null;
|
||||
}
|
||||
do {
|
||||
inst = inst.parent;
|
||||
} while (inst && inst.tag !== DomComponent);
|
||||
return inst || null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openGauss is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import { getNearestVNode } from '../dom/DOMInternalKeys';
|
||||
import { WrappedEvent } from './EventWrapper';
|
||||
import { VNode } from '../renderer/vnode/VNode';
|
||||
import { AnyNativeEvent, ListenerUnitList } from './Types';
|
||||
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
||||
import { collectMouseListeners } from './ListenerGetter';
|
||||
import { getNearestMountedVNode } from './utils';
|
||||
|
||||
/**
|
||||
* 背景: mouseEnter和mouseLeave事件不冒泡,所以无法直接委托给根节点进行代理
|
||||
* 实现方案:利用mouseout、mouseover事件的,找到事件触发的起点和终点,判断出鼠标移动轨迹,在轨迹中的节点触发mouseEnter和mouseLeave事件
|
||||
* 步骤:
|
||||
* 1. 根节点绑定mouseout和mouseover事件
|
||||
* 2. 事件触发后找到事件的起点和终点
|
||||
* 3. 封装装enter和leave事件
|
||||
* 4. 根据起止点找到公共父节点,作为事件冒泡的终点
|
||||
* 5. 遍历treeNode,找到每个节点绑定的mouseEnter和mouseLeave监听方法
|
||||
* 例如: mouseOut事件由D->C, A节点作为公共父节点,将触发 D、B的mouseLeave事件和C节点的mouseEnter事件
|
||||
* A
|
||||
* / \
|
||||
* B C
|
||||
* / \
|
||||
* D E
|
||||
*
|
||||
*/
|
||||
|
||||
function getWrapperEvents(nativeEventTarget, fromInst, toInst, nativeEvent, targetInst): (WrappedEvent | null)[] {
|
||||
const vWindow = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget : nativeEventTarget.ownerDocument.defaultView;
|
||||
|
||||
// 起点或者终点为空的话默认值为所在window
|
||||
const fromNode = fromInst?.realNode || vWindow;
|
||||
const toNode = toInst?.realNode || vWindow;
|
||||
let leave: WrappedEvent | null = null;
|
||||
let enter: WrappedEvent | null = null;
|
||||
const nativeTargetInst = getNearestVNode(nativeEventTarget);
|
||||
|
||||
// 在Mounted的dom节点上render一个子组件,系统中存在两个根节点,子节点的mouseout事件触发两次,取离target近的根节点生效
|
||||
if (nativeTargetInst === targetInst) {
|
||||
leave = new WrappedEvent('onMouseLeave', 'mouseleave', nativeEvent);
|
||||
leave.target = fromNode;
|
||||
leave.relatedTarget = toNode;
|
||||
|
||||
enter = new WrappedEvent('onMouseEnter', 'mouseenter', nativeEvent);
|
||||
enter.target = toNode;
|
||||
enter.relatedTarget = fromNode;
|
||||
}
|
||||
return [leave, enter];
|
||||
}
|
||||
|
||||
function getEndpointVNode(
|
||||
domEventName: string,
|
||||
targetInst: null | VNode,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
): (VNode | null)[] {
|
||||
let fromVNode;
|
||||
let toVNode;
|
||||
if (domEventName === 'mouseover') {
|
||||
fromVNode = null;
|
||||
toVNode = targetInst;
|
||||
} else {
|
||||
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
|
||||
fromVNode = targetInst;
|
||||
toVNode = related ? getNearestVNode(related) : null;
|
||||
if (toVNode !== null) {
|
||||
const nearestMounted = getNearestMountedVNode(toVNode);
|
||||
if (toVNode !== nearestMounted || (toVNode.tag !== DomComponent && toVNode.tag !== DomText)) {
|
||||
toVNode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [fromVNode, toVNode];
|
||||
}
|
||||
|
||||
export function getMouseEnterListeners(
|
||||
domEventName: string,
|
||||
targetInst: null | VNode,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: null | EventTarget,
|
||||
): ListenerUnitList {
|
||||
|
||||
// 获取起点和终点的VNode
|
||||
const [fromVNode, toVNode] = getEndpointVNode(domEventName, targetInst, nativeEvent);
|
||||
if (fromVNode === toVNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取包装后的leave和enter事件
|
||||
const [leave, enter] = getWrapperEvents(nativeEventTarget, fromVNode, toVNode, nativeEvent, targetInst);
|
||||
|
||||
// 收集事件的监听方法
|
||||
return collectMouseListeners(leave, enter, fromVNode, toVNode);
|
||||
}
|
||||
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import { VNode } from '../renderer/vnode/VNode';
|
||||
import { Addition, FlagUtils } from '../renderer/vnode/VNodeFlags';
|
||||
import { TreeRoot } from '../renderer/vnode/VNodeTags';
|
||||
|
||||
export function isInputElement(dom?: HTMLElement): boolean {
|
||||
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
|
||||
}
|
||||
|
@ -20,6 +24,26 @@ export function isInputElement(dom?: HTMLElement): boolean {
|
|||
export function setPropertyWritable(obj, propName) {
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, propName);
|
||||
if (!desc || !desc.writable) {
|
||||
Object.defineProperty(obj, propName, { writable: true });
|
||||
Object.defineProperty(obj, propName, {writable: true});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取离 vNode 最近的已挂载 vNode,包含它自己
|
||||
export function getNearestMountedVNode(vNode: VNode): null | VNode {
|
||||
let node = vNode;
|
||||
let target = vNode;
|
||||
// 如果没有alternate,说明是可能是未插入的新树,需要处理插入的副作用。
|
||||
while (node.parent) {
|
||||
// 存在更新,节点未挂载,查找父节点,但是父节点也可能未挂载,需要继续往上查找无更新节点
|
||||
if (FlagUtils.hasFlag(node, Addition)) {
|
||||
target = node.parent;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
// 如果根节点是 Dom 类型节点,表示已经挂载
|
||||
if (node.tag === TreeRoot) {
|
||||
return target;
|
||||
}
|
||||
// 如果没有找到根节点,意味着Tree已经卸载或者未挂载
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@ import type { VNode } from '../Types';
|
|||
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
import { popCurrentRoot, pushCurrentRoot } from '../RootStack';
|
||||
import { listenSimulatedDelegatedEvents } from '../../event/EventBinding';
|
||||
|
||||
export function bubbleRender(processing: VNode) {
|
||||
resetNamespaceCtx(processing);
|
||||
listenSimulatedDelegatedEvents(processing);
|
||||
popCurrentRoot();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,14 +40,20 @@ export class FlagUtils {
|
|||
static removeFlag(node: VNode, flag: number) {
|
||||
node.flags &= ~flag;
|
||||
}
|
||||
|
||||
static removeLifecycleEffectFlags(node) {
|
||||
node.flags &= ~LifecycleEffectArr;
|
||||
}
|
||||
|
||||
static hasAnyFlag(node: VNode) {
|
||||
// 有标志位
|
||||
return node.flags !== InitFlag;
|
||||
}
|
||||
|
||||
static hasFlag(node: VNode, flag) {
|
||||
return (node.flags & flag) !== 0;
|
||||
}
|
||||
|
||||
static setNoFlags(node: VNode) {
|
||||
node.flags = InitFlag;
|
||||
}
|
||||
|
@ -55,6 +61,7 @@ export class FlagUtils {
|
|||
static markAddition(node: VNode) {
|
||||
node.flags |= Addition;
|
||||
}
|
||||
|
||||
static setAddition(node: VNode) {
|
||||
node.flags = Addition;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"name": "@cloudsop/horizon",
|
||||
"description": "Horizon is a JavaScript framework library.",
|
||||
"version": "0.0.51",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"libs/*"
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openGauss is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||
|
||||
describe('mouseenter和mouseleave事件测试', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('在iframe中mouseleave事件的relateTarget属性', () => {
|
||||
const iframe = document.createElement('iframe');
|
||||
container.appendChild(iframe);
|
||||
const iframeDocument = iframe.contentDocument;
|
||||
iframeDocument.write(
|
||||
'<!DOCTYPE html><html><head></head><body><div></div></body></html>',
|
||||
);
|
||||
iframeDocument.close();
|
||||
|
||||
const leaveEvents = [];
|
||||
const node = Horizon.render(
|
||||
<div
|
||||
onMouseLeave={e => {
|
||||
e.persist();
|
||||
leaveEvents.push(e);
|
||||
}}
|
||||
/>,
|
||||
iframeDocument.body.getElementsByTagName('div')[0],
|
||||
);
|
||||
|
||||
node.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: iframe.contentWindow,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(leaveEvents.length).toBe(1);
|
||||
expect(leaveEvents[0].target).toBe(node);
|
||||
expect(leaveEvents[0].relatedTarget).toBe(iframe.contentWindow);
|
||||
});
|
||||
|
||||
it('在iframe中mouseenter事件的relateTarget属性', () => {
|
||||
const iframe = document.createElement('iframe');
|
||||
container.appendChild(iframe);
|
||||
const iframeDocument = iframe.contentDocument;
|
||||
iframeDocument.write(
|
||||
'<!DOCTYPE html><html><head></head><body><div></div></body></html>',
|
||||
);
|
||||
iframeDocument.close();
|
||||
|
||||
const enterEvents = [];
|
||||
const node = Horizon.render(
|
||||
<div
|
||||
onMouseEnter={e => {
|
||||
e.persist();
|
||||
enterEvents.push(e);
|
||||
}}
|
||||
/>,
|
||||
iframeDocument.body.getElementsByTagName('div')[0],
|
||||
);
|
||||
|
||||
node.dispatchEvent(
|
||||
new MouseEvent('mouseover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: null,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(enterEvents.length).toBe(1);
|
||||
expect(enterEvents[0].target).toBe(node);
|
||||
expect(enterEvents[0].relatedTarget).toBe(iframe.contentWindow);
|
||||
});
|
||||
|
||||
it('从新渲染的子组件触发mouseout事件,子组件响应mouseenter事件,父节点不响应', () => {
|
||||
let parentEnterCalls = 0;
|
||||
let childEnterCalls = 0;
|
||||
let parent = null;
|
||||
|
||||
class Parent extends Horizon.Component {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => parentEnterCalls++}
|
||||
ref={node => (parent = node)}>
|
||||
{this.props.showChild && (
|
||||
<div onMouseEnter={() => childEnterCalls++}/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Horizon.render(<Parent/>, container);
|
||||
Horizon.render(<Parent showChild={true}/>, container);
|
||||
|
||||
parent.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: parent.firstChild,
|
||||
}),
|
||||
);
|
||||
expect(childEnterCalls).toBe(1);
|
||||
expect(parentEnterCalls).toBe(0);
|
||||
});
|
||||
|
||||
it('render一个新组件,兄弟节点触发mouseout事件,mouseenter事件响应一次', done => {
|
||||
const mockFn1 = jest.fn();
|
||||
const mockFn2 = jest.fn();
|
||||
const mockFn3 = jest.fn();
|
||||
|
||||
class Parent extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.parentEl = Horizon.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Horizon.render(<MouseEnterDetect/>, this.parentEl.current);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={this.parentEl} id="parent" onMouseLeave={mockFn3}/>;
|
||||
}
|
||||
}
|
||||
|
||||
class MouseEnterDetect extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.firstEl = Horizon.createRef();
|
||||
this.siblingEl = Horizon.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.siblingEl.current.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: this.firstEl.current,
|
||||
}),
|
||||
);
|
||||
expect(mockFn1.mock.calls.length).toBe(1);
|
||||
expect(mockFn2.mock.calls.length).toBe(1);
|
||||
expect(mockFn3.mock.calls.length).toBe(0);
|
||||
done();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Horizon.Fragment>
|
||||
<div ref={this.firstEl} id="first" onMouseEnter={mockFn1}/>
|
||||
<div ref={this.siblingEl} id="sibling" onMouseLeave={mockFn2}/>
|
||||
</Horizon.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Horizon.render(<Parent/>, container);
|
||||
});
|
||||
|
||||
it('未被horizon管理的节点触发mouseout事件,mouseenter事件也能正常触发', done => {
|
||||
const mockFn = jest.fn();
|
||||
|
||||
class Parent extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.parentEl = Horizon.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Horizon.render(<MouseEnterDetect/>, this.parentEl.current);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={this.parentEl}/>;
|
||||
}
|
||||
}
|
||||
|
||||
class MouseEnterDetect extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.divRef = Horizon.createRef();
|
||||
this.siblingEl = Horizon.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const attachedNode = document.createElement('div');
|
||||
this.divRef.current.appendChild(attachedNode);
|
||||
attachedNode.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: this.siblingEl.current,
|
||||
}),
|
||||
);
|
||||
expect(mockFn.mock.calls.length).toBe(1);
|
||||
done();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div ref={this.divRef}>
|
||||
<div ref={this.siblingEl} onMouseEnter={mockFn}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Horizon.render(<Parent/>, container);
|
||||
});
|
||||
|
||||
it('外部portal节点触发的mouseout事件,根节点的mouseleave事件也能响应', () => {
|
||||
const divRef = Horizon.createRef();
|
||||
const onMouseLeave = jest.fn();
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<div onMouseLeave={onMouseLeave} id="parent">
|
||||
{Horizon.createPortal(<div ref={divRef} id="sub"/>, document.body)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Horizon.render(<Component/>, container);
|
||||
|
||||
divRef.current.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: document.body,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(onMouseLeave).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('外部portal节点触发的mouseout事件,根节点的mouseEnter事件也能响应', () => {
|
||||
const divRef = Horizon.createRef();
|
||||
const otherDivRef = Horizon.createRef();
|
||||
const onMouseEnter = jest.fn();
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<div ref={divRef}>
|
||||
{Horizon.createPortal(
|
||||
<div ref={otherDivRef} onMouseEnter={onMouseEnter}/>,
|
||||
document.body,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Horizon.render(<Component/>, container);
|
||||
|
||||
divRef.current.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: otherDivRef.current,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(onMouseEnter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue