Match-id-c6fa3b4d5383d1b9910565ce95be74fa36a714a1

This commit is contained in:
* 2022-06-27 15:24:42 +08:00 committed by *
commit 70027b0a1f
16 changed files with 206 additions and 270 deletions

View File

@ -1,4 +1,4 @@
import { allDelegatedHorizonEvents } from '../../event/EventCollection'; import { allDelegatedHorizonEvents } from '../../event/EventHub';
import { updateCommonProp } from './UpdateCommonProp'; import { updateCommonProp } from './UpdateCommonProp';
import { setStyles } from './StyleHandler'; import { setStyles } from './StyleHandler';
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding'; import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
@ -19,7 +19,6 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
setStyles(dom, propVal); setStyles(dom, propVal);
} else if (isEventProp(propName)) { } else if (isEventProp(propName)) {
// 事件监听属性处理 // 事件监听属性处理
// TODO
const currentRoot = getCurrentRoot(); const currentRoot = getCurrentRoot();
if (!allDelegatedHorizonEvents.has(propName)) { if (!allDelegatedHorizonEvents.has(propName)) {
listenNonDelegatedEvent(propName, dom, propVal); listenNonDelegatedEvent(propName, dom, propVal);

View File

@ -1,27 +1,29 @@
/** /**
* *
*/ */
import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventCollection';
import {isDocument} from '../dom/utils/Common';
import { import {
getNearestVNode, allDelegatedHorizonEvents,
getNonDelegatedListenerMap, allDelegatedNativeEvents,
} from '../dom/DOMInternalKeys'; } from './EventHub';
import {runDiscreteUpdates} from '../renderer/TreeBuilder'; import { isDocument } from '../dom/utils/Common';
import {isMounted} from '../renderer/vnode/VNodeUtils'; import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
import {SuspenseComponent} from '../renderer/vnode/VNodeTags'; import { runDiscreteUpdates } from '../renderer/TreeBuilder';
import {handleEventMain} from './HorizonEventMain'; import { handleEventMain } from './HorizonEventMain';
import {decorateNativeEvent} from './customEvents/EventFactory'; import { decorateNativeEvent } from './EventWrapper';
import { VNode } from '../renderer/vnode/VNode'; import { VNode } from '../renderer/vnode/VNode';
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4); const listeningMarker =
'_horizonListening' +
Math.random()
.toString(36)
.slice(4);
// 触发委托事件 // 触发委托事件
function triggerDelegatedEvent( function triggerDelegatedEvent(
nativeEvtName: string, nativeEvtName: string,
isCapture: boolean, isCapture: boolean,
targetDom: EventTarget, targetDom: EventTarget,
nativeEvent, // 事件对象event nativeEvent // 事件对象event
) { ) {
// 执行之前的调度事件 // 执行之前的调度事件
runDiscreteUpdates(); runDiscreteUpdates();
@ -33,11 +35,7 @@ function triggerDelegatedEvent(
} }
// 监听委托事件 // 监听委托事件
function listenToNativeEvent( function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, isCapture: boolean): void {
nativeEvtName: string,
delegatedElement: Element,
isCapture: boolean,
): void {
let dom: Element | Document = delegatedElement; let dom: Element | Document = delegatedElement;
// document层次可能触发selectionchange事件为了捕获这类事件selectionchange事件绑定在document节点上 // document层次可能触发selectionchange事件为了捕获这类事件selectionchange事件绑定在document节点上
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) { if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
@ -70,10 +68,16 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
const isCapture = isCaptureEvent(eventName); const isCapture = isCaptureEvent(eventName);
const nativeEvents = allDelegatedHorizonEvents.get(eventName); const nativeEvents = allDelegatedHorizonEvents.get(eventName);
nativeEvents.forEach(nativeEvents => {
listenToNativeEvent(nativeEvents, currentRoot.realNode, isCapture); nativeEvents.forEach(nativeEvent => {
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
if (!currentRoot.delegatedNativeEvents.has(nativeFullName)) {
listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
currentRoot.delegatedNativeEvents.add(nativeFullName);
}
}); });
} }
// 通过horizon事件名获取到native事件名 // 通过horizon事件名获取到native事件名
function getNativeEvtName(horizonEventName, capture) { function getNativeEvtName(horizonEventName, capture) {
let nativeName; let nativeName;
@ -105,11 +109,7 @@ function getWrapperListener(horizonEventName, nativeEvtName, targetElement, list
} }
// 非委托事件单独监听到各自dom节点 // 非委托事件单独监听到各自dom节点
export function listenNonDelegatedEvent( export function listenNonDelegatedEvent(horizonEventName: string, domElement: Element, listener): void {
horizonEventName: string,
domElement: Element,
listener,
): void {
const isCapture = isCaptureEvent(horizonEventName); const isCapture = isCaptureEvent(horizonEventName);
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture); const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);

View File

@ -1,15 +0,0 @@
import {horizonEventToNativeMap} from './const';
// 需要委托的horizon事件和原生事件对应关系
export const allDelegatedHorizonEvents = new Map();
// 所有委托的原生事件集合
export const allDelegatedNativeEvents = new Set();
horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
allDelegatedHorizonEvents.set(horizonEvent, dependencies);
allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
dependencies.forEach(d => {
allDelegatedNativeEvents.add(d);
});
});

View File

@ -1,3 +1,7 @@
// 需要委托的horizon事件和原生事件对应关系
export const allDelegatedHorizonEvents = new Map();
// 所有委托的原生事件集合
export const allDelegatedNativeEvents = new Set();
// Horizon事件和原生事件对应关系 // Horizon事件和原生事件对应关系
export const horizonEventToNativeMap = new Map([ export const horizonEventToNativeMap = new Map([
@ -33,10 +37,9 @@ export const horizonEventToNativeMap = new Map([
['onAnimationEnd', ['animationend']], ['onAnimationEnd', ['animationend']],
['onAnimationIteration', ['animationiteration']], ['onAnimationIteration', ['animationiteration']],
['onAnimationStart', ['animationstart']], ['onAnimationStart', ['animationstart']],
['onTransitionEnd', ['transitionend']] ['onTransitionEnd', ['transitionend']],
]); ]);
export const NativeEventToHorizonMap = {
export const CommonEventToHorizonMap = {
click: 'click', click: 'click',
dblclick: 'doubleClick', dblclick: 'doubleClick',
contextmenu: 'contextMenu', contextmenu: 'contextMenu',
@ -69,11 +72,22 @@ export const CommonEventToHorizonMap = {
compositionend: 'compositionEnd', compositionend: 'compositionEnd',
compositionupdate: 'compositionUpdate', compositionupdate: 'compositionUpdate',
}; };
export const CHAR_CODE_SPACE = 32; export const CHAR_CODE_SPACE = 32;
export const EVENT_TYPE_BUBBLE = 'Bubble'; export const EVENT_TYPE_BUBBLE = 'Bubble';
export const EVENT_TYPE_CAPTURE = 'Capture'; export const EVENT_TYPE_CAPTURE = 'Capture';
export const EVENT_TYPE_ALL = 'All'; export const EVENT_TYPE_ALL = 'All';
horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
allDelegatedHorizonEvents.set(horizonEvent, dependencies);
allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
dependencies.forEach(d => {
allDelegatedNativeEvents.add(d);
});
});
export function transformToHorizonEvent(nativeEvtName: string) {
const name = NativeEventToHorizonMap[nativeEvtName];
// 例dragEnd -> onDragEnd
return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`;
}

View File

@ -1,19 +1,76 @@
import type { AnyNativeEvent } from './Types'; import { AnyNativeEvent, ListenerUnitList } from './Types';
import { ListenerUnitList } from './Types';
import type { VNode } from '../renderer/Types'; import type { VNode } from '../renderer/Types';
import { isInputElement, setPropertyWritable } from './utils';
import { CommonEventToHorizonMap, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, horizonEventToNativeMap } from './const'; import { decorateNativeEvent } from './EventWrapper';
import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
import { setPropertyWritable } from './utils';
import { decorateNativeEvent } from './customEvents/EventFactory';
import { getListenersFromTree } from './ListenerGetter'; import { getListenersFromTree } from './ListenerGetter';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer'; import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
import { findRoot } from '../renderer/vnode/VNodeUtils'; import { findRoot } from '../renderer/vnode/VNodeUtils';
import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler'; import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler';
import {
EVENT_TYPE_ALL,
EVENT_TYPE_BUBBLE,
EVENT_TYPE_CAPTURE,
horizonEventToNativeMap,
transformToHorizonEvent,
} from './EventHub';
import { getDomTag } from '../dom/utils/Common';
import { updateInputValueIfChanged } from '../dom/valueHandler/ValueChangeHandler';
import { getDom } from '../dom/DOMInternalKeys';
// web规范鼠标右键key值 // web规范鼠标右键key值
const RIGHT_MOUSE_BUTTON = 2; const RIGHT_MOUSE_BUTTON = 2;
// 返回是否需要触发change事件标记
// | 元素 | 事件 | 需要值变更 |
// | --- | --- | --------------- |
// | <select/> / <input type="file/> | change | NO |
// | <input type="checkbox" /> <input type="radio" /> | click | YES |
// | <input type="input /> / <input type="text" /> | input / change | YES |
function shouldTriggerChangeEvent(targetDom, evtName) {
const { type } = targetDom;
const domTag = getDomTag(targetDom);
if (domTag === 'select' || (domTag === 'input' && type === 'file')) {
return evtName === 'change';
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
if (evtName === 'click') {
return updateInputValueIfChanged(targetDom);
}
} else if (isInputElement(targetDom)) {
if (evtName === 'input' || evtName === 'change') {
return updateInputValueIfChanged(targetDom);
}
}
return false;
}
/**
*
* input/textarea/select的onChange事件
*/
function getChangeListeners(
nativeEvtName: string,
nativeEvt: AnyNativeEvent,
vNode: null | VNode,
): ListenerUnitList {
if (!vNode) {
return [];
}
const targetDom = getDom(vNode);
// 判断是否需要触发change事件
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
const event = decorateNativeEvent(
'onChange',
'change',
nativeEvt,
);
return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
}
return [];
}
// 获取事件触发的普通事件监听方法队列 // 获取事件触发的普通事件监听方法队列
function getCommonListeners( function getCommonListeners(
nativeEvtName: string, nativeEvtName: string,
@ -22,8 +79,7 @@ function getCommonListeners(
target: null | EventTarget, target: null | EventTarget,
isCapture: boolean, isCapture: boolean,
): ListenerUnitList { ): ListenerUnitList {
const name = CommonEventToHorizonMap[nativeEvtName]; const horizonEvtName = transformToHorizonEvent(nativeEvtName);
const horizonEvtName = !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`; // 例dragEnd -> onDragEnd
if (!horizonEvtName) { if (!horizonEvtName) {
return []; return [];
@ -66,13 +122,16 @@ function processListeners(listenerList: ListenerUnitList): void {
}); });
} }
function getProcessListeners( // 触发可以被执行的horizon事件监听
function triggerHorizonEvents(
nativeEvtName: string, nativeEvtName: string,
vNode: VNode | null,
nativeEvent: AnyNativeEvent,
target,
isCapture: boolean, isCapture: boolean,
): ListenerUnitList { nativeEvent: AnyNativeEvent,
vNode: VNode | null,
) {
const target = nativeEvent.target || nativeEvent.srcElement;
let hasTriggeredChangeEvent = false;
// 触发普通委托事件 // 触发普通委托事件
let listenerList: ListenerUnitList = getCommonListeners( let listenerList: ListenerUnitList = getCommonListeners(
nativeEvtName, nativeEvtName,
@ -83,34 +142,22 @@ function getProcessListeners(
); );
// 触发特殊handler委托事件 // 触发特殊handler委托事件
if (!isCapture) { if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
if (horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) { const changeListeners = getChangeListeners(
listenerList = listenerList.concat(getChangeListeners( nativeEvtName,
nativeEvtName, nativeEvent,
nativeEvent, vNode,
vNode, );
)); if (changeListeners.length) {
hasTriggeredChangeEvent = true;
listenerList = listenerList.concat(changeListeners);
} }
} }
return listenerList;
}
// 触发可以被执行的horizon事件监听
function triggerHorizonEvents(
nativeEvtName: string,
isCapture: boolean,
nativeEvent: AnyNativeEvent,
vNode: VNode | null,
) {
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
// 获取委托事件队列
const listenerList = getProcessListeners(nativeEvtName, vNode, nativeEvent, nativeEventTarget, isCapture);
// 处理触发的事件队列 // 处理触发的事件队列
processListeners(listenerList); processListeners(listenerList);
return listenerList; return hasTriggeredChangeEvent;
} }
@ -141,15 +188,12 @@ export function handleEventMain(
// 没有事件在执行,经过调度再执行事件 // 没有事件在执行,经过调度再执行事件
isInEventsExecution = true; isInEventsExecution = true;
let shouldDispatchUpdate = false; let hasTriggeredChangeEvent = false;
try { try {
const listeners = asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode)); hasTriggeredChangeEvent = asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
if (listeners.length) {
shouldDispatchUpdate = true;
}
} finally { } finally {
isInEventsExecution = false; isInEventsExecution = false;
if (shouldDispatchUpdate) { if (hasTriggeredChangeEvent) {
runDiscreteUpdates(); runDiscreteUpdates();
// 若是Radio同步同组其他Radio的Handler Value // 若是Radio同步同组其他Radio的Handler Value
syncRadiosHandler(nativeEvent.target as Element); syncRadiosHandler(nativeEvent.target as Element);

View File

@ -1,7 +1,7 @@
import { VNode } from '../renderer/Types'; import { VNode } from '../renderer/Types';
import { DomComponent } from '../renderer/vnode/VNodeTags'; import { DomComponent } from '../renderer/vnode/VNodeTags';
import { EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE } from './const';
import { AnyNativeEvent, ListenerUnitList } from './Types'; import { AnyNativeEvent, ListenerUnitList } from './Types';
import { EVENT_TYPE_ALL, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE } from './EventHub';
// 从vnode属性中获取事件listener // 从vnode属性中获取事件listener
function getListenerFromVNode(vNode: VNode, eventName: string): Function | null { function getListenerFromVNode(vNode: VNode, eventName: string): Function | null {

View File

@ -1,62 +0,0 @@
import {decorateNativeEvent} from '../customEvents/EventFactory';
import {getDom} from '../../dom/DOMInternalKeys';
import {updateInputValueIfChanged} from '../../dom/valueHandler/ValueChangeHandler';
import {isInputElement} from '../utils';
import {EVENT_TYPE_ALL} from '../const';
import {AnyNativeEvent, ListenerUnitList} from '../Types';
import {
getListenersFromTree,
} from '../ListenerGetter';
import {VNode} from '../../renderer/Types';
import {getDomTag} from '../../dom/utils/Common';
// 返回是否需要触发change事件标记
// | 元素 | 事件 | 需要值变更 |
// | --- | --- | --------------- |
// | <select/> / <input type="file/> | change | NO |
// | <input type="checkbox" /> <input type="radio" /> | click | YES |
// | <input type="input /> / <input type="text" /> | input / change | YES |
function shouldTriggerChangeEvent(targetDom, evtName) {
const { type } = targetDom;
const domTag = getDomTag(targetDom);
if (domTag === 'select' || (domTag === 'input' && type === 'file')) {
return evtName === 'change';
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
if (evtName === 'click') {
return updateInputValueIfChanged(targetDom);
}
} else if (isInputElement(targetDom)) {
if (evtName === 'input' || evtName === 'change') {
return updateInputValueIfChanged(targetDom);
}
}
return false;
}
/**
*
* input/textarea/select的onChange事件
*/
export function getListeners(
nativeEvtName: string,
nativeEvt: AnyNativeEvent,
vNode: null | VNode
): ListenerUnitList {
if (!vNode) {
return [];
}
const targetDom = getDom(vNode);
// 判断是否需要触发change事件
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
const event = decorateNativeEvent(
'onChange',
'change',
nativeEvt,
);
return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
}
return [];
}

View File

@ -78,6 +78,7 @@ export class VNode {
// 根节点数据 // 根节点数据
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点 toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
delegatedEvents: Set<string> delegatedEvents: Set<string>
delegatedNativeEvents: Set<string>
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用 belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用
@ -98,6 +99,7 @@ export class VNode {
this.task = null; this.task = null;
this.toUpdateNodes = new Set<VNode>(); this.toUpdateNodes = new Set<VNode>();
this.delegatedEvents = new Set<string>(); this.delegatedEvents = new Set<string>();
this.delegatedNativeEvents = new Set<string>();
this.updates = null; this.updates = null;
this.stateCallbacks = null; this.stateCallbacks = null;
this.state = null; this.state = null;

View File

@ -30,6 +30,7 @@ describe('useEffect Hook Test', () => {
expect(document.getElementById('p').style.display).toBe('block'); expect(document.getElementById('p').style.display).toBe('block');
// 点击按钮触发num加1 // 点击按钮触发num加1
container.querySelector('button').click(); container.querySelector('button').click();
expect(document.getElementById('p').style.display).toBe('none'); expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click(); container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline'); expect(container.querySelector('p').style.display).toBe('inline');

View File

@ -22,16 +22,6 @@ describe('Dom Input', () => {
).not.toThrow(); ).not.toThrow();
}); });
it('checked属性受控时无法更改', () => {
Horizon.render(<input type='checkbox' checked={true} onChange={() => {
LogUtils.log('checkbox click');
}} />, container);
container.querySelector('input').click();
// 点击复选框不会改变checked的值
expect(LogUtils.getAndClear()).toEqual(['checkbox click']);
expect(container.querySelector('input').checked).toBe(true);
});
it('复选框的value属性值可以改变', () => { it('复选框的value属性值可以改变', () => {
Horizon.render( Horizon.render(
<input type='checkbox' value='' onChange={() => { <input type='checkbox' value='' onChange={() => {
@ -96,30 +86,6 @@ describe('Dom Input', () => {
).not.toThrow(); ).not.toThrow();
}); });
it('value属性受控时无法更改', () => {
const realNode = Horizon.render(<input type='text' value={'text'} onChange={() => {
LogUtils.log('text change');
}} />, container);
// 模拟改变text输入框的值
// 先修改
Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'value',
).set.call(realNode, 'abcd');
// 再触发事件
realNode.dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true,
}),
);
// 确实发生了input事件
expect(LogUtils.getAndClear()).toEqual(['text change']);
// value受控不会改变
expect(container.querySelector('input').value).toBe('text');
});
it('value值会转为字符串', () => { it('value值会转为字符串', () => {
const realNode = Horizon.render(<input type='text' value={1} />, container); const realNode = Horizon.render(<input type='text' value={1} />, container);
expect(realNode.value).toBe('1'); expect(realNode.value).toBe('1');
@ -249,30 +215,6 @@ describe('Dom Input', () => {
expect(document.getElementById('d').checked).toBe(true); expect(document.getElementById('d').checked).toBe(true);
}); });
it('受控radio的状态', () => {
Horizon.render(
<>
<input type='radio' name='a' checked={true} />
<input id='b' type='radio' name='a' checked={false} />
</>, container);
expect(container.querySelector('input').checked).toBe(true);
expect(document.getElementById('b').checked).toBe(false);
Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'checked',
).set.call(document.getElementById('b'), true);
// 再触发事件
document.getElementById('b').dispatchEvent(
new Event('click', {
bubbles: true,
cancelable: true,
}),
);
// 模拟点击单选框B两个受控radio的状态不会改变
expect(container.querySelector('input').checked).toBe(true);
expect(document.getElementById('b').checked).toBe(false);
});
it('name改变不影响相同name的radio', () => { it('name改变不影响相同name的radio', () => {
const inputRef = Horizon.createRef(); const inputRef = Horizon.createRef();
const App = () => { const App = () => {

View File

@ -53,37 +53,6 @@ describe('Dom Select', () => {
expect(realNode.value).toBe('React'); expect(realNode.value).toBe('React');
}); });
it('受控select', () => {
const selectNode = (
<select value='Vue'>
<option value='React'>React.js</option>
<option value='Vue'>Vue.js</option>
<option value='Angular'>Angular.js</option>
</select>
);
const realNode = Horizon.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
// 先修改
Object.getOwnPropertyDescriptor(
HTMLSelectElement.prototype,
'value',
).set.call(realNode, 'React');
// 再触发事件
container.querySelector('select').dispatchEvent(
new Event('change', {
bubbles: true,
cancelable: true,
}),
);
// 鼠标改变受控select不生效
Horizon.render(selectNode, container);
// 'React'项没有被选中
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(true);
expect(realNode.value).toBe('Vue');
});
it('受控select转为不受控会保存原来select', () => { it('受控select转为不受控会保存原来select', () => {
const selectNode = ( const selectNode = (
<select value='Vue'> <select value='Vue'>
@ -338,4 +307,4 @@ describe('Dom Select', () => {
expect(realNode.options[1].selected).toBe(false); expect(realNode.options[1].selected).toBe(false);
expect(realNode.options[2].selected).toBe(false); expect(realNode.options[2].selected).toBe(false);
}); });
}); });

View File

@ -38,26 +38,6 @@ describe('Dom Textarea', () => {
expect(realNode.value).toBe('React'); expect(realNode.value).toBe('React');
}); });
it('受控组件value不变', () => {
let realNode = Horizon.render(<textarea value='text' />, container);
expect(realNode.getAttribute('value')).toBe(null);
expect(realNode.value).toBe('text');
// 先修改
Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
'value',
).set.call(realNode, 'textabc');
// 再触发事件
container.querySelector('textarea').dispatchEvent(
new Event('change', {
bubbles: true,
cancelable: true,
}),
);
// 组件受控想要改变value需要通过onChange改变state
expect(realNode.value).toBe('text');
});
it('设置defaultValue', () => { it('设置defaultValue', () => {
let defaultVal = 'Vue'; let defaultVal = 'Vue';
const textareaNode = <textarea defaultValue={defaultVal} />; const textareaNode = <textarea defaultValue={defaultVal} />;
@ -147,4 +127,4 @@ describe('Dom Textarea', () => {
expect(realNode.value).toBe('1234'); expect(realNode.value).toBe('1234');
}); });
}); });

View File

@ -1,6 +1,13 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as TestUtils from '../jest/testUtils'; import * as TestUtils from '../jest/testUtils';
function dispatchChangeEvent(input) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputValueSetter.call(input, 'test');
input.dispatchEvent(new Event('input', { bubbles: true }));
}
describe('事件', () => { describe('事件', () => {
const LogUtils = TestUtils.getLogUtils(); const LogUtils = TestUtils.getLogUtils();
it('根节点挂载全量事件', () => { it('根节点挂载全量事件', () => {
@ -162,11 +169,7 @@ describe('事件', () => {
LogUtils.log('change'); LogUtils.log('change');
}, },
}); });
dispatchChangeEvent(inputRef.current);
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputValueSetter.call(inputRef.current, 'test');
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
expect(LogUtils.getAndClear()).toEqual(['change']); expect(LogUtils.getAndClear()).toEqual(['change']);
}); });
@ -209,4 +212,63 @@ describe('事件', () => {
// 先选择选项1radio1应该重新触发onchange // 先选择选项1radio1应该重新触发onchange
clickRadioAndExpect(radio1Ref.current, [2, 1]); clickRadioAndExpect(radio1Ref.current, [2, 1]);
}); });
it('多根节点下,事件挂载正确', () => {
const root1 = document.createElement('div');
const root2 = document.createElement('div');
root1.key = 'root1';
root2.key = 'root2';
let input1, input2, update1, update2;
function App1() {
const [props, setProps] = Horizon.useState({});
update1 = setProps;
return (
<input
{...props}
ref={n => (input1 = n)}
onChange={() => {
LogUtils.log('input1 changed');
}}
/>
);
}
function App2() {
const [props, setProps] = Horizon.useState({});
update2 = setProps;
return (
<input
{...props}
ref={n => (input2 = n)}
onChange={() => {
LogUtils.log('input2 changed');
}}
/>
);
}
// 多根mount阶段挂载onChange事件
Horizon.render(<App1 key={1} />, root1);
Horizon.render(<App2 key={2} />, root2);
dispatchChangeEvent(input1);
expect(LogUtils.getAndClear()).toEqual(['input1 changed']);
dispatchChangeEvent(input2);
expect(LogUtils.getAndClear()).toEqual(['input2 changed']);
// 多根update阶段挂载onClick事件
update1({
onClick: () => LogUtils.log('input1 clicked'),
});
update2({
onClick: () => LogUtils.log('input2 clicked'),
});
input1.click();
expect(LogUtils.getAndClear()).toEqual(['input1 clicked']);
input2.click();
expect(LogUtils.getAndClear()).toEqual(['input2 clicked']);
});
}); });

View File

@ -1,4 +1,4 @@
import { allDelegatedNativeEvents } from '../../../libs/horizon/src/event/EventCollection'; import { allDelegatedNativeEvents } from '@cloudsop/horizon/src/event/EventHub';
//import * as LogUtils from './logUtils'; //import * as LogUtils from './logUtils';
export const stopBubbleOrCapture = (e, value) => { export const stopBubbleOrCapture = (e, value) => {
@ -107,4 +107,4 @@ export function getLogUtils() {
logger = new LogUtils(); logger = new LogUtils();
} }
return logger; return logger;
} }