diff --git a/app_define.json b/app_define.json index cd13e351..a674d06c 100644 --- a/app_define.json +++ b/app_define.json @@ -1 +1 @@ -{"fileVersion":"1","name":"Horizon","serviceId":"067e5bef6cd240ae9460229d107a99a6","description":"","version":"1.0.0","type":"microService","processes":{"Horizon":{"subscribes":[]}}} \ No newline at end of file +{"fileVersion":"1","name":"Inula","serviceId":"067e5bef6cd240ae9460229d107a99a6","description":"","version":"1.0.0","type":"microService","processes":{"Inula":{"subscribes":[]}}} diff --git a/babel.config.js b/babel.config.js index fa0e4b8c..522db7a3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -20,8 +20,8 @@ module.exports = { [ '@babel/plugin-transform-react-jsx', { - pragma: 'Horizon.createElement', - pragmaFrag: 'Horizon.Fragment', + pragma: 'Inula.createElement', + pragmaFrag: 'Inula.Fragment', }, ], ['@babel/plugin-proposal-class-properties', { loose: true }], diff --git a/jest.config.js b/jest.config.js index 410e7c1c..5c55a50d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -26,8 +26,10 @@ module.exports = { testEnvironment: 'jest-environment-jsdom-sixteen', testMatch: [ + // '/scripts/__tests__/InulaXTest/edgeCases/deepVariableObserver.test.tsx', + // '/scripts/__tests__/InulaXTest/StateManager/StateMap.test.tsx', '/scripts/__tests__/**/*.test.js', - '/scripts/__tests__/**/*.test.tsx' + '/scripts/__tests__/**/*.test.tsx', ], timers: 'fake', diff --git a/libs/horizon/README.md b/libs/horizon/README.md deleted file mode 100644 index c44e6d74..00000000 --- a/libs/horizon/README.md +++ /dev/null @@ -1 +0,0 @@ -# `horizon` diff --git a/libs/horizon/src/horizonx/devtools/constants.ts b/libs/horizon/src/horizonx/devtools/constants.ts deleted file mode 100644 index 3e105125..00000000 --- a/libs/horizon/src/horizonx/devtools/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const INITIALIZED = 'horizonx store initialized'; -export const STATE_CHANGE = 'horizonx state change'; -export const SUBSCRIBED = 'horizonx subscribed'; -export const UNSUBSCRIBED = 'horizonx unsubscribed'; -export const ACTION = 'horizonx action'; -export const ACTION_QUEUED = 'horizonx action queued'; -export const QUEUE_PENDING = 'horizonx queue pending'; -export const QUEUE_FINISHED = 'horizonx queue finished'; -export const RENDER_TRIGGERED = 'horizonx render triggered'; -export const OBSERVED_COMPONENTS = 'horizonx observed components'; diff --git a/libs/horizon/src/horizonx/devtools/index.ts b/libs/horizon/src/horizonx/devtools/index.ts deleted file mode 100644 index 35f924e6..00000000 --- a/libs/horizon/src/horizonx/devtools/index.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { getStore, getAllStores } from '../store/StoreHandler'; -import { OBSERVED_COMPONENTS } from './constants'; - -const sessionId = Date.now(); - -// this function is used to detect devtool connection -export function isPanelActive() { - return window['__HORIZON_DEV_HOOK__']; -} - -// serializes store and creates expanded object with baked-in containing current computed values -function makeStoreSnapshot({ type, data }) { - const expanded = {}; - Object.keys(data.store.$c).forEach(key => { - expanded[key] = data.store[key]; - }); - data.store.expanded = expanded; - const snapshot = makeProxySnapshot({ - data, - type, - sessionId, - }); - return snapshot; -} - -// safely serializes variables containing values wrapped in Proxy object -function makeProxySnapshot(obj) { - let clone; - try { - if (!obj) { - return obj; - } - if (obj.nativeEvent) return obj.type + 'Event'; - if (typeof obj === 'function') { - return obj.toString(); - } - if (Array.isArray(obj)) { - clone = []; - obj.forEach(item => clone.push(makeProxySnapshot(item))); - return clone; - } else if (typeof obj === 'object') { - clone = {}; - Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value))); - return clone; - } - return obj; - } catch (err) { - throw console.log('cannot serialize object. ' + err); - } -} - -export const devtools = { - // returns vNode id from horizon devtools - getVNodeId: vNode => { - if (!isPanelActive()) return; - window['__HORIZON_DEV_HOOK__'].send(); // update list first - return window['__HORIZON_DEV_HOOK__'].getVnodeId(vNode); - }, - // sends horizonx devtool message to extension - emit: (type, data) => { - if (!isPanelActive()) return; - window.postMessage({ - type: 'HORIZON_DEV_TOOLS', - payload: makeStoreSnapshot({ type, data }), - from: 'dev tool hook', - }); - }, -}; - -// collects components that are dependant on horizonx store and their ids -function getAffectedComponents() { - const allStores = getAllStores(); - const keys = Object.keys(allStores); - let res = {}; - keys.forEach(key => { - const subRes = new Set(); - const process = Array.from(allStores[key].$config.state._horizonObserver.keyVNodes.values()); - while (process.length) { - let pivot = process.shift(); - if (pivot?.tag) subRes.add(pivot); - if (pivot?.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item)); - } - res[key] = Array.from(subRes).map(vnode => { - return { - name: vnode?.type - .toString() - .replace(/\{.*\}/, '{...}') - .replace('function ', ''), - nodeId: window.__HORIZON_DEV_HOOK__.getVnodeId(vnode), - }; - }); - }); - - return res; -} - -// listens to messages from background -window.addEventListener('message', messageEvent => { - if (messageEvent?.data?.payload?.type === 'horizonx request observed components') { - // get observed components - setTimeout(() => { - window.postMessage({ - type: 'HORIZON_DEV_TOOLS', - payload: { type: OBSERVED_COMPONENTS, data: getAffectedComponents() }, - from: 'dev tool hook', - }); - }, 100); - } - - // executes store action - if (messageEvent.data?.payload?.type === 'horizonx executue action') { - const data = messageEvent.data.payload.data; - const store = getStore(data.storeId); - if (!store?.[data.action]) return; - - const action = store[data.action]; - const params = data.params; - action(...params); - } -}); diff --git a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts deleted file mode 100644 index 45526106..00000000 --- a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * InulaJS 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 { getObserver } from '../ProxyHandler'; -import { isSame, isValidIntegerKey } from '../../CommonUtils'; -import { get as objectGet } from './ObjectProxyHandler'; -import { resolveMutation } from '../../CommonUtils'; -import { isPanelActive } from '../../devtools'; - -export function createArrayProxy(rawObj: any[]): any[] { - const handle = { - get, - set, - }; - - return new Proxy(rawObj, handle); -} - -function get(rawObj: any[], key: string, receiver: any) { - if (key === 'watch') { - const observer = getObserver(rawObj); - - return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { - if (!observer.watchers[prop]) { - observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; - } - observer.watchers[prop].push(handler); - return () => { - observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); - }; - }; - } - - if (isValidIntegerKey(key) || key === 'length') { - return objectGet(rawObj, key, receiver); - } - - return Reflect.get(rawObj, key, receiver); -} - -function set(rawObj: any[], key: string, value: any, receiver: any) { - const oldValue = rawObj[key]; - const oldLength = rawObj.length; - const newValue = value; - - const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - - const ret = Reflect.set(rawObj, key, newValue, receiver); - - const newLength = rawObj.length; - const observer = getObserver(rawObj); - - const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : { mutation: true, from: [], to: rawObj }; - - if (!isSame(newValue, oldValue)) { - // 值不一样,触发监听器 - if (observer.watchers?.[key]) { - observer.watchers[key].forEach(cb => { - cb(key, oldValue, newValue, mutation); - }); - } - - // 触发属性变化 - observer.setProp(key, mutation); - } - - if (oldLength !== newLength) { - // 触发数组的大小变化 - observer.setProp('length', mutation); - } - - return ret; -} diff --git a/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts deleted file mode 100644 index f351065b..00000000 --- a/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * InulaJS 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 { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; -import { isMap, isWeakMap, isSame } from '../../CommonUtils'; -import { resolveMutation } from '../../CommonUtils'; -import { isPanelActive } from '../../devtools'; - -const COLLECTION_CHANGE = '_collectionChange'; -const handler = { - get, - set, - add, - delete: deleteFun, - clear, - has, - entries, - forEach, - keys, - values, - // 判断Symbol类型,兼容IE - [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf, -}; - -export function createCollectionProxy(rawObj: Object, hookObserver = true): Object { - const boundHandler = {}; - Object.entries(handler).forEach(([id, val]) => { - boundHandler[id] = (...args: any[]) => { - return (val as any)(...args, hookObserver); - }; - }); - return new Proxy(rawObj, { ...boundHandler }); -} - -function get(rawObj: { size: number }, key: any, receiver: any): any { - if (key === 'size') { - return size(rawObj); - } else if (key === 'get') { - return getFun.bind(null, rawObj); - } else if (Object.prototype.hasOwnProperty.call(handler, key)) { - const value = Reflect.get(handler, key, receiver); - return value.bind(null, rawObj); - } else if (key === 'watch') { - const observer = getObserver(rawObj); - - return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { - if (!observer.watchers[prop]) { - observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; - } - observer.watchers[prop].push(handler); - return () => { - observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); - }; - }; - } - - return Reflect.get(rawObj, key, receiver); -} - -function getFun(rawObj: { get: (key: any) => any }, key: any) { - const observer = getObserver(rawObj); - observer.useProp(key); - - const value = rawObj.get(key); - // 对于value也需要进一步代理 - const valProxy = createProxy(value, hookObserverMap.get(rawObj)); - - return valProxy; -} - -// Map的set方法 -function set( - rawObj: { get: (key: any) => any; set: (key: any, value: any) => any; has: (key: any) => boolean }, - key: any, - value: any -) { - const oldValue = rawObj.get(key); - const newValue = value; - rawObj.set(key, newValue); - const valChange = !isSame(newValue, oldValue); - const observer = getObserver(rawObj); - - const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : { mutation: true, from: null, to: rawObj }; - - if (valChange || !rawObj.has(key)) { - observer.setProp(COLLECTION_CHANGE, mutation); - } - - if (valChange) { - if (observer.watchers?.[key]) { - observer.watchers[key].forEach(cb => { - cb(key, oldValue, newValue, mutation); - }); - } - - observer.setProp(key, mutation); - } - - return rawObj; -} - -// Set的add方法 -function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object { - const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - if (!rawObj.has(value)) { - rawObj.add(value); - - const observer = getObserver(rawObj); - const mutation = isPanelActive() - ? resolveMutation(oldCollection, rawObj) - : { mutation: true, from: null, to: rawObj }; - observer.setProp(value, mutation); - observer.setProp(COLLECTION_CHANGE, mutation); - } - - return rawObj; -} - -function has(rawObj: { has: (string) => boolean }, key: any): boolean { - const observer = getObserver(rawObj); - observer.useProp(key); - - return rawObj.has(key); -} - -function clear(rawObj: { size: number; clear: () => void }) { - const oldSize = rawObj.size; - rawObj.clear(); - - if (oldSize > 0) { - const observer = getObserver(rawObj); - observer.allChange(); - } -} - -function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) { - const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - if (rawObj.has(key)) { - rawObj.delete(key); - - const observer = getObserver(rawObj); - const mutation = isPanelActive() - ? resolveMutation(oldCollection, rawObj) - : { mutation: true, from: null, to: rawObj }; - observer.setProp(key, mutation); - observer.setProp(COLLECTION_CHANGE, mutation); - - return true; - } - - return false; -} - -function size(rawObj: { size: number }) { - const observer = getObserver(rawObj); - observer.useProp(COLLECTION_CHANGE); - return rawObj.size; -} - -function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.keys()); -} - -function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.values()); -} - -function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.entries(), true); -} - -function forOf(rawObj: { - entries: () => { next: () => { value: any; done: boolean } }; - values: () => { next: () => { value: any; done: boolean } }; -}) { - const isMapType = isMap(rawObj) || isWeakMap(rawObj); - const iterator = isMapType ? rawObj.entries() : rawObj.values(); - return wrapIterator(rawObj, iterator, isMapType); -} - -function forEach( - rawObj: { forEach: (callback: (value: any, key: any) => void) => void }, - callback: (valProxy: any, keyProxy: any, rawObj: any) => void -) { - const observer = getObserver(rawObj); - observer.useProp(COLLECTION_CHANGE); - rawObj.forEach((value, key) => { - const valProxy = createProxy(value, hookObserverMap.get(rawObj)); - const keyProxy = createProxy(key, hookObserverMap.get(rawObj)); - // 最后一个参数要返回代理对象 - return callback(valProxy, keyProxy, rawObj); - }); -} - -function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) { - const observer = getObserver(rawObj); - const hookObserver = hookObserverMap.get(rawObj); - observer.useProp(COLLECTION_CHANGE); - - return { - next() { - const { value, done } = rawIt.next(); - if (done) { - return { value: createProxy(value, hookObserver), done }; - } - - observer.useProp(COLLECTION_CHANGE); - - let newVal; - if (isPair) { - newVal = [createProxy(value[0], hookObserver), createProxy(value[1], hookObserver)]; - } else { - newVal = createProxy(value, hookObserver); - } - - return { value: newVal, done }; - }, - // 判断Symbol类型,兼容IE - [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() { - return this; - }, - }; -} diff --git a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts deleted file mode 100644 index 19ea5be2..00000000 --- a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * InulaJS 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 { isSame, resolveMutation } from '../../CommonUtils'; -import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; -import { OBSERVER_KEY } from '../../Constants'; -import { isPanelActive } from '../../devtools'; - -export function createObjectProxy(rawObj: T, singleLevel = false): ProxyHandler { - const proxy = new Proxy(rawObj, { - get: (...args) => get(...args, singleLevel), - set, - }); - - return proxy; -} - -export function get(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any { - // The observer object of symbol ('_horizonObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep. - if (key === OBSERVER_KEY) { - return undefined; - } - - const observer = getObserver(rawObj); - - if (key === 'watch') { - return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => { - if (!observer.watchers[prop]) { - observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; - } - observer.watchers[prop].push(handler); - return () => { - observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); - }; - }; - } - - if (key === 'addListener') { - return observer.addListener.bind(observer); - } - - if (key === 'removeListener') { - return observer.removeListener.bind(observer); - } - - observer.useProp(key); - - const value = Reflect.get(rawObj, key, receiver); - - // 对于prototype不做代理 - if (key !== 'prototype') { - // 对于value也需要进一步代理 - const valProxy = singleLevel ? value : createProxy(value, hookObserverMap.get(rawObj)); - - return valProxy; - } - - return value; -} - -export function set(rawObj: object, key: string, value: any, receiver: any): boolean { - const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - const observer = getObserver(rawObj); - - if (value && key == 'removeListener') { - observer.removeListener(value); - } - const oldValue = rawObj[key]; - const newValue = value; - - const ret = Reflect.set(rawObj, key, newValue, receiver); - const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : { mutation: true, from: null, to: rawObj }; - - if (!isSame(newValue, oldValue)) { - if (observer.watchers?.[key]) { - observer.watchers[key].forEach(cb => { - cb(key, oldValue, newValue, mutation); - }); - } - observer.setProp(key, mutation); - } - return ret; -} diff --git a/libs/inula/README.md b/libs/inula/README.md new file mode 100644 index 00000000..95818b1f --- /dev/null +++ b/libs/inula/README.md @@ -0,0 +1 @@ +# `inula` diff --git a/libs/horizon/global.d.ts b/libs/inula/global.d.ts similarity index 90% rename from libs/horizon/global.d.ts rename to libs/inula/global.d.ts index 7919828b..963ff285 100644 --- a/libs/horizon/global.d.ts +++ b/libs/inula/global.d.ts @@ -19,3 +19,5 @@ declare var isDev: boolean; declare var isTest: boolean; declare const __VERSION__: string; +declare var setImmediate: Function; +declare var __INULA_DEV_HOOK__: any; diff --git a/libs/horizon/index.ts b/libs/inula/index.ts similarity index 80% rename from libs/horizon/index.ts rename to libs/inula/index.ts index 52829cc9..7668ddb2 100644 --- a/libs/horizon/index.ts +++ b/libs/inula/index.ts @@ -18,6 +18,8 @@ import { TYPE_PROFILER as Profiler, TYPE_STRICT_MODE as StrictMode, TYPE_SUSPENSE as Suspense, + TYPE_FORWARD_REF as ForwardRef, + TYPE_MEMO as Memo, } from './src/external/JSXElementType'; import { Component, PureComponent } from './src/renderer/components/BaseClassComponent'; @@ -42,9 +44,6 @@ import { useState, useDebugValue, } from './src/renderer/hooks/HookExternal'; -import { asyncUpdates } from './src/renderer/TreeBuilder'; -import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue'; -import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler'; import { isContextProvider, isContextConsumer, @@ -55,17 +54,11 @@ import { isLazy, isMemo, isPortal, -} from './src/external/HorizonIs'; -import { createStore, useStore, clearStore } from './src/horizonx/store/StoreHandler'; -import * as reduxAdapter from './src/horizonx/adapters/redux'; -import { watch } from './src/horizonx/proxy/watch'; - -// act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 -const act = fun => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -}; +} from './src/external/InulaIs'; +import { createStore, useStore, clearStore } from './src/inulax/store/StoreHandler'; +import * as reduxAdapter from './src/inulax/adapters/redux'; +import { watch } from './src/inulax/proxy/watch'; +import { act } from './src/external/TestUtil'; import { render, @@ -75,7 +68,10 @@ import { unmountComponentAtNode, } from './src/dom/DOMExternal'; -const Horizon = { +import { syncUpdates as flushSync } from './src/renderer/TreeBuilder'; +import { toRaw } from './src/inulax/proxy/ProxyHandler'; + +const Inula = { Children, createRef, Component, @@ -94,10 +90,6 @@ const Horizon = { useReducer, useRef, useState, - Fragment, - Profiler, - StrictMode, - Suspense, createElement, cloneElement, isValidElement, @@ -107,6 +99,7 @@ const Horizon = { findDOMNode, unmountComponentAtNode, act, + flushSync, createStore, useStore, clearStore, @@ -121,6 +114,12 @@ const Horizon = { isPortal, isContextProvider, isContextConsumer, + ForwardRef, + Memo, + Fragment, + Profiler, + StrictMode, + Suspense, }; export const version = __VERSION__; @@ -143,10 +142,6 @@ export { useReducer, useRef, useState, - Fragment, - Profiler, - StrictMode, - Suspense, createElement, cloneElement, isValidElement, @@ -156,12 +151,14 @@ export { findDOMNode, unmountComponentAtNode, act, - // 状态管理器HorizonX接口 + flushSync, + // 状态管理器InulaX接口 createStore, useStore, clearStore, reduxAdapter, watch, + toRaw, // 兼容ReactIs isFragment, isElement, @@ -172,6 +169,12 @@ export { isPortal, isContextProvider, isContextConsumer, + ForwardRef, + Memo, + Fragment, + Profiler, + StrictMode, + Suspense, }; -export default Horizon; +export default Inula; diff --git a/libs/horizon/src/renderer/components/ForwardRef.ts b/libs/inula/jsx-dev-runtime.ts similarity index 70% rename from libs/horizon/src/renderer/components/ForwardRef.ts rename to libs/inula/jsx-dev-runtime.ts index 7acb1290..f768f977 100644 --- a/libs/horizon/src/renderer/components/ForwardRef.ts +++ b/libs/inula/jsx-dev-runtime.ts @@ -1,7 +1,7 @@ /* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * - * InulaJS is licensed under Mulan PSL v2. + * 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: * @@ -13,11 +13,12 @@ * See the Mulan PSL v2 for more details. */ -import { TYPE_FORWARD_REF } from '../../external/JSXElementType'; +import { + TYPE_FRAGMENT as Fragment, +} from './src/external/JSXElementType'; +import { jsx as jsxDEV } from './src/external/JSXElement'; -export function forwardRef(render: Function) { - return { - vtype: TYPE_FORWARD_REF, - render, - }; -} +export { + jsxDEV, + Fragment +}; diff --git a/libs/horizon/src/event/utils.ts b/libs/inula/jsx-runtime.ts similarity index 55% rename from libs/horizon/src/event/utils.ts rename to libs/inula/jsx-runtime.ts index c5bbb267..f130dc06 100644 --- a/libs/horizon/src/event/utils.ts +++ b/libs/inula/jsx-runtime.ts @@ -1,7 +1,7 @@ /* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * - * InulaJS is licensed under Mulan PSL v2. + * 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: * @@ -13,13 +13,13 @@ * See the Mulan PSL v2 for more details. */ -export function isInputElement(dom?: HTMLElement): boolean { - return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement; -} +import { + TYPE_FRAGMENT as Fragment, +} from './src/external/JSXElementType'; +import { jsx, jsx as jsxs } from './src/external/JSXElement'; -export function setPropertyWritable(obj, propName) { - const desc = Object.getOwnPropertyDescriptor(obj, propName); - if (!desc || !desc.writable) { - Object.defineProperty(obj, propName, { writable: true }); - } -} +export { + jsx, + jsxs, + Fragment +}; diff --git a/libs/horizon/npm/index.js b/libs/inula/npm/index.js similarity index 83% rename from libs/horizon/npm/index.js rename to libs/inula/npm/index.js index b2c3033b..7cf8fa19 100644 --- a/libs/horizon/npm/index.js +++ b/libs/inula/npm/index.js @@ -16,7 +16,7 @@ 'use strict'; if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/horizon.production.min.js'); + module.exports = require('./cjs/inula.production.min.js'); } else { - module.exports = require('./cjs/horizon.development.js'); + module.exports = require('./cjs/inula.development.js'); } diff --git a/libs/horizon/package.json b/libs/inula/package.json similarity index 59% rename from libs/horizon/package.json rename to libs/inula/package.json index 8b7332a0..0e57bbe8 100644 --- a/libs/horizon/package.json +++ b/libs/inula/package.json @@ -1,10 +1,10 @@ { - "name": "@cloudsop/horizon", - "description": "Horizon is a JavaScript framework library.", + "name": "inulajs", + "description": "InulaJS is a JavaScript framework library.", "keywords": [ - "horizon" + "inula" ], - "version": "0.0.33", + "version": "0.0.52", "homepage": "", "bugs": "", "main": "index.js", diff --git a/libs/horizon/src/dom/DOMExternal.ts b/libs/inula/src/dom/DOMExternal.ts similarity index 94% rename from libs/horizon/src/dom/DOMExternal.ts rename to libs/inula/src/dom/DOMExternal.ts index bbbece59..b8ca5cb4 100644 --- a/libs/horizon/src/dom/DOMExternal.ts +++ b/libs/inula/src/dom/DOMExternal.ts @@ -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') { @@ -71,7 +73,7 @@ function executeRender(children: any, container: Container, callback?: Callback) } function findDOMNode(domOrEle?: Element): null | Element | Text { - if (domOrEle == null) { + if (domOrEle === null || domOrEle === undefined) { return null; } @@ -101,7 +103,7 @@ function removeRootEventLister(container: Container) { // 卸载入口 function destroy(container: Container): boolean { - if (container && container._treeRoot) { + if (container._treeRoot) { syncUpdates(() => { executeRender(null, container, () => { removeRootEventLister(container); diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/inula/src/dom/DOMInternalKeys.ts similarity index 94% rename from libs/horizon/src/dom/DOMInternalKeys.ts rename to libs/inula/src/dom/DOMInternalKeys.ts index 3c356644..baa40fcc 100644 --- a/libs/horizon/src/dom/DOMInternalKeys.ts +++ b/libs/inula/src/dom/DOMInternalKeys.ts @@ -22,9 +22,9 @@ import type { Container, Props } from './DOMOperator'; import { DomComponent, DomText, TreeRoot } from '../renderer/vnode/VNodeTags'; -const INTERNAL_VNODE = '_horizon_VNode'; -const INTERNAL_PROPS = '_horizon_Props'; -const INTERNAL_NONDELEGATEEVENTS = '_horizon_NonDelegatedEvents'; +const INTERNAL_VNODE = '_inula_VNode'; +const INTERNAL_PROPS = '_inula_Props'; +const INTERNAL_NONDELEGATEEVENTS = '_inula_NonDelegatedEvents'; // 通过 VNode 实例获取 DOM 节点 export function getDom(vNode: VNode): Element | Text | null { diff --git a/libs/horizon/src/dom/DOMOperator.ts b/libs/inula/src/dom/DOMOperator.ts similarity index 86% rename from libs/horizon/src/dom/DOMOperator.ts rename to libs/inula/src/dom/DOMOperator.ts index cf0fd739..a4adebb4 100644 --- a/libs/horizon/src/dom/DOMOperator.ts +++ b/libs/inula/src/dom/DOMOperator.ts @@ -16,7 +16,7 @@ import { saveVNode, updateVNodeProps } from './DOMInternalKeys'; import { createDom } from './utils/DomCreator'; import { getSelectionInfo, resetSelectionRange, SelectionData } from './SelectionRangeHandler'; -import { shouldAutoFocus } from './utils/Common'; +import { isDocument, shouldAutoFocus } from './utils/Common'; import { NSS } from './utils/DomCreator'; import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler'; import type { VNode } from '../renderer/Types'; @@ -26,6 +26,7 @@ import { isNativeElement, validateProps } from './validators/ValidateProps'; import { watchValueChange } from './valueHandler/ValueChangeHandler'; import { DomComponent, DomText } from '../renderer/vnode/VNodeTags'; import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp'; +import {getCurrentRoot} from '../renderer/RootStack'; export type Props = Record & { autoFocus?: boolean; @@ -45,7 +46,7 @@ function getChildNS(parentNS: string | null, tagName: string): string { return NSS.html; } - if (parentNS == null || parentNS === NSS.html) { + if (parentNS === null || parentNS === NSS.html) { // 没有父命名空间,或父命名空间为xhtml return NSS[tagName] ?? NSS.html; } @@ -70,7 +71,12 @@ export function resetAfterSubmit(): void { // 创建 DOM 对象 export function newDom(tagName: string, props: Props, parentNamespace: string, vNode: VNode): Element { - const dom: Element = createDom(tagName, parentNamespace); + // document取值于treeRoot对应的DOM的ownerDocument。 + // 解决:在iframe中使用top的inula时,inula在创建DOM时用到的document并不是iframe的document,而是top中的document的问题。 + const rootDom = getCurrentRoot().realNode; + const doc = isDocument(rootDom) ? rootDom : rootDom.ownerDocument; + + const dom: Element = createDom(tagName, parentNamespace, doc); // 将 vNode 节点挂到 DOM 对象上 saveVNode(vNode, dom); // 将属性挂到 DOM 对象上 @@ -124,7 +130,8 @@ export function isTextChild(type: string, props: Props): boolean { return ( props.dangerouslySetInnerHTML && typeof props.dangerouslySetInnerHTML === 'object' && - props.dangerouslySetInnerHTML.__html != null + props.dangerouslySetInnerHTML.__html !== null && + props.dangerouslySetInnerHTML.__html !== undefined ); } } @@ -135,14 +142,14 @@ export function newTextDom(text: string, processing: VNode): Text { return textNode; } -// 提交vNode的类型为Component或者Text的更新 +// 提交vNode的类型为DomComponent或者DomText的更新 export function submitDomUpdate(tag: string, vNode: VNode) { const newProps = vNode.props; const element: Element | null = vNode.realNode; if (tag === DomComponent) { // DomComponent类型 - if (element != null) { + if (element !== null && element !== undefined) { const type = vNode.type; const changeList = vNode.changeList; vNode.changeList = null; @@ -152,7 +159,14 @@ export function submitDomUpdate(tag: string, vNode: VNode) { updateVNodeProps(element, newProps); // 应用diff更新Properties. // 当一个选中的radio改变名称,浏览器使另一个radio的复选框为false. - if (type === 'input' && newProps.type === 'radio' && newProps.name != null && newProps.checked != null) { + if ( + type === 'input' + && newProps.type === 'radio' + && newProps.name !== null + && newProps.name !== undefined + && newProps.checked !== null + && newProps.checked !== undefined + ) { updateCommonProp(element, 'checked', newProps.checked, true); } const isNativeTag = isNativeElement(type, newProps); @@ -196,7 +210,7 @@ export function hideDom(tag: string, dom: Element | Text) { } // 不隐藏元素 -export function unHideDom(tag: string, dom: Element | Text, props: Props) { +export function unHideDom(tag: string, dom: Element | Text, props?: Props) { if (tag === DomComponent) { dom.style.display = adjustStyleValue('display', props?.style?.display ?? ''); } else if (tag === DomText) { diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/inula/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts similarity index 88% rename from libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts rename to libs/inula/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index 32427a36..b3d83f8a 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/inula/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import { allDelegatedHorizonEvents } from '../../event/EventHub'; +import { allDelegatedInulaEvents } from '../../event/EventHub'; import { updateCommonProp } from './UpdateCommonProp'; import { setStyles } from './StyleHandler'; import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding'; @@ -35,7 +35,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i } else if (isEventProp(propName)) { // 事件监听属性处理 const currentRoot = getCurrentRoot(); - if (!allDelegatedHorizonEvents.has(propName)) { + if (!allDelegatedInulaEvents.has(propName)) { listenNonDelegatedEvent(propName, dom, propVal); } else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) { lazyDelegateOnRoot(currentRoot, propName); @@ -48,7 +48,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i } } else if (propName === 'dangerouslySetInnerHTML') { dom.innerHTML = propVal.__html; - } else if (!isInit || (isInit && propVal != null)) { + } else if (!isInit || (propVal !== null && propVal !== undefined)) { updateCommonProp(dom, propName, propVal, isNativeTag); } } @@ -70,7 +70,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { for (let i = 0; i < oldPropsLength; i++) { propName = keysOfOldProps[i]; // 新属性中包含该属性或者该属性为空值的属性不需要处理 - if (keysOfNewProps.includes(propName) || oldProps[propName] == null) { + if ( oldProps[propName] === null || oldProps[propName] === undefined || keysOfNewProps.includes(propName)) { continue; } @@ -84,7 +84,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { } else if (propName === 'autoFocus' || propName === 'children' || propName === 'dangerouslySetInnerHTML') { continue; } else if (isEventProp(propName)) { - if (!allDelegatedHorizonEvents.has(propName)) { + if (!allDelegatedInulaEvents.has(propName)) { toUpdateProps[propName] = null; } } else { @@ -103,9 +103,13 @@ export function compareProps(oldProps: Object, newProps: Object): Object { for (let i = 0; i < keysOfNewProps.length; i++) { propName = keysOfNewProps[i]; newPropValue = newProps[propName]; - oldPropValue = oldProps != null ? oldProps[propName] : null; + oldPropValue = oldProps !== null && oldProps !== undefined ? oldProps[propName] : null; - if (newPropValue === oldPropValue || (newPropValue == null && oldPropValue == null)) { + if ( + newPropValue === oldPropValue + || ((newPropValue === null || newPropValue === undefined) + && (oldPropValue === null || oldPropValue === undefined)) + ) { // 新旧属性值未发生变化,或者新旧属性皆为空值,不需要进行处理 continue; } @@ -140,7 +144,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { } else if (propName === 'dangerouslySetInnerHTML') { newHTML = newPropValue ? newPropValue.__html : undefined; oldHTML = oldPropValue ? oldPropValue.__html : undefined; - if (newHTML != null) { + if (newHTML !== null && newHTML !== undefined) { if (oldHTML !== newHTML) { toUpdateProps[propName] = newPropValue; } @@ -151,7 +155,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { } } else if (isEventProp(propName)) { const currentRoot = getCurrentRoot(); - if (!allDelegatedHorizonEvents.has(propName)) { + if (!allDelegatedInulaEvents.has(propName)) { toUpdateProps[propName] = newPropValue; } else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) { lazyDelegateOnRoot(currentRoot, propName); diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts b/libs/inula/src/dom/DOMPropertiesHandler/StyleHandler.ts similarity index 69% rename from libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts rename to libs/inula/src/dom/DOMPropertiesHandler/StyleHandler.ts index fa442f1d..def4be1b 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts +++ b/libs/inula/src/dom/DOMPropertiesHandler/StyleHandler.ts @@ -13,6 +13,37 @@ * See the Mulan PSL v2 for more details. */ +/** + * 不需要加长度单位的 css 属性 + */ +const noUnitCSS = [ + 'animationIterationCount', + 'columnCount', + 'columns', + 'gridArea', + 'fontWeight', + 'lineClamp', + 'lineHeight', + 'opacity', + 'order', + 'orphans', + 'tabSize', + 'widows', + 'zIndex', + 'zoom', +]; + +const length = noUnitCSS.length; +for (let i = 0; i < length; i++) { + const cssKey = noUnitCSS[i]; + const attributeKey = cssKey.charAt(0).toUpperCase() + cssKey.slice(1); + + // css 兼容性前缀 webkit: chrome, mo: IE或者Edge, Moz: 火狐 + noUnitCSS.push('Webkit' + attributeKey); + noUnitCSS.push('mo' + attributeKey); + noUnitCSS.push('Moz' + attributeKey); +} + function isNeedUnitCSS(styleName: string) { return !( noUnitCSS.includes(styleName) || @@ -36,7 +67,7 @@ export function adjustStyleValue(name, value) { if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) { validValue = `${value}px`; - } else if (value === '' || value == null || typeof value === 'boolean') { + } else if (value === '' || value === null || value === undefined || typeof value === 'boolean') { validValue = ''; } @@ -54,27 +85,12 @@ export function setStyles(dom, styles) { const style = dom.style; Object.keys(styles).forEach(name => { const styleVal = styles[name]; - - style[name] = adjustStyleValue(name, styleVal); + // 以--开始的样式直接设置即可 + if (name.indexOf('--') === 0) { + style.setProperty(name, styleVal); + } else { + // 使用这种赋值方式,浏览器可以将'WebkitLineClamp', 'backgroundColor'分别识别为'-webkit-line-clamp'和'backgroud-color' + style[name] = adjustStyleValue(name, styleVal); + } }); } - -/** - * 不需要加长度单位的 css 属性 - */ -const noUnitCSS = [ - 'animationIterationCount', - 'columnCount', - 'columns', - 'gridArea', - 'fontWeight', - 'lineClamp', - 'lineHeight', - 'opacity', - 'order', - 'orphans', - 'tabSize', - 'widows', - 'zIndex', - 'zoom', -]; diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts b/libs/inula/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts similarity index 100% rename from libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts rename to libs/inula/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts diff --git a/libs/horizon/src/dom/SelectionRangeHandler.ts b/libs/inula/src/dom/SelectionRangeHandler.ts similarity index 99% rename from libs/horizon/src/dom/SelectionRangeHandler.ts rename to libs/inula/src/dom/SelectionRangeHandler.ts index 62e8c6d2..b0b8c88f 100644 --- a/libs/horizon/src/dom/SelectionRangeHandler.ts +++ b/libs/inula/src/dom/SelectionRangeHandler.ts @@ -30,7 +30,7 @@ function setSelectionRange(dom: HTMLInputElement | HTMLTextAreaElement, range) { const { start, end } = range; let realEnd = end; - if (realEnd == null) { + if (realEnd === null || realEnd === undefined) { realEnd = start; } diff --git a/libs/horizon/src/dom/utils/Common.ts b/libs/inula/src/dom/utils/Common.ts similarity index 92% rename from libs/horizon/src/dom/utils/Common.ts rename to libs/inula/src/dom/utils/Common.ts index 4105ade0..50dc845f 100644 --- a/libs/horizon/src/dom/utils/Common.ts +++ b/libs/inula/src/dom/utils/Common.ts @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import { HorizonDom } from './Interface'; +import { InulaDom } from './Interface'; import { Props } from '../DOMOperator'; /** * 获取当前聚焦的 input 或者 textarea 元素 * @param doc 指定 document */ -export function getFocusedDom(doc?: Document): HorizonDom | null { +export function getFocusedDom(doc?: Document): InulaDom | null { const currentDocument = doc ?? document; return currentDocument.activeElement ?? currentDocument.body; @@ -84,3 +84,7 @@ const types = ['button', 'input', 'select', 'textarea']; export function shouldAutoFocus(tagName: string, props: Props): boolean { return types.includes(tagName) ? Boolean(props.autoFocus) : false; } + +export function isNotNull(object: any): boolean { + return object !== null && object !== undefined; +} diff --git a/libs/horizon/src/dom/utils/DomCreator.ts b/libs/inula/src/dom/utils/DomCreator.ts similarity index 89% rename from libs/horizon/src/dom/utils/DomCreator.ts rename to libs/inula/src/dom/utils/DomCreator.ts index 611a49e6..92c8ec90 100644 --- a/libs/horizon/src/dom/utils/DomCreator.ts +++ b/libs/inula/src/dom/utils/DomCreator.ts @@ -20,15 +20,15 @@ export const NSS = { }; // 创建DOM元素 -export function createDom(tagName: string, parentNamespace: string): Element { +export function createDom(tagName: string, parentNamespace: string, doc: Document): Element { let dom: Element; const selfNamespace = NSS[tagName] || NSS.html; const ns = parentNamespace !== NSS.html ? parentNamespace : selfNamespace; if (ns !== NSS.html) { - dom = document.createElementNS(ns, tagName); + dom = doc.createElementNS(ns, tagName); } else { - dom = document.createElement(tagName); + dom = doc.createElement(tagName); } return dom; } diff --git a/libs/horizon/src/dom/utils/Interface.ts b/libs/inula/src/dom/utils/Interface.ts similarity index 80% rename from libs/horizon/src/dom/utils/Interface.ts rename to libs/inula/src/dom/utils/Interface.ts index 367bcebf..9cbc2536 100644 --- a/libs/horizon/src/dom/utils/Interface.ts +++ b/libs/inula/src/dom/utils/Interface.ts @@ -17,8 +17,8 @@ export interface Props { [propName: string]: any; } -export interface HorizonSelect extends HTMLSelectElement { +export interface InulaSelect extends HTMLSelectElement { _multiple?: boolean; } -export type HorizonDom = Element | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; +export type InulaDom = Element | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; diff --git a/libs/horizon/src/dom/validators/PropertiesData.ts b/libs/inula/src/dom/validators/PropertiesData.ts similarity index 100% rename from libs/horizon/src/dom/validators/PropertiesData.ts rename to libs/inula/src/dom/validators/PropertiesData.ts diff --git a/libs/horizon/src/dom/validators/ValidateProps.ts b/libs/inula/src/dom/validators/ValidateProps.ts similarity index 94% rename from libs/horizon/src/dom/validators/ValidateProps.ts rename to libs/inula/src/dom/validators/ValidateProps.ts index 8481d84b..1a328a33 100644 --- a/libs/horizon/src/dom/validators/ValidateProps.ts +++ b/libs/inula/src/dom/validators/ValidateProps.ts @@ -73,7 +73,7 @@ export function isInvalidValue( propDetails: PropDetails | null, isNativeTag: boolean ): boolean { - if (value == null) { + if (value === null || value === undefined) { return true; } @@ -98,13 +98,13 @@ export function validateProps(type, props) { return; } - // 非内置的变迁 + // 非内置的元素 if (!isNativeElement(type, props)) { return; } // style属性必须是对象 - if (props.style != null && typeof props.style !== 'object') { + if (props.style !== null && props.style !== undefined && typeof props.style !== 'object') { throw new Error('style should be a object.'); } diff --git a/libs/horizon/src/dom/valueHandler/InputValueHandler.ts b/libs/inula/src/dom/valueHandler/InputValueHandler.ts similarity index 93% rename from libs/horizon/src/dom/valueHandler/InputValueHandler.ts rename to libs/inula/src/dom/valueHandler/InputValueHandler.ts index cbee328c..0b29bcfd 100644 --- a/libs/horizon/src/dom/valueHandler/InputValueHandler.ts +++ b/libs/inula/src/dom/valueHandler/InputValueHandler.ts @@ -29,7 +29,7 @@ function getInitValue(dom: HTMLInputElement, props: Props) { export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) { // checked属于必填属性,无法置 let { checked } = props; - if (checked == null) { + if (checked === undefined) { checked = getInitValue(dom, props).initChecked; } @@ -45,12 +45,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) { export function updateInputValue(dom: HTMLInputElement, props: Props) { const { value, checked } = props; - if (value != null) { + if (value !== undefined) { // 处理 dom.value 逻辑 if (dom.value !== String(value)) { dom.value = String(value); } - } else if (checked != null) { + } else if (checked !== undefined) { updateCommonProp(dom, 'checked', checked, true); } } @@ -60,7 +60,7 @@ export function setInitInputValue(dom: HTMLInputElement, props: Props) { const { value, defaultValue } = props; const { initValue, initChecked } = getInitValue(dom, props); - if (value != null || defaultValue != null) { + if (value !== undefined || defaultValue !== undefined) { // value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串 const initValueStr = String(initValue); diff --git a/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts b/libs/inula/src/dom/valueHandler/OptionValueHandler.ts similarity index 100% rename from libs/horizon/src/dom/valueHandler/OptionValueHandler.ts rename to libs/inula/src/dom/valueHandler/OptionValueHandler.ts diff --git a/libs/horizon/src/dom/valueHandler/SelectValueHandler.ts b/libs/inula/src/dom/valueHandler/SelectValueHandler.ts similarity index 84% rename from libs/horizon/src/dom/valueHandler/SelectValueHandler.ts rename to libs/inula/src/dom/valueHandler/SelectValueHandler.ts index d20dfee7..083fed75 100644 --- a/libs/horizon/src/dom/valueHandler/SelectValueHandler.ts +++ b/libs/inula/src/dom/valueHandler/SelectValueHandler.ts @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import { HorizonSelect, Props } from '../utils/Interface'; +import { InulaSelect, Props } from '../utils/Interface'; function updateMultipleValue(options, newValues) { const newValueSet = new Set(); @@ -54,14 +54,14 @@ function updateValue(options, newValues: any, isMultiple: boolean) { } } -export function getSelectPropsWithoutValue(dom: HorizonSelect, properties: Object) { +export function getSelectPropsWithoutValue(dom: InulaSelect, properties: Object) { return { ...properties, value: undefined, }; } -export function updateSelectValue(dom: HorizonSelect, props: Props, isInit = false) { +export function updateSelectValue(dom: InulaSelect, props: Props, isInit = false) { const { value, defaultValue, multiple } = props; const oldMultiple = dom._multiple !== undefined ? dom._multiple : dom.multiple; @@ -69,18 +69,18 @@ export function updateSelectValue(dom: HorizonSelect, props: Props, isInit = fal dom._multiple = newMultiple; // 设置了 value 属性 - if (value != null) { + if (value !== null && value !== undefined) { updateValue(dom.options, value, newMultiple); } else if (oldMultiple !== newMultiple) { // 修改了 multiple 属性 // 切换 multiple 之后,如果设置了 defaultValue 需要重新应用 - if (defaultValue != null) { + if (defaultValue !== null && defaultValue !== undefined) { updateValue(dom.options, defaultValue, newMultiple); } else { // 恢复到未选定状态 updateValue(dom.options, newMultiple ? [] : '', newMultiple); } - } else if (isInit && defaultValue != null) { + } else if (isInit && defaultValue !== null && defaultValue !== undefined) { // 设置了 defaultValue 属性 updateValue(dom.options, defaultValue, newMultiple); } diff --git a/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts b/libs/inula/src/dom/valueHandler/TextareaValueHandler.ts similarity index 95% rename from libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts rename to libs/inula/src/dom/valueHandler/TextareaValueHandler.ts index a9052e70..e66cd28e 100644 --- a/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts +++ b/libs/inula/src/dom/valueHandler/TextareaValueHandler.ts @@ -19,7 +19,7 @@ import { Props } from '../utils/Interface'; function getInitValue(props: Props) { const { value } = props; - if (value == null) { + if (value === undefined) { const { defaultValue, children } = props; let initValue = defaultValue; @@ -30,7 +30,7 @@ function getInitValue(props: Props) { } // defaultValue 属性未配置,置为空字符串 - initValue = initValue != null ? initValue : ''; + initValue = initValue ?? ''; return initValue; } else { return value; diff --git a/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts b/libs/inula/src/dom/valueHandler/ValueChangeHandler.ts similarity index 96% rename from libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts rename to libs/inula/src/dom/valueHandler/ValueChangeHandler.ts index 0d390902..99968321 100644 --- a/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts +++ b/libs/inula/src/dom/valueHandler/ValueChangeHandler.ts @@ -14,7 +14,7 @@ */ /** - * Horizon的输入框和文本框的change事件在原生的change事件上做了一层处理 + * Inula的输入框和文本框的change事件在原生的change事件上做了一层处理 * 只有值发生变化时才会触发change事件。 */ diff --git a/libs/horizon/src/dom/valueHandler/index.ts b/libs/inula/src/dom/valueHandler/index.ts similarity index 81% rename from libs/horizon/src/dom/valueHandler/index.ts rename to libs/inula/src/dom/valueHandler/index.ts index b432b658..6a9ad5a3 100644 --- a/libs/horizon/src/dom/valueHandler/index.ts +++ b/libs/inula/src/dom/valueHandler/index.ts @@ -18,21 +18,21 @@ * 处理组件被代理和不被代理情况下的不同逻辑 */ -import { HorizonDom, HorizonSelect, Props } from '../utils/Interface'; +import { InulaDom, InulaSelect, Props } from '../utils/Interface'; import { getInputPropsWithoutValue, setInitInputValue, updateInputValue } from './InputValueHandler'; import { getOptionPropsWithoutValue } from './OptionValueHandler'; import { getSelectPropsWithoutValue, updateSelectValue } from './SelectValueHandler'; import { getTextareaPropsWithoutValue, updateTextareaValue } from './TextareaValueHandler'; // 获取元素除了被代理的值以外的属性 -function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) { +function getPropsWithoutValue(type: string, dom: InulaDom, props: Props) { switch (type) { case 'input': return getInputPropsWithoutValue(dom, props); case 'option': return getOptionPropsWithoutValue(dom, props); case 'select': - return getSelectPropsWithoutValue(dom, props); + return getSelectPropsWithoutValue(dom, props); case 'textarea': return getTextareaPropsWithoutValue(dom, props); default: @@ -41,13 +41,13 @@ function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) { } // 其它属性挂载完成后处理被代理值相关的属性 -function setInitValue(type: string, dom: HorizonDom, props: Props) { +function setInitValue(type: string, dom: InulaDom, props: Props) { switch (type) { case 'input': setInitInputValue(dom, props); break; case 'select': - updateSelectValue(dom, props, true); + updateSelectValue(dom, props, true); break; case 'textarea': updateTextareaValue(dom, props, true); @@ -58,13 +58,13 @@ function setInitValue(type: string, dom: HorizonDom, props: Props) { } // 更新需要适配的属性 -function updateValue(type: string, dom: HorizonDom, props: Props) { +function updateValue(type: string, dom: InulaDom, props: Props) { switch (type) { case 'input': updateInputValue(dom, props); break; case 'select': - updateSelectValue(dom, props); + updateSelectValue(dom, props); break; case 'textarea': updateTextareaValue(dom, props); diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/inula/src/event/EventBinding.ts similarity index 65% rename from libs/horizon/src/event/EventBinding.ts rename to libs/inula/src/event/EventBinding.ts index 462137d2..aefcf1b4 100644 --- a/libs/horizon/src/event/EventBinding.ts +++ b/libs/inula/src/event/EventBinding.ts @@ -16,16 +16,14 @@ /** * 事件绑定实现,分为绑定委托事件和非委托事件 */ -import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventHub'; +import { allDelegatedInulaEvents, simulatedDelegatedEvents } from './EventHub'; import { isDocument } from '../dom/utils/Common'; import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys'; import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder'; -import { handleEventMain } from './HorizonEventMain'; +import { handleEventMain } from './InulaEventMain'; import { decorateNativeEvent } from './EventWrapper'; import { VNode } from '../renderer/vnode/VNode'; -const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4); - // 触发委托事件 function triggerDelegatedEvent( nativeEvtName: string, @@ -56,37 +54,48 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i return listener; } +// 是否捕获事件 +function isCaptureEvent(inulaEventName) { + if (inulaEventName === 'onLostPointerCapture' || inulaEventName === 'onGotPointerCapture') { + return false; + } + return inulaEventName.slice(-7) === 'Capture'; +} + // 事件懒委托,当用户定义事件后,再进行委托到根节点 export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) { currentRoot.delegatedEvents.add(eventName); const isCapture = isCaptureEvent(eventName); - const nativeEvents = allDelegatedHorizonEvents.get(eventName); + const nativeEvents = allDelegatedInulaEvents.get(eventName); nativeEvents.forEach(nativeEvent => { const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent; // 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听 - let events = currentRoot.realNode.$EV; - - if (!events) { - events = (currentRoot.realNode as any).$EV = {}; - } + currentRoot.realNode.$EV = currentRoot.realNode.$EV ?? {}; + const events = currentRoot.realNode.$EV; if (!events[nativeFullName]) { - const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); - events[nativeFullName] = listener; + events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); } }); } -// 通过horizon事件名获取到native事件名 -function getNativeEvtName(horizonEventName, capture) { +// 利用冒泡事件模拟不冒泡事件,需要直接在根节点绑定 +export function listenSimulatedDelegatedEvents(root: VNode) { + for (let i = 0; i < simulatedDelegatedEvents.length; i++) { + lazyDelegateOnRoot(root, simulatedDelegatedEvents[i]); + } +} + +// 通过inula事件名获取到native事件名 +function getNativeEvtName(inulaEventName, capture) { let nativeName; if (capture) { - nativeName = horizonEventName.slice(2, -7); + nativeName = inulaEventName.slice(2, -7); } else { - nativeName = horizonEventName.slice(2); + nativeName = inulaEventName.slice(2); } if (!nativeName) { return ''; @@ -94,18 +103,10 @@ function getNativeEvtName(horizonEventName, capture) { return nativeName.toLowerCase(); } -// 是否捕获事件 -function isCaptureEvent(horizonEventName) { - if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') { - return false; - } - return horizonEventName.slice(-7) === 'Capture'; -} - // 封装监听函数 -function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) { +function getWrapperListener(inulaEventName, nativeEvtName, targetElement, listener) { return event => { - const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event); + const customEvent = decorateNativeEvent(inulaEventName, nativeEvtName, event); asyncUpdates(() => { listener(customEvent); }); @@ -113,16 +114,16 @@ function getWrapperListener(horizonEventName, nativeEvtName, targetElement, list } // 非委托事件单独监听到各自dom节点 -export function listenNonDelegatedEvent(horizonEventName: string, domElement: Element, listener): void { - const isCapture = isCaptureEvent(horizonEventName); - const nativeEvtName = getNativeEvtName(horizonEventName, isCapture); +export function listenNonDelegatedEvent(inulaEventName: string, domElement: Element, listener): void { + const isCapture = isCaptureEvent(inulaEventName); + const nativeEvtName = getNativeEvtName(inulaEventName, isCapture); // 先判断是否存在老的监听事件,若存在则移除 const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement); - const currentListener = nonDelegatedListenerMap.get(horizonEventName); + const currentListener = nonDelegatedListenerMap.get(inulaEventName); if (currentListener) { domElement.removeEventListener(nativeEvtName, currentListener); - nonDelegatedListenerMap.delete(horizonEventName); + nonDelegatedListenerMap.delete(inulaEventName); } if (typeof listener !== 'function') { @@ -130,8 +131,8 @@ export function listenNonDelegatedEvent(horizonEventName: string, domElement: El } // 为了和委托事件对外行为一致,将事件对象封装成CustomBaseEvent - const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener); + const wrapperListener = getWrapperListener(inulaEventName, nativeEvtName, domElement, listener); // 添加新的监听 - nonDelegatedListenerMap.set(horizonEventName, wrapperListener); + nonDelegatedListenerMap.set(inulaEventName, wrapperListener); domElement.addEventListener(nativeEvtName, wrapperListener, isCapture); } diff --git a/libs/horizon/src/event/EventHub.ts b/libs/inula/src/event/EventHub.ts similarity index 79% rename from libs/horizon/src/event/EventHub.ts rename to libs/inula/src/event/EventHub.ts index faf0a8de..d813b247 100644 --- a/libs/horizon/src/event/EventHub.ts +++ b/libs/inula/src/event/EventHub.ts @@ -13,13 +13,16 @@ * See the Mulan PSL v2 for more details. */ -// 需要委托的horizon事件和原生事件对应关系 -export const allDelegatedHorizonEvents = new Map(); +// 需要委托的inula事件和原生事件对应关系 +export const allDelegatedInulaEvents = new Map(); + +// 模拟委托事件,不冒泡事件需要利用其他事件来触发冒泡过程 +export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave']; // 所有委托的原生事件集合 export const allDelegatedNativeEvents = new Set(); -// Horizon事件和原生事件对应关系 -export const horizonEventToNativeMap = new Map([ +// Inula事件和原生事件对应关系 +export const inulaEventToNativeMap = new Map([ ['onKeyPress', ['keypress']], ['onTextInput', ['textInput']], ['onClick', ['click']], @@ -49,13 +52,15 @@ export const horizonEventToNativeMap = new Map([ ['onCompositionUpdate', ['compositionupdate']], ['onChange', ['change', 'click', 'focusout', 'input']], ['onSelect', ['select']], + ['onMouseEnter', ['mouseout', 'mouseover']], + ['onMouseLeave', ['mouseout', 'mouseover']], ['onAnimationEnd', ['animationend']], ['onAnimationIteration', ['animationiteration']], ['onAnimationStart', ['animationstart']], ['onTransitionEnd', ['transitionend']], ]); -export const NativeEventToHorizonMap = { +export const NativeEventToInulaMap = { click: 'click', wheel: 'wheel', dblclick: 'doubleClick', @@ -94,17 +99,17 @@ export const EVENT_TYPE_BUBBLE = 'Bubble'; export const EVENT_TYPE_CAPTURE = 'Capture'; export const EVENT_TYPE_ALL = 'All'; -horizonEventToNativeMap.forEach((dependencies, horizonEvent) => { - allDelegatedHorizonEvents.set(horizonEvent, dependencies); - allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies); +inulaEventToNativeMap.forEach((dependencies, inulaEvent) => { + allDelegatedInulaEvents.set(inulaEvent, dependencies); + allDelegatedInulaEvents.set(inulaEvent + 'Capture', dependencies); dependencies.forEach(d => { allDelegatedNativeEvents.add(d); }); }); -export function transformToHorizonEvent(nativeEvtName: string) { - const name = NativeEventToHorizonMap[nativeEvtName]; +export function transformToInulaEvent(nativeEvtName: string) { + const name = NativeEventToInulaMap[nativeEvtName]; // 例:dragEnd -> onDragEnd return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`; } diff --git a/libs/horizon/src/event/EventWrapper.ts b/libs/inula/src/event/EventWrapper.ts similarity index 98% rename from libs/horizon/src/event/EventWrapper.ts rename to libs/inula/src/event/EventWrapper.ts index 72d848d7..2b6fa932 100644 --- a/libs/horizon/src/event/EventWrapper.ts +++ b/libs/inula/src/event/EventWrapper.ts @@ -37,6 +37,9 @@ export class WrappedEvent { key: string; currentTarget: EventTarget | null = null; + target: HTMLElement; + relatedTarget: HTMLElement; + stopPropagation: () => void; preventDefault: () => void; diff --git a/libs/horizon/src/event/FormValueController.ts b/libs/inula/src/event/FormValueController.ts similarity index 92% rename from libs/horizon/src/event/FormValueController.ts rename to libs/inula/src/event/FormValueController.ts index b1e8a74d..6a3d39a6 100644 --- a/libs/horizon/src/event/FormValueController.ts +++ b/libs/inula/src/event/FormValueController.ts @@ -14,7 +14,7 @@ */ import { getVNodeProps } from '../dom/DOMInternalKeys'; -import { getDomTag } from '../dom/utils/Common'; +import { getDomTag, isNotNull } from '../dom/utils/Common'; import { Props } from '../dom/utils/Interface'; import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler'; import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler'; @@ -37,15 +37,26 @@ export function shouldControlValue(): boolean { return changeEventTargets !== null && changeEventTargets.length > 0; } -// 从缓存队列中对受控组件进行赋值 -export function tryControlValue() { - if (!changeEventTargets) { - return; +function controlInputValue(inputDom: HTMLInputElement, props: Props) { + const { name, type } = props; + + // 如果是 radio,找出同一form内,name相同的Radio,更新它们Handler的Value + if (type === 'radio' && isNotNull(name)) { + const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`); + for (let i = 0; i < radioList.length; i++) { + const radio = radioList[i]; + if (radio === inputDom) { + continue; + } + if (isNotNull(radio.form) && isNotNull(inputDom.form) && radio.form !== inputDom.form) { + continue; + } + + updateInputHandlerIfChanged(radio); + } + } else { + updateInputValue(inputDom, props); } - changeEventTargets.forEach(target => { - controlValue(target); - }); - changeEventTargets = null; } // 受控组件值重新赋值 @@ -66,24 +77,13 @@ function controlValue(target: Element) { } } -function controlInputValue(inputDom: HTMLInputElement, props: Props) { - const { name, type } = props; - - // 如果是 radio,找出同一form内,name相同的Radio,更新它们Handler的Value - if (type === 'radio' && name != null) { - const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`); - for (let i = 0; i < radioList.length; i++) { - const radio = radioList[i]; - if (radio === inputDom) { - continue; - } - if (radio.form != null && inputDom.form != null && radio.form !== inputDom.form) { - continue; - } - - updateInputHandlerIfChanged(radio); - } - } else { - updateInputValue(inputDom, props); +// 从缓存队列中对受控组件进行赋值 +export function tryControlValue() { + if (!changeEventTargets) { + return; } + changeEventTargets.forEach(target => { + controlValue(target); + }); + changeEventTargets = null; } diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/inula/src/event/InulaEventMain.ts similarity index 79% rename from libs/horizon/src/event/HorizonEventMain.ts rename to libs/inula/src/event/InulaEventMain.ts index 45dade43..08e5f892 100644 --- a/libs/horizon/src/event/HorizonEventMain.ts +++ b/libs/inula/src/event/InulaEventMain.ts @@ -24,13 +24,14 @@ import { EVENT_TYPE_ALL, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, - horizonEventToNativeMap, - transformToHorizonEvent, + inulaEventToNativeMap, + transformToInulaEvent, } from './EventHub'; 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; @@ -93,9 +94,9 @@ function getCommonListeners( target: null | EventTarget, isCapture: boolean ): ListenerUnitList { - const horizonEvtName = transformToHorizonEvent(nativeEvtName); + const inulaEvtName = transformToInulaEvent(nativeEvtName); - if (!horizonEvtName) { + if (!inulaEvtName) { return []; } @@ -112,8 +113,8 @@ function getCommonListeners( nativeEvtName = 'blur'; } - const horizonEvent = decorateNativeEvent(horizonEvtName, nativeEvtName, nativeEvent); - return getListenersFromTree(vNode, horizonEvtName, horizonEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE); + const inulaEvent = decorateNativeEvent(inulaEvtName, nativeEvtName, nativeEvent); + return getListenersFromTree(vNode, inulaEvtName, inulaEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE); } // 按顺序执行事件队列 @@ -131,8 +132,8 @@ function processListeners(listenerList: ListenerUnitList): void { }); } -// 触发可以被执行的horizon事件监听 -function triggerHorizonEvents( +// 触发可以被执行的inula事件监听 +function triggerInulaEvents( nativeEvtName: string, isCapture: boolean, nativeEvent: AnyNativeEvent, @@ -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 (inulaEventToNativeMap.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); - } + if (!isCapture && inulaEventToNativeMap.get('onChange')!.includes(nativeEvtName)) { + changeEvents = getChangeListeners(nativeEvtName, nativeEvent, vNode, target); } // 处理触发的事件队列 - processListeners(listenerList); + processListeners([...listenerList, ...mouseEnterListeners, ...changeEvents]); } // 其他事件正在执行中标记 @@ -176,14 +185,14 @@ export function handleEventMain( // 有事件正在执行,同步执行事件 if (isInEventsExecution) { - triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode); + triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode); return; } // 没有事件在执行,经过调度再执行事件 isInEventsExecution = true; try { - asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode)); + asyncUpdates(() => triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode)); } finally { isInEventsExecution = false; if (shouldControlValue()) { diff --git a/libs/horizon/src/event/ListenerGetter.ts b/libs/inula/src/event/ListenerGetter.ts similarity index 51% rename from libs/horizon/src/event/ListenerGetter.ts rename to libs/inula/src/event/ListenerGetter.ts index 57c2c82c..2a387771 100644 --- a/libs/horizon/src/event/ListenerGetter.ts +++ b/libs/inula/src/event/ListenerGetter.ts @@ -40,11 +40,11 @@ function getListenerFromVNode(vNode: VNode, eventName: string): Function | null // 获取监听事件 export function getListenersFromTree( targetVNode: VNode | null, - horizonEvtName: string | null, + inulaEvtName: string | null, nativeEvent: WrappedEvent, eventType: string ): ListenerUnitList { - if (!horizonEvtName) { + if (!inulaEvtName) { return []; } @@ -57,7 +57,7 @@ export function getListenersFromTree( const { realNode, tag } = vNode; if (tag === DomComponent && realNode !== null) { if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) { - const captureName = horizonEvtName + EVENT_TYPE_CAPTURE; + const captureName = inulaEvtName + EVENT_TYPE_CAPTURE; const captureListener = getListenerFromVNode(vNode, captureName); if (captureListener) { listeners.unshift({ @@ -70,7 +70,7 @@ export function getListenersFromTree( } if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) { - const bubbleListener = getListenerFromVNode(vNode, horizonEvtName); + const bubbleListener = getListenerFromVNode(vNode, inulaEvtName); if (bubbleListener) { listeners.push({ vNode, @@ -86,3 +86,91 @@ export function getListenersFromTree( return listeners; } + + +// 获取父节点 +function getParent(inst: VNode | null): VNode | null { + if (inst === null) { + return null; + } + do { + inst = inst.parent; + } while (inst && inst.tag !== DomComponent); + return inst || null; +} + +// 寻找两个节点的共同最近祖先,如果没有则返回null +function getCommonAncestor(instA: VNode, instB: VNode): VNode | null { + const parentsSet = new Set(); + 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 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; +} + +// 获取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]; +} diff --git a/libs/inula/src/event/MouseEvent.ts b/libs/inula/src/event/MouseEvent.ts new file mode 100644 index 00000000..3eb38123 --- /dev/null +++ b/libs/inula/src/event/MouseEvent.ts @@ -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); +} + + diff --git a/libs/horizon/src/event/Types.ts b/libs/inula/src/event/Types.ts similarity index 92% rename from libs/horizon/src/event/Types.ts rename to libs/inula/src/event/Types.ts index e4f839aa..4ef5125a 100644 --- a/libs/horizon/src/event/Types.ts +++ b/libs/inula/src/event/Types.ts @@ -18,13 +18,13 @@ import { WrappedEvent } from './EventWrapper'; export type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event; -export interface HorizonEventListener { +export interface InulaEventListener { (event: WrappedEvent): void; } export type ListenerUnit = { vNode: null | VNode; - listener: HorizonEventListener; + listener: InulaEventListener; currentTarget: EventTarget; event: WrappedEvent; }; diff --git a/libs/inula/src/event/utils.ts b/libs/inula/src/event/utils.ts new file mode 100644 index 00000000..594a70b2 --- /dev/null +++ b/libs/inula/src/event/utils.ts @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 { 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; +} + +export function setPropertyWritable(obj, propName) { + const desc = Object.getOwnPropertyDescriptor(obj, propName); + if (!desc || !desc.writable) { + 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; +} diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/inula/src/external/ChildrenUtil.ts similarity index 91% rename from libs/horizon/src/external/ChildrenUtil.ts rename to libs/inula/src/external/ChildrenUtil.ts index 58f435ae..64d56f28 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/inula/src/external/ChildrenUtil.ts @@ -17,16 +17,51 @@ import { throwIfTrue } from '../renderer/utils/throwIfTrue'; import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType'; import { isValidElement, JSXElement } from './JSXElement'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; // 生成key function getItemKey(item: any, index: number): string { - if (typeof item === 'object' && item !== null && item.key != null) { + if (typeof item === 'object' && item !== null && item.key !== null && item.key !== undefined) { return '.$' + item.key; } // 使用36进制减少生成字符串的长度以节省空间 return '.' + index.toString(36); } +function processArrayChildren(children: any, arr: Array, prefix: string, callback: Function) { + for (let i = 0; i < children.length; i++) { + const childItem = children[i]; + const nextPrefix = prefix + getItemKey(childItem, i); + mapChildrenToArray(childItem, arr, nextPrefix, callback); + } +} + +function callMapFun(children: any, arr: Array, prefix: string, callback: Function) { + let mappedChild = callback(children); + if (Array.isArray(mappedChild)) { + // 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item + processArrayChildren(mappedChild, arr, prefix + '/', subChild => subChild); + } else if (mappedChild !== null && mappedChild !== undefined) { + // 给一个key值,确保返回的对象一定带有key + if (isValidElement(mappedChild)) { + const childKey = prefix === '' ? getItemKey(children, 0) : ''; + const mappedKey = getItemKey(mappedChild, 0); + const newKey = + prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) ? '.$' + mappedChild.key : ''); + // 返回一个修改key的children + mappedChild = JSXElement( + mappedChild.type, + newKey, + mappedChild.ref, + mappedChild[BELONG_CLASS_VNODE_KEY], + mappedChild.props, + mappedChild.src + ); + } + arr.push(mappedChild); + } +} + function mapChildrenToArray(children: any, arr: Array, prefix: string, callback?: Function): number | void { const type = typeof children; switch (type) { @@ -53,45 +88,11 @@ function mapChildrenToArray(children: any, arr: Array, prefix: string, call processArrayChildren(children, arr, prefix, callback); return; } - throw new Error('Object is invalid as a Horizon child. '); + throw new Error('Object is invalid as a Inula child. '); // No Default } } -function processArrayChildren(children: any, arr: Array, prefix: string, callback: Function) { - for (let i = 0; i < children.length; i++) { - const childItem = children[i]; - const nextPrefix = prefix + getItemKey(childItem, i); - mapChildrenToArray(childItem, arr, nextPrefix, callback); - } -} - -function callMapFun(children: any, arr: Array, prefix: string, callback: Function) { - let mappedChild = callback(children); - if (Array.isArray(mappedChild)) { - // 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item - processArrayChildren(mappedChild, arr, prefix + '/', subChild => subChild); - } else if (mappedChild !== null && mappedChild !== undefined) { - // 给一个key值,确保返回的对象一定带有key - if (isValidElement(mappedChild)) { - const childKey = prefix === '' ? getItemKey(children, 0) : ''; - const mappedKey = getItemKey(mappedChild, 0); - const newKey = - prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) ? '.$' + mappedChild.key : ''); - // 返回一个修改key的children - mappedChild = JSXElement( - mappedChild.type, - newKey, - mappedChild.ref, - mappedChild.belongClassVNode, - mappedChild.props, - mappedChild.src - ); - } - arr.push(mappedChild); - } -} - // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg function mapChildren(children: any, func: Function, context?: any): Array { if (children === null || children === undefined) { @@ -118,7 +119,7 @@ const Children = { return n; }, only: children => { - throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.'); + throwIfTrue(!isValidElement(children), 'Inula.Children.only function received invalid element.'); return children; }, toArray: children => { diff --git a/libs/horizon/src/external/HorizonIs.ts b/libs/inula/src/external/InulaIs.ts similarity index 100% rename from libs/horizon/src/external/HorizonIs.ts rename to libs/inula/src/external/InulaIs.ts diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/inula/src/external/JSXElement.ts similarity index 65% rename from libs/horizon/src/external/JSXElement.ts rename to libs/inula/src/external/JSXElement.ts index 69de1d39..f3920254 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/inula/src/external/JSXElement.ts @@ -16,6 +16,7 @@ import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { getProcessingClassVNode } from '../renderer/GlobalVar'; import { Source } from '../renderer/Types'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; /** * vtype 节点的类型,这里固定是element @@ -25,10 +26,10 @@ import { Source } from '../renderer/Types'; * props 其他常规属性 */ export function JSXElement(type, key, ref, vNode, props, source: Source | null) { - return { + const ele = { // 元素标识符 vtype: TYPE_COMMON_ELEMENT, - src: isDev ? source : null, + src: null, // 属于元素的内置属性 type: type, @@ -36,14 +37,28 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null) ref: ref, props: props, - // 所属的class组件 - belongClassVNode: vNode, + // 所属的class组件,clonedeep jsxElement时需要防止无限循环 + [BELONG_CLASS_VNODE_KEY]: vNode, }; -} + // 兼容IE11不支持Symbol + if (typeof BELONG_CLASS_VNODE_KEY === 'string') { + Object.defineProperty(ele, BELONG_CLASS_VNODE_KEY, { + configurable: false, + enumerable: false, + value: vNode, + }); + } + if (isDev) { + // 为了test判断两个 JSXElement 对象是否相等时忽略src属性,需要设置src的enumerable为false + Object.defineProperty(ele, 'src', { + configurable: false, + enumerable: false, + writable: false, + value: source, + }); + } -function isValidKey(key) { - const keyArray = ['key', 'ref', '__source', '__self']; - return !keyArray.includes(key); + return ele; } function mergeDefault(sourceObj, defaultObj) { @@ -54,19 +69,20 @@ function mergeDefault(sourceObj, defaultObj) { }); } +// ['key', 'ref', '__source', '__self']属性不从setting获取 +const keyArray = ['key', 'ref', '__source', '__self']; + function buildElement(isClone, type, setting, children) { // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null - const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : null; - const ref = setting && setting.ref !== undefined ? setting.ref : isClone ? type.ref : null; + const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); + const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null); const props = isClone ? { ...type.props } : {}; - let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); + let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode(); if (setting !== null && setting !== undefined) { - const keys = Object.keys(setting); - const keyLength = keys.length; - for (let i = 0; i < keyLength; i++) { - const k = keys[i]; - if (isValidKey(k)) { + + for (const k in setting) { + if (!keyArray.includes(k)) { props[k] = setting[k]; } } @@ -90,7 +106,6 @@ function buildElement(isClone, type, setting, children) { lineNumber: setting.__source.lineNumber, }; } - return JSXElement(element, key, ref, vNode, props, src); } @@ -107,3 +122,12 @@ export function cloneElement(element, setting, ...children) { export function isValidElement(element) { return !!(element && element.vtype === TYPE_COMMON_ELEMENT); } + +// 兼容高版本的babel编译方式 +export function jsx(type, setting, key) { + if (setting.key === undefined && key !== undefined) { + setting.key = key; + } + + return buildElement(false, type, setting, []); +} diff --git a/libs/horizon/src/external/JSXElementType.ts b/libs/inula/src/external/JSXElementType.ts similarity index 100% rename from libs/horizon/src/external/JSXElementType.ts rename to libs/inula/src/external/JSXElementType.ts diff --git a/libs/inula/src/external/TestUtil.ts b/libs/inula/src/external/TestUtil.ts new file mode 100644 index 00000000..3b5b24ca --- /dev/null +++ b/libs/inula/src/external/TestUtil.ts @@ -0,0 +1,81 @@ +/* + * 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 {asyncUpdates} from '../renderer/TreeBuilder'; +import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; +import {hasAsyncEffects, runAsyncEffects} from '../renderer/submit/HookEffectHandler'; +import {isPromise} from '../renderer/ErrorHandler'; + +interface Thenable { + then(resolve: (val?: any) => void, reject: (err: any) => void): void; +} + +// 防止死循环 +const LOOPING_LIMIT = 50; +let loopingCount = 0; +function callRenderQueue() { + callRenderQueueImmediate(); + + while (hasAsyncEffects() && loopingCount < LOOPING_LIMIT) { + loopingCount++; + runAsyncEffects(); + // effects可能产生刷新任务,这里再执行一次 + callRenderQueueImmediate(); + } +} + +// act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 +function act(fun: () => void | Thenable): Thenable { + const funRet = asyncUpdates(fun); + + callRenderQueue(); + + // 如果fun返回的是Promise + if (isPromise(funRet)) { + // testing-library会返回Promise + return { + then(resolve, reject) { + funRet.then( + () => { + if (typeof setImmediate === 'function') { + // 通过setImmediate回调,用于等待业务的setTimeout完成 + setImmediate(() => { + callRenderQueue(); + resolve(); + }); + } else { + callRenderQueue(); + resolve(); + } + }, + err => { + reject(err); + }, + ); + }, + }; + } else { + return { + then(resolve) { + resolve(); + }, + }; + } +} + +export { + act +}; diff --git a/libs/horizon/src/external/devtools.ts b/libs/inula/src/external/devtools.ts similarity index 99% rename from libs/horizon/src/external/devtools.ts rename to libs/inula/src/external/devtools.ts index b1802cc7..21648f4b 100644 --- a/libs/horizon/src/external/devtools.ts +++ b/libs/inula/src/external/devtools.ts @@ -128,7 +128,7 @@ export const helper = { }; export function injectUpdater() { - const hook = window.__HORIZON_DEV_HOOK__; + const hook = window.__INULA_DEV_HOOK__; if (hook) { hook.init(helper); } diff --git a/libs/horizon/src/horizonx/CommonUtils.ts b/libs/inula/src/inulax/CommonUtils.ts similarity index 83% rename from libs/horizon/src/horizonx/CommonUtils.ts rename to libs/inula/src/inulax/CommonUtils.ts index fcf60054..c4330834 100644 --- a/libs/horizon/src/horizonx/CommonUtils.ts +++ b/libs/inula/src/inulax/CommonUtils.ts @@ -84,7 +84,6 @@ export function isSame(x, y) { export function getDetailedType(val: any) { if (val === undefined) return 'undefined'; if (val === null) return 'null'; - if (isCollection(val)) return 'collection'; if (isPromise(val)) return 'promise'; if (isArray(val)) return 'array'; if (isWeakMap(val)) return 'weakMap'; @@ -116,12 +115,30 @@ export function resolveMutation(from, to) { if (res[i].mutation) found = true; } } - // TODO: resolve shifts + + // need to resolve shifts return { mutation: found, items: res, from, to }; } case 'object': { - let keys = Object.keys({ ...from, ...to }); + if (from._type && from._type === to._type) { + if (from._type === 'Map') { + const entries = resolveMutation(from.entries, to.entries); + return { + mutation: entries.items.some(item => item.mutation), + from, + to, + entries: entries.items, + }; + } + + if (from._type === 'Set') { + const values = resolveMutation(from.values, to.values); + return { mutation: values.items.some(item => item.mutation), from, to, values: values.items }; + } + } + + let keys = Object.keys({ ...from, ...to }).filter(key => key !== '_inulaObserver'); const res = {}; let found = false; keys.forEach(key => { @@ -142,8 +159,6 @@ export function resolveMutation(from, to) { return { mutation: found, attributes: res, from, to }; } - // TODO: implement collections - default: { if (from === to) return { mutation: false }; @@ -151,3 +166,9 @@ export function resolveMutation(from, to) { } } } + +export function omit(obj, ...attrs) { + let res = { ...obj }; + attrs.forEach(attr => delete res[attr]); + return res; +} diff --git a/libs/horizon/src/horizonx/Constants.ts b/libs/inula/src/inulax/Constants.ts similarity index 79% rename from libs/horizon/src/horizonx/Constants.ts rename to libs/inula/src/inulax/Constants.ts index 92b87923..1e841db7 100644 --- a/libs/horizon/src/horizonx/Constants.ts +++ b/libs/inula/src/inulax/Constants.ts @@ -13,4 +13,6 @@ * See the Mulan PSL v2 for more details. */ -export const OBSERVER_KEY = '_horizonObserver'; +export const OBSERVER_KEY = typeof Symbol === 'function' ? Symbol('_inulaObserver') : '_inulaObserver'; + +export const RAW_VALUE = '_rawValue'; diff --git a/libs/horizon/src/horizonx/adapters/redux.ts b/libs/inula/src/inulax/adapters/redux.ts similarity index 81% rename from libs/horizon/src/horizonx/adapters/redux.ts rename to libs/inula/src/inulax/adapters/redux.ts index e4d8292e..a196a486 100644 --- a/libs/horizon/src/horizonx/adapters/redux.ts +++ b/libs/inula/src/inulax/adapters/redux.ts @@ -53,6 +53,40 @@ export type ReduxMiddleware = ( type Reducer = (state: any, action: ReduxAction) => any; +function mergeData(state, data) { + if (!data) { + state.stateWrapper = data; + return; + } + + if (Array.isArray(data) && Array.isArray(state?.stateWrapper)) { + state.stateWrapper.length = data.length; + data.forEach((item, idx) => { + if (item != state.stateWrapper[idx]) { + state.stateWrapper[idx] = item; + } + }); + return; + } + + if (typeof data === 'object' && typeof state?.stateWrapper === 'object') { + Object.keys(state.stateWrapper).forEach(key => { + if (!Object.prototype.hasOwnProperty.call(data, key)) { + delete state.stateWrapper[key]; + } + }); + + Object.entries(data).forEach(([key, value]) => { + if (state.stateWrapper[key] !== value) { + state.stateWrapper[key] = value; + } + }); + return; + } + + state.stateWrapper = data; +} + export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler { const store = createStoreX({ id: 'defaultStore', @@ -92,13 +126,13 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): replaceReducer: newReducer => { reducer = newReducer; }, - _horizonXstore: store, + _inulaXstore: store, dispatch: store.$a.dispatch, }; enhancers && enhancers(result); - result.dispatch({ type: 'HorizonX' }); + result.dispatch({ type: 'InulaX' }); store.reduxHandler = result; @@ -106,7 +140,8 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): } export function combineReducers(reducers: { [key: string]: Reducer }): Reducer { - return (state = {}, action) => { + return (state, action) => { + state = state || {}; const newState = {}; Object.entries(reducers).forEach(([key, reducer]) => { newState[key] = reducer(state[key], action); @@ -115,12 +150,6 @@ export function combineReducers(reducers: { [key: string]: Reducer }): Reducer { }; } -export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { - return store => { - return applyMiddlewares(store, middlewares); - }; -} - function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void { middlewares = middlewares.slice(); middlewares.reverse(); @@ -131,6 +160,12 @@ function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware store.dispatch = dispatch; } +export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { + return store => { + return applyMiddlewares(store, middlewares); + }; +} + type ActionCreator = (...params: any[]) => ReduxAction; type ActionCreators = { [key: string]: ActionCreator }; export type BoundActionCreator = (...params: any[]) => void; @@ -162,7 +197,7 @@ export function compose(...middlewares: ReduxMiddleware[]) { }; } -// HorizonX batches updates by default, this function is only for backwards compatibility +// InulaX batches updates by default, this function is only for backwards compatibility export function batch(fn: () => void) { fn(); } diff --git a/libs/horizon/src/horizonx/adapters/reduxReact.ts b/libs/inula/src/inulax/adapters/reduxReact.ts similarity index 98% rename from libs/horizon/src/horizonx/adapters/reduxReact.ts rename to libs/inula/src/inulax/adapters/reduxReact.ts index 31468796..d4dddbd3 100644 --- a/libs/horizon/src/horizonx/adapters/reduxReact.ts +++ b/libs/inula/src/inulax/adapters/reduxReact.ts @@ -30,7 +30,7 @@ export function Provider({ context: Context; children?: any[]; }) { - const Context = context; // NOTE: bind redux API to horizon API requires this renaming; + const Context = context; // NOTE: bind redux API to inula API requires this renaming; return createElement(Context.Provider, { value: store }, children); } diff --git a/libs/horizon/src/horizonx/adapters/reduxThunk.ts b/libs/inula/src/inulax/adapters/reduxThunk.ts similarity index 100% rename from libs/horizon/src/horizonx/adapters/reduxThunk.ts rename to libs/inula/src/inulax/adapters/reduxThunk.ts diff --git a/libs/inula/src/inulax/devtools/constants.ts b/libs/inula/src/inulax/devtools/constants.ts new file mode 100644 index 00000000..f6478862 --- /dev/null +++ b/libs/inula/src/inulax/devtools/constants.ts @@ -0,0 +1,10 @@ +export const INITIALIZED = 'inulax store initialized'; +export const STATE_CHANGE = 'inulax state change'; +export const SUBSCRIBED = 'inulax subscribed'; +export const UNSUBSCRIBED = 'inulax unsubscribed'; +export const ACTION = 'inulax action'; +export const ACTION_QUEUED = 'inulax action queued'; +export const QUEUE_PENDING = 'inulax queue pending'; +export const QUEUE_FINISHED = 'inulax queue finished'; +export const RENDER_TRIGGERED = 'inulax render triggered'; +export const OBSERVED_COMPONENTS = 'inulax observed components'; diff --git a/libs/inula/src/inulax/devtools/index.ts b/libs/inula/src/inulax/devtools/index.ts new file mode 100644 index 00000000..e7b51b78 --- /dev/null +++ b/libs/inula/src/inulax/devtools/index.ts @@ -0,0 +1,226 @@ +import { isDomVNode } from '../../renderer/vnode/VNodeUtils'; +import { isMap, isSet, isWeakMap, isWeakSet } from '../CommonUtils'; +import { getStore, getAllStores } from '../store/StoreHandler'; +import { OBSERVED_COMPONENTS } from './constants'; + +const sessionId = Date.now(); + +// this function is used to detect devtool connection +export function isPanelActive() { + return window['__INULA_DEV_HOOK__']; +} + +// safely serializes variables containing values wrapped in Proxy object +function getType(value) { + if (!value) return 'nullish'; + if (value.nativeEvent) return 'event'; + if (typeof value === 'function') return 'function'; + if (value.constructor?.name === 'VNode') return 'vnode'; + if (isWeakMap(value)) return 'weakMap'; + if (isWeakSet(value)) return 'weakSet'; + if (isMap(value)) return 'map'; + if (isSet(value)) return 'set'; + if (Array.isArray(value)) return 'array'; + if (typeof value === 'object') return 'object'; + return 'primitive'; +} + +function makeProxySnapshot(obj, visited: any[] = []) { + const type = getType(obj); + let clone; + + try { + //NULLISH VALUE + if (type === 'nullish') { + return obj; + } + //EVENT + if (type === 'event') return obj.type + 'Event'; + // FUNCTION + if (type === 'function') { + return obj.toString(); + } + // VNODE + if (type === 'vnode') { + return { + _type: 'VNode', + id: window['__INULA_DEV_HOOK__'].getVnodeId(obj), + tag: obj.tag, + }; + } + // WEAK MAP + if (type === 'weakMap') { + return { + _type: 'WeakMap', + }; + } + // WEAK SET + if (type === 'weakSet') { + return { + _type: 'WeakSet', + }; + } + // MAP + if (type === 'map') { + return { + _type: 'Map', + entries: Array.from(obj.entries()).map(([key, value]) => ({ + key: makeProxySnapshot(key), + value: makeProxySnapshot(value), + })), + }; + } + // SET + if (type === 'set') { + return { + _type: 'Set', + values: Array.from(obj).map(value => makeProxySnapshot(value)), + }; + } + // ARRAY + if (type === 'array') { + if (visited.some(item => item === obj)) return ``; + clone = []; + obj.forEach(item => clone.push(makeProxySnapshot(item, visited.concat([obj])))); + return clone; + } + // OBJECT + if (type === 'object') { + if (visited.some(item => item === obj)) return ``; + clone = {}; + Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value, visited.concat([obj])))); + return clone; + } + // PRIMITIVE + return obj; + } catch (err) { + console.error('cannot serialize object. ', { err, obj, type }); + } +} + +// serializes store and creates expanded object with baked-in containing current computed values +function makeStoreSnapshot({ type, data }) { + const expanded = {}; + Object.keys(data.store.$c).forEach(key => { + expanded[key] = data.store[key]; + }); + data.store.expanded = expanded; + const snapshot = makeProxySnapshot({ + data, + type, + sessionId, + }); + return snapshot; +} + +export const devtools = { + // returns vNode id from inula devtools + getVNodeId: vNode => { + if (!isPanelActive()) { + return null; + } + window['__INULA_DEV_HOOK__'].send(); // update list first + return window['__INULA_DEV_HOOK__'].getVnodeId(vNode); + }, + // sends inulax devtool message to extension + emit: (type, data) => { + if (!isPanelActive()) { + return; + } + window.postMessage({ + type: 'INULA_DEV_TOOLS', + payload: makeStoreSnapshot({ type, data }), + from: 'dev tool hook', + }, ''); + }, +}; + +// collects components that are dependant on inulax store and their ids +function getAffectedComponents() { + const allStores = getAllStores(); + const keys = Object.keys(allStores); + const res = {}; + keys.forEach(key => { + if (!allStores[key].$config.state._inulaObserver.keyVNodes) { + res[key] = []; + return; + } + const subRes = new Set(); + const process = Array.from(allStores[key].$config.state._inulaObserver.keyVNodes.values()); + while (process.length) { + const pivot = process.shift() as { tag: 'string' }; + if (pivot.tag) subRes.add(pivot); + if (pivot.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item)); + } + res[key] = Array.from(subRes).map(vNode => { + return { + name: vNode?.type + .toString() + .replace(/\{.*\}/, '{...}') + .replace('function ', ''), + nodeId: window.__INULA_DEV_HOOK__.getVnodeId(vNode), + }; + }); + }); + + return res; +} + +// listens to messages from background +window.addEventListener('message', (messageEvent?) => { + if (messageEvent?.data?.payload?.type === 'inulax request observed components') { + // get observed components + setTimeout(() => { + window.postMessage({ + type: 'INULA_DEV_TOOLS', + payload: { type: OBSERVED_COMPONENTS, data: getAffectedComponents() }, + from: 'dev tool hook', + }, ''); + }, 100); + } + + // executes store action + if (messageEvent.data?.payload?.type === 'inulax executue action') { + const data = messageEvent.data.payload.data; + const store = getStore(data.storeId); + if (!store?.[data.action]) return; + + const action = store[data.action]; + const params = data.params; + action(...params); + } + + // queues store action + if (messageEvent?.data?.payload?.type === 'inulax queue action') { + const data = messageEvent.data.payload.data; + const store = getStore(data.storeId); + if (!store?.[data.action]) return; + + const action = store.$queue[data.action]; + const params = data.params; + action(...params); + } + + // queues change store state + if (messageEvent?.data?.payload?.type === 'inulax change state') { + const data = messageEvent.data.payload; + const store = getStore(data.storeId); + if (!store) return; + let parent = store.$s; + if (data.operation === 'edit') { + try { + const path = messageEvent.data.payload.path; + + while (path.length > 1) { + parent = parent[path.pop()]; + } + + parent[path[0]] = messageEvent.data.payload.value; + } catch (err) { + console.error(err); + } + } + + // need to implement add and delete element + } +}); diff --git a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts b/libs/inula/src/inulax/proxy/HooklessObserver.ts similarity index 96% rename from libs/horizon/src/horizonx/proxy/HooklessObserver.ts rename to libs/inula/src/inulax/proxy/HooklessObserver.ts index 5bb8a9b9..aea366d9 100644 --- a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts +++ b/libs/inula/src/inulax/proxy/HooklessObserver.ts @@ -31,15 +31,16 @@ export class HooklessObserver implements IObserver { this.listeners = this.listeners.filter(item => item != listener); } + getListeners() { + return this.listeners; + } + setProp(key: string | symbol, mutation: any): void { this.triggerChangeListeners(mutation); } triggerChangeListeners(mutation: any): void { this.listeners.forEach(listener => { - if (!listener) { - return; - } listener(mutation); }); } diff --git a/libs/horizon/src/horizonx/proxy/Observer.ts b/libs/inula/src/inulax/proxy/Observer.ts similarity index 98% rename from libs/horizon/src/horizonx/proxy/Observer.ts rename to libs/inula/src/inulax/proxy/Observer.ts index 56ffd78c..286c2410 100644 --- a/libs/horizon/src/horizonx/proxy/Observer.ts +++ b/libs/inula/src/inulax/proxy/Observer.ts @@ -81,7 +81,7 @@ export class Observer implements IObserver { const vNodes = this.keyVNodes.get(key); //NOTE: using Set directly can lead to deadlock const vNodeArray = Array.from(vNodes || []); - vNodeArray?.forEach((vNode: VNode) => { + vNodeArray.forEach((vNode: VNode) => { if (vNode.isStoreChange) { // VNode已经被触发过,不再重复触发 return; @@ -97,10 +97,6 @@ export class Observer implements IObserver { } triggerUpdate(vNode: VNode): void { - if (!vNode) { - return; - } - // 触发VNode更新 launchUpdateFromVNode(vNode); } diff --git a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts b/libs/inula/src/inulax/proxy/ProxyHandler.ts similarity index 64% rename from libs/horizon/src/horizonx/proxy/ProxyHandler.ts rename to libs/inula/src/inulax/proxy/ProxyHandler.ts index 5d29f34b..c06c7d71 100644 --- a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts +++ b/libs/inula/src/inulax/proxy/ProxyHandler.ts @@ -20,14 +20,30 @@ import { isArray, isCollection, isObject } from '../CommonUtils'; import { createArrayProxy } from './handlers/ArrayProxyHandler'; import { createCollectionProxy } from './handlers/CollectionProxyHandler'; import type { IObserver } from '../types'; -import { OBSERVER_KEY } from '../Constants'; +import { OBSERVER_KEY, RAW_VALUE } from '../Constants'; // 保存rawObj -> Proxy const proxyMap = new WeakMap(); export const hookObserverMap = new WeakMap(); -export function createProxy(rawObj: any, id, isHookObserver = true): any { +export function getObserver(rawObj: any): Observer { + return rawObj[OBSERVER_KEY]; +} + +const setObserverKey = typeof OBSERVER_KEY === 'string' + ? (rawObj, observer) => { + Object.defineProperty(rawObj, OBSERVER_KEY, { + configurable: false, + enumerable: false, + value: observer, + }); + } + : (rawObj, observer) => { + rawObj[OBSERVER_KEY] = observer; + }; + +export function createProxy(rawObj: any, listener: { current: (...args) => any }, isHookObserver = true): any { // 不是对象(是原始数据类型)不用代理 if (!(rawObj && isObject(rawObj))) { return rawObj; @@ -48,7 +64,7 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any { let observer: IObserver = getObserver(rawObj); if (!observer) { observer = isHookObserver ? new Observer() : new HooklessObserver(); - rawObj[OBSERVER_KEY] = observer; + setObserverKey(rawObj, observer); } hookObserverMap.set(rawObj, isHookObserver); @@ -56,16 +72,35 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any { // 创建Proxy let proxyObj; if (!isHookObserver) { - proxyObj = createObjectProxy(rawObj, true); + proxyObj = createObjectProxy(rawObj, { + current: change => { + listener.current(change); + }, + }, + true); } else if (isArray(rawObj)) { // 数组 - proxyObj = createArrayProxy(rawObj as []); + proxyObj = createArrayProxy(rawObj as [], { + current: change => { + listener.current(change); + }, + }); } else if (isCollection(rawObj)) { // 集合 - proxyObj = createCollectionProxy(rawObj); + proxyObj = createCollectionProxy(rawObj, { + current: change => { + listener.current(change); + }, + }, + true); } else { // 原生对象 或 函数 - proxyObj = createObjectProxy(rawObj); + proxyObj = createObjectProxy(rawObj, { + current: change => { + listener.current(change); + }, + }, + false); } proxyMap.set(rawObj, proxyObj); @@ -74,6 +109,6 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any { return proxyObj; } -export function getObserver(rawObj: any): Observer { - return rawObj[OBSERVER_KEY]; +export function toRaw(observed: T): T { + return observed && (observed)[RAW_VALUE]; } diff --git a/libs/inula/src/inulax/proxy/handlers/ArrayProxyHandler.ts b/libs/inula/src/inulax/proxy/handlers/ArrayProxyHandler.ts new file mode 100644 index 00000000..0907f33b --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/ArrayProxyHandler.ts @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { isSame, isValidIntegerKey } from '../../CommonUtils'; +import { resolveMutation } from '../../CommonUtils'; +import { isPanelActive } from '../../devtools'; +import { OBSERVER_KEY, RAW_VALUE } from '../../Constants'; + +function set(rawObj: any[], key: string, value: any, receiver: any) { + const oldValue = rawObj[key]; + const oldLength = rawObj.length; + const newValue = value; + + const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + + const ret = Reflect.set(rawObj, key, newValue, receiver); + + const newLength = rawObj.length; + const observer = getObserver(rawObj); + + const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : resolveMutation(null, rawObj); + + if (!isSame(newValue, oldValue)) { + // 值不一样,触发监听器 + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue, mutation); + }); + } + + // 触发属性变化 + observer.setProp(key, mutation); + } + + if (oldLength !== newLength) { + // 触发数组的大小变化 + observer.setProp('length', mutation); + } + + return ret; +} + +export function createArrayProxy(rawObj: any[], listener: { current: (...args) => any }): any[] { + let listeners = [] as ((...args) => void)[]; + + function objectGet(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any { + // The observer object of symbol ('_inulaObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep. + if (key === OBSERVER_KEY) { + return undefined; + } + + const observer = getObserver(rawObj); + + if (key === 'watch') { + return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + + observer.useProp(key); + + const value = Reflect.get(rawObj, key, receiver); + + // 对于prototype不做代理 + if (key !== 'prototype') { + // 对于value也需要进一步代理 + const valProxy = singleLevel + ? value + : createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [key]: change.mutation.from }, + { ...rawObj, [key]: change.mutation.to } + ); + listener.current(mutation); + listeners.forEach(lst => lst(mutation)); + }, + }, + hookObserverMap.get(rawObj) + ); + + return valProxy; + } + + return value; + } + + function get(rawObj: any[], key: string, receiver: any) { + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (isValidIntegerKey(key) || key === 'length') { + return objectGet(rawObj, key, receiver); + } + + if (key === RAW_VALUE) { + return rawObj; + } + + return Reflect.get(rawObj, key, receiver); + } + + const handle = { + get, + set, + }; + + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + + return new Proxy(rawObj, handle); +} diff --git a/libs/inula/src/inulax/proxy/handlers/CollectionProxyHandler.ts b/libs/inula/src/inulax/proxy/handlers/CollectionProxyHandler.ts new file mode 100644 index 00000000..d2f3cbb7 --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/CollectionProxyHandler.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 { isWeakMap, isWeakSet, isSet } from '../../CommonUtils'; +import { createWeakSetProxy } from './WeakSetProxy'; +import { createSetProxy } from './SetProxy'; +import { createWeakMapProxy } from './WeakMapProxy'; +import { createMapProxy } from './MapProxy'; + +export function createCollectionProxy( + rawObj: Object, + listener: { current: (...args) => any }, + hookObserver = true +): Object { + if (isWeakSet(rawObj)) { + return createWeakSetProxy(rawObj, listener, hookObserver); + } + if (isSet(rawObj)) { + return createSetProxy(rawObj, listener, hookObserver); + } + if (isWeakMap(rawObj)) { + return createWeakMapProxy(rawObj, listener, hookObserver); + } + return createMapProxy(rawObj, listener, hookObserver); +} diff --git a/libs/inula/src/inulax/proxy/handlers/MapProxy.ts b/libs/inula/src/inulax/proxy/handlers/MapProxy.ts new file mode 100644 index 00000000..8529f35c --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/MapProxy.ts @@ -0,0 +1,413 @@ +/* + * 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 { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { isSame } from '../../CommonUtils'; +import { resolveMutation } from '../../CommonUtils'; +import { isPanelActive } from '../../devtools'; +import { RAW_VALUE } from '../../Constants'; + +const COLLECTION_CHANGE = '_collectionChange'; + +export function createMapProxy( + rawObj: Object, + listener: { current: (...args) => any }, + hookObserver = true +): Object { + let listeners: ((mutation) => {})[] = []; + let oldData: [any, any][] = []; + let proxies = new Map(); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function getFun(rawObj: { get: (key: any) => any; has: (key: any) => boolean }, key: any): any { + const keyProxy = rawObj.has(key) ? key : proxies.get(key); + if (!keyProxy) return; + const observer = getObserver(rawObj); + observer.useProp(key); + const value = rawObj.get(keyProxy); + + // 对于value也需要进一步代理 + const valProxy = createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [key]: change.mutation.from }, + { ...rawObj, [key]: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + + return valProxy; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Map的set方法 + function set( + rawObj: { + get: (key: any) => any; + set: (key: any, value: any) => any; + has: (key: any) => boolean; + entries: () => [any, any][]; + }, + key: any, + value: any + ): any { + if (rawObj.has(key) || rawObj.has(proxies.get(key))) { + // VALUE CHANGE (whole value for selected key is changed) + const oldValue = rawObj.get(proxies.get(key)); + if (isSame(value, oldValue)) return; + rawObj.set(proxies.get(key), value); + const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : resolveMutation(null, rawObj); + const observer = getObserver(rawObj); + observer.setProp(COLLECTION_CHANGE, mutation); + + if (observer.watchers[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, value, mutation); + }); + } + + observer.setProp(key, mutation); + oldData = [...Array.from(rawObj.entries())]; + } else { + // NEW VALUE + const keyProxy = createProxy(key, { + current: change => { + // KEY CHANGE + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, ['_keyChange']: change.mutation.from }, + { ...rawObj, ['_keyChange']: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + proxies.set(key, keyProxy); + + rawObj.set(keyProxy, value); + const observer = getObserver(rawObj); + const mutation = resolveMutation( + { + _type: 'Map', + entries: oldData, + }, + { + _type: 'Map', + entries: Array.from(rawObj.entries()), + } + ); + observer.setProp(COLLECTION_CHANGE, mutation); + + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, null, value, mutation); + }); + } + observer.setProp(key, mutation); + oldData = [...Array.from(rawObj.entries())]; + } + + return rawObj; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function has(rawObj: { has: (any) => boolean }, key: any): boolean { + const observer = getObserver(rawObj); + observer.useProp(key); + if (rawObj.has(key)) { + return true; + } + return proxies.has(key); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function clear(rawObj: { size: number; clear: () => void; entries: () => [any, any][] }) { + const oldSize = rawObj.size; + rawObj.clear(); + + if (oldSize > 0) { + const observer = getObserver(rawObj); + observer.allChange(); + oldData = [...Array.from(rawObj.entries())]; + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function deleteFun( + rawObj: { has: (key: any) => boolean; delete: (key: any) => void; entries: () => [any, any][] }, + key: any + ) { + if (rawObj.has(key) || proxies.has(key)) { + rawObj.delete(key || proxies.get(key)); + + const observer = getObserver(rawObj); + const mutation = resolveMutation( + { + _type: 'Map', + entries: oldData, + }, + { + _type: 'Map', + entries: Array.from(rawObj.entries()), + } + ); + observer.setProp(key, mutation); + observer.setProp(COLLECTION_CHANGE, mutation); + + oldData = [...Array.from(rawObj.entries())]; + return true; + } + + return false; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function forEach( + rawObj: { forEach: (callback: (value: any, key: any) => void) => void }, + callback: (valProxy: any, keyProxy: any, rawObj: any) => void + ) { + const observer = getObserver(rawObj); + observer.useProp(COLLECTION_CHANGE); + rawObj.forEach((value, key) => { + const keyProxy = createProxy(value, { + current: change => { + //KEY ATTRIBUTES CHANGED + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, ['_keyChange']: change.mutation.from }, + { ...rawObj, ['_keyChange']: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + const valProxy = createProxy(key, { + current: change => { + // VALUE ATTRIBUTE CHANGED + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, key: change.mutation.from }, + { ...rawObj, key: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + // 最后一个参数要返回代理对象 + return callback(keyProxy, valProxy, rawObj); + }); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, type) { + const observer = getObserver(rawObj); + const hookObserver = hookObserverMap.get(rawObj); + observer.useProp(COLLECTION_CHANGE); + + return { + next() { + const { value, done } = rawIt.next(); + if (done) { + return { + value: createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [value]: change.mutation.from }, + { ...rawObj, [value]: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserver + ), + done, + }; + } + + observer.useProp(COLLECTION_CHANGE); + let newVal; + if (type === 'entries') { + //ENTRY CHANGED + newVal = [ + createProxy(value[0], { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, ['itemChange']: { key: change.mutation.from, value: value[1] } }, + { ...rawObj, ['itemChange']: { key: change.mutation.to, value: value[1] } } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserver + ), + createProxy(value[1], { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, item: { key: value[0], value: change.mutation.from } }, + { ...rawObj, item: { key: value[0], value: change.mutation.to } } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserver + ), + ]; + } else { + // SINGLE VALUE CHANGED + newVal = createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [type === 'keys' ? 'key' : 'value']: change.mutation.from }, + { ...rawObj, [type === 'keys' ? 'key' : 'value']: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserver + ); + } + + return { value: newVal, done }; + }, + // 判断Symbol类型,兼容IE + [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() { + return this; + }, + }; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function size(rawObj: { size: number }) { + const observer = getObserver(rawObj); + observer.useProp(COLLECTION_CHANGE); + return rawObj.size; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.keys(), 'keys'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.values(), 'values'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.entries(), 'entries'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function forOf(rawObj: { + entries: () => { next: () => { value: any; done: boolean } }; + values: () => { next: () => { value: any; done: boolean } }; + }) { + return wrapIterator(rawObj, rawObj.entries(), 'entries'); + } + + const handler = { + get, + set, + delete: deleteFun, + clear, + has, + entries, + forEach, + keys, + values, + // 判断Symbol类型,兼容IE + [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf, + }; + + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (key === 'size') { + return size(rawObj); + } + + if (key === 'get') { + return getFun.bind(null, rawObj); + } + + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } + + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + + if (key === RAW_VALUE) { + return rawObj; + } + + return Reflect.get(rawObj, key, receiver); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const boundHandler = {}; + Object.entries(handler).forEach(([id, val]) => { + boundHandler[id] = (...args: any[]) => { + return (val as any)(...args, hookObserver); + }; + }); + + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + return new Proxy(rawObj, { ...boundHandler }); +} diff --git a/libs/inula/src/inulax/proxy/handlers/ObjectProxyHandler.ts b/libs/inula/src/inulax/proxy/handlers/ObjectProxyHandler.ts new file mode 100644 index 00000000..c9e5521d --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/ObjectProxyHandler.ts @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 { isSame, resolveMutation } from '../../CommonUtils'; +import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { OBSERVER_KEY, RAW_VALUE } from '../../Constants'; +import { isPanelActive } from '../../devtools'; + +function set(rawObj: object, key: string, value: any, receiver: any): boolean { + const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + const observer = getObserver(rawObj); + + const oldValue = rawObj[key]; + const newValue = value; + + const ret = Reflect.set(rawObj, key, newValue, receiver); + const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : resolveMutation(null, rawObj); + + if (!isSame(newValue, oldValue)) { + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue, mutation); + }); + } + observer.setProp(key, mutation); + } + return ret; +} + +export function createObjectProxy( + rawObj: T, + listener: { current: (...args) => any }, + singleLevel = false +): ProxyHandler { + let listeners = [] as ((...args) => void)[]; + + function get(rawObj: object, key: string | symbol, receiver: any): any { + // The observer object of symbol ('_inulaObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep. + if (key === OBSERVER_KEY) { + return undefined; + } + + const observer = getObserver(rawObj); + + if (key === 'watch') { + return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + + if (key === RAW_VALUE) { + return rawObj; + } + + observer.useProp(key); + + const value = Reflect.get(rawObj, key, receiver); + + // 对于prototype不做代理 + if (key !== 'prototype') { + // 对于value也需要进一步代理 + const valProxy = singleLevel + ? value + : createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [key]: change.mutation.from }, + { ...rawObj, [key]: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + + return valProxy; + } + + return value; + } + + const proxy = new Proxy(rawObj, { + get, + set, + }); + + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + + return proxy; +} diff --git a/libs/inula/src/inulax/proxy/handlers/SetProxy.ts b/libs/inula/src/inulax/proxy/handlers/SetProxy.ts new file mode 100644 index 00000000..933ad73c --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/SetProxy.ts @@ -0,0 +1,306 @@ +/* + * 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 { resolveMutation } from '../../CommonUtils'; +import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { RAW_VALUE } from '../../Constants'; + +const COLLECTION_CHANGE = '_collectionChange'; + +export function createSetProxy( + rawObj: T, + listener: { current: (...args) => any }, + hookObserver = true +): ProxyHandler { + let listeners: ((mutation) => {})[] = []; + let proxies = new WeakMap(); + + // Set的add方法 + function add(rawObj: { add: (any) => void; has: (any) => boolean; values: () => any[] }, value: any): Object { + if (!rawObj.has(proxies.get(value))) { + const proxy = createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, valueChange: change.mutation.from }, + { ...rawObj, valueChange: change.mutation.to } + ); + listener.current({ + ...change, + mutation, + }); + listeners.forEach(lst => + lst({ + ...change, + mutation, + }) + ); + }, + }, + hookObserverMap.get(rawObj) + ); + const oldValues = Array.from(rawObj.values()); + + proxies.set(value, proxy); + + rawObj.add(proxies.get(value)); + + const observer = getObserver(rawObj); + const mutation = resolveMutation( + { + _type: 'Set', + values: oldValues, + }, + { + _type: 'Set', + values: Array.from(rawObj.values()), + } + ); + + observer.setProp(value, mutation); + observer.setProp(COLLECTION_CHANGE, mutation); + } + + return rawObj; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function has(rawObj: { has: (string) => boolean }, value: any): boolean { + const observer = getObserver(rawObj); + observer.useProp(value); + + return rawObj.has(proxies.get(value)); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function deleteFun( + rawObj: { has: (key: any) => boolean; delete: (value: any) => void; values: () => any[] }, + value: any + ) { + const val = rawObj.has(proxies.get(value)) ? proxies.get(value) : value; + if (rawObj.has(val)) { + const oldValues = Array.from(rawObj.values()); + rawObj.delete(val); + + proxies.delete(value); + + const observer = getObserver(rawObj); + const mutation = resolveMutation( + { + _type: 'Set', + values: oldValues, + }, + { + _type: 'Set', + values: Array.from(rawObj.values()), + } + ); + + observer.setProp(value, mutation); + observer.setProp(COLLECTION_CHANGE, mutation); + + return true; + } + + return false; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function clear(rawObj: { size: number; clear: () => void }) { + const oldSize = rawObj.size; + rawObj.clear(); + + if (oldSize > 0) { + const observer = getObserver(rawObj); + observer.allChange(); + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function size(rawObj: { size: number }) { + const observer = getObserver(rawObj); + observer.useProp(COLLECTION_CHANGE); + return rawObj.size; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const handler = { + get, + add, + delete: deleteFun, + has, + clear, + forEach, + forOf, + entries, + keys, + values, + [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf, + }; + + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } + + if (key === 'size') { + return size(rawObj); + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === RAW_VALUE) { + return rawObj; + } + + return Reflect.get(rawObj, key, receiver); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }) { + const observer = getObserver(rawObj); + const hookObserver = hookObserverMap.get(rawObj); + observer.useProp(COLLECTION_CHANGE); + + return { + next() { + const currentListener = { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, valueChange: change.mutation.from }, + { ...rawObj, valueChange: change.mutation.to } + ); + listener.current({ + ...change, + mutation, + }); + listeners.forEach(lst => + lst({ + ...change, + mutation, + }) + ); + }, + }; + const { value, done } = rawIt.next(); + if (done) { + return { value: createProxy(value, currentListener, hookObserver), done }; + } + + observer.useProp(COLLECTION_CHANGE); + + let newVal; + newVal = createProxy(value, currentListener, hookObserver); + + return { value: newVal, done }; + }, + // 判断Symbol类型,兼容IE + [typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() { + return this; + }, + }; + } + + function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.keys()); + } + + function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.values()); + } + + function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.entries()); + } + + function forOf(rawObj: { + entries: () => { next: () => { value: any; done: boolean } }; + values: () => { next: () => { value: any; done: boolean } }; + }) { + const iterator = rawObj.values(); + return wrapIterator(rawObj, iterator); + } + + function forEach( + rawObj: { forEach: (callback: (value: any, key: any) => void) => void }, + callback: (valProxy: any, keyProxy: any, rawObj: any) => void + ) { + const observer = getObserver(rawObj); + observer.useProp(COLLECTION_CHANGE); + rawObj.forEach((value, key) => { + const currentListener = { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, valueChange: change.mutation.from }, + { ...rawObj, valueChange: change.mutation.to } + ); + listener.current({ + ...change, + mutation, + }); + listeners.forEach(lst => + lst({ + ...change, + mutation, + }) + ); + }, + }; + const valProxy = createProxy(value, currentListener, hookObserverMap.get(rawObj)); + const keyProxy = createProxy(key, currentListener, hookObserverMap.get(rawObj)); + // 最后一个参数要返回代理对象 + return callback(valProxy, keyProxy, rawObj); + }); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const boundHandler = {}; + Object.entries(handler).forEach(([id, val]) => { + boundHandler[id] = (...args: any[]) => { + return (val as any)(...args, hookObserver); + }; + }); + return new Proxy(rawObj, { ...boundHandler }); +} diff --git a/libs/inula/src/inulax/proxy/handlers/WeakMapProxy.ts b/libs/inula/src/inulax/proxy/handlers/WeakMapProxy.ts new file mode 100644 index 00000000..0c818b3c --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/WeakMapProxy.ts @@ -0,0 +1,204 @@ +/* + * 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 { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { isSame } from '../../CommonUtils'; +import { resolveMutation } from '../../CommonUtils'; +import { isPanelActive } from '../../devtools'; +import { RAW_VALUE } from '../../Constants'; + +const COLLECTION_CHANGE = '_collectionChange'; + +export function createWeakMapProxy( + rawObj: Object, + listener: { current: (...args) => any }, + hookObserver = true +): Object { + let listeners: ((mutation) => {})[] = []; + + const handler = { + get, + set, + add, + delete: deleteFun, + clear, + has, + }; + + function getFun(rawObj: { get: (key: any) => any }, key: any) { + const observer = getObserver(rawObj); + observer.useProp(key); + + const value = rawObj.get(key); + // 对于value也需要进一步代理 + const valProxy = createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [key]: change.mutation.from }, + { ...rawObj, [key]: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + + return valProxy; + } + + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (key === 'get') { + return getFun.bind(null, rawObj); + } + + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } + + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + + if (key === RAW_VALUE) { + return rawObj; + } + + return Reflect.get(rawObj, key, receiver); + } + + // Map的set方法 + function set( + rawObj: { get: (key: any) => any; set: (key: any, value: any) => any; has: (key: any) => boolean }, + key: any, + value: any + ) { + const oldValue = rawObj.get(key); + const newValue = value; + rawObj.set(key, newValue); + const valChange = !isSame(newValue, oldValue); + const observer = getObserver(rawObj); + + const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : resolveMutation(null, rawObj); + + if (valChange || !rawObj.has(key)) { + observer.setProp(COLLECTION_CHANGE, mutation); + } + + if (valChange) { + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue, mutation); + }); + } + + observer.setProp(key, mutation); + } + + return rawObj; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set的add方法 + function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object { + const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + if (!rawObj.has(value)) { + rawObj.add(value); + + const observer = getObserver(rawObj); + const mutation = isPanelActive() + ? resolveMutation(oldCollection, rawObj) + : { mutation: true, from: null, to: rawObj }; + observer.setProp(value, mutation); + observer.setProp(COLLECTION_CHANGE, mutation); + } + + return rawObj; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function has(rawObj: { has: (string) => boolean }, key: any): boolean { + const observer = getObserver(rawObj); + observer.useProp(key); + + return rawObj.has(key); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function clear(rawObj: { size: number; clear: () => void }) { + const oldSize = rawObj.size; + rawObj.clear(); + + if (oldSize > 0) { + const observer = getObserver(rawObj); + observer.allChange(); + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) { + const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + if (rawObj.has(key)) { + rawObj.delete(key); + + const observer = getObserver(rawObj); + const mutation = isPanelActive() + ? resolveMutation(oldCollection, rawObj) + : { mutation: true, from: null, to: rawObj }; + observer.setProp(key, mutation); + observer.setProp(COLLECTION_CHANGE, mutation); + + return true; + } + + return false; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const boundHandler = {}; + Object.entries(handler).forEach(([id, val]) => { + boundHandler[id] = (...args: any[]) => { + return (val as any)(...args, hookObserver); + }; + }); + return new Proxy(rawObj, { ...boundHandler }); +} diff --git a/libs/inula/src/inulax/proxy/handlers/WeakSetProxy.ts b/libs/inula/src/inulax/proxy/handlers/WeakSetProxy.ts new file mode 100644 index 00000000..e1a843cd --- /dev/null +++ b/libs/inula/src/inulax/proxy/handlers/WeakSetProxy.ts @@ -0,0 +1,141 @@ +/* + * 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 { resolveMutation } from '../../CommonUtils'; +import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; +import { RAW_VALUE } from '../../Constants'; + +export function createWeakSetProxy( + rawObj: T, + listener: { current: (...args) => any }, + hookObserver = true, +): ProxyHandler { + let listeners: ((mutation) => {})[] = []; + let proxies = new WeakMap(); + + const handler = { + get, + add, + delete: deleteFun, + has, + }; + + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === RAW_VALUE) { + return rawObj; + } + + return Reflect.get(rawObj, key, receiver); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set的add方法 + function add(rawObj: { add: (any) => void; has: (any) => boolean }, value: any): Object { + if (!rawObj.has(proxies.get(value))) { + const proxy = createProxy(value, { + current: change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + let mutation = resolveMutation( + { ...rawObj, [value]: change.mutation.from }, + { ...rawObj, [value]: change.mutation.to } + ); + listener.current({ ...change, mutation }); + listeners.forEach(lst => lst({ ...change, mutation })); + }, + }, + hookObserverMap.get(rawObj) + ); + + proxies.set(value, proxy); + + rawObj.add(proxies.get(value)); + + const observer = getObserver(rawObj); + const mutation = { mutation: true, from: rawObj, to: value }; + + observer.setProp(value, mutation); + } + + return rawObj; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function has(rawObj: { has: (string) => boolean }, value: any): boolean { + const observer = getObserver(rawObj); + observer.useProp(value); + + return rawObj.has(proxies.get(value)); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function deleteFun(rawObj: { has: (key: any) => boolean; delete: (value: any) => void }, value: any) { + if (rawObj.has(proxies.get(value))) { + rawObj.delete(proxies.get(value)); + + proxies.delete(value); + + const observer = getObserver(rawObj); + const mutation = { mutation: true, from: value, to: rawObj }; + + observer.setProp(value, mutation); + + return true; + } + + return false; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + getObserver(rawObj).addListener(change => { + if (!change.parents) change.parents = []; + change.parents.push(rawObj); + listener.current(change); + listeners.forEach(lst => lst(change)); + }); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const boundHandler = {}; + Object.entries(handler).forEach(([id, val]) => { + boundHandler[id] = (...args: any[]) => { + return (val as any)(...args, hookObserver); + }; + }); + return new Proxy(rawObj, { ...boundHandler }); +} diff --git a/libs/horizon/src/horizonx/proxy/readonlyProxy.ts b/libs/inula/src/inulax/proxy/readonlyProxy.ts similarity index 100% rename from libs/horizon/src/horizonx/proxy/readonlyProxy.ts rename to libs/inula/src/inulax/proxy/readonlyProxy.ts diff --git a/libs/horizon/src/horizonx/proxy/watch.ts b/libs/inula/src/inulax/proxy/watch.ts similarity index 100% rename from libs/horizon/src/horizonx/proxy/watch.ts rename to libs/inula/src/inulax/proxy/watch.ts diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/inula/src/inulax/store/StoreHandler.ts similarity index 88% rename from libs/horizon/src/horizonx/store/StoreHandler.ts rename to libs/inula/src/inulax/store/StoreHandler.ts index 4956fe47..47099f98 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/inula/src/inulax/store/StoreHandler.ts @@ -38,6 +38,7 @@ import { ACTION_QUEUED, INITIALIZED, QUEUE_FINISHED, + QUEUE_PENDING, STATE_CHANGE, SUBSCRIBED, UNSUBSCRIBED, @@ -52,6 +53,100 @@ const idGenerator = { const storeMap = new Map>(); +// 通过该方法执行store.$queue中的action +function tryNextAction(storeObj, proxyObj, config, plannedActions) { + if (!plannedActions.length) { + if (proxyObj.$pending) { + const timestamp = Date.now(); + const duration = timestamp - proxyObj.$pending; + proxyObj.$pending = false; + devtools.emit(QUEUE_FINISHED, { + store: storeObj, + endedAt: timestamp, + duration, + }); + } + return; + } + + const nextAction = plannedActions.shift()!; + const result = config.actions + ? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload) + : undefined; + + if (isPromise(result)) { + result.then(value => { + nextAction.resolve(value); + tryNextAction(storeObj, proxyObj, config, plannedActions); + }); + } else { + nextAction.resolve(result); + tryNextAction(storeObj, proxyObj, config, plannedActions); + } +} + +// 删除Observers中保存的这个VNode的相关数据 +export function clearVNodeObservers(vNode: VNode) { + if (!vNode.observers) { + return; + } + + vNode.observers.forEach(observer => { + observer.clearByVNode(vNode); + }); + + vNode.observers.clear(); +} + +// 注册VNode销毁时的清理动作 +function registerDestroyFunction() { + const processingVNode = getProcessingVNode(); + + // 获取不到当前运行的VNode,说明不在组件中运行,属于非法场景 + if (!processingVNode) { + return; + } + + if (!processingVNode.observers) { + processingVNode.observers = new Set(); + } + + // 函数组件 + if (processingVNode.tag === FunctionComponent) { + const vNodeRef = useRef(processingVNode); + + useEffect(() => { + return () => { + clearVNodeObservers(vNodeRef.current); + vNodeRef.current.observers = null; + }; + }, []); + } else if (processingVNode.tag === ClassComponent) { + // 类组件 + if (!processingVNode.classComponentWillUnmount) { + processingVNode.classComponentWillUnmount = vNode => { + clearVNodeObservers(vNode); + vNode.observers = null; + }; + } + } +} + +// createStore返回的是一个getStore的函数,这个函数必须要在组件(函数/类组件)里面被执行,因为要注册VNode销毁时的清理动作 +function createGetStore, C extends UserComputedValues>( + storeObj: StoreObj +): () => StoreObj { + const getStore = () => { + if (!storeObj.$config.options?.isReduxAdapter) { + registerDestroyFunction(); + } + + return storeObj; + }; + + return getStore; +} + export function createStore, C extends UserComputedValues>( config: StoreConfig ): () => StoreObj { @@ -62,7 +157,11 @@ export function createStore, C extend const id = config.id || idGenerator.get('UNNAMED_STORE'); - const proxyObj = createProxy(config.state, id, !config.options?.isReduxAdapter); + const listener = { + current: listener => {}, + }; + + const proxyObj = createProxy(config.state, listener, !config.options?.isReduxAdapter); proxyObj.$pending = false; @@ -76,16 +175,28 @@ export function createStore, C extend $c: $c as ComputedValues, $queue: $queue as QueuedStoreActions, $config: config, + $listeners: [ + change => { + devtools.emit(STATE_CHANGE, { + store: storeObj, + change, + }); + }, + ], $subscribe: listener => { devtools.emit(SUBSCRIBED, { store: storeObj, listener }); - proxyObj.addListener(listener); + storeObj.$listeners.push(listener); }, $unsubscribe: listener => { - devtools.emit(UNSUBSCRIBED, storeObj); - proxyObj.removeListener(listener); + devtools.emit(UNSUBSCRIBED, { store: storeObj }); + storeObj.$listeners = storeObj.$listeners.filter(item => item != listener); }, } as unknown as StoreObj; + listener.current = (...args) => { + storeObj.$listeners.forEach(listener => listener(...args)); + }; + const plannedActions: PlannedAction>[] = []; // 包装actions @@ -104,7 +215,11 @@ export function createStore, C extend }); return new Promise(resolve => { if (!proxyObj.$pending) { - proxyObj.$pending = true; + proxyObj.$pending = Date.now(); + devtools.emit(QUEUE_PENDING, { + store: storeObj, + startedAt: proxyObj.$pending, + }); const result = config.actions![action].bind(storeObj, proxyObj)(...payload); @@ -192,101 +307,9 @@ export function createStore, C extend store: storeObj, }); - proxyObj.addListener(change => { - devtools.emit(STATE_CHANGE, { - store: storeObj, - change, - }); - }); - return createGetStore(storeObj); } -// 通过该方法执行store.$queue中的action -function tryNextAction(storeObj, proxyObj, config, plannedActions) { - if (!plannedActions.length) { - proxyObj.$pending = false; - return; - } - - const nextAction = plannedActions.shift()!; - const result = config.actions - ? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload) - : undefined; - - if (isPromise(result)) { - result.then(value => { - nextAction.resolve(value); - tryNextAction(storeObj, proxyObj, config, plannedActions); - }); - } else { - nextAction.resolve(result); - tryNextAction(storeObj, proxyObj, config, plannedActions); - } -} - -// createStore返回的是一个getStore的函数,这个函数必须要在组件(函数/类组件)里面被执行,因为要注册VNode销毁时的清理动作 -function createGetStore, C extends UserComputedValues>( - storeObj: StoreObj -): () => StoreObj { - const getStore = () => { - if (!storeObj.$config.options?.isReduxAdapter) { - registerDestroyFunction(); - } - - return storeObj; - }; - - return getStore; -} - -// 删除Observers中保存的这个VNode的相关数据 -export function clearVNodeObservers(vNode: VNode) { - if (!vNode.observers) { - return; - } - - vNode.observers.forEach(observer => { - observer.clearByVNode(vNode); - }); - - vNode.observers.clear(); -} - -// 注册VNode销毁时的清理动作 -function registerDestroyFunction() { - const processingVNode = getProcessingVNode(); - - // 获取不到当前运行的VNode,说明不在组件中运行,属于非法场景 - if (!processingVNode) { - return; - } - - if (!processingVNode.observers) { - processingVNode.observers = new Set(); - } - - // 函数组件 - if (processingVNode.tag === FunctionComponent) { - const vNodeRef = useRef(processingVNode); - - useEffect(() => { - return () => { - clearVNodeObservers(vNodeRef.current); - vNodeRef.current.observers = null; - }; - }, []); - } else if (processingVNode.tag === ClassComponent) { - // 类组件 - if (!processingVNode.classComponentWillUnmount) { - processingVNode.classComponentWillUnmount = vNode => { - clearVNodeObservers(vNode); - vNode.observers = null; - }; - } - } -} - // 函数组件中使用的hook export function useStore, C extends UserComputedValues>( id: string diff --git a/libs/horizon/src/horizonx/types.d.ts b/libs/inula/src/inulax/types.d.ts similarity index 99% rename from libs/horizon/src/horizonx/types.d.ts rename to libs/inula/src/inulax/types.d.ts index 5c62a678..6c6b6cba 100644 --- a/libs/horizon/src/horizonx/types.d.ts +++ b/libs/inula/src/inulax/types.d.ts @@ -61,6 +61,7 @@ export type StoreObj, C extends UserC $a: StoreActions; $c: UserComputedValues; $queue: QueuedStoreActions; + $listeners; $subscribe: (listener: (mutation) => void) => void; $unsubscribe: (listener: (mutation) => void) => void; } & { [K in keyof S]: S[K] } & { [K in keyof A]: Action } & { [K in keyof C]: ReturnType }; diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/inula/src/renderer/ContextSaver.ts similarity index 100% rename from libs/horizon/src/renderer/ContextSaver.ts rename to libs/inula/src/renderer/ContextSaver.ts diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/inula/src/renderer/ErrorHandler.ts similarity index 98% rename from libs/horizon/src/renderer/ErrorHandler.ts rename to libs/inula/src/renderer/ErrorHandler.ts index c075c05b..ce7ae550 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/inula/src/renderer/ErrorHandler.ts @@ -72,7 +72,7 @@ function createClassErrorUpdate(vNode: VNode, error: any): Update { } return update; } -function isPromise(error: any): error is PromiseType { +export function isPromise(error: any): error is PromiseType { return error !== null && typeof error === 'object' && typeof error.then === 'function'; } // 处理capture和bubble阶段抛出的错误 diff --git a/libs/horizon/src/renderer/ExecuteMode.ts b/libs/inula/src/renderer/ExecuteMode.ts similarity index 100% rename from libs/horizon/src/renderer/ExecuteMode.ts rename to libs/inula/src/renderer/ExecuteMode.ts diff --git a/libs/horizon/src/renderer/GlobalVar.ts b/libs/inula/src/renderer/GlobalVar.ts similarity index 100% rename from libs/horizon/src/renderer/GlobalVar.ts rename to libs/inula/src/renderer/GlobalVar.ts diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/inula/src/renderer/Renderer.ts similarity index 100% rename from libs/horizon/src/renderer/Renderer.ts rename to libs/inula/src/renderer/Renderer.ts diff --git a/libs/horizon/src/renderer/RootStack.ts b/libs/inula/src/renderer/RootStack.ts similarity index 73% rename from libs/horizon/src/renderer/RootStack.ts rename to libs/inula/src/renderer/RootStack.ts index f5b88d1f..b5eadc6c 100644 --- a/libs/horizon/src/renderer/RootStack.ts +++ b/libs/inula/src/renderer/RootStack.ts @@ -14,15 +14,21 @@ */ import { VNode } from './vnode/VNode'; -const currentRootStack: VNode[] = []; + +const currentRootStack: (VNode | undefined)[] = []; +let index = -1; export function getCurrentRoot() { - return currentRootStack[currentRootStack.length - 1]; + return currentRootStack[index]; } export function pushCurrentRoot(root: VNode) { - return currentRootStack.push(root); + index++; + currentRootStack[index] = root; } export function popCurrentRoot() { - return currentRootStack.pop(); + const target = currentRootStack[index]; + currentRootStack[index] = undefined; + index--; + return target; } diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/inula/src/renderer/TreeBuilder.ts similarity index 94% rename from libs/horizon/src/renderer/TreeBuilder.ts rename to libs/inula/src/renderer/TreeBuilder.ts index 2e28c75f..f079b896 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/inula/src/renderer/TreeBuilder.ts @@ -139,7 +139,7 @@ function bubbleVNode(vNode: VNode): void { node = parent; // 更新processing,抛出异常时可以使用 processing = node; - } while (node !== null); + } while (node); // 修改结果 if (getBuildResult() === BuildInComplete) { @@ -179,6 +179,11 @@ function isEqualByIndex(idx: number, pathArrays: string[][]) { function getChildByIndex(vNode: VNode, idx: number) { let node = vNode.child; for (let i = 0; i < idx; i++) { + // 场景:当组件被销毁,业务若异步(定时器)调用setState修改状态,可能出现路径错误,此处进行保护。 + if (node === null || node === undefined) { + return null; + } + node = node.next; } return node; @@ -220,7 +225,7 @@ export function calcStartUpdateVNode(treeRoot: VNode) { const pathIndex = Number(startNodePath[i]); node = getChildByIndex(node, pathIndex)!; // 路径错误时,回退到从根更新 - if (node == null) { + if (node === null) { return treeRoot; } } @@ -228,6 +233,40 @@ export function calcStartUpdateVNode(treeRoot: VNode) { return node; } +// 在局部更新时,从上到下恢复父节点的context和PortalStack +function recoverTreeContext(vNode: VNode) { + const contextProviders: VNode[] = []; + let parent = vNode.parent; + while (parent !== null) { + if (parent.tag === ContextProvider) { + contextProviders.unshift(parent); + } + if (parent.tag === DomPortal) { + pushCurrentRoot(parent); + } + parent = parent.parent; + } + + contextProviders.forEach(node => { + setContext(node, node.props.value); + }); +} + +// 在局部更新时,从下到上重置父节点的context +function resetTreeContext(vNode: VNode) { + let parent = vNode.parent; + + while (parent !== null) { + if (parent.tag === ContextProvider) { + resetContext(parent); + } + if (parent.tag === DomPortal) { + popCurrentRoot(); + } + parent = parent.parent; + } +} + // ============================== 深度遍历 ============================== function buildVNodeTree(treeRoot: VNode) { const preMode = copyExecuteMode(); @@ -299,43 +338,11 @@ function buildVNodeTree(treeRoot: VNode) { setExecuteMode(preMode); } -// 在局部更新时,从上到下恢复父节点的context和PortalStack -function recoverTreeContext(vNode: VNode) { - const contextProviders: VNode[] = []; - let parent = vNode.parent; - while (parent !== null) { - if (parent.tag === ContextProvider) { - contextProviders.unshift(parent); - } - if (parent.tag === DomPortal) { - pushCurrentRoot(parent); - } - parent = parent.parent; - } - contextProviders.forEach(node => { - setContext(node, node.props.value); - }); -} - -// 在局部更新时,从下到上重置父节点的context -function resetTreeContext(vNode: VNode) { - let parent = vNode.parent; - - while (parent !== null) { - if (parent.tag === ContextProvider) { - resetContext(parent); - } - if (parent.tag === DomPortal) { - popCurrentRoot(); - } - parent = parent.parent; - } -} - // 总体任务入口 function renderFromRoot(treeRoot) { runAsyncEffects(); pushCurrentRoot(treeRoot); + // 1. 构建vNode树 buildVNodeTree(treeRoot); @@ -346,11 +353,12 @@ function renderFromRoot(treeRoot) { // 2. 提交变更 submitToRender(treeRoot); + popCurrentRoot(); - if (window.__HORIZON_DEV_HOOK__) { - const hook = window.__HORIZON_DEV_HOOK__; - // injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量 - // Horizon 代码初次加载时不会初始化 helper + if (window.__INULA_DEV_HOOK__) { + const hook = window.__INULA_DEV_HOOK__; + // injector.js 可能在 Inula 代码之后加载,此时无 __INULA_DEV_HOOK__ 全局变量 + // Inula 代码初次加载时不会初始化 helper if (!hook.isInit) { injectUpdater(); } @@ -390,7 +398,7 @@ export function launchUpdateFromVNode(vNode: VNode) { ) { // 不是渲染阶段触发 - // 业务直接调用Horizon.render的时候会进入这个分支,同步渲染。 + // 业务直接调用Inula.render的时候会进入这个分支,同步渲染。 // 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。 renderFromRoot(treeRoot); } else { @@ -403,7 +411,7 @@ export function launchUpdateFromVNode(vNode: VNode) { } } -// ============================== HorizonDOM使用 ============================== +// ============================== InulaDOM使用 ============================== export function runDiscreteUpdates() { if (checkMode(ByAsync) || checkMode(InRender)) { // 已经渲染,不能再同步执行待工作的任务,有可能是被生命周期或effect触发的事件导致的,如el.focus() diff --git a/libs/horizon/src/renderer/Types.ts b/libs/inula/src/renderer/Types.ts similarity index 94% rename from libs/horizon/src/renderer/Types.ts rename to libs/inula/src/renderer/Types.ts index c514bf5c..870f129b 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/inula/src/renderer/Types.ts @@ -13,6 +13,8 @@ * See the Mulan PSL v2 for more details. */ +import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode'; + export { VNode } from './vnode/VNode'; type Trigger = (A) => void; @@ -32,7 +34,7 @@ export type JSXElement = { key: any; ref: any; props: any; - belongClassVNode: any; + [BELONG_CLASS_VNODE_KEY]: any; }; export type ProviderType = { @@ -77,3 +79,5 @@ export type Source = { fileName: string; lineNumber: number; }; + +export type Callback = () => void; diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/inula/src/renderer/UpdateHandler.ts similarity index 93% rename from libs/horizon/src/renderer/UpdateHandler.ts rename to libs/inula/src/renderer/UpdateHandler.ts index 7f678065..21f40299 100644 --- a/libs/horizon/src/renderer/UpdateHandler.ts +++ b/libs/inula/src/renderer/UpdateHandler.ts @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import type { VNode } from './Types'; +import type { VNode, Callback } from './Types'; import { FlagUtils, ShouldCapture } from './vnode/VNodeFlags'; export type Update = { @@ -22,8 +22,6 @@ export type Update = { callback: Callback | null; }; -export type Callback = () => any; - export type Updates = Array | null; export enum UpdateState { @@ -37,8 +35,8 @@ export enum UpdateState { export function newUpdate(): Update { return { type: UpdateState.Update, // 更新的类型 - content: null, // ClassComponent的content是setState第一个参数,TreeRoot的content是HorizonDOM.render的第一个参数 - callback: null, // setState的第二个参数,HorizonDOM.render的第三个参数 + content: null, // ClassComponent的content是setState第一个参数,TreeRoot的content是InulaDOM.render的第一个参数 + callback: null, // setState的第二个参数,InulaDOM.render的第三个参数 }; } diff --git a/libs/horizon/src/renderer/components/BaseClassComponent.ts b/libs/inula/src/renderer/components/BaseClassComponent.ts similarity index 86% rename from libs/horizon/src/renderer/components/BaseClassComponent.ts rename to libs/inula/src/renderer/components/BaseClassComponent.ts index e9ad2fd3..34fa7dc9 100644 --- a/libs/horizon/src/renderer/components/BaseClassComponent.ts +++ b/libs/inula/src/renderer/components/BaseClassComponent.ts @@ -13,6 +13,8 @@ * See the Mulan PSL v2 for more details. */ +import {Callback} from '../Types'; + /** * Component的api setState和forceUpdate在实例生成阶段实现 */ @@ -29,9 +31,9 @@ class Component { this.context = context; } - setState(state: S) { + setState(state: S, callback?: Callback) { if (isDev) { - console.error('Cant not call `this.setState` in the constructor of class component, it will do nothing'); + console.error('Can not call `this.setState` in the constructor of class component, it will do nothing'); } } } diff --git a/libs/horizon/src/renderer/components/CreatePortal.ts b/libs/inula/src/renderer/components/CreatePortal.ts similarity index 100% rename from libs/horizon/src/renderer/components/CreatePortal.ts rename to libs/inula/src/renderer/components/CreatePortal.ts diff --git a/libs/horizon/src/renderer/components/CreateRef.ts b/libs/inula/src/renderer/components/CreateRef.ts similarity index 100% rename from libs/horizon/src/renderer/components/CreateRef.ts rename to libs/inula/src/renderer/components/CreateRef.ts diff --git a/libs/inula/src/renderer/components/ForwardRef.ts b/libs/inula/src/renderer/components/ForwardRef.ts new file mode 100644 index 00000000..836735c2 --- /dev/null +++ b/libs/inula/src/renderer/components/ForwardRef.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 {TYPE_FORWARD_REF, TYPE_MEMO} from '../../external/JSXElementType'; + +export function forwardRef(render: Function) { + const forwardRefJSXElement = { + vtype: TYPE_FORWARD_REF, + $$typeof: TYPE_FORWARD_REF, // 规避三方件hoist-non-react-statics中,通过$$typeof获取类型,但获取不到,导致render被覆盖 + render, + }; + + // 控制vtype不能修改,规避三方件hoist-non-react-statics修改vtype导致问题 + Object.defineProperty(forwardRefJSXElement, 'vtype', { + configurable: false, + writable: false, + }); + + return forwardRefJSXElement; +} diff --git a/libs/horizon/src/renderer/components/Lazy.ts b/libs/inula/src/renderer/components/Lazy.ts similarity index 100% rename from libs/horizon/src/renderer/components/Lazy.ts rename to libs/inula/src/renderer/components/Lazy.ts diff --git a/libs/horizon/src/renderer/components/Memo.ts b/libs/inula/src/renderer/components/Memo.ts similarity index 67% rename from libs/horizon/src/renderer/components/Memo.ts rename to libs/inula/src/renderer/components/Memo.ts index 2ae1e094..6b27be77 100644 --- a/libs/horizon/src/renderer/components/Memo.ts +++ b/libs/inula/src/renderer/components/Memo.ts @@ -16,9 +16,18 @@ import { TYPE_MEMO } from '../../external/JSXElementType'; export function memo(type, compare?: (oldProps: Props, newProps: Props) => boolean) { - return { + const memoJSXElement = { vtype: TYPE_MEMO, + $$typeof: TYPE_MEMO, // 规避三方件hoist-non-react-statics中,通过$$typeof获取类型,但获取不到,导致type被覆盖 type: type, compare: compare === undefined ? null : compare, }; + + // 控制vtype不能修改,规避三方件hoist-non-react-statics修改vtype导致问题 + Object.defineProperty(memoJSXElement, 'vtype', { + configurable: false, + writable: false, + }); + + return memoJSXElement; } diff --git a/libs/horizon/src/renderer/components/context/Context.ts b/libs/inula/src/renderer/components/context/Context.ts similarity index 100% rename from libs/horizon/src/renderer/components/context/Context.ts rename to libs/inula/src/renderer/components/context/Context.ts diff --git a/libs/horizon/src/renderer/components/context/CreateContext.ts b/libs/inula/src/renderer/components/context/CreateContext.ts similarity index 100% rename from libs/horizon/src/renderer/components/context/CreateContext.ts rename to libs/inula/src/renderer/components/context/CreateContext.ts diff --git a/libs/horizon/src/renderer/diff/DiffTools.ts b/libs/inula/src/renderer/diff/DiffTools.ts similarity index 100% rename from libs/horizon/src/renderer/diff/DiffTools.ts rename to libs/inula/src/renderer/diff/DiffTools.ts diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/inula/src/renderer/diff/nodeDiffComparator.ts similarity index 97% rename from libs/horizon/src/renderer/diff/nodeDiffComparator.ts rename to libs/inula/src/renderer/diff/nodeDiffComparator.ts index 660e3ab9..e6a6f32a 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/inula/src/renderer/diff/nodeDiffComparator.ts @@ -27,6 +27,7 @@ import { import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools'; import { travelChildren } from '../vnode/VNodeUtils'; import { markVNodePath } from '../utils/vNodePath'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; enum DiffCategory { TEXT_NODE = 'TEXT_NODE', @@ -166,11 +167,11 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { if (oldNode === null || !isSameType(oldNode, newChild)) { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } else { resultNode = updateVNode(oldNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } break; } else if (newChild.vtype === TYPE_PORTAL) { @@ -181,6 +182,10 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { } break; } + break; + } + default: { + break; } } @@ -231,6 +236,19 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, return null; } +// 设置vNode中的cIndex属性,cIndex是节点在children中的位置 +function setVNodesCIndex(startChild: VNode | null, startIdx: number) { + let node: VNode | null = startChild; + let idx = startIdx; + + while (node !== null) { + node.cIndex = idx; + markVNodePath(node); + node = node.next; + idx++; + } +} + // diff数组类型的节点,核心算法 function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array): VNode | null { let resultingFirstChild: VNode | null = null; @@ -360,7 +378,7 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC // 4. 新节点还有一部分,但是老节点已经没有了 if (oldNode === null) { let isDirectAdd = false; - // TODO: 是否可以扩大至非dom类型节点 + // 是否可以扩大至非dom类型节点待确认 // 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点 if ( parentNode.tag === DomComponent && @@ -478,19 +496,6 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC return resultingFirstChild; } -// 设置vNode中的cIndex属性,cIndex是节点在children中的位置 -function setVNodesCIndex(startChild: VNode | null, startIdx: number) { - let node: VNode | null = startChild; - let idx = startIdx; - - while (node !== null) { - node.cIndex = idx; - markVNodePath(node); - node = node.next; - idx++; - } -} - // 新节点是迭代器类型 function diffIteratorNodesHandler( parentNode: VNode, @@ -512,7 +517,7 @@ function diffIteratorNodesHandler( } // 新节点是字符串类型 -function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode, isComparing: boolean) { +function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode | null, isComparing: boolean) { let newTextNode: VNode | null = null; // 第一个vNode是Text,则复用 @@ -559,7 +564,7 @@ function diffObjectNodeHandler( } let resultNode: VNode | null = null; - let startDelVNode = firstChildVNode; + let startDelVNode: VNode | null = firstChildVNode; if (newChild.vtype === TYPE_COMMON_ELEMENT) { if (canReuseNode) { // 可以复用 @@ -570,7 +575,7 @@ function diffObjectNodeHandler( } else if (isSameType(canReuseNode, newChild)) { resultNode = updateVNode(canReuseNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; startDelVNode = resultNode.next; resultNode.next = null; } @@ -583,7 +588,7 @@ function diffObjectNodeHandler( } else { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } } } else if (newChild.vtype === TYPE_PORTAL) { diff --git a/libs/horizon/src/renderer/hooks/BaseHook.ts b/libs/inula/src/renderer/hooks/BaseHook.ts similarity index 96% rename from libs/horizon/src/renderer/hooks/BaseHook.ts rename to libs/inula/src/renderer/hooks/BaseHook.ts index 5a5694c8..fe9389fa 100644 --- a/libs/horizon/src/renderer/hooks/BaseHook.ts +++ b/libs/inula/src/renderer/hooks/BaseHook.ts @@ -34,7 +34,7 @@ export function setCurrentHook(hook: Hook | null) { currentHook = hook; } -export function throwNotInFuncError() { +export function throwNotInFuncError(): never { throw Error('Hooks should be used inside function component.'); } @@ -52,7 +52,7 @@ export function createHook(state: any = null): Hook { return currentHook; } -export function getNextHook(hook: Hook, hooks: Array>) { +export function getNextHook(hook: Hook, hooks: Array>): Hook | null { return hooks[hook.hIndex + 1] || null; } diff --git a/libs/horizon/src/renderer/hooks/EffectConstant.js b/libs/inula/src/renderer/hooks/EffectConstant.js similarity index 100% rename from libs/horizon/src/renderer/hooks/EffectConstant.js rename to libs/inula/src/renderer/hooks/EffectConstant.js diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/inula/src/renderer/hooks/HookExternal.ts similarity index 83% rename from libs/horizon/src/renderer/hooks/HookExternal.ts rename to libs/inula/src/renderer/hooks/HookExternal.ts index 32be1591..41523b2a 100644 --- a/libs/horizon/src/renderer/hooks/HookExternal.ts +++ b/libs/inula/src/renderer/hooks/HookExternal.ts @@ -27,22 +27,25 @@ import { getProcessingVNode } from '../GlobalVar'; import { Ref, Trigger } from './HookType'; type BasicStateAction = ((S) => S) | S; -type Dispatch = (A) => void; +type Dispatch = (value: A) => void; export function useContext(Context: ContextType): T { const processingVNode = getProcessingVNode(); return getNewContext(processingVNode!, Context, true); } - -export function useState(initialState: (() => S) | S): [S, Dispatch>] { +export function useState(): [S | undefined, Dispatch>] +export function useState(initialState: (() => S) | S): [S, Dispatch>] +export function useState(initialState?: (() => S) | S): [S, Dispatch>] { return useStateImpl(initialState); } -export function useReducer(reducer: (S, A) => S, initialArg: I, init?: (I) => S): [S, Trigger] | void { +export function useReducer(reducer: (S, A) => S, initialArg: I, init?: (I) => S): [S, Trigger] { return useReducerImpl(reducer, initialArg, init); } -export function useRef(initialValue: T): Ref { +export function useRef(): Ref +export function useRef(initialValue: T): Ref +export function useRef(initialValue?: T): Ref { return useRefImpl(initialValue); } diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/inula/src/renderer/hooks/HookMain.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/HookMain.ts rename to libs/inula/src/renderer/hooks/HookMain.ts index 1b63ed5a..27dec0a6 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/inula/src/renderer/hooks/HookMain.ts @@ -18,6 +18,12 @@ import type { VNode } from '../Types'; import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook'; import { HookStage, setHookStage } from './HookStage'; +function resetGlobalVariable() { + setHookStage(null); + setLastTimeHook(null); + setCurrentHook(null); +} + // hook对外入口 export function runFunctionWithHooks, Arg>( funcComp: (props: Props, arg: Arg) => any, @@ -57,9 +63,3 @@ export function runFunctionWithHooks, Arg>( return comp; } - -function resetGlobalVariable() { - setHookStage(null); - setLastTimeHook(null); - setCurrentHook(null); -} diff --git a/libs/horizon/src/renderer/hooks/HookStage.ts b/libs/inula/src/renderer/hooks/HookStage.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/HookStage.ts rename to libs/inula/src/renderer/hooks/HookStage.ts diff --git a/libs/horizon/src/renderer/hooks/HookType.ts b/libs/inula/src/renderer/hooks/HookType.ts similarity index 97% rename from libs/horizon/src/renderer/hooks/HookType.ts rename to libs/inula/src/renderer/hooks/HookType.ts index 5c57454a..54b5158f 100644 --- a/libs/horizon/src/renderer/hooks/HookType.ts +++ b/libs/inula/src/renderer/hooks/HookType.ts @@ -57,4 +57,4 @@ export type Ref = { current: V; }; -export type Trigger = (A) => void; +export type Trigger = (state: A) => void; diff --git a/libs/horizon/src/renderer/hooks/UseCallbackHook.ts b/libs/inula/src/renderer/hooks/UseCallbackHook.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/UseCallbackHook.ts rename to libs/inula/src/renderer/hooks/UseCallbackHook.ts diff --git a/libs/horizon/src/renderer/hooks/UseEffectHook.ts b/libs/inula/src/renderer/hooks/UseEffectHook.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/UseEffectHook.ts rename to libs/inula/src/renderer/hooks/UseEffectHook.ts index 18cdb5ba..67d68df4 100644 --- a/libs/horizon/src/renderer/hooks/UseEffectHook.ts +++ b/libs/inula/src/renderer/hooks/UseEffectHook.ts @@ -21,27 +21,17 @@ import { getHookStage, HookStage } from './HookStage'; import { isArrayEqual } from '../utils/compare'; import { getProcessingVNode } from '../GlobalVar'; -export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { - // 异步触发的effect - useEffect(effectFunc, deps, EffectConstant.Effect); -} +function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect { + const effect: Effect = { + effect: effectFunc, + removeEffect: removeFunc, + dependencies: deps, + effectConstant: effectConstant, + }; -export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { - // 同步触发的effect - useEffect(effectFunc, deps, EffectConstant.LayoutEffect); -} + getProcessingVNode().effectList.push(effect); -function useEffect(effectFunc: () => (() => void) | void, deps: Array | void | null, effectType: number): void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - if (stage === HookStage.Init) { - useEffectForInit(effectFunc, deps, effectType); - } else if (stage === HookStage.Update) { - useEffectForUpdate(effectFunc, deps, effectType); - } + return effect; } export function useEffectForInit(effectFunc, deps, effectType): void { @@ -76,15 +66,25 @@ export function useEffectForUpdate(effectFunc, deps, effectType): void { hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType); } -function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect { - const effect: Effect = { - effect: effectFunc, - removeEffect: removeFunc, - dependencies: deps, - effectConstant: effectConstant, - }; +function useEffect(effectFunc: () => (() => void) | void, deps: Array | void | null, effectType: number): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } - getProcessingVNode().effectList.push(effect); - - return effect; + if (stage === HookStage.Init) { + useEffectForInit(effectFunc, deps, effectType); + } else if (stage === HookStage.Update) { + useEffectForUpdate(effectFunc, deps, effectType); + } +} + +export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { + // 异步触发的effect + useEffect(effectFunc, deps, EffectConstant.Effect); +} + +export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { + // 同步触发的effect + useEffect(effectFunc, deps, EffectConstant.LayoutEffect); } diff --git a/libs/horizon/src/renderer/hooks/UseImperativeHook.ts b/libs/inula/src/renderer/hooks/UseImperativeHook.ts similarity index 91% rename from libs/horizon/src/renderer/hooks/UseImperativeHook.ts rename to libs/inula/src/renderer/hooks/UseImperativeHook.ts index 83f1a74d..1871af8f 100644 --- a/libs/horizon/src/renderer/hooks/UseImperativeHook.ts +++ b/libs/inula/src/renderer/hooks/UseImperativeHook.ts @@ -17,26 +17,9 @@ import { useLayoutEffectImpl } from './UseEffectHook'; import { getHookStage } from './HookStage'; import { throwNotInFuncError } from './BaseHook'; import type { Ref } from './HookType'; +import { isNotNull } from '../../dom/utils/Common'; -export function useImperativeHandleImpl( - ref: { current: R | null } | ((any) => any) | null | void, - func: () => R, - dependencies?: Array | null -): void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null; - useLayoutEffectImpl(effectFunc.bind(null, func, ref), params); -} - -function isNotNull(object: any): boolean { - return object !== null && object !== undefined; -} - -function effectFunc(func: () => R, ref: Ref | ((any) => any) | null): (() => void) | void { +function effectFunc(func: () => R, ref: Ref | ((any) => any) | null): (() => void) | null { if (typeof ref === 'function') { const value = func(); ref(value); @@ -51,4 +34,19 @@ function effectFunc(func: () => R, ref: Ref | ((any) => any) | null): (() ref.current = null; }; } + return null; +} + +export function useImperativeHandleImpl( + ref: { current: R | null } | ((any) => any) | null | void, + func: () => R, + dependencies?: Array | null +): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null; + useLayoutEffectImpl(effectFunc.bind(null, func, ref), params); } diff --git a/libs/horizon/src/renderer/hooks/UseMemoHook.ts b/libs/inula/src/renderer/hooks/UseMemoHook.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/UseMemoHook.ts rename to libs/inula/src/renderer/hooks/UseMemoHook.ts diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/inula/src/renderer/hooks/UseReducerHook.ts similarity index 100% rename from libs/horizon/src/renderer/hooks/UseReducerHook.ts rename to libs/inula/src/renderer/hooks/UseReducerHook.ts index edc4673e..ec348396 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/inula/src/renderer/hooks/UseReducerHook.ts @@ -22,29 +22,6 @@ import { getHookStage, HookStage } from './HookStage'; import type { VNode } from '../Types'; import { getProcessingVNode } from '../GlobalVar'; -export function useReducerImpl( - reducer: (S, A) => S, - initArg: P, - init?: (P) => S, - isUseState?: boolean -): [S, Trigger] | void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - if (stage === HookStage.Init) { - return useReducerForInit(reducer, initArg, init, isUseState); - } else if (stage === HookStage.Update) { - // 获取当前的hook - const currentHook = getCurrentHook(); - // 获取currentHook的更新数组 - const currentHookUpdates = (currentHook.state as Reducer).updates; - - return updateReducerHookState(currentHookUpdates, currentHook, reducer); - } -} - // 构造新的Update数组 function insertUpdate(action: A, hook: Hook): Update { const newUpdate: Update = { @@ -116,6 +93,25 @@ export function useReducerForInit(reducer, initArg, init, isUseState?: boo return [hook.state.stateValue, trigger]; } +// 计算stateValue值 +function calculateNewState(currentHookUpdates: Array>, currentHook, reducer: (S, A) => S) { + const reducerObj = currentHook.state; + let state = reducerObj.stateValue; + + // 循环遍历更新数组,计算新的状态值 + currentHookUpdates.forEach(update => { + // 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState + if (update.didCalculated && reducerObj.isUseState) { + state = update.state; + } else { + const action = update.action; + state = reducer(state, action); + } + }); + + return state; +} + // 更新hook.state function updateReducerHookState(currentHookUpdates, currentHook, reducer): [S, Trigger] { if (currentHookUpdates !== null) { @@ -135,21 +131,25 @@ function updateReducerHookState(currentHookUpdates, currentHook, reducer): return [currentHook.state.stateValue, currentHook.state.trigger]; } -// 计算stateValue值 -function calculateNewState(currentHookUpdates: Array>, currentHook, reducer: (S, A) => S) { - const reducerObj = currentHook.state; - let state = reducerObj.stateValue; +export function useReducerImpl( + reducer: (S, A) => S, + initArg: P, + init?: (P) => S, + isUseState?: boolean +): [S, Trigger] | void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } - // 循环遍历更新数组,计算新的状态值 - currentHookUpdates.forEach(update => { - // 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState - if (update.didCalculated && reducerObj.isUseState) { - state = update.state; - } else { - const action = update.action; - state = reducer(state, action); - } - }); + if (stage === HookStage.Init) { + return useReducerForInit(reducer, initArg, init, isUseState); + } else if (stage === HookStage.Update) { + // 获取当前的hook + const currentHook = getCurrentHook(); + // 获取currentHook的更新数组 + const currentHookUpdates = (currentHook.state as Reducer).updates; - return state; + return updateReducerHookState(currentHookUpdates, currentHook, reducer); + } } diff --git a/libs/horizon/src/renderer/hooks/UseRefHook.ts b/libs/inula/src/renderer/hooks/UseRefHook.ts similarity index 95% rename from libs/horizon/src/renderer/hooks/UseRefHook.ts rename to libs/inula/src/renderer/hooks/UseRefHook.ts index 3f5b0c37..6b154750 100644 --- a/libs/horizon/src/renderer/hooks/UseRefHook.ts +++ b/libs/inula/src/renderer/hooks/UseRefHook.ts @@ -17,7 +17,7 @@ import { createHook, getCurrentHook, throwNotInFuncError } from './BaseHook'; import { getHookStage, HookStage } from './HookStage'; import type { Ref } from './HookType'; -export function useRefImpl(value: V): Ref { +export function useRefImpl(value?: V): Ref { const stage = getHookStage(); if (stage === null) { throwNotInFuncError(); diff --git a/libs/horizon/src/renderer/hooks/UseStateHook.ts b/libs/inula/src/renderer/hooks/UseStateHook.ts similarity index 90% rename from libs/horizon/src/renderer/hooks/UseStateHook.ts rename to libs/inula/src/renderer/hooks/UseStateHook.ts index 84ef4ca2..b20e9f6a 100644 --- a/libs/horizon/src/renderer/hooks/UseStateHook.ts +++ b/libs/inula/src/renderer/hooks/UseStateHook.ts @@ -21,6 +21,6 @@ function defaultReducer(state: S, action: ((S) => S) | S): S { return typeof action === 'function' ? action(state) : action; } -export function useStateImpl(initArg: (() => S) | S): [S, Trigger<((S) => S) | S>] { +export function useStateImpl(initArg?: (() => S) | S): [S, Trigger<((S) => S) | S>] { return useReducerImpl(defaultReducer, initArg, undefined, true); } diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/inula/src/renderer/render/BaseComponent.ts similarity index 97% rename from libs/horizon/src/renderer/render/BaseComponent.ts rename to libs/inula/src/renderer/render/BaseComponent.ts index 7c86a0ca..e3b08455 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/inula/src/renderer/render/BaseComponent.ts @@ -21,7 +21,7 @@ import { FlagUtils } from '../vnode/VNodeFlags'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import componentRenders from './index'; import { setProcessingVNode } from '../GlobalVar'; -import { clearVNodeObservers } from '../../horizonx/store/StoreHandler'; +import { clearVNodeObservers } from '../../inulax/store/StoreHandler'; import { pushCurrentRoot } from '../RootStack'; // 复用vNode时,也需对树的上下文值处理,如context,portal, namespaceContext diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/inula/src/renderer/render/ClassComponent.ts similarity index 100% rename from libs/horizon/src/renderer/render/ClassComponent.ts rename to libs/inula/src/renderer/render/ClassComponent.ts diff --git a/libs/horizon/src/renderer/render/ContextConsumer.ts b/libs/inula/src/renderer/render/ContextConsumer.ts similarity index 100% rename from libs/horizon/src/renderer/render/ContextConsumer.ts rename to libs/inula/src/renderer/render/ContextConsumer.ts diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/inula/src/renderer/render/ContextProvider.ts similarity index 99% rename from libs/horizon/src/renderer/render/ContextProvider.ts rename to libs/inula/src/renderer/render/ContextProvider.ts index 39c91ba5..9734eb45 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/inula/src/renderer/render/ContextProvider.ts @@ -64,7 +64,7 @@ function handleContextChange(processing: VNode, context: ContextType): void node => { const depContexts = node.depContexts; if (depContexts && depContexts.length) { - isMatch = matchDependencies(depContexts, context, node) ?? isMatch; + isMatch = matchDependencies(depContexts, context, node) || isMatch; } }, node => diff --git a/libs/horizon/src/renderer/render/DomComponent.ts b/libs/inula/src/renderer/render/DomComponent.ts similarity index 98% rename from libs/horizon/src/renderer/render/DomComponent.ts rename to libs/inula/src/renderer/render/DomComponent.ts index f7517fb8..c35305cf 100644 --- a/libs/horizon/src/renderer/render/DomComponent.ts +++ b/libs/inula/src/renderer/render/DomComponent.ts @@ -54,7 +54,7 @@ export function bubbleRender(processing: VNode) { const type = processing.type; const newProps = processing.props; - if (!processing.isCreated && processing.realNode != null) { + if (!processing.isCreated && processing.realNode !== null) { // 更新dom属性 updateDom(processing, type, newProps); diff --git a/libs/horizon/src/renderer/render/DomPortal.ts b/libs/inula/src/renderer/render/DomPortal.ts similarity index 92% rename from libs/horizon/src/renderer/render/DomPortal.ts rename to libs/inula/src/renderer/render/DomPortal.ts index ceeb23c4..242e9f8f 100644 --- a/libs/horizon/src/renderer/render/DomPortal.ts +++ b/libs/inula/src/renderer/render/DomPortal.ts @@ -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(); } diff --git a/libs/horizon/src/renderer/render/DomText.ts b/libs/inula/src/renderer/render/DomText.ts similarity index 89% rename from libs/horizon/src/renderer/render/DomText.ts rename to libs/inula/src/renderer/render/DomText.ts index 1acd5003..8e90e88b 100644 --- a/libs/horizon/src/renderer/render/DomText.ts +++ b/libs/inula/src/renderer/render/DomText.ts @@ -18,6 +18,7 @@ import type { VNode } from '../Types'; import { throwIfTrue } from '../utils/throwIfTrue'; import { newTextDom } from '../../dom/DOMOperator'; import { FlagUtils } from '../vnode/VNodeFlags'; +import { isNull } from '../../dom/utils/Common'; export function captureRender(): VNode | null { return null; @@ -26,7 +27,7 @@ export function captureRender(): VNode | null { export function bubbleRender(processing: VNode) { const newText = processing.props; - if (!processing.isCreated && processing.realNode != null) { + if (!processing.isCreated && processing.realNode !== null) { // 更新 const oldText = processing.oldProps; // 如果文本不同,将其标记为更新 @@ -40,7 +41,7 @@ export function bubbleRender(processing: VNode) { throwIfTrue( processing.realNode === null, 'We must have new text for new mounted node. This error is likely ' + - 'caused by a bug in Horizon. Please file an issue.' + 'caused by a bug in Inula. Please file an issue.' ); } // 获得对应节点 diff --git a/libs/horizon/src/renderer/render/ForwardRef.ts b/libs/inula/src/renderer/render/ForwardRef.ts similarity index 100% rename from libs/horizon/src/renderer/render/ForwardRef.ts rename to libs/inula/src/renderer/render/ForwardRef.ts diff --git a/libs/horizon/src/renderer/render/Fragment.ts b/libs/inula/src/renderer/render/Fragment.ts similarity index 100% rename from libs/horizon/src/renderer/render/Fragment.ts rename to libs/inula/src/renderer/render/Fragment.ts diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/inula/src/renderer/render/FunctionComponent.ts similarity index 100% rename from libs/horizon/src/renderer/render/FunctionComponent.ts rename to libs/inula/src/renderer/render/FunctionComponent.ts diff --git a/libs/horizon/src/renderer/render/LazyComponent.ts b/libs/inula/src/renderer/render/LazyComponent.ts similarity index 100% rename from libs/horizon/src/renderer/render/LazyComponent.ts rename to libs/inula/src/renderer/render/LazyComponent.ts diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/inula/src/renderer/render/MemoComponent.ts similarity index 92% rename from libs/horizon/src/renderer/render/MemoComponent.ts rename to libs/inula/src/renderer/render/MemoComponent.ts index 1703a7d9..a229093c 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/inula/src/renderer/render/MemoComponent.ts @@ -31,7 +31,9 @@ export function bubbleRender() {} export function captureMemoComponent(processing: VNode, shouldUpdate: boolean): VNode | null { const Component = processing.type; // 合并 函数组件或类组件 的defaultProps - const newProps = mergeDefaultProps(Component, processing.props); + let newProps = mergeDefaultProps(Component, processing.props); + // 解决Inula.memo(Inula.forwardRef(()=>{}))两层包装的场景 + newProps = mergeDefaultProps(Component.type, newProps); if (processing.isCreated) { let newChild: VNode | null = null; diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/inula/src/renderer/render/SuspenseComponent.ts similarity index 99% rename from libs/horizon/src/renderer/render/SuspenseComponent.ts rename to libs/inula/src/renderer/render/SuspenseComponent.ts index 38da6211..65708e49 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/inula/src/renderer/render/SuspenseComponent.ts @@ -165,7 +165,7 @@ function canCapturePromise(vNode: VNode | null): boolean { // 处理Suspense子组件抛出的promise export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, promise: PromiseType): boolean { - let vNode = parent; + let vNode: VNode | null = parent; // 向上找到最近的不在fallback状态的Suspense,并触发重新渲染 do { diff --git a/libs/horizon/src/renderer/render/TreeRoot.ts b/libs/inula/src/renderer/render/TreeRoot.ts similarity index 100% rename from libs/horizon/src/renderer/render/TreeRoot.ts rename to libs/inula/src/renderer/render/TreeRoot.ts diff --git a/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts b/libs/inula/src/renderer/render/class/ClassLifeCycleProcessor.ts similarity index 97% rename from libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts rename to libs/inula/src/renderer/render/class/ClassLifeCycleProcessor.ts index efc1444a..553e1be1 100644 --- a/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts +++ b/libs/inula/src/renderer/render/class/ClassLifeCycleProcessor.ts @@ -35,7 +35,7 @@ export function callDerivedStateFromProps( const newState = getDerivedStateFromProps(nextProps, oldState); // 组件未返回state,需要返回旧的preState - processing.state = newState === null || newState === undefined ? oldState : { ...oldState, ...newState }; + processing.state = newState ? { ...oldState, ...newState } : oldState; } } diff --git a/libs/horizon/src/renderer/render/index.ts b/libs/inula/src/renderer/render/index.ts similarity index 100% rename from libs/horizon/src/renderer/render/index.ts rename to libs/inula/src/renderer/render/index.ts diff --git a/libs/horizon/src/renderer/submit/HookEffectHandler.ts b/libs/inula/src/renderer/submit/HookEffectHandler.ts similarity index 97% rename from libs/horizon/src/renderer/submit/HookEffectHandler.ts rename to libs/inula/src/renderer/submit/HookEffectHandler.ts index daa40c44..604ec043 100644 --- a/libs/horizon/src/renderer/submit/HookEffectHandler.ts +++ b/libs/inula/src/renderer/submit/HookEffectHandler.ts @@ -25,6 +25,11 @@ import { EffectConstant } from '../hooks/EffectConstant'; let hookEffects: Array = []; let hookRemoveEffects: Array = []; + +export function hasAsyncEffects() { + return hookEffects.length > 0 || hookRemoveEffects.length > 0; +} + // 是否正在异步调度effects let isScheduling = false; @@ -35,28 +40,6 @@ export function isSchedulingEffects() { return isScheduling; } -export function callUseEffects(vNode: VNode) { - const effectList: EffectList = vNode.effectList; - if (effectList !== null) { - effectList.forEach(effect => { - const { effectConstant } = effect; - if ( - (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && - (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect - ) { - hookEffects.push(effect); - hookRemoveEffects.push(effect); - - // 异步调用 - if (!isScheduling) { - isScheduling = true; - runAsync(runAsyncEffects); - } - } - }); - } -} - export function runAsyncEffects() { const preMode = copyExecuteMode(); changeMode(InRender, true); @@ -93,6 +76,28 @@ export function runAsyncEffects() { setExecuteMode(preMode); } +export function callUseEffects(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + if (effectList !== null) { + effectList.forEach(effect => { + const { effectConstant } = effect; + if ( + (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && + (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect + ) { + hookEffects.push(effect); + hookRemoveEffects.push(effect); + + // 异步调用 + if (!isScheduling) { + isScheduling = true; + runAsync(runAsyncEffects); + } + } + }); + } +} + // 在销毁vNode的时候调用remove export function callEffectRemove(vNode: VNode) { const effectList: EffectList = vNode.effectList; diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/inula/src/renderer/submit/LifeCycleHandler.ts similarity index 96% rename from libs/horizon/src/renderer/submit/LifeCycleHandler.ts rename to libs/inula/src/renderer/submit/LifeCycleHandler.ts index d936bd15..35aaa7ba 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/inula/src/renderer/submit/LifeCycleHandler.ts @@ -52,6 +52,7 @@ import { import { handleSubmitError } from '../ErrorHandler'; import { travelVNodeTree, clearVNode, isDomVNode, getSiblingDom } from '../vnode/VNodeUtils'; import { shouldAutoFocus } from '../../dom/utils/Common'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; function callComponentWillUnmount(vNode: VNode, instance: any) { try { @@ -154,6 +155,22 @@ function hideOrUnhideAllChildren(vNode, isHidden) { ); } +function handleRef(vNode: VNode, ref, val) { + if (ref !== null && ref !== undefined) { + const refType = typeof ref; + + if (refType === 'function') { + ref(val); + } else if (refType === 'object') { + (ref).current = val; + } else { + if (vNode[BELONG_CLASS_VNODE_KEY] && vNode[BELONG_CLASS_VNODE_KEY].realNode) { + vNode[BELONG_CLASS_VNODE_KEY].realNode.refs[String(ref)] = val; + } + } + } +} + function attachRef(vNode: VNode) { const ref = vNode.ref; @@ -166,60 +183,6 @@ function detachRef(vNode: VNode, isOldRef?: boolean) { handleRef(vNode, ref, null); } -function handleRef(vNode: VNode, ref, val) { - if (ref !== null && ref !== undefined) { - const refType = typeof ref; - - if (refType === 'function') { - ref(val); - } else if (refType === 'object') { - (ref).current = val; - } else { - if (vNode.belongClassVNode && vNode.belongClassVNode.realNode) { - vNode.belongClassVNode.realNode.refs[String(ref)] = val; - } - } - } -} - -// 卸载一个vNode,不会递归 -function unmountVNode(vNode: VNode): void { - switch (vNode.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: { - callEffectRemove(vNode); - break; - } - case ClassComponent: { - detachRef(vNode); - - const instance = vNode.realNode; - // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 - // suspense打断时不需要触发WillUnmount - if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { - callComponentWillUnmount(vNode, instance); - } - - // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 - if (vNode.classComponentWillUnmount) { - vNode.classComponentWillUnmount(vNode); - vNode.classComponentWillUnmount = null; - } - break; - } - case DomComponent: { - detachRef(vNode); - break; - } - case DomPortal: { - // 这里会递归 - unmountDomComponents(vNode); - break; - } - } -} - // 卸载vNode,递归遍历子vNode function unmountNestedVNodes(vNode: VNode): void { travelVNodeTree( @@ -235,59 +198,6 @@ function unmountNestedVNodes(vNode: VNode): void { ); } -function submitAddition(vNode: VNode): void { - let parent = vNode.parent; - let parentDom; - let tag; - while (parent !== null) { - tag = parent.tag; - if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) { - parentDom = parent.realNode; - break; - } - parent = parent.parent; - } - - if ((parent.flags & ResetText) === ResetText) { - // 在insert之前先reset - clearText(parentDom); - FlagUtils.removeFlag(parent, ResetText); - } - - if ((vNode.flags & DirectAddition) === DirectAddition) { - insertOrAppendPlacementNode(vNode, null, parentDom); - FlagUtils.removeFlag(vNode, DirectAddition); - return; - } - const before = getSiblingDom(vNode); - insertOrAppendPlacementNode(vNode, before, parentDom); -} - -function insertOrAppendPlacementNode(node: VNode, beforeDom: Element | null, parent: Element | Container): void { - const { tag, realNode } = node; - - if (isDomVNode(node)) { - insertDom(parent, realNode, beforeDom); - } else if (tag === DomPortal) { - // 这里不做处理,直接在portal中处理 - } else { - // 插入子节点们 - let child = node.child; - while (child !== null) { - insertOrAppendPlacementNode(child, beforeDom, parent); - child = child.next; - } - } -} - -function insertDom(parent, realNode, beforeDom) { - if (beforeDom) { - insertDomBefore(parent, realNode, beforeDom); - } else { - appendChildElement(parent, realNode); - } -} - // 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount() function unmountDomComponents(vNode: VNode): void { let currentParentIsValid = false; @@ -339,11 +249,105 @@ function unmountDomComponents(vNode: VNode): void { ); } +// 卸载一个vNode,不会递归 +function unmountVNode(vNode: VNode): void { + switch (vNode.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: { + callEffectRemove(vNode); + break; + } + case ClassComponent: { + detachRef(vNode); + + const instance = vNode.realNode; + // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 + // suspense打断时不需要触发WillUnmount + if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { + callComponentWillUnmount(vNode, instance); + } + + // InulaX会在classComponentWillUnmount中清除对VNode的引入用 + if (vNode.classComponentWillUnmount) { + vNode.classComponentWillUnmount(vNode); + vNode.classComponentWillUnmount = null; + } + break; + } + case DomComponent: { + detachRef(vNode); + break; + } + case DomPortal: { + // 这里会递归 + unmountDomComponents(vNode); + break; + } + default: { + break; + } + } +} + +function insertDom(parent, realNode, beforeDom) { + if (beforeDom) { + insertDomBefore(parent, realNode, beforeDom); + } else { + appendChildElement(parent, realNode); + } +} + +function insertOrAppendPlacementNode(node: VNode, beforeDom: Element | null, parent: Element | Container): void { + const { tag, realNode } = node; + + if (isDomVNode(node)) { + insertDom(parent, realNode, beforeDom); + } else if (tag === DomPortal) { + // 这里不做处理,直接在portal中处理 + } else { + // 插入子节点们 + let child = node.child; + while (child !== null) { + insertOrAppendPlacementNode(child, beforeDom, parent); + child = child.next; + } + } +} + +function submitAddition(vNode: VNode): void { + let parent = vNode.parent; + let parentDom; + let tag; + while (parent !== null) { + tag = parent.tag; + if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) { + parentDom = parent.realNode; + break; + } + parent = parent.parent; + } + + if ((parent.flags & ResetText) === ResetText) { + // 在insert之前先reset + clearText(parentDom); + FlagUtils.removeFlag(parent, ResetText); + } + + if ((vNode.flags & DirectAddition) === DirectAddition) { + insertOrAppendPlacementNode(vNode, null, parentDom); + FlagUtils.removeFlag(vNode, DirectAddition); + return; + } + const before = getSiblingDom(vNode); + insertOrAppendPlacementNode(vNode, before, parentDom); +} + function submitClear(vNode: VNode): void { const realNode = vNode.realNode; - const cloneDom = realNode.cloneNode(false); // 复制节点后horizon添加给dom的属性未能复制 + const cloneDom = realNode.cloneNode(false); // 复制节点后inula添加给dom的属性未能复制 // 真实 dom 获取的keys只包含新增的属性 - // 比如真实 dom 拿到的 keys 一般只有两个 horizon 自定义属性 + // 比如真实 dom 拿到的 keys 一般只有两个 inula 自定义属性 // 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式 const customizeKeys = Object.keys(realNode); const keyLength = customizeKeys.length; @@ -394,6 +398,13 @@ function submitDeletion(vNode: VNode): void { clearVNode(vNode); } +function submitSuspenseComponent(vNode: VNode) { + const { childStatus } = vNode.suspenseState; + if (childStatus !== SuspenseChildStatus.Init) { + hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback); + } +} + function submitUpdate(vNode: VNode): void { switch (vNode.tag) { case FunctionComponent: @@ -413,13 +424,9 @@ function submitUpdate(vNode: VNode): void { listenToPromise(vNode); break; } - } -} - -function submitSuspenseComponent(vNode: VNode) { - const { childStatus } = vNode.suspenseState; - if (childStatus !== SuspenseChildStatus.Init) { - hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback); + default: { + break; + } } } diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/inula/src/renderer/submit/Submit.ts similarity index 100% rename from libs/horizon/src/renderer/submit/Submit.ts rename to libs/inula/src/renderer/submit/Submit.ts index ab3f4494..784e3aa9 100644 --- a/libs/horizon/src/renderer/submit/Submit.ts +++ b/libs/inula/src/renderer/submit/Submit.ts @@ -41,63 +41,6 @@ const LOOPING_UPDATE_LIMIT = 50; let loopingUpdateCount = 0; let lastRoot: VNode | null = null; -export function submitToRender(treeRoot) { - treeRoot.shouldUpdate = treeRoot.childShouldUpdate; - // 置空task,让才能加入新的render任务 - treeRoot.task = null; - - const startVNode = getStartVNode(); - - if (FlagUtils.hasAnyFlag(startVNode)) { - // 把自己加上 - if (startVNode.dirtyNodes === null) { - startVNode.dirtyNodes = [startVNode]; - } else { - startVNode.dirtyNodes.push(startVNode); - } - } - - const dirtyNodes = startVNode.dirtyNodes; - if (dirtyNodes !== null && dirtyNodes.length) { - const preMode = copyExecuteMode(); - changeMode(InRender, true); - - prepareForSubmit(); - // before submit阶段 - beforeSubmit(dirtyNodes); - - // submit阶段 - submit(dirtyNodes); - - resetAfterSubmit(); - - // after submit阶段 - afterSubmit(dirtyNodes); - - setExecuteMode(preMode); - dirtyNodes.length = 0; - startVNode.dirtyNodes = null; - } - - if (isSchedulingEffects()) { - setSchedulingEffects(false); - } - - // 统计root同步重渲染的次数,如果太多可能是无线循环 - countLoopingUpdate(treeRoot); - - // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 - tryRenderFromRoot(treeRoot); - - if (rootThrowError) { - const error = rootThrowError; - rootThrowError = null; - throw error; - } - - return null; -} - function beforeSubmit(dirtyNodes: Array) { let node; const nodesLength = dirtyNodes.length; @@ -214,3 +157,60 @@ export function checkLoopingUpdateLimit() { ); } } + +export function submitToRender(treeRoot) { + treeRoot.shouldUpdate = treeRoot.childShouldUpdate; + // 置空task,让才能加入新的render任务 + treeRoot.task = null; + + const startVNode = getStartVNode(); + + if (FlagUtils.hasAnyFlag(startVNode)) { + // 把自己加上 + if (startVNode.dirtyNodes === null) { + startVNode.dirtyNodes = [startVNode]; + } else { + startVNode.dirtyNodes.push(startVNode); + } + } + + const dirtyNodes = startVNode.dirtyNodes; + if (dirtyNodes !== null && dirtyNodes.length) { + const preMode = copyExecuteMode(); + changeMode(InRender, true); + + prepareForSubmit(); + // before submit阶段 + beforeSubmit(dirtyNodes); + + // submit阶段 + submit(dirtyNodes); + + resetAfterSubmit(); + + // after submit阶段 + afterSubmit(dirtyNodes); + + setExecuteMode(preMode); + dirtyNodes.length = 0; + startVNode.dirtyNodes = null; + } + + if (isSchedulingEffects()) { + setSchedulingEffects(false); + } + + // 统计root同步重渲染的次数,如果太多可能是无线循环 + countLoopingUpdate(treeRoot); + + // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 + tryRenderFromRoot(treeRoot); + + if (rootThrowError) { + const error = rootThrowError; + rootThrowError = null; + throw error; + } + + return null; +} diff --git a/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts b/libs/inula/src/renderer/taskExecutor/BrowserAsync.ts similarity index 74% rename from libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts rename to libs/inula/src/renderer/taskExecutor/BrowserAsync.ts index fb192127..3764e5fc 100644 --- a/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts +++ b/libs/inula/src/renderer/taskExecutor/BrowserAsync.ts @@ -19,12 +19,22 @@ let isMessageLoopRunning = false; let browserCallback = null; -const { port1, port2 } = new MessageChannel(); +let port1 = null; +let port2 = null; +let isTestRuntime = false; export function isOverTime() { return false; } +function asyncCall() { + if (isTestRuntime) { + setTimeout(callRenderTasks, 0); + } else { + port2.postMessage(null); + } +} + // 1、设置deadline;2、回调TaskExecutor传过来的browserCallback const callRenderTasks = () => { if (browserCallback === null) { @@ -41,21 +51,30 @@ const callRenderTasks = () => { browserCallback = null; } else { // 还有task,继续调用 - port2.postMessage(null); + asyncCall(); } } catch (error) { - port2.postMessage(null); + asyncCall(); throw error; } }; -port1.onmessage = callRenderTasks; +if (typeof MessageChannel === 'function') { + const mc = new MessageChannel(); + port1 = mc.port1; + port1.onmessage = callRenderTasks; + port2 = mc.port2; +} else { + // 测试环境没有 MessageChannel + isTestRuntime = true; +} export function requestBrowserCallback(callback) { browserCallback = callback; if (!isMessageLoopRunning) { isMessageLoopRunning = true; - port2.postMessage(null); + asyncCall(); } } + diff --git a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts b/libs/inula/src/renderer/taskExecutor/RenderQueue.ts similarity index 97% rename from libs/horizon/src/renderer/taskExecutor/RenderQueue.ts rename to libs/inula/src/renderer/taskExecutor/RenderQueue.ts index b76f03b7..4f03cbd1 100644 --- a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts +++ b/libs/inula/src/renderer/taskExecutor/RenderQueue.ts @@ -27,16 +27,6 @@ let callingQueueTask: any | null = null; // 防止重入 let isCallingRenderQueue = false; -export function callRenderQueueImmediate() { - if (callingQueueTask !== null) { - // 取消异步调度 - cancelTask(callingQueueTask); - callingQueueTask = null; - } - - callRenderQueue(); -} - // 执行render回调 function callRenderQueue() { if (!isCallingRenderQueue && renderQueue !== null) { @@ -45,7 +35,7 @@ function callRenderQueue() { try { let callback; - while ((callback = renderQueue.shift())) { + while (callback = renderQueue.shift()) { callback(); } @@ -58,6 +48,16 @@ function callRenderQueue() { } } +export function callRenderQueueImmediate() { + if (callingQueueTask !== null) { + // 取消异步调度 + cancelTask(callingQueueTask); + callingQueueTask = null; + } + + callRenderQueue(); +} + export function pushRenderCallback(callback: RenderCallback) { if (renderQueue === null) { renderQueue = [callback]; diff --git a/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts b/libs/inula/src/renderer/taskExecutor/TaskExecutor.ts similarity index 100% rename from libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts rename to libs/inula/src/renderer/taskExecutor/TaskExecutor.ts diff --git a/libs/horizon/src/renderer/taskExecutor/TaskQueue.ts b/libs/inula/src/renderer/taskExecutor/TaskQueue.ts similarity index 95% rename from libs/horizon/src/renderer/taskExecutor/TaskQueue.ts rename to libs/inula/src/renderer/taskExecutor/TaskQueue.ts index 4705657a..1d564405 100644 --- a/libs/horizon/src/renderer/taskExecutor/TaskQueue.ts +++ b/libs/inula/src/renderer/taskExecutor/TaskQueue.ts @@ -63,12 +63,12 @@ export function add(node: Node): void { export function first(): Node | null { const val: Node | null | undefined = taskQueue[0]; - return val !== undefined ? val : null; + return val ?? null; } export function shift(): Node | null { const val = taskQueue.shift(); - return val !== undefined ? val : null; + return val ?? null; } export function remove(node: Node) { diff --git a/libs/horizon/src/renderer/utils/compare.ts b/libs/inula/src/renderer/utils/compare.ts similarity index 100% rename from libs/horizon/src/renderer/utils/compare.ts rename to libs/inula/src/renderer/utils/compare.ts diff --git a/libs/horizon/src/renderer/utils/throwIfTrue.ts b/libs/inula/src/renderer/utils/throwIfTrue.ts similarity index 100% rename from libs/horizon/src/renderer/utils/throwIfTrue.ts rename to libs/inula/src/renderer/utils/throwIfTrue.ts diff --git a/libs/horizon/src/renderer/utils/vNodePath.ts b/libs/inula/src/renderer/utils/vNodePath.ts similarity index 100% rename from libs/horizon/src/renderer/utils/vNodePath.ts rename to libs/inula/src/renderer/utils/vNodePath.ts diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/inula/src/renderer/vnode/VNode.ts similarity index 90% rename from libs/horizon/src/renderer/vnode/VNode.ts rename to libs/inula/src/renderer/vnode/VNode.ts index 8076e8f6..da1b59aa 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/inula/src/renderer/vnode/VNode.ts @@ -36,7 +36,9 @@ import type { VNodeTag } from './VNodeTags'; import type { RefType, ContextType, SuspenseState, Source } from '../Types'; import type { Hook } from '../hooks/HookType'; import { InitFlag } from './VNodeFlags'; -import { Observer } from '../../horizonx/proxy/Observer'; +import { Observer } from '../../inulax/proxy/Observer'; + +export const BELONG_CLASS_VNODE_KEY = typeof Symbol === 'function' ? Symbol('belongClassVNode') : 'belongClassVNode'; export class VNode { tag: VNodeTag; @@ -50,7 +52,7 @@ export class VNode { child: VNode | null = null; // 子节点 next: VNode | null = null; // 兄弟节点 cIndex = 0; // 节点在children数组中的位置 - eIndex = 0; // HorizonElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致 + eIndex = 0; // InulaElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致 ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上 oldProps: any = null; @@ -60,7 +62,7 @@ export class VNode { changeList: any; // DOM的变更列表 effectList: any[] | null; // useEffect 的更新数组 updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组 - stateCallbacks: any[] | null; // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组 + stateCallbacks: any[] | null; // 存放存在setState的第二个参数和InulaDOM.render的第三个参数所在的node数组 isForceUpdate: boolean; // 是否使用强制更新 isSuspended = false; // 是否被suspense打断更新 state: any; // ClassComponent和TreeRoot的状态 @@ -89,7 +91,7 @@ export class VNode { oldChild: VNode | null = null; promiseResolve: boolean; // suspense的promise是否resolve devProps: any; // 用于dev插件临时保存更新props值 - suspenseState: SuspenseState; + suspenseState: SuspenseState | null; path = ''; // 保存从根到本节点的路径 @@ -97,12 +99,12 @@ export class VNode { toUpdateNodes: Set | null; // 保存要更新的节点 delegatedEvents: Set; - belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + [BELONG_CLASS_VNODE_KEY]: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 - // 状态管理器HorizonX使用 + // 状态管理器InulaX使用 isStoreChange: boolean; observers: Set | null = null; // 记录这个函数组件/类组件依赖哪些Observer - classComponentWillUnmount: ((vNode: VNode) => any) | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 + classComponentWillUnmount: ((vNode: VNode) => any) | null; // InulaX会在classComponentWillUnmount中清除对VNode的引入用 src: Source | null; // 节点所在代码位置 constructor(tag: VNodeTag, props: any, key: null | string, realNode) { @@ -200,6 +202,8 @@ export class VNode { break; case Profiler: break; + default: + break; } } } diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/inula/src/renderer/vnode/VNodeCreator.ts similarity index 96% rename from libs/horizon/src/renderer/vnode/VNodeCreator.ts rename to libs/inula/src/renderer/vnode/VNodeCreator.ts index 62a4e472..31cf3d19 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/inula/src/renderer/vnode/VNodeCreator.ts @@ -74,7 +74,7 @@ export function getLazyVNodeTag(lazyComp: any): string { } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { return typeLazyMap[lazyComp.vtype]; } - throw Error("Horizon can't resolve the content of lazy"); + throw Error("Inula can't resolve the content of lazy"); } // 创建processing @@ -136,7 +136,7 @@ export function createUndeterminedVNode(type, key, props, source: Source | null) vNodeTag = typeMap[type.vtype]; isLazy = type.vtype === TYPE_LAZY; } else { - throw Error(`Component type is invalid, got: ${type == null ? type : componentType}`); + throw Error(`Component type is invalid, got: ${type === null || type === undefined ? type : componentType}`); } const vNode = newVirtualNode(vNodeTag, key, props); @@ -183,7 +183,7 @@ export function createTreeRootVNode(container) { return vNode; } -// TODO: 暂时保留给测试用例使用,后续修改测试用例 +// 暂时保留给测试用例使用,后续修改测试用例 export function createVNode(tag: VNodeTag | string, ...secondArg) { let vNode = null; switch (tag) { @@ -194,6 +194,8 @@ export function createVNode(tag: VNodeTag | string, ...secondArg) { vNode.updates = []; break; + default: + break; } return vNode; diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/inula/src/renderer/vnode/VNodeFlags.ts similarity index 72% rename from libs/horizon/src/renderer/vnode/VNodeFlags.ts rename to libs/inula/src/renderer/vnode/VNodeFlags.ts index faf567cf..0380b48c 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/inula/src/renderer/vnode/VNodeFlags.ts @@ -36,64 +36,71 @@ export const ForceUpdate = /** */ 1 << 12; // For suspense export const Clear = /** */ 1 << 13; const LifecycleEffectArr = Update | Callback | Ref | Snapshot; -export class FlagUtils { - static removeFlag(node: VNode, flag: number) { +export const FlagUtils = { + removeFlag(node: VNode, flag: number) { node.flags &= ~flag; - } - static removeLifecycleEffectFlags(node) { + }, + + removeLifecycleEffectFlags(node) { node.flags &= ~LifecycleEffectArr; - } - static hasAnyFlag(node: VNode) { + }, + + hasAnyFlag(node: VNode) { // 有标志位 return node.flags !== InitFlag; - } + }, - static setNoFlags(node: VNode) { + hasFlag(node: VNode, flag) { + return (node.flags & flag) !== 0; + }, + + setNoFlags(node: VNode) { node.flags = InitFlag; - } + }, - static markAddition(node: VNode) { + markAddition(node: VNode) { node.flags |= Addition; - } - static setAddition(node: VNode) { + }, + + setAddition(node: VNode) { node.flags = Addition; - } + }, - static markDirectAddition(node: VNode) { + markDirectAddition(node: VNode) { node.flags |= DirectAddition; - } - static markUpdate(node: VNode) { + }, + markUpdate(node: VNode) { node.flags |= Update; - } - static setDeletion(node: VNode) { + }, + setDeletion(node: VNode) { node.flags = Deletion; - } - static markContentReset(node: VNode) { + }, + markContentReset(node: VNode) { node.flags |= ResetText; - } - static markCallback(node: VNode) { + }, + markCallback(node: VNode) { node.flags |= Callback; - } - static markDidCapture(node: VNode) { + }, + markDidCapture(node: VNode) { node.flags |= DidCapture; - } - static markShouldCapture(node: VNode) { + }, + markShouldCapture(node: VNode) { node.flags |= ShouldCapture; - } - static markRef(node: VNode) { + }, + markRef(node: VNode) { node.flags |= Ref; - } - static markSnapshot(node: VNode) { + }, + markSnapshot(node: VNode) { node.flags |= Snapshot; - } - static markInterrupted(node: VNode) { + }, + markInterrupted(node: VNode) { node.flags |= Interrupted; - } - static markForceUpdate(node: VNode) { + }, + markForceUpdate(node: VNode) { node.flags |= ForceUpdate; - } + }, - static markClear(node: VNode) { + markClear(node: VNode) { node.flags |= Clear; } } diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/inula/src/renderer/vnode/VNodeShouldUpdate.ts similarity index 100% rename from libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts rename to libs/inula/src/renderer/vnode/VNodeShouldUpdate.ts diff --git a/libs/horizon/src/renderer/vnode/VNodeTags.ts b/libs/inula/src/renderer/vnode/VNodeTags.ts similarity index 100% rename from libs/horizon/src/renderer/vnode/VNodeTags.ts rename to libs/inula/src/renderer/vnode/VNodeTags.ts diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/inula/src/renderer/vnode/VNodeUtils.ts similarity index 86% rename from libs/horizon/src/renderer/vnode/VNodeUtils.ts rename to libs/inula/src/renderer/vnode/VNodeUtils.ts index 2f8445a0..7f960098 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/inula/src/renderer/vnode/VNodeUtils.ts @@ -17,15 +17,19 @@ * 提供:vNode的“遍历”,“查找”,“判断”的相关工具方法 */ -import type { VNode } from '../Types'; +import type {VNode} from '../Types'; -import { DomComponent, DomPortal, DomText, TreeRoot } from './VNodeTags'; -import { isComment } from '../../dom/utils/Common'; -import { getNearestVNode } from '../../dom/DOMInternalKeys'; -import { Addition, InitFlag } from './VNodeFlags'; +import {DomComponent, DomPortal, DomText, TreeRoot} from './VNodeTags'; +import {getNearestVNode} from '../../dom/DOMInternalKeys'; +import {Addition, InitFlag} from './VNodeFlags'; +import { BELONG_CLASS_VNODE_KEY } from './VNode'; -export function travelChildren(beginVNode: VNode, handleVNode: Function, isFinish?: Function) { - let node: VNode | null = beginVNode; +export function travelChildren( + beginVNode: VNode | null, + handleVNode: (node: VNode) => void, + isFinish?: (node: VNode) => boolean +) { + let node = beginVNode; while (node !== null) { if (isFinish && isFinish(node)) { @@ -41,15 +45,16 @@ export function travelChildren(beginVNode: VNode, handleVNode: Function, isFinis // 从beginVNode开始深度遍历vNode树,对每个vNode调用handleVNode方法 export function travelVNodeTree( beginVNode: VNode, - handleVNode: Function, + handleVNode: (node: VNode) => VNode | boolean | null | void, childFilter: ((node: VNode) => boolean) | null, // 返回true不处理child finishVNode: VNode, // 结束遍历节点,有时候和beginVNode不相同 - handleWhenToParent: Function | null -): VNode | null { + handleWhenToParent: ((node: VNode) => void) | null +): VNode | boolean | null | void { let node = beginVNode; while (true) { const ret = handleVNode(node); + // 如果处理一个vNode时有返回值,则中断遍历 if (ret) { return ret; @@ -68,6 +73,8 @@ export function travelVNodeTree( return null; } + const isFun = typeof handleWhenToParent === 'function'; + // 找兄弟,没有就往上再找兄弟 while (node.next === null) { if (node.parent === null || node.parent === finishVNode) { @@ -75,8 +82,8 @@ export function travelVNodeTree( } node = node.parent; - if (typeof handleWhenToParent === 'function') { - handleWhenToParent(node); + if (isFun) { + handleWhenToParent!(node); } } // 找到兄弟 @@ -89,14 +96,20 @@ export function travelVNodeTree( // 置空vNode export function clearVNode(vNode: VNode) { vNode.isCleared = true; + + // 孩子节点的parent也置空 + travelChildren(vNode.child, (node) => { + node.parent = null; + }); vNode.child = null; + + vNode.parent = null; vNode.next = null; vNode.depContexts = null; vNode.dirtyNodes = null; vNode.state = null; vNode.hooks = null; vNode.props = null; - vNode.parent = null; vNode.suspenseState = null; vNode.changeList = null; vNode.effectList = null; @@ -111,9 +124,9 @@ export function clearVNode(vNode: VNode) { vNode.toUpdateNodes = null; - vNode.belongClassVNode = null; - if (window.__HORIZON_DEV_HOOK__) { - const hook = window.__HORIZON_DEV_HOOK__; + vNode[BELONG_CLASS_VNODE_KEY] = null; + if (window.__INULA_DEV_HOOK__) { + const hook = window.__INULA_DEV_HOOK__; hook.deleteVNode(vNode); } } @@ -129,7 +142,7 @@ function isDomContainer(vNode: VNode): boolean { } export function findDomVNode(vNode: VNode): VNode | null { - return travelVNodeTree( + const ret = travelVNodeTree( vNode, node => { if (node.tag === DomComponent || node.tag === DomText) { @@ -141,6 +154,8 @@ export function findDomVNode(vNode: VNode): VNode | null { vNode, null ); + + return ret as VNode | null; } export function findDOMByClassInst(inst) { @@ -154,13 +169,6 @@ export function findDOMByClassInst(inst) { return domVNode !== null ? domVNode.realNode : null; } -// 判断dom树是否已经挂载 -export function isMounted(vNode: VNode) { - const rootNode = getTreeRootVNode(vNode); - // 如果根节点是 Dom 类型节点,表示已经挂载 - return rootNode.tag === TreeRoot; -} - function getTreeRootVNode(vNode) { let node = vNode; while (node.parent) { @@ -169,6 +177,13 @@ function getTreeRootVNode(vNode) { return node; } +// 判断dom树是否已经挂载 +export function isMounted(vNode: VNode) { + const rootNode = getTreeRootVNode(vNode); + // 如果根节点是 Dom 类型节点,表示已经挂载 + return rootNode.tag === TreeRoot; +} + // 找到相邻的DOM export function getSiblingDom(vNode: VNode): Element | null { let node: VNode = vNode; diff --git a/package.json b/package.json index f2d728f2..d2c20795 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "name": "inulajs", + "description": "InulaJS is a JavaScript framework library.", + "version": "0.0.52", "private": true, "workspaces": [ "libs/*" @@ -8,10 +11,8 @@ "prettier": "prettier -w libs/**/*.ts", "build": "rollup --config ./scripts/rollup/rollup.config.js", "build:watch": "rollup --watch --config ./scripts/rollup/rollup.config.js", - "build:3rdLib": "node ./scripts/gen3rdLib.js build:3rdLib", - "build:3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js build:3rdLib-dev", - "build:horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js build:horizon3rdLib-dev", - "build-types": "tsc -p ./libs/horizon/index.ts --emitDeclarationOnly --declaration --declarationDir ./build/horizon/@types --skipLibCheck || echo \"WARNING: TSC exited with status $?\"", + "build:inula3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js build:inula3rdLib-dev", + "build-types": "tsc -p libs/inula/index.ts --emitDeclarationOnly --declaration --declarationDir ./build/inula/@types --skipLibCheck || echo \\\"WARNING: TSC exited with status $?\\\"", "debug-test": "yarn test --debug", "test": "jest --config=jest.config.js", "watch-test": "yarn test --watch --dev" @@ -35,11 +36,11 @@ "@babel/plugin-transform-object-super": "7.16.7", "@babel/plugin-transform-parameters": "7.16.7", "@babel/plugin-transform-react-jsx": "7.16.7", + "@babel/plugin-transform-react-jsx-source": "^7.16.7", "@babel/plugin-transform-runtime": "7.16.7", "@babel/plugin-transform-shorthand-properties": "7.16.7", "@babel/plugin-transform-spread": "7.16.7", "@babel/plugin-transform-template-literals": "7.16.7", - "@babel/plugin-transform-react-jsx-source": "^7.16.7", "@babel/preset-env": "7.16.7", "@babel/preset-typescript": "7.16.7", "@rollup/plugin-babel": "^5.3.1", @@ -50,6 +51,7 @@ "@typescript-eslint/eslint-plugin": "4.8.0", "@typescript-eslint/parser": "4.8.0", "babel-jest": "^27.5.1", + "ejs": "^3.1.8", "eslint": "7.13.0", "eslint-config-prettier": "^6.9.0", "eslint-plugin-jest": "^22.15.0", @@ -67,5 +69,6 @@ "engines": { "node": ">=10.x", "npm": ">=7.x" - } + }, + "dependencies": {} } diff --git a/scripts/__tests__/ActTest/act.test.js b/scripts/__tests__/ActTest/act.test.js new file mode 100644 index 00000000..e0768094 --- /dev/null +++ b/scripts/__tests__/ActTest/act.test.js @@ -0,0 +1,53 @@ +/* + * 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 Inula, { render, useState, act, useEffect } from '../../../libs/inula/index'; + +describe('Inula.act function Test', () => { + it('The act can wait for the useEffect update to complete.', function () { + const Parent = props => { + const [buttonOptions, setBtn] = useState([]); + const [checkedRows, setCheckedRows] = useState([]); + + useEffect(() => { + setBtn([1, 2, 3]); + }, [checkedRows.length]); + + return ( +
+ +
+ ); + }; + + const Child = props => { + const { buttonOptions } = props; + const [btnList, setBtnList] = useState(0); + + useEffect(() => { + setBtnList(buttonOptions.length); + }, [buttonOptions]); + + return
{btnList}
; + }; + + act(() => { + render(, container); + }); + + // act能够等待useEffect触发的update完成 + expect(container.querySelector('#childDiv').innerHTML).toEqual('3'); + }); +}); diff --git a/scripts/__tests__/ComponentTest/ClassRefs.test.js b/scripts/__tests__/ComponentTest/ClassRefs.test.js new file mode 100644 index 00000000..c898e069 --- /dev/null +++ b/scripts/__tests__/ComponentTest/ClassRefs.test.js @@ -0,0 +1,51 @@ +/* + * 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 Inula from '../../../libs/inula/index'; + +describe('Class refs Test', () => { + it('Parent can get Child instance by refs', function () { + let pInst; + + class Parent extends Inula.Component { + componentDidMount() { + pInst = this; + } + + render() { + return ( +
+ +
childDiv
+
+
+ ); + } + } + + class Child extends Inula.Component { + state = { y: 0 }; + + render() { + return
{this.props.children}
; + } + } + + Inula.render(, container); + + expect(pInst.refs['child'].state.y).toEqual(0); + expect(pInst.refs['childDiv'].innerHTML).toEqual('childDiv'); + }); +}); diff --git a/scripts/__tests__/ComponentTest/ComponentError.test.js b/scripts/__tests__/ComponentTest/ComponentError.test.js index aa25bcf0..3a16c00b 100755 --- a/scripts/__tests__/ComponentTest/ComponentError.test.js +++ b/scripts/__tests__/ComponentTest/ComponentError.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('Component Error Test', () => { @@ -24,11 +24,11 @@ describe('Component Error Test', () => { jest.spyOn(console, 'error').mockImplementation(); expect(() => { - Horizon.render(, document.createElement('div')); + Inula.render(, document.createElement('div')); }).toThrow('Component type is invalid, got: null'); expect(() => { - Horizon.render(, document.createElement('div')); + Inula.render(, document.createElement('div')); }).toThrow('Component type is invalid, got: undefined'); const App = () => { @@ -42,7 +42,7 @@ describe('Component Error Test', () => { }; expect(() => { - Horizon.render(, document.createElement('div')); + Inula.render(, document.createElement('div')); }).toThrow('Component type is invalid, got: null'); AppChild = () => { @@ -52,7 +52,7 @@ describe('Component Error Test', () => { }; expect(() => { - Horizon.render(, document.createElement('div')); + Inula.render(, document.createElement('div')); }).toThrow('Component type is invalid, got: undefined'); }); }); diff --git a/scripts/__tests__/ComponentTest/Context.test.js b/scripts/__tests__/ComponentTest/Context.test.js index a3e6c562..70cc7111 100644 --- a/scripts/__tests__/ComponentTest/Context.test.js +++ b/scripts/__tests__/ComponentTest/Context.test.js @@ -13,18 +13,18 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('Context Test', () => { const LogUtils = getLogUtils(); - it('Provider及其内部consumer组件都不受制于shouldComponentUpdate函数或者Horizon.memo()', () => { + it('Provider及其内部consumer组件都不受制于shouldComponentUpdate函数或者Inula.memo()', () => { const LanguageTypes = { JAVA: 'Java', JAVASCRIPT: 'JavaScript', }; const defaultValue = { type: LanguageTypes.JAVASCRIPT }; - const SystemLanguageContext = Horizon.createContext(defaultValue); + const SystemLanguageContext = Inula.createContext(defaultValue); const SystemLanguageConsumer = SystemLanguageContext.Consumer; const SystemLanguageProvider = (props) => { LogUtils.log('SystemLanguageProvider'); @@ -47,7 +47,7 @@ describe('Context Test', () => { ); }; - class Middle extends Horizon.Component { + class Middle extends Inula.Component { shouldComponentUpdate() { return false; } @@ -70,7 +70,7 @@ describe('Context Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Java'); expect(LogUtils.getAndClear()).toEqual([ 'App', @@ -82,14 +82,14 @@ describe('Context Test', () => { ]); // 组件不变,Middle没有更新,消费者也不会执行 - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Java'); expect(LogUtils.getAndClear()).toEqual([ 'App', 'SystemLanguageProvider' ]); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('JavaScript'); // 组件更新,但是Middle没有更新,会绕过Middle expect(LogUtils.getAndClear()).toEqual([ @@ -104,7 +104,7 @@ describe('Context Test', () => { ONE: 1, TWO: 2, }; - const NumberContext = Horizon.createContext(0); + const NumberContext = Inula.createContext(0); const NumberConsumer = NumberContext.Consumer; const NumberProvider = (props) => { LogUtils.log(`SystemLanguageProvider: ${props.type}`); @@ -127,7 +127,7 @@ describe('Context Test', () => { ); }; - class Middle extends Horizon.Component { + class Middle extends Inula.Component { shouldComponentUpdate() { return false; } @@ -151,7 +151,7 @@ describe('Context Test', () => { }; // Consumer决定于距离它最近的provider - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('2'); expect(LogUtils.getAndClear()).toEqual([ 'App', @@ -162,7 +162,7 @@ describe('Context Test', () => { 'Consumer DOM mutations' ]); // 更新 - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('3'); expect(LogUtils.getAndClear()).toEqual([ 'App', @@ -177,8 +177,8 @@ describe('Context Test', () => { ONE: 1, TWO: 2, }; - const NumberContext = Horizon.createContext(0); - const NewNumberContext = Horizon.createContext(1); + const NumberContext = Inula.createContext(0); + const NewNumberContext = Inula.createContext(1); const NumberConsumer = NumberContext.Consumer; const NumberProvider = props => { return ( @@ -195,7 +195,7 @@ describe('Context Test', () => { ); }; - class Middle extends Horizon.Component { + class Middle extends Inula.Component { shouldComponentUpdate() { return false; } @@ -234,18 +234,18 @@ describe('Context Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); // 没有匹配到Provider,会使用defaultValue expect(container.querySelector('p').innerHTML).toBe('0'); // 更新,设置value为undefined - Horizon.render(, container); + Inula.render(, container); // 设置value为undefined时,defaultValue不生效 expect(container.querySelector('p').innerHTML).toBe(''); }); it('不同provider下的多个consumer', () => { - const NumContext = Horizon.createContext(1); + const NumContext = Inula.createContext(1); const Consumer = NumContext.Consumer; function Provider(props) { @@ -260,7 +260,7 @@ describe('Context Test', () => { ); } - class Middle extends Horizon.Component { + class Middle extends Inula.Component { shouldComponentUpdate() { return false; } @@ -290,22 +290,22 @@ describe('Context Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('4'); expect(container.querySelector('#p').innerHTML).toBe('2'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('6'); expect(container.querySelector('#p').innerHTML).toBe('3'); }); it('consumer里的child更新是不会重新渲染', () => { - const NumContext = Horizon.createContext(1); + const NumContext = Inula.createContext(1); const Consumer = NumContext.Consumer; let setNum; const ReturnDom = props => { - const [num, _setNum] = Horizon.useState(0); + const [num, _setNum] = Inula.useState(0); setNum = _setNum; LogUtils.log('ReturnDom'); return ( @@ -326,7 +326,7 @@ describe('Context Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Context: 2, Num: 0'); expect(LogUtils.getAndClear()).toEqual([ 'Consumer', @@ -339,11 +339,11 @@ describe('Context Test', () => { it('consumer可以拿到其他context的值', () => { - const NumContext = Horizon.createContext(1); - const TypeContext = Horizon.createContext('typeA'); + const NumContext = Inula.createContext(1); + const TypeContext = Inula.createContext('typeA'); const NumAndType = () => { - const type = Horizon.useContext(TypeContext); + const type = Inula.useContext(TypeContext); return ( {value => { @@ -364,23 +364,23 @@ describe('Context Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeB'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeR'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('Num: 8, Type: typeR'); }); // antd menu 级连context场景,menu路径使用级联context实现 it('nested context', () => { - const NestedContext = Horizon.createContext([]); + const NestedContext = Inula.createContext([]); let updateContext; function App() { - const [state, useState] = Horizon.useState([]); + const [state, useState] = Inula.useState([]); updateContext = useState; return ( @@ -390,13 +390,13 @@ describe('Context Test', () => { ); } - const div1Ref = Horizon.createRef(); - const div2Ref = Horizon.createRef(); + const div1Ref = Inula.createRef(); + const div2Ref = Inula.createRef(); let updateSub1; function Sub1() { - const path = Horizon.useContext(NestedContext); - const [_, setState] = Horizon.useState({}); + const path = Inula.useContext(NestedContext); + const [_, setState] = Inula.useState({}); updateSub1 = () => setState({}); return ( @@ -406,7 +406,7 @@ describe('Context Test', () => { } function Sub2() { - const path = Horizon.useContext(NestedContext); + const path = Inula.useContext(NestedContext); return ( @@ -416,7 +416,7 @@ describe('Context Test', () => { } function Sub3() { - const path = Horizon.useContext(NestedContext); + const path = Inula.useContext(NestedContext); return ( @@ -426,7 +426,7 @@ describe('Context Test', () => { } function Son({ divRef }) { - const path = Horizon.useContext(NestedContext); + const path = Inula.useContext(NestedContext); return (
{path.join(',')}
@@ -434,7 +434,7 @@ describe('Context Test', () => { ); } - Horizon.render(, container); + Inula.render(, container); updateSub1(); expect(div1Ref.current.innerHTML).toEqual('1'); expect(div2Ref.current.innerHTML).toEqual('2,3'); diff --git a/scripts/__tests__/ComponentTest/DiffAlgorithm.test.js b/scripts/__tests__/ComponentTest/DiffAlgorithm.test.js index ccd501c3..c81f16b2 100644 --- a/scripts/__tests__/ComponentTest/DiffAlgorithm.test.js +++ b/scripts/__tests__/ComponentTest/DiffAlgorithm.test.js @@ -13,13 +13,13 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('Diff Algorithm', () => { it('null should diff correctly', () => { const fn = jest.fn(); - class C extends Horizon.Component { + class C extends Inula.Component { constructor() { super(); fn(); @@ -33,7 +33,7 @@ describe('Diff Algorithm', () => { let update; function App() { - const [current, setCurrent] = Horizon.useState(1); + const [current, setCurrent] = Inula.useState(1); update = setCurrent; return ( <> @@ -44,7 +44,7 @@ describe('Diff Algorithm', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(fn).toHaveBeenCalledTimes(1); update(2); diff --git a/scripts/__tests__/ComponentTest/ForwardRef.test.js b/scripts/__tests__/ComponentTest/ForwardRef.test.js index 16f4cf5b..18261cc9 100644 --- a/scripts/__tests__/ComponentTest/ForwardRef.test.js +++ b/scripts/__tests__/ComponentTest/ForwardRef.test.js @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('ForwardRef', () => { const LogUtils = getLogUtils(); it('ForwardRef包裹的函数组件应该正常触发effect', () => { function App(props, ref) { - Horizon.useEffect(() => { + Inula.useEffect(() => { LogUtils.log('effect'); return () => { LogUtils.log('effect remove'); @@ -29,32 +29,32 @@ describe('ForwardRef', () => { return ; } - const Wrapper = Horizon.forwardRef(App); + const Wrapper = Inula.forwardRef(App); - Horizon.act(() => { - Horizon.render(, container); + Inula.act(() => { + Inula.render(, container); }); expect(LogUtils.getAndClear()).toEqual(['effect']); - Horizon.act(() => { - Horizon.render(, container); + Inula.act(() => { + Inula.render(, container); }); expect(LogUtils.getAndClear()).toEqual(['effect remove', 'effect']); }); it('memo组件包裹的类组件', () => { - class Component extends Horizon.Component { + class Component extends Inula.Component { render() { return ; } } - const Wrapper = Horizon.memo(Component); + const Wrapper = Inula.memo(Component); - Horizon.act(() => { - Horizon.render(, container); + Inula.act(() => { + Inula.render(, container); }); - Horizon.act(() => { - Horizon.render(, container); + Inula.act(() => { + Inula.render(, container); }); }); }); diff --git a/scripts/__tests__/ComponentTest/FragmentComponent.test.js b/scripts/__tests__/ComponentTest/FragmentComponent.test.js index c0b79fb1..ce8650ef 100755 --- a/scripts/__tests__/ComponentTest/FragmentComponent.test.js +++ b/scripts/__tests__/ComponentTest/FragmentComponent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { Text } from '../jest/commonComponents'; import { getLogUtils } from '../jest/testUtils'; @@ -23,24 +23,24 @@ describe('Fragment', () => { useEffect, useRef, act, - } = Horizon; + } = Inula; it('可以渲染空元素', () => { const element = ( - + ); - Horizon.render(element, container); + Inula.render(element, container); expect(container.textContent).toBe(''); }); it('可以渲染单个元素', () => { const element = ( - + - + ); - Horizon.render(element, container); + Inula.render(element, container); expect(LogUtils.getAndClear()).toEqual(['Fragment']); expect(container.textContent).toBe('Fragment'); @@ -48,12 +48,12 @@ describe('Fragment', () => { it('可以渲染混合元素', () => { const element = ( - + Java and - + ); - Horizon.render(element, container); + Inula.render(element, container); expect(LogUtils.getAndClear()).toEqual(['JavaScript']); expect(container.textContent).toBe('Java and JavaScript'); @@ -67,7 +67,7 @@ describe('Fragment', () => { ); - Horizon.render(element, container); + Inula.render(element, container); expect(LogUtils.getAndClear()).toEqual(['Java', 'JavaScript']); expect(container.textContent).toBe('JavaJavaScript'); @@ -103,18 +103,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 切换到不同层级Fragment时,副作用状态不会保留 expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1'); @@ -145,18 +145,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态会保留 expect(LogUtils.getNotClear()).toEqual(['useEffect']); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); expect(container.textContent).toBe('1'); @@ -188,18 +188,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态不会保留 expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1232'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1'); @@ -234,18 +234,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态不会保留 expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1'); @@ -286,18 +286,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态会保留 expect(LogUtils.getNotClear()).toEqual(['useEffect']); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); expect(container.textContent).toBe('1'); @@ -338,18 +338,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态会保留 expect(LogUtils.getNotClear()).toEqual(['useEffect']); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); expect(container.textContent).toBe('1'); @@ -380,18 +380,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态会保留 expect(LogUtils.getNotClear()).toEqual(['useEffect']); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); expect(container.textContent).toBe('1'); @@ -426,18 +426,18 @@ describe('Fragment', () => { }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态会保留 expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('[1]'); @@ -459,29 +459,29 @@ describe('Fragment', () => { const App = (props) => { return props.change ? ( - + - + ) : ( - + - + ); }; act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); act(() => { - Horizon.render(, container); + Inula.render(, container); }); // 状态不会保留 expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('2'); act(() => { - Horizon.render(, container); + Inula.render(, container); }); expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1'); diff --git a/scripts/__tests__/ComponentTest/FunctionComponent.test.js b/scripts/__tests__/ComponentTest/FunctionComponent.test.js index 7793cda6..81e10fe4 100644 --- a/scripts/__tests__/ComponentTest/FunctionComponent.test.js +++ b/scripts/__tests__/ComponentTest/FunctionComponent.test.js @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('FunctionComponent Test', () => { it('渲染无状态组件', () => { const App = (props) => { return

{props.text}

; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('app'); }); @@ -29,13 +29,13 @@ describe('FunctionComponent Test', () => { return

{props.text}

; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('app'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('ABC'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('abc'); }); @@ -44,10 +44,10 @@ describe('FunctionComponent Test', () => { return

{props.text}

; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('app'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.querySelector('p')).toBe(null); }); @@ -56,8 +56,38 @@ describe('FunctionComponent Test', () => { return
; }; - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); expect(realNode).toBe(null); }); + it('测试函数组件的defaultProps:Inula.memo(Inula.forwardRef(()=>{}))两层包装的场景后,defaultProps依然正常', () => { + const App = () => { + return ; + }; + + const DefaultPropsComp = Inula.forwardRef(props => { + return
{props.name}
; + }); + DefaultPropsComp.defaultProps = { + name: 'Hello!', + }; + const DefaultPropsCompMemo = Inula.memo(DefaultPropsComp); + + Inula.render(, container); + expect(container.querySelector('div').innerHTML).toBe('Hello!'); + }); + + it('测试', () => { + const App = () => { + return ; + }; + + const StyleComp = props => { + return
{props.name}
; + }; + + Inula.render(, container); + expect(container.querySelector('div').style['_values']['--max-segment-num']).toBe(10); + }); + }); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js b/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js index 579f1d7d..cb599e32 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js @@ -13,10 +13,10 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; describe('useCallback Hook Test', () => { - const { useState, useCallback } = Horizon; + const { useState, useCallback } = Inula; it('测试useCallback', () => { const App = (props) => { @@ -31,7 +31,7 @@ describe('useCallback Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('0'); // 点击按钮触发num加1 container.querySelector('button').click(); @@ -40,7 +40,7 @@ describe('useCallback Hook Test', () => { container.querySelector('button').click(); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('1'); // 依赖项有变化,点击按钮num增加 container.querySelector('button').click(); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index 4fc48f97..decc1a94 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -13,10 +13,10 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; describe('useContext Hook Test', () => { - const { useState, useContext, createContext, act, unmountComponentAtNode } = Horizon; + const { useState, useContext, createContext, act, unmountComponentAtNode } = Inula; it('简单使用useContext', () => { const LanguageTypes = { @@ -24,7 +24,7 @@ describe('useContext Hook Test', () => { JAVASCRIPT: 'JavaScript', }; const defaultValue = { type: LanguageTypes.JAVASCRIPT }; - const SystemLanguageContext = Horizon.createContext(defaultValue); + const SystemLanguageContext = Inula.createContext(defaultValue); const SystemLanguageProvider = ({ type, children }) => { return ( @@ -49,11 +49,11 @@ describe('useContext Hook Test', () => {
); }; - Horizon.render(, container); + Inula.render(, container); // 测试当Provider未提供时,获取到的默认值'JavaScript'。 expect(container.querySelector('p').innerHTML).toBe('JavaScript'); unmountComponentAtNode(container); - Horizon.render(, container); + Inula.render(, container); // 测试当Provider提供时,可以获取到Provider的值'Java'。 expect(container.querySelector('p').innerHTML).toBe('Java'); // 测试当Provider改变时,可以获取到最新Provider的值。 @@ -63,7 +63,7 @@ describe('useContext Hook Test', () => { it('更新后useContext仍能获取到context', () => { const Context = createContext({}); - const ref = Horizon.createRef(); + const ref = Inula.createRef(); function App() { return ( @@ -87,7 +87,7 @@ describe('useContext Hook Test', () => { return
{context.text}
; } - Horizon.render(, container); + Inula.render(, container); expect(ref.current.innerHTML).toBe('context'); update(); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js index 24799f55..a6da51ae 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; @@ -25,7 +25,7 @@ describe('useEffect Hook Test', () => { memo, forwardRef, act, - } = Horizon; + } = Inula; const LogUtils = getLogUtils(); it('简单使用useEffect', () => { @@ -41,7 +41,7 @@ describe('useEffect Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById('p').style.display).toBe('block'); // 点击按钮触发num加1 container.querySelector('button').click(); @@ -57,7 +57,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => { + Inula.render(, container, () => { LogUtils.log('num effect'); }); // 第一次渲染为同步,所以同步执行的可以写在act里做判断 @@ -65,7 +65,7 @@ describe('useEffect Hook Test', () => { expect(container.textContent).toBe('op'); }); act(() => { - Horizon.render(null, container, () => { + Inula.render(null, container, () => { LogUtils.log('num effect89'); }); // 第二次渲染为异步,所以同步执行的不可以写在act里做判断,act里拿到的为空数组 @@ -87,7 +87,7 @@ describe('useEffect Hook Test', () => { }; const na = ; // 必须设置key值,否则在diff的时候na会被视为不同组件 - Horizon.render([, na], container); + Inula.render([, na], container); expect(LogUtils.getAndClear()).toEqual([ 'App', 'NewApp' @@ -95,7 +95,7 @@ describe('useEffect Hook Test', () => { expect(container.textContent).toBe('AppNewApp'); expect(LogUtils.getAndClear()).toEqual([]); // 在执行新的render前,会执行完上一次render的useEffect,所以LogUtils会加入'NewApp effect'。 - Horizon.render([na], container); + Inula.render([na], container); expect(LogUtils.getAndClear()).toEqual(['NewApp effect', 'NewApp']); expect(container.textContent).toBe('NewApp'); expect(LogUtils.getAndClear()).toEqual([]); @@ -119,7 +119,7 @@ describe('useEffect Hook Test', () => { return ; }; // 必须设置key值,否则在diff的时候na会被视为不同组件 - Horizon.render([, ], container); + Inula.render([, ], container); expect(LogUtils.getAndClear()).toEqual([ 'App', 'NewApp', @@ -137,7 +137,7 @@ describe('useEffect Hook Test', () => { const App = () => { useLayoutEffect(() => { LogUtils.log('App Layout effect'); - Horizon.render(, newContainer); + Inula.render(, newContainer); }); return ; }; @@ -148,7 +148,7 @@ describe('useEffect Hook Test', () => { return ; }; // 必须设置key值,否则在diff的时候na会被视为不同组件 - Horizon.render([, ], container); + Inula.render([, ], container); expect(LogUtils.getAndClear()).toEqual([ 'App', 'NewApp', @@ -168,13 +168,13 @@ describe('useEffect Hook Test', () => { return ; }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']); expect(container.textContent).toEqual('num: 0'); }); expect(LogUtils.getAndClear()).toEqual(['First effect [0]']); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); // 此时异步执行,act执行完后会执行新render的useEffect @@ -197,13 +197,13 @@ describe('useEffect Hook Test', () => { return ; }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']); expect(container.textContent).toEqual('num: 0'); }); expect(LogUtils.getAndClear()).toEqual(['First effect [0]', 'Second effect [0]']); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); // 第二次render时异步执行,act保证所有效果都已更新,所以先常规记录日志 // 然后记录useEffect的日志 @@ -246,7 +246,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 'num: 0,word: App', 'num Layouteffect [0]', @@ -261,21 +261,21 @@ describe('useEffect Hook Test', () => { act(() => { // 此时word改变,num不变 - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ - 'num: 0,word: Horizon', + 'num: 0,word: Inula', 'word Layouteffect destroy', - 'word Layouteffect [Horizon]', + 'word Layouteffect [Inula]', 'callback effect', // 最后执行异步的 'word effect destroy', - 'word effect [Horizon]', + 'word effect [Inula]', ]); act(() => { // 此时num和word的所有effect都销毁 - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'num Layouteffect destroy', @@ -299,7 +299,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 'num: 0', 'callback effect' @@ -311,7 +311,7 @@ describe('useEffect Hook Test', () => { ]); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'num: 1', @@ -324,7 +324,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual([]); act(() => { - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'callback effect', @@ -346,7 +346,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 'num: 0', 'callback effect' @@ -358,7 +358,7 @@ describe('useEffect Hook Test', () => { ]); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'num: 1', @@ -369,7 +369,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual([]); act(() => { - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'callback effect', @@ -382,7 +382,7 @@ describe('useEffect Hook Test', () => { it('useEffect里使用useState(1', () => { let setNum; const App = () => { - const [num, _setNum] = Horizon.useState(0); + const [num, _setNum] = Inula.useState(0); useEffect(() => { LogUtils.log(`num effect [${num}]`); setNum = () => _setNum(1); @@ -397,7 +397,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 'num: 0', 'num Layouteffect [0]', @@ -430,7 +430,7 @@ describe('useEffect Hook Test', () => { return ; }; - Horizon.render(, container, () => LogUtils.log('App callback effect')); + Inula.render(, container, () => LogUtils.log('App callback effect')); expect(LogUtils.getAndClear()).toEqual(['Num: 0', 'App callback effect']); expect(container.textContent).toEqual('Num: 0'); act(() => { @@ -460,7 +460,7 @@ describe('useEffect Hook Test', () => { return ; }); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 0, 'callback effect' @@ -471,7 +471,7 @@ describe('useEffect Hook Test', () => { // 不会重新渲染 act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual(['callback effect']); expect(container.textContent).toEqual('0'); @@ -490,7 +490,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual([]); act(() => { - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 'callback effect', @@ -512,7 +512,7 @@ describe('useEffect Hook Test', () => { return ; }, compare); act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 0, 'callback effect' @@ -523,7 +523,7 @@ describe('useEffect Hook Test', () => { // 不会重新渲染 act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual(['callback effect']); expect(container.textContent).toEqual('0'); @@ -531,7 +531,7 @@ describe('useEffect Hook Test', () => { // 会重新渲染 act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ 1, @@ -544,7 +544,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual([]); act(() => { - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy 1']); expect(container.textContent).toEqual(''); @@ -565,7 +565,7 @@ describe('useEffect Hook Test', () => { return ; }; act(() => { - Horizon.render(, container, () => + Inula.render(, container, () => LogUtils.log('App callback effect'), ); expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'App callback effect']); @@ -575,7 +575,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual(['throw Error']); act(() => { - Horizon.render(null, container, () => + Inula.render(null, container, () => LogUtils.log('App callback effect'), ); }); @@ -607,14 +607,14 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('num effect')); + Inula.render(, container, () => LogUtils.log('num effect')); expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']); expect(container.textContent).toBe('Number: 0'); }); expect(LogUtils.getAndClear()).toEqual(['num effect [0]']); act(() => { - Horizon.render(null, container, () => LogUtils.log('num effect')); + Inula.render(null, container, () => LogUtils.log('num effect')); }); expect(LogUtils.getAndClear()).toEqual(['num effect', 'num effect destroy 0']); expect(container.textContent).toBe(''); @@ -633,7 +633,7 @@ describe('useEffect Hook Test', () => { return ; }; - Horizon.render(, container, () => LogUtils.log('num effect')); + Inula.render(, container, () => LogUtils.log('num effect')); expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']); expect(container.textContent).toBe('Number: 0'); @@ -663,13 +663,13 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('num effect')); + Inula.render(, container, () => LogUtils.log('num effect')); expect(LogUtils.getAndClear()).toEqual(['useEffect', 'num effect']); }); expect(LogUtils.getAndClear()).toEqual(['effect']); act(() => { - Horizon.render(null, container); + Inula.render(null, container); }); // 不会处理setNum(1) expect(LogUtils.getAndClear()).toEqual(['effect destroy']); @@ -678,7 +678,7 @@ describe('useEffect Hook Test', () => { it('当组件的更新方法在卸载函数中,组件的子组件更新不会告警', () => { const App = () => { LogUtils.log('App'); - const appRef = Horizon.createRef(null); + const appRef = Inula.createRef(null); useEffect(() => { LogUtils.log('App effect'); return () => { @@ -701,7 +701,7 @@ describe('useEffect Hook Test', () => { AppChild = forwardRef(AppChild); act(() => { - Horizon.render(, container, () => LogUtils.log('num effect')); + Inula.render(, container, () => LogUtils.log('num effect')); expect(LogUtils.getAndClear()).toEqual([ 'App', 'AppChild', @@ -711,7 +711,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual(['Child effect', 'App effect']); act(() => { - Horizon.render(null, container); + Inula.render(null, container); }); // 销毁时执行appRef.current(1)不会报错 expect(LogUtils.getAndClear()).toEqual(['App effect destroy']); @@ -737,7 +737,7 @@ describe('useEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('num effect')); + Inula.render(, container, () => LogUtils.log('num effect')); expect(LogUtils.getAndClear()).toEqual([ 'App', 'AppChild', @@ -747,7 +747,7 @@ describe('useEffect Hook Test', () => { expect(LogUtils.getAndClear()).toEqual(['Child effect']); act(() => { - Horizon.render(null, container); + Inula.render(null, container); }); // 销毁时执行 props.setNum(1);不会报错 expect(LogUtils.getAndClear()).toEqual(['Child effect destroy']); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js index a44c58b2..1de72341 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { Text } from '../../jest/commonComponents'; import { getLogUtils } from '../../jest/testUtils'; @@ -23,8 +23,8 @@ describe('useImperativeHandle Hook Test', () => { useImperativeHandle, forwardRef, act, - } = Horizon; - const { unmountComponentAtNode } = Horizon; + } = Inula; + const { unmountComponentAtNode } = Inula; const LogUtils = getLogUtils(); it('测试useImperativeHandle', () => { @@ -41,9 +41,9 @@ describe('useImperativeHandle Hook Test', () => { App = forwardRef(App); App1 = forwardRef(App1); - const counter = Horizon.createRef(null); - const counter1 = Horizon.createRef(null); - Horizon.render(, container); + const counter = Inula.createRef(null); + const counter1 = Inula.createRef(null); + Inula.render(, container); expect(counter.current.num).toBe(0); act(() => { counter.current.setNum(1); @@ -53,7 +53,7 @@ describe('useImperativeHandle Hook Test', () => { // 清空container unmountComponentAtNode(container); - Horizon.render(, container); + Inula.render(, container); expect(counter1.current.num1).toBe(0); act(() => { counter1.current.setNum1(1); @@ -76,9 +76,9 @@ describe('useImperativeHandle Hook Test', () => { App = forwardRef(App); App1 = forwardRef(App1); - const counter = Horizon.createRef(null); - const counter1 = Horizon.createRef(null); - Horizon.render(, container); + const counter = Inula.createRef(null); + const counter1 = Inula.createRef(null); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([0]); expect(counter.current.num).toBe(0); act(() => { @@ -90,7 +90,7 @@ describe('useImperativeHandle Hook Test', () => { // 清空container unmountComponentAtNode(container); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([0]); expect(counter1.current.num1).toBe(0); act(() => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js index 8cba787c..66bfbf4e 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; @@ -23,7 +23,7 @@ describe('useLayoutEffect Hook Test', () => { useEffect, useLayoutEffect, act, - } = Horizon; + } = Inula; const LogUtils = getLogUtils(); it('简单使用useLayoutEffect', () => { const App = () => { @@ -38,7 +38,7 @@ describe('useLayoutEffect Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById('p').style.display).toBe('none'); container.querySelector('button').click(); expect(container.querySelector('p').style.display).toBe('inline'); @@ -51,7 +51,7 @@ describe('useLayoutEffect Hook Test', () => { }); return ; }; - Horizon.render(, container, () => LogUtils.log('Sync effect')); + Inula.render(, container, () => LogUtils.log('Sync effect')); expect(LogUtils.getAndClear()).toEqual([ 1, // 同步在渲染之后 @@ -60,7 +60,7 @@ describe('useLayoutEffect Hook Test', () => { ]); expect(container.querySelector('p').innerHTML).toBe('1'); // 更新 - Horizon.render(, container, () => LogUtils.log('Sync effect')); + Inula.render(, container, () => LogUtils.log('Sync effect')); expect(LogUtils.getAndClear()).toEqual([ 2, 'LayoutEffect', @@ -87,7 +87,7 @@ describe('useLayoutEffect Hook Test', () => { }; act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); expect(LogUtils.getAndClear()).toEqual([ 'num: 0', 'num Layouteffect [0]', @@ -98,7 +98,7 @@ describe('useLayoutEffect Hook Test', () => { // 更新 act(() => { - Horizon.render(, container, () => LogUtils.log('callback effect')); + Inula.render(, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ // 异步effect @@ -116,7 +116,7 @@ describe('useLayoutEffect Hook Test', () => { ]); act(() => { - Horizon.render(null, container, () => LogUtils.log('callback effect')); + Inula.render(null, container, () => LogUtils.log('callback effect')); }); expect(LogUtils.getAndClear()).toEqual([ // 同步Layouteffect销毁 diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js index 59a25a09..fb1fe711 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js @@ -13,12 +13,12 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useMemo Hook Test', () => { - const { useMemo, useState } = Horizon; + const { useMemo, useState } = Inula; const LogUtils = getLogUtils(); it('测试useMemo', () => { @@ -38,7 +38,7 @@ describe('useMemo Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('App'); expect(container.querySelector('#p').innerHTML).toBe('1'); // 修改useMemo的依赖项,num会加一,text会改变。 @@ -63,26 +63,26 @@ describe('useMemo Hook Test', () => { }, [props._num]); return ; }; - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([ 0, 1 ]); expect(container.textContent).toBe('1'); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([ 1, 2 ]); expect(container.textContent).toBe('2'); - Horizon.render(, container); + Inula.render(, container); // 不会触发useMemo expect(LogUtils.getAndClear()).toEqual([2]); expect(container.textContent).toBe('2'); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([ 2, 3 @@ -106,16 +106,16 @@ describe('useMemo Hook Test', () => { return 2; }; - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual(['num 1', 1]); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual(['num 1', 1]); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual(['num 1', 1]); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual(['num 2', 2]); }); }); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js index 379d85ac..806e3662 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js @@ -13,10 +13,10 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; describe('useReducer Hook Test', () => { - const { useReducer } = Horizon; + const { useReducer } = Inula; it('简单使用useReducer', () => { const intlCar = { logo: '', price: 0 }; @@ -59,7 +59,7 @@ describe('useReducer Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe(''); expect(container.querySelector('#senP').innerHTML).toBe('0'); // 触发bmw @@ -77,7 +77,7 @@ describe('useReducer Hook Test', () => { const reducer = () => { return { data: nextId++ }; }; - const btnRef = Horizon.createRef(); + const btnRef = Inula.createRef(); const Main = () => { const [{ data }, dispatch] = useReducer(reducer, { data: 0 }); const dispatchLogging = () => { @@ -95,8 +95,8 @@ describe('useReducer Hook Test', () => { ); }; - Horizon.render(
, container); - Horizon.act(() => { + Inula.render(
, container); + Inula.act(() => { btnRef.current.click(); }); expect(nextId).toBe(2); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js index 570f029b..ebefe6b8 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js @@ -13,12 +13,12 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useRef Hook Test', () => { - const { useState, useRef } = Horizon; + const { useState, useRef } = Inula; const LogUtils = getLogUtils(); it('测试useRef', () => { @@ -36,7 +36,7 @@ describe('useRef Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('1'); expect(container.querySelector('#sp').innerHTML).toBe('1'); // 点击按钮触发num加1,ref不变 @@ -59,7 +59,7 @@ describe('useRef Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([1]); expect(container.querySelector('p').innerHTML).toBe('1'); // 点击按钮触发ref.current加1 diff --git a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js index b5e7409f..0c7e60ab 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; @@ -24,7 +24,7 @@ describe('useState Hook Test', () => { useImperativeHandle, memo, act, - } = Horizon; + } = Inula; const LogUtils = getLogUtils(); it('简单使用useState', () => { @@ -37,7 +37,7 @@ describe('useState Hook Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('0'); // 点击按钮触发num加1 container.querySelector('button').click(); @@ -59,7 +59,7 @@ describe('useState Hook Test', () => {

); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('00'); container.querySelector('p').click(); expect(container.querySelector('p').innerHTML).toBe('12'); @@ -81,7 +81,7 @@ describe('useState Hook Test', () => {

); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('0'); container.querySelector('p').click(); expect(container.querySelector('p').innerHTML).toBe('2'); @@ -96,7 +96,7 @@ describe('useState Hook Test', () => { setNum = _setNum; return ; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('0'); expect(LogUtils.getAndClear()).toEqual([0]); // useState修改state 时,设置相同的值,函数组件不会重新渲染 @@ -115,8 +115,8 @@ describe('useState Hook Test', () => { return

{num}

; }); - const ref = Horizon.createRef(null); - Horizon.render(, container); + const ref = Inula.createRef(null); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([1]); expect(container.querySelector('p').innerHTML).toBe('1'); // 设置num为3 @@ -133,11 +133,11 @@ describe('useState Hook Test', () => { setNum = _setNum; return ; }); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([0]); expect(container.querySelector('p').innerHTML).toBe('0'); // 不会重新渲染 - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([]); expect(container.querySelector('p').innerHTML).toBe('0'); // 会重新渲染 @@ -166,7 +166,7 @@ describe('useState Hook Test', () => { return ; }; - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual(['Number: 0, Count: 0']); expect(container.textContent).toBe('Number: 0, Count: 0'); act(() => { @@ -178,7 +178,7 @@ describe('useState Hook Test', () => { jest.spyOn(console, 'error').mockImplementation(); expect(() => { - Horizon.render(, container); + Inula.render(, container); }).toThrow('Hooks are less than expected, please check whether the hook is written in the condition.'); }); }); diff --git a/scripts/__tests__/ComponentTest/JsxElement.test.js b/scripts/__tests__/ComponentTest/JsxElement.test.js new file mode 100644 index 00000000..5f3e573a --- /dev/null +++ b/scripts/__tests__/ComponentTest/JsxElement.test.js @@ -0,0 +1,48 @@ +/* + * 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 Inula from '../../../libs/inula/index'; + +describe('JSX Element test', () => { + it('symbol attribute prevent cloneDeep unlimited loop', function () { + + function cloneDeep(obj) { + const result = {}; + Object.keys(obj).forEach(key => { + if (obj[key] && typeof obj[key] === 'object') { + result[key] = cloneDeep(obj[key]); + } else { + result[key] = obj[key]; + } + }) + return result; + } + class Demo extends Inula.Component { + render() { + return ( +
+ hello +
+ ); + } + } + + const ele = Inula.createElement(Demo); + const copy = cloneDeep(ele); + expect(copy.vtype).toEqual(ele.vtype); + expect(Object.getOwnPropertySymbols(copy).length).toEqual(0); + }); +}); + diff --git a/scripts/__tests__/ComponentTest/LazyComponent.test.js b/scripts/__tests__/ComponentTest/LazyComponent.test.js index c50fede6..9f23e68e 100755 --- a/scripts/__tests__/ComponentTest/LazyComponent.test.js +++ b/scripts/__tests__/ComponentTest/LazyComponent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { Text } from '../jest/commonComponents'; import { getLogUtils } from '../jest/testUtils'; @@ -23,8 +23,8 @@ describe('LazyComponent Test', () => { return { default: component }; }); - it('Horizon.lazy()', async () => { - class LazyComponent extends Horizon.Component { + it('Inula.lazy()', async () => { + class LazyComponent extends Inula.Component { static defaultProps = { language: 'Java' }; render() { @@ -33,12 +33,12 @@ describe('LazyComponent Test', () => { } } - const Lazy = Horizon.lazy(() => mockImport(LazyComponent)); + const Lazy = Inula.lazy(() => mockImport(LazyComponent)); - Horizon.render( - }> + Inula.render( + }> - , + , container ); @@ -47,10 +47,10 @@ describe('LazyComponent Test', () => { expect(container.querySelector('span')).toBe(null); await Promise.resolve(); - Horizon.render( - }> + Inula.render( + }> - , + , container ); @@ -59,16 +59,16 @@ describe('LazyComponent Test', () => { }); it('同步解析', async () => { - const LazyApp = Horizon.lazy(() => ({ + const LazyApp = Inula.lazy(() => ({ then(cb) { cb({ default: Text }); }, })); - Horizon.render( - Loading...}> + Inula.render( + Loading...}> - , + , container ); @@ -77,7 +77,7 @@ describe('LazyComponent Test', () => { }); it('异常捕获边界', async () => { - class ErrorBoundary extends Horizon.Component { + class ErrorBoundary extends Inula.Component { state = {}; static getDerivedStateFromError(error) { return { message: error.message }; @@ -90,7 +90,7 @@ describe('LazyComponent Test', () => { } const LazyComponent = () => { - const [num, setNum] = Horizon.useState(0); + const [num, setNum] = Inula.useState(0); if (num === 2) { throw new Error('num is 2'); } else { @@ -103,24 +103,24 @@ describe('LazyComponent Test', () => { } }; - const LazyApp = Horizon.lazy(() => mockImport(LazyComponent)); + const LazyApp = Inula.lazy(() => mockImport(LazyComponent)); - Horizon.render( + Inula.render( - Loading...}> + Loading...}> - + , container ); expect(container.textContent).toBe('Loading...'); await Promise.resolve(); - Horizon.render( + Inula.render( - }> + }> - + , container ); @@ -133,7 +133,7 @@ describe('LazyComponent Test', () => { }); it('componentDidCatch捕获异常', async () => { - class ErrorBoundary extends Horizon.Component { + class ErrorBoundary extends Inula.Component { state = { catchError: false, error: null, @@ -157,7 +157,7 @@ describe('LazyComponent Test', () => { } const LazyComponent = () => { - const [num, setNum] = Horizon.useState(0); + const [num, setNum] = Inula.useState(0); if (num === 2) { throw new Error('num is 2'); } else { @@ -170,24 +170,24 @@ describe('LazyComponent Test', () => { } }; - const LazyApp = Horizon.lazy(() => mockImport(LazyComponent)); + const LazyApp = Inula.lazy(() => mockImport(LazyComponent)); - Horizon.render( + Inula.render( - Loading...}> + Loading...}> - + , container ); expect(container.textContent).toBe('Loading...'); await Promise.resolve(); - Horizon.render( + Inula.render( - }> + }> - + , container ); @@ -202,25 +202,25 @@ describe('LazyComponent Test', () => { it('#24 配合memo', async () => { const fnComp = () => { - return

horizon

; + return

inula

; }; - const LazyApp = Horizon.lazy(() => ({ + const LazyApp = Inula.lazy(() => ({ then(cb) { - cb({ default: Horizon.memo(() => fnComp, false) }); + cb({ default: Inula.memo(() => fnComp, false) }); }, })); expect(() => { - Horizon.render( - Loading...}> + Inula.render( + Loading...}> - , + , container ); - Horizon.render( - Loading...}> + Inula.render( + Loading...}> - , + , container ); }).not.toThrow(); diff --git a/scripts/__tests__/ComponentTest/LifeCycle.test.js b/scripts/__tests__/ComponentTest/LifeCycle.test.js index 2ab719ef..8969e8a9 100644 --- a/scripts/__tests__/ComponentTest/LifeCycle.test.js +++ b/scripts/__tests__/ComponentTest/LifeCycle.test.js @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('LifeCycle Test', () => { const LogUtils = getLogUtils(); describe('LifeCycle function', () => { it('不能在componentWillMount里setState', () => { - class App extends Horizon.Component { + class App extends Inula.Component { state = {}; UNSAFE_componentWillMount() { @@ -34,13 +34,13 @@ describe('LifeCycle Test', () => { } } - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); // 不能在componentWillMount里setState expect(realNode.textContent).toBe(undefined); }); it('componentDidMount里调用setState()将触发额外渲染', () => { - class ChildApp extends Horizon.Component { + class ChildApp extends Inula.Component { constructor(props) { super(props); } @@ -54,7 +54,7 @@ describe('LifeCycle Test', () => { } } - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); LogUtils.log('constructor'); @@ -75,7 +75,7 @@ describe('LifeCycle Test', () => { } } - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); // 确实触发了额外渲染 expect(LogUtils.getAndClear()).toEqual([ 'constructor', @@ -89,7 +89,7 @@ describe('LifeCycle Test', () => { }); it('调用 this.setState() 通常不会触发 UNSAFE_componentWillReceiveProps()', () => { - class App extends Horizon.Component { + class App extends Inula.Component { state = {}; update = () => { @@ -108,14 +108,14 @@ describe('LifeCycle Test', () => { } } - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); expect(realNode.textContent).toBe(undefined); realNode.update(); expect(LogUtils.getAndClear()).toEqual([]); }); it('不能在componentWillReceiveProps里setState', () => { - class ChildApp extends Horizon.Component { + class ChildApp extends Inula.Component { state = {}; UNSAFE_componentWillReceiveProps() { @@ -127,7 +127,7 @@ describe('LifeCycle Test', () => { return
{this.state.text}
; } } - class App extends Horizon.Component { + class App extends Inula.Component { state = {}; update = () => { @@ -139,7 +139,7 @@ describe('LifeCycle Test', () => { } } - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); expect(realNode.textContent).toBe(undefined); realNode.update(); expect(LogUtils.getAndClear()).toEqual([ @@ -151,7 +151,7 @@ describe('LifeCycle Test', () => { }); it('shouldComponentUpdate与getDerivedStateFromProps', () => { - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); this.state = { @@ -175,24 +175,24 @@ describe('LifeCycle Test', () => { } } - Horizon.render(, container); + Inula.render(, container); // 初次渲染不会调用shouldComponentUpdate expect(LogUtils.getAndClear()).toEqual([]); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); // getDerivedStateFromProps判断state没有变化时,会调用shouldComponentUpdate expect(LogUtils.getAndClear()).toEqual(['shouldComponentUpdate']); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); // getDerivedStateFromProps判断state变化时,会调用shouldComponentUpdate expect(LogUtils.getAndClear()).toEqual(['shouldComponentUpdate']); expect(container.querySelector('p').innerHTML).toBe('2'); }); it('如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()', () => { - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); this.state = { @@ -219,22 +219,22 @@ describe('LifeCycle Test', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); // 不会调用componentDidUpdate() expect(LogUtils.getAndClear()).toEqual([]); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); // 调用componentDidUpdate() expect(LogUtils.getAndClear()).toEqual(['componentDidUpdate']); expect(container.querySelector('p').innerHTML).toBe('2'); }); it('getSnapshotBeforeUpdate()的返回值会作为componentDidUpdate()的第三个参数', () => { - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); this.state = { @@ -263,11 +263,11 @@ describe('LifeCycle Test', () => { return

{this.state.num}

; } } - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([]); expect(container.querySelector('p').innerHTML).toBe(''); - Horizon.render(, container); + Inula.render(, container); // Snapshot作为componentDidUpdate()的第三个参数 expect(LogUtils.getAndClear()).toEqual([ 'getSnapshotBeforeUpdate prevProps:undefined prevState:undefined', @@ -275,14 +275,14 @@ describe('LifeCycle Test', () => { ]); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([ 'getSnapshotBeforeUpdate prevProps:1 prevState:1', 'componentDidUpdate prevProps:1 prevState:1 snapshot:Snapshot', ]); expect(container.querySelector('p').innerHTML).toBe('1'); - Horizon.render(, container); + Inula.render(, container); expect(LogUtils.getAndClear()).toEqual([ 'getSnapshotBeforeUpdate prevProps:1 prevState:1', 'componentDidUpdate prevProps:1 prevState:1 snapshot:Snapshot', @@ -291,7 +291,7 @@ describe('LifeCycle Test', () => { }); it('无论什么原因触发了渲染,只要有渲染就会触发getDerivedStateFromProps', () => { - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); this.state = { @@ -308,8 +308,8 @@ describe('LifeCycle Test', () => { return

{this.state.num}

; } } - let realNode = Horizon.render(, container); - realNode = Horizon.render(, container); + let realNode = Inula.render(, container); + realNode = Inula.render(, container); realNode.forceUpdate(); // 触发了3次渲染 expect(LogUtils.getAndClear()).toEqual([ @@ -321,7 +321,7 @@ describe('LifeCycle Test', () => { }); it('生命周期执行顺序', () => { - class ChildApp extends Horizon.Component { + class ChildApp extends Inula.Component { UNSAFE_componentWillMount() { LogUtils.log('Child componentWillMount'); } @@ -350,7 +350,7 @@ describe('LifeCycle Test', () => { } } - class App extends Horizon.Component { + class App extends Inula.Component { UNSAFE_componentWillMount() { LogUtils.log('componentWillMount'); } @@ -379,7 +379,7 @@ describe('LifeCycle Test', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe('1'); expect(LogUtils.getAndClear()).toEqual([ 'componentWillMount', @@ -387,7 +387,7 @@ describe('LifeCycle Test', () => { 'Child componentDidMount', 'componentDidMount' ]); - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe('2'); expect(LogUtils.getAndClear()).toEqual([ 'componentWillReceiveProps', @@ -399,7 +399,7 @@ describe('LifeCycle Test', () => { 'Child componentDidUpdate', 'componentDidUpdate' ]); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(LogUtils.getAndClear()).toEqual([ 'componentWillUnmount', @@ -408,7 +408,7 @@ describe('LifeCycle Test', () => { }); it('新生命周期执行顺序', () => { - class ChildApp extends Horizon.Component { + class ChildApp extends Inula.Component { static getDerivedStateFromProps(props, state) { LogUtils.log('Child getDerivedStateFromProps'); } @@ -434,7 +434,7 @@ describe('LifeCycle Test', () => { } } - class App extends Horizon.Component { + class App extends Inula.Component { static getDerivedStateFromProps(props, state) { LogUtils.log('getDerivedStateFromProps'); } @@ -460,7 +460,7 @@ describe('LifeCycle Test', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe('1'); expect(LogUtils.getAndClear()).toEqual([ 'getDerivedStateFromProps', @@ -468,7 +468,7 @@ describe('LifeCycle Test', () => { 'Child componentDidMount', 'componentDidMount' ]); - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe('2'); expect(LogUtils.getAndClear()).toEqual([ 'getDerivedStateFromProps', @@ -480,7 +480,7 @@ describe('LifeCycle Test', () => { 'Child componentDidUpdate', 'componentDidUpdate' ]); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(LogUtils.getAndClear()).toEqual([ 'componentWillUnmount', diff --git a/scripts/__tests__/ComponentTest/Memo.test.js b/scripts/__tests__/ComponentTest/Memo.test.js index 8ff8cd20..2af413b4 100644 --- a/scripts/__tests__/ComponentTest/Memo.test.js +++ b/scripts/__tests__/ComponentTest/Memo.test.js @@ -13,18 +13,18 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('Memo Test', () => { it('Memo should not make the path wrong', function () { let updateApp; function Child() { - const [_, update] = Horizon.useState({}); + const [_, update] = Inula.useState({}); updateApp = () => update({}); return
; } - const MemoChild = Horizon.memo(Child); + const MemoChild = Inula.memo(Child); function App() { return ( @@ -33,14 +33,14 @@ describe('Memo Test', () => { ); } - const MemoApp = Horizon.memo(App); - Horizon.render( + const MemoApp = Inula.memo(App); + Inula.render(
, container ); - Horizon.render( + Inula.render(
diff --git a/scripts/__tests__/ComponentTest/PortalComponent.test.js b/scripts/__tests__/ComponentTest/PortalComponent.test.js index 808689f5..2fc35030 100755 --- a/scripts/__tests__/ComponentTest/PortalComponent.test.js +++ b/scripts/__tests__/ComponentTest/PortalComponent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; import dispatchChangeEvent from '../utils/dispatchChangeEvent'; @@ -23,23 +23,23 @@ describe('PortalComponent Test', () => { it('将子节点渲染到存在于父组件以外的 DOM 节点', () => { const portalRoot = document.createElement('div'); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); this.element = portalRoot; } render() { - return Horizon.createPortal(this.props.child, this.element); + return Inula.createPortal(this.props.child, this.element); } } - Horizon.render(PortalApp
} />, container); + Inula.render(PortalApp} />, container); expect(container.textContent).toBe(''); //
PortalApp
被渲染到了portalRoot而非container expect(portalRoot.textContent).toBe('PortalApp'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe(''); }); @@ -48,7 +48,7 @@ describe('PortalComponent Test', () => { const portalRoot1st = document.createElement('div'); const portalRoot2nd = document.createElement('div'); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); this.element = portalRoot1st; @@ -57,19 +57,19 @@ describe('PortalComponent Test', () => { render() { return [ - Horizon.createPortal(this.props.child, this.element), - Horizon.createPortal(this.props.child, this.newElement), + Inula.createPortal(this.props.child, this.element), + Inula.createPortal(this.props.child, this.newElement), ]; } } - Horizon.render(PortalApp} />, container); + Inula.render(PortalApp} />, container); expect(container.textContent).toBe(''); //
PortalApp
被渲染到了portalRoot而非container expect(portalRoot1st.textContent).toBe('PortalApp'); expect(portalRoot2nd.textContent).toBe('PortalApp'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(portalRoot1st.textContent).toBe(''); expect(portalRoot2nd.textContent).toBe(''); @@ -80,7 +80,7 @@ describe('PortalComponent Test', () => { const portalRoot2nd = document.createElement('div'); const portalRoot3rd = document.createElement('div'); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); this.element = portalRoot1st; @@ -91,24 +91,24 @@ describe('PortalComponent Test', () => { render() { return [
PortalApp1st
, - Horizon.createPortal( - [
PortalApp4
, Horizon.createPortal(this.props.child, this.element3rd)], + Inula.createPortal( + [
PortalApp4
, Inula.createPortal(this.props.child, this.element3rd)], this.element ),
PortalApp2nd
, - Horizon.createPortal(this.props.child, this.newElement), + Inula.createPortal(this.props.child, this.newElement), ]; } } - Horizon.render(PortalApp} />, container); + Inula.render(PortalApp} />, container); expect(container.textContent).toBe('PortalApp1stPortalApp2nd'); //
PortalApp4
会挂载在this.element上 expect(portalRoot1st.textContent).toBe('PortalApp4'); expect(portalRoot2nd.textContent).toBe('PortalApp'); expect(portalRoot3rd.textContent).toBe('PortalApp'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(portalRoot1st.textContent).toBe(''); expect(portalRoot2nd.textContent).toBe(''); @@ -117,50 +117,50 @@ describe('PortalComponent Test', () => { it('改变Portal的参数', () => { const portalRoot = document.createElement('div'); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); this.element = portalRoot; } render() { - return Horizon.createPortal(this.props.child, this.element); + return Inula.createPortal(this.props.child, this.element); } } - Horizon.render(PortalApp} />, container); + Inula.render(PortalApp} />, container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe('PortalApp'); - Horizon.render(AppPortal} />, container); + Inula.render(AppPortal} />, container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe('AppPortal'); - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe('portal'); - Horizon.render(, container); + Inula.render(, container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe(''); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); expect(container.textContent).toBe(''); expect(portalRoot.textContent).toBe(''); }); it('通过Portal进行事件冒泡', () => { const portalRoot = document.createElement('div'); - const buttonRef = Horizon.createRef(); + const buttonRef = Inula.createRef(); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); this.element = portalRoot; } render() { - return Horizon.createPortal(this.props.child, this.element); + return Inula.createPortal(this.props.child, this.element); } } @@ -187,7 +187,7 @@ describe('PortalComponent Test', () => { ); }; - Horizon.render(, container); + Inula.render(, container); const event = document.createEvent('Event'); event.initEvent('click', true, true); buttonRef.current.dispatchEvent(event); @@ -200,21 +200,21 @@ describe('PortalComponent Test', () => { }); it('Create portal at app root should not add event listener multiple times', () => { - const btnRef = Horizon.createRef(); + const btnRef = Inula.createRef(); - class PortalApp extends Horizon.Component { + class PortalApp extends Inula.Component { constructor(props) { super(props); } render() { - return Horizon.createPortal(this.props.child, container); + return Inula.createPortal(this.props.child, container); } } const onClick = jest.fn(); - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); } @@ -229,13 +229,13 @@ describe('PortalComponent Test', () => { } } - Horizon.render(, container); + Inula.render(, container); btnRef.current.click(); expect(onClick).toHaveBeenCalledTimes(1); }); it('#76 Portal onChange should activate', () => { - class Dialog extends Horizon.Component { + class Dialog extends Inula.Component { node; constructor(props) { @@ -245,20 +245,20 @@ describe('PortalComponent Test', () => { } render() { - return Horizon.createPortal(this.props.children, this.node); + return Inula.createPortal(this.props.children, this.node); } } let showPortalInput; const fn = jest.fn(); - const inputRef = Horizon.createRef(); + const inputRef = Inula.createRef(); function App() { const Input = () => { - const [show, setShow] = Horizon.useState(false); + const [show, setShow] = Inula.useState(false); showPortalInput = setShow; - Horizon.useEffect(() => { + Inula.useEffect(() => { setTimeout(() => { setShow(true); }, 0); @@ -280,7 +280,7 @@ describe('PortalComponent Test', () => { ); } - Horizon.render(, container); + Inula.render(, container); showPortalInput(true); jest.advanceTimersToNextTimer(); dispatchChangeEvent(inputRef.current, 'test'); diff --git a/scripts/__tests__/ComponentTest/SuspenseComponent.test.js b/scripts/__tests__/ComponentTest/SuspenseComponent.test.js index 753373bc..37279de8 100755 --- a/scripts/__tests__/ComponentTest/SuspenseComponent.test.js +++ b/scripts/__tests__/ComponentTest/SuspenseComponent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { Text } from '../jest/commonComponents'; import { getLogUtils } from '../jest/testUtils'; @@ -42,18 +42,18 @@ describe('SuspenseComponent Test', () => { it('挂载lazy组件', async () => { // 用同步的代码来实现异步操作 - class LazyComponent extends Horizon.Component { + class LazyComponent extends Inula.Component { render() { return ; } } - const Lazy = Horizon.lazy(() => mockImport(LazyComponent)); + const Lazy = Inula.lazy(() => mockImport(LazyComponent)); - Horizon.render( - }> + Inula.render( + }> - , + , container ); @@ -61,10 +61,10 @@ describe('SuspenseComponent Test', () => { expect(container.textContent).toBe('Loading...'); await Promise.resolve(); - Horizon.render( - }> + Inula.render( + }> - , + , container ); expect(LogUtils.getAndClear()).toEqual([5]); diff --git a/scripts/__tests__/DomTest/Attribute.test.js b/scripts/__tests__/DomTest/Attribute.test.js index e8aa843b..bf560e0e 100755 --- a/scripts/__tests__/DomTest/Attribute.test.js +++ b/scripts/__tests__/DomTest/Attribute.test.js @@ -13,48 +13,48 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('Dom Attribute', () => { it('属性值为null或undefined时,不会设置此属性', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').getAttribute('id')).toBe('div'); - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('id')).toBe(false); - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('id')).toBe(false); }); it('可以设置未知的属性', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('abcd')).toBe(true); expect(container.querySelector('div').getAttribute('abcd')).toBe('abcd'); }); it('未知属性的值为null或undefined时,不会设置此属性', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('abcd')).toBe(false); - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('abcd')).toBe(false); }); it('未知属性的值为数字时,属性值会转为字符串', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').getAttribute('abcd')).toBe('0'); - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').getAttribute('abcd')).toBe('-3'); - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').getAttribute('abcd')).toBe('123.45'); }); it('访问节点的标准属性时可以拿到属性值,访问节点的非标准属性时会得到undefined', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').id).toBe('div'); expect(container.querySelector('div').abcd).toBe(undefined); }); it('特性方法', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('abcd')).toBe(true); expect(container.querySelector('div').getAttribute('abcd')).toBe('0'); container.querySelector('div').setAttribute('abcd', 4); @@ -64,7 +64,7 @@ describe('Dom Attribute', () => { }); it('特性大小写不敏感', () => { - Horizon.render(
, container); + Inula.render(
, container); expect(container.querySelector('div').hasAttribute('abcd')).toBe(true); expect(container.querySelector('div').hasAttribute('ABCD')).toBe(true); expect(container.querySelector('div').getAttribute('abcd')).toBe('0'); @@ -72,21 +72,27 @@ describe('Dom Attribute', () => { }); it('使用 data- 开头的特性时,会映射到DOM的dataset属性且中划线格式会变成驼峰格式', () => { - Horizon.render(
, container); + Inula.render(
, container); container.querySelector('div').setAttribute('data-first-name', 'Tom'); expect(container.querySelector('div').dataset.firstName).toBe('Tom'); }); it('style 自动加px', () => { - const div = Horizon.render(
, container); + const div = Inula.render(
, container); expect(window.getComputedStyle(div).getPropertyValue('width')).toBe('10px'); expect(window.getComputedStyle(div).getPropertyValue('height')).toBe('20px'); }); + it('WebkitLineClamp和lineClamp样式不会把数字转换成字符串或者追加"px"', () => { + Inula.render(
, container); + // 浏览器可以将WebkitLineClamp识别为-webkit-line-clamp,测试框架不可以 + expect(container.querySelector('div').style.WebkitLineClamp).toBe(2); + }); + it('空字符串做属性名', () => { const emptyStringProps = { '': '' }; expect(() => { - Horizon.render(
, container); + Inula.render(
, container); }).not.toThrow(); }); }); diff --git a/scripts/__tests__/DomTest/DomInput.test.js b/scripts/__tests__/DomTest/DomInput.test.js index ef9964d2..98b79119 100755 --- a/scripts/__tests__/DomTest/DomInput.test.js +++ b/scripts/__tests__/DomTest/DomInput.test.js @@ -14,37 +14,37 @@ */ /* eslint-disable @typescript-eslint/no-empty-function */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('Dom Input', () => { - const { act } = Horizon; + const { act } = Inula; const LogUtils = getLogUtils(); describe('type checkbox', () => { it('没有设置checked属性时,控制台不会报错', () => { expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); }); it('checked属性为undefined或null时且没有onChange属性或没有readOnly={true},控制台不会报错', () => { expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); }); it('复选框的value属性值可以改变', () => { - Horizon.render( + Inula.render( { LogUtils.log('checkbox click'); }} />, container, ); - Horizon.render( + Inula.render( { LogUtils.log('checkbox click'); }} />, @@ -56,7 +56,7 @@ describe('Dom Input', () => { }); it('复选框不设置value属性值时会设置value为"on"', () => { - Horizon.render( + Inula.render( , container, ); @@ -65,21 +65,21 @@ describe('Dom Input', () => { }); it('测试defaultChecked与更改defaultChecked', () => { - Horizon.render( + Inula.render( , container, ); expect(container.querySelector('input').value).toBe('on'); expect(container.querySelector('input').checked).toBe(false); - Horizon.render( + Inula.render( , container, ); expect(container.querySelector('input').value).toBe('on'); expect(container.querySelector('input').checked).toBe(true); - Horizon.render( + Inula.render( , container, ); @@ -91,86 +91,86 @@ describe('Dom Input', () => { describe('type text', () => { it('value属性为undefined或null时且没有onChange属性或没有readOnly={true},控制台不会报错', () => { expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); expect(() => - Horizon.render(, container), + Inula.render(, container), ).not.toThrow(); }); it('value值会转为字符串', () => { - const realNode = Horizon.render(, container); + const realNode = Inula.render(, container); expect(realNode.value).toBe('1'); }); it('value值可以被设置为true/false', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); expect(realNode.value).toBe('1'); - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('true'); - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('false'); }); it('value值可以被设置为object', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); expect(realNode.value).toBe('1'); const value = { toString: () => { return 'value'; } }; - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('value'); }); it('设置defaultValue', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); expect(realNode.value).toBe('1'); expect(realNode.getAttribute('value')).toBe('1'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); // 测试defaultValue为boolean类型 - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('true'); expect(realNode.getAttribute('value')).toBe('true'); - Horizon.unmountComponentAtNode(container); - realNode = Horizon.render(, container); + Inula.unmountComponentAtNode(container); + realNode = Inula.render(, container); expect(realNode.value).toBe('false'); expect(realNode.getAttribute('value')).toBe('false'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); const value = { toString: () => { return 'default'; } }; - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('default'); expect(realNode.getAttribute('value')).toBe('default'); }); it('value为0、defaultValue为1,input 的value应该为0', () => { - const input = Horizon.render(, container); + const input = Inula.render(, container); expect(input.getAttribute('value')).toBe('0'); }); it('name属性', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); expect(realNode.name).toBe('name'); expect(realNode.getAttribute('name')).toBe('name'); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); // 没有设置name属性 - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.name).toBe(''); expect(realNode.getAttribute('name')).toBe(null); }); it('受控input可以触发onChange', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value', @@ -189,19 +189,19 @@ describe('Dom Input', () => { describe('type radio', () => { it('radio的value可以更新', () => { - let realNode = Horizon.render(, container); + let realNode = Inula.render(, container); expect(realNode.value).toBe(''); expect(realNode.getAttribute('value')).toBe(''); - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('false'); expect(realNode.getAttribute('value')).toBe('false'); - realNode = Horizon.render(, container); + realNode = Inula.render(, container); expect(realNode.value).toBe('true'); expect(realNode.getAttribute('value')).toBe('true'); }); it('相同name且在同一表单的radio互斥', () => { - Horizon.render( + Inula.render( <> @@ -231,9 +231,9 @@ describe('Dom Input', () => { }); it('name改变不影响相同name的radio', () => { - const inputRef = Horizon.createRef(); + const inputRef = Inula.createRef(); const App = () => { - const [isNum, setNum] = Horizon.useState(false); + const [isNum, setNum] = Inula.useState(false); const inputName = isNum ? 'secondName' : 'firstName'; const buttonClick = () => { @@ -259,7 +259,7 @@ describe('Dom Input', () => {
); }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').checked).toBe(false); expect(inputRef.current.checked).toBe(true); // 点击button,触发setNum @@ -271,15 +271,15 @@ describe('Dom Input', () => { describe('type submit', () => { it('type submit value', () => { - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(false); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe(''); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe('submit'); }); @@ -287,15 +287,15 @@ describe('Dom Input', () => { describe('type reset', () => { it('type reset value', () => { - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(false); - Horizon.unmountComponentAtNode(container); + Inula.unmountComponentAtNode(container); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe(''); - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe('reset'); }); @@ -303,13 +303,13 @@ describe('Dom Input', () => { describe('type number', () => { it('value值会把number类型转为字符串,且.xx转为0.xx', () => { - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe('0.12'); }); it('value值会把number类型转为字符串,且.xx转为0.xx', () => { - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe('0.12'); }); @@ -317,11 +317,11 @@ describe('Dom Input', () => { it('改变node.value值', () => { let setNum; const App = () => { - const [num, _setNum] = Horizon.useState(''); + const [num, _setNum] = Inula.useState(''); setNum = _setNum; return ; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe(''); act(() => { @@ -333,11 +333,11 @@ describe('Dom Input', () => { it('node.value精度', () => { let setNum; const App = () => { - const [num, _setNum] = Horizon.useState(0.0000); + const [num, _setNum] = Inula.useState(0.0000); setNum = _setNum; return ; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').getAttribute('value')).toBe('0'); act(() => { setNum(1.0000); @@ -350,7 +350,7 @@ describe('Dom Input', () => { const App = () => { return ; }; - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').getAttribute('value')).toBe('1'); expect(container.querySelector('input').value).toBe('1'); @@ -374,17 +374,17 @@ describe('Dom Input', () => { describe('type reset', () => { it('type reset的value值', () => { - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe('0.12'); - Horizon.unmountComponentAtNode(container); - Horizon.render(, container); + Inula.unmountComponentAtNode(container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(true); expect(container.querySelector('input').getAttribute('value')).toBe(''); - Horizon.unmountComponentAtNode(container); - Horizon.render(, container); + Inula.unmountComponentAtNode(container); + Inula.render(, container); expect(container.querySelector('input').hasAttribute('value')).toBe(false); }); }); diff --git a/scripts/__tests__/DomTest/DomSelect.test.js b/scripts/__tests__/DomTest/DomSelect.test.js index a126ab4c..4a718601 100755 --- a/scripts/__tests__/DomTest/DomSelect.test.js +++ b/scripts/__tests__/DomTest/DomSelect.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('Dom Select', () => { it('设置value', () => { @@ -24,12 +24,12 @@ describe('Dom Select', () => { ); - const realNode = Horizon.render(selectNode, container); + const realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[1].selected).toBe(true); realNode.value = 'React'; // 改变value会影响select的状态 - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(realNode.options[0].selected).toBe(true); expect(realNode.value).toBe('React'); }); @@ -47,7 +47,7 @@ describe('Dom Select', () => { ); - const realNode = Horizon.render(selectNode, container); + const realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[1].selected).toBe(true); selectValue = { @@ -63,7 +63,7 @@ describe('Dom Select', () => { ); // 改变value会影响select的状态 - Horizon.render(newSelectNode, container); + Inula.render(newSelectNode, container); expect(realNode.options[0].selected).toBe(true); expect(realNode.value).toBe('React'); }); @@ -76,7 +76,7 @@ describe('Dom Select', () => { ); - const realNode = Horizon.render(selectNode, container); + const realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[1].selected).toBe(true); const newSelectNode = ( @@ -86,7 +86,7 @@ describe('Dom Select', () => { ); - Horizon.render(newSelectNode, container); + Inula.render(newSelectNode, container); // selected不变 expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(true); @@ -102,13 +102,13 @@ describe('Dom Select', () => { ); - let realNode = Horizon.render(selectNode, container); + let realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[1].selected).toBe(true); defaultVal = 'React'; // 改变defaultValue没有影响 - realNode = Horizon.render(selectNode, container); + realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(true); @@ -122,7 +122,7 @@ describe('Dom Select', () => { ); - let realNode = Horizon.render(selectNode, container); + let realNode = Inula.render(selectNode, container); expect(realNode.value).toBe('Vue'); expect(realNode.options[1].selected).toBe(true); @@ -139,7 +139,7 @@ describe('Dom Select', () => { }), ); // 鼠标改变受控select生效,select不受控 - Horizon.render(selectNode, container); + Inula.render(selectNode, container); // 'React'项没被选中 expect(realNode.options[0].selected).toBe(true); expect(realNode.options[1].selected).toBe(false); @@ -156,7 +156,7 @@ describe('Dom Select', () => { ); expect( - () => Horizon.render(selectNode, container) + () => Inula.render(selectNode, container) ).toThrowError('newValues.forEach is not a function'); }); @@ -169,7 +169,7 @@ describe('Dom Select', () => { ); expect( - () => Horizon.render(selectNode, container) + () => Inula.render(selectNode, container) ).not.toThrow(); expect(document.getElementById('se').options[0].selected).toBe(false); expect(document.getElementById('se').options[1].selected).toBe(true); @@ -183,7 +183,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(false); expect(document.getElementById('se').options[1].selected).toBe(true); expect(document.getElementById('se').options[2].selected).toBe(true); @@ -198,7 +198,7 @@ describe('Dom Select', () => { ); expect( - () => Horizon.render(selectNode, container) + () => Inula.render(selectNode, container) ).not.toThrow(); expect(document.getElementById('se').options[0].selected).toBe(false); expect(document.getElementById('se').options[1].selected).toBe(true); @@ -212,7 +212,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(true); expect(document.getElementById('se').options[1].selected).toBe(false); expect(document.getElementById('se').options[2].selected).toBe(false); @@ -226,7 +226,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(false); expect(document.getElementById('se').options[1].selected).toBe(true); expect(document.getElementById('se').options[2].selected).toBe(true); @@ -239,7 +239,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(true); expect(document.getElementById('se').options[1].selected).toBe(false); expect(document.getElementById('se').options[2].selected).toBe(false); @@ -253,7 +253,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(true); expect(document.getElementById('se').options[1].selected).toBe(false); expect(document.getElementById('se').options[2].selected).toBe(false); @@ -266,7 +266,7 @@ describe('Dom Select', () => { ); - Horizon.render(selectNode, container); + Inula.render(selectNode, container); expect(document.getElementById('se').options[0].selected).toBe(false); expect(document.getElementById('se').options[1].selected).toBe(true); expect(document.getElementById('se').options[2].selected).toBe(true); @@ -280,7 +280,7 @@ describe('Dom Select', () => { ); - const realNode = Horizon.render(selectNode, container); + const realNode = Inula.render(selectNode, container); expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(true); expect(realNode.options[2].selected).toBe(false); @@ -294,7 +294,7 @@ describe('Dom Select', () => { ); - const realNode = Horizon.render(selectNode, container); + const realNode = Inula.render(selectNode, container); expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(true); expect(realNode.options[2].selected).toBe(false); @@ -305,7 +305,7 @@ describe('Dom Select', () => { ); - Horizon.render(newNode, container); + Inula.render(newNode, container); expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(false); @@ -317,7 +317,7 @@ describe('Dom Select', () => { ); // 重新添加不会影响 - Horizon.render(newSelectNode, container); + Inula.render(newSelectNode, container); expect(realNode.options[0].selected).toBe(false); expect(realNode.options[1].selected).toBe(false); expect(realNode.options[2].selected).toBe(false); diff --git a/scripts/__tests__/DomTest/DomTextarea.test.js b/scripts/__tests__/DomTest/DomTextarea.test.js index 5944a92a..000e9644 100644 --- a/scripts/__tests__/DomTest/DomTextarea.test.js +++ b/scripts/__tests__/DomTest/DomTextarea.test.js @@ -13,20 +13,20 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; describe('Dom Textarea', () => { it('设置value', () => { - let realNode = Horizon.render(, container); + Inula.render(, container); expect(realNode.value).toBe('false'); }); @@ -92,14 +92,14 @@ describe('Dom Textarea', () => { const textareaNode = ( , container); + let realNode = Inula.render(, container); expect(realNode.value).toBe('1234'); - realNode = Horizon.render(, container); + realNode = Inula.render(, container); // realNode.value依旧为1234 expect(realNode.value).toBe('1234'); }); diff --git a/scripts/__tests__/EventTest/EventMain.test.js b/scripts/__tests__/EventTest/EventMain.test.js index e0341d72..c189f7de 100644 --- a/scripts/__tests__/EventTest/EventMain.test.js +++ b/scripts/__tests__/EventTest/EventMain.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import * as TestUtils from '../jest/testUtils'; function dispatchChangeEvent(input) { @@ -38,7 +38,7 @@ describe('事件', () => { ); }; - Horizon.render(, container); + Inula.render(, container); const a = container.querySelector('button'); a.click(); expect(LogUtils.getAndClear()).toEqual([ @@ -54,7 +54,7 @@ describe('事件', () => { it('returns 0', () => { let keyCode = null; - const node = Horizon.render( + const node = Inula.render( { keyCode = e.keyCode; @@ -87,7 +87,7 @@ describe('事件', () => { ); }; - Horizon.render(, container); + Inula.render(, container); container.querySelector('button').click(); expect(LogUtils.getAndClear()).toEqual([ @@ -114,7 +114,7 @@ describe('事件', () => { ); }; - Horizon.render(, container); + Inula.render(, container); container.querySelector('button').click(); expect(LogUtils.getAndClear()).toEqual([ @@ -133,7 +133,7 @@ describe('事件', () => {
); }; - Horizon.render(, container); + Inula.render(, container); container.querySelector('div').addEventListener( 'click', () => { @@ -162,15 +162,15 @@ describe('事件', () => { it('动态增加事件', () => { let update; - let inputRef = Horizon.createRef(); + let inputRef = Inula.createRef(); function Test() { - const [inputProps, setProps] = Horizon.useState({}); + const [inputProps, setProps] = Inula.useState({}); update = setProps; return ; } - Horizon.render(, container); + Inula.render(, container); update({ onChange: () => { LogUtils.log('change'); @@ -193,10 +193,10 @@ describe('事件', () => { radio2Called++; } - const radio1Ref = Horizon.createRef(); - const radio2Ref = Horizon.createRef(); + const radio1Ref = Inula.createRef(); + const radio2Ref = Inula.createRef(); - Horizon.render( + Inula.render( <> @@ -228,7 +228,7 @@ describe('事件', () => { let input1, input2, update1, update2; function App1() { - const [props, setProps] = Horizon.useState({}); + const [props, setProps] = Inula.useState({}); update1 = setProps; return ( { } function App2() { - const [props, setProps] = Horizon.useState({}); + const [props, setProps] = Inula.useState({}); update2 = setProps; return ( @@ -257,8 +257,8 @@ describe('事件', () => { } // 多根mount阶段挂载onChange事件 - Horizon.render(, root1); - Horizon.render(, root2); + Inula.render(, root1); + Inula.render(, root2); dispatchChangeEvent(input1); expect(LogUtils.getAndClear()).toEqual(['input1 changed']); diff --git a/scripts/__tests__/EventTest/FocusEvent.test.js b/scripts/__tests__/EventTest/FocusEvent.test.js index 10978d2d..b0f2954c 100644 --- a/scripts/__tests__/EventTest/FocusEvent.test.js +++ b/scripts/__tests__/EventTest/FocusEvent.test.js @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('合成焦点事件', () => { const LogUtils = getLogUtils(); it('onFocus', () => { - const realNode = Horizon.render( + const realNode = Inula.render( LogUtils.log(`onFocus: ${event.type}`)} onFocusCapture={event => LogUtils.log(`onFocusCapture: ${event.type}`)} @@ -40,7 +40,7 @@ describe('合成焦点事件', () => { }); it('onBlur', () => { - const realNode = Horizon.render( + const realNode = Inula.render( LogUtils.log(`onBlur: ${event.type}`)} onBlurCapture={event => LogUtils.log(`onBlurCapture: ${event.type}`)} diff --git a/scripts/__tests__/EventTest/KeyboardEvent.test.js b/scripts/__tests__/EventTest/KeyboardEvent.test.js index 675ef450..e6077aca 100644 --- a/scripts/__tests__/EventTest/KeyboardEvent.test.js +++ b/scripts/__tests__/EventTest/KeyboardEvent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import {getLogUtils} from '../jest/testUtils'; describe('Keyboard Event', () => { @@ -29,7 +29,7 @@ describe('Keyboard Event', () => { }; it('keydown,keypress,keyup的keycode,charcode', () => { - const node = Horizon.render( + const node = Inula.render( { LogUtils.log('onKeyUp: keycode: ' + e.keyCode + ',charcode: ' + e.charCode); @@ -50,7 +50,7 @@ describe('Keyboard Event', () => { }); it('keypress的keycode,charcode', () => { - const node = Horizon.render( + const node = Inula.render( { LogUtils.log('onKeyPress: keycode: ' + e.keyCode + ',charcode: ' + e.charCode); @@ -66,7 +66,7 @@ describe('Keyboard Event', () => { }); it('当charcode为13,且不设置keycode的时候', () => { - const node = Horizon.render( + const node = Inula.render( { LogUtils.log('onKeyPress: keycode: ' + e.keyCode + ',charcode: ' + e.charCode); @@ -81,7 +81,7 @@ describe('Keyboard Event', () => { }); it('keydown,keypress,keyup的code', () => { - const node = Horizon.render( + const node = Inula.render( { LogUtils.log('onKeyUp: code: ' + e.code); @@ -124,7 +124,7 @@ describe('Keyboard Event', () => { expect(e.isPropagationStopped()).toBe(true); LogUtils.log(e.type + ' handle'); }; - const div = Horizon.render( + const div = Inula.render(
{ + 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( + '
', + ); + iframeDocument.close(); + + const leaveEvents = []; + const node = Inula.render( +
{ + 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( + '
', + ); + iframeDocument.close(); + + const enterEvents = []; + const node = Inula.render( +
{ + 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 Inula.Component { + render() { + return ( +
parentEnterCalls++} + ref={node => (parent = node)}> + {this.props.showChild && ( +
childEnterCalls++}/> + )} +
+ ); + } + } + + Inula.render(, container); + Inula.render(, 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 Inula.Component { + constructor(props) { + super(props); + this.parentEl = Inula.createRef(); + } + + componentDidMount() { + Inula.render(, this.parentEl.current); + } + + render() { + return
; + } + } + + class MouseEnterDetect extends Inula.Component { + constructor(props) { + super(props); + this.firstEl = Inula.createRef(); + this.siblingEl = Inula.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 ( + +
+
+ + ); + } + } + + Inula.render(, container); + }); + + it('未被inula管理的节点触发mouseout事件,mouseenter事件也能正常触发', done => { + const mockFn = jest.fn(); + + class Parent extends Inula.Component { + constructor(props) { + super(props); + this.parentEl = Inula.createRef(); + } + + componentDidMount() { + Inula.render(, this.parentEl.current); + } + + render() { + return
; + } + } + + class MouseEnterDetect extends Inula.Component { + constructor(props) { + super(props); + this.divRef = Inula.createRef(); + this.siblingEl = Inula.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 ( +
+
+
+ ); + } + } + + Inula.render(, container); + }); + + it('外部portal节点触发的mouseout事件,根节点的mouseleave事件也能响应', () => { + const divRef = Inula.createRef(); + const onMouseLeave = jest.fn(); + + function Component() { + return ( +
+ {Inula.createPortal(
, document.body)} +
+ ); + } + + Inula.render(, container); + + divRef.current.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: document.body, + }), + ); + + expect(onMouseLeave).toHaveBeenCalledTimes(1); + }); + + it('外部portal节点触发的mouseout事件,根节点的mouseEnter事件也能响应', () => { + const divRef = Inula.createRef(); + const otherDivRef = Inula.createRef(); + const onMouseEnter = jest.fn(); + + function Component() { + return ( +
+ {Inula.createPortal( +
, + document.body, + )} +
+ ); + } + + Inula.render(, container); + + divRef.current.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: otherDivRef.current, + }), + ); + + expect(onMouseEnter).toHaveBeenCalledTimes(1); + }); +}); + + diff --git a/scripts/__tests__/EventTest/MouseEvent.test.js b/scripts/__tests__/EventTest/MouseEvent.test.js index 62abb34c..46cc6522 100644 --- a/scripts/__tests__/EventTest/MouseEvent.test.js +++ b/scripts/__tests__/EventTest/MouseEvent.test.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('MouseEvent Test', () => { @@ -21,7 +21,7 @@ describe('MouseEvent Test', () => { describe('onClick Test', () => { it('绑定this', () => { - class App extends Horizon.Component { + class App extends Inula.Component { constructor(props) { super(props); this.state = { @@ -52,7 +52,7 @@ describe('MouseEvent Test', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container.querySelector('p').innerHTML).toBe('0'); expect(container.querySelector('#p').innerHTML).toBe('100'); // 点击按钮触发num加1 @@ -65,7 +65,7 @@ describe('MouseEvent Test', () => { it('点击触发', () => { const handleClick = jest.fn(); - Horizon.render(, container); + Inula.render(, container); container.querySelector('button').click(); expect(handleClick).toHaveBeenCalledTimes(1); for (let i = 0; i < 5; i++) { @@ -76,8 +76,8 @@ describe('MouseEvent Test', () => { it('disable不触发click', () => { const handleClick = jest.fn(); - const spanRef = Horizon.createRef(); - Horizon.render( + const spanRef = Inula.createRef(); + Inula.render( , @@ -90,7 +90,7 @@ describe('MouseEvent Test', () => { }); const test = (name, config) => { - const node = Horizon.render(config, container); + const node = Inula.render(config, container); let event = new MouseEvent(name, { relatedTarget: null, bubbles: true, @@ -163,7 +163,7 @@ describe('MouseEvent Test', () => { }); it('KeyboardEvent.getModifierState should not fail', () => { - const input = Horizon.render( { e.getModifierState('CapsLock'); }} diff --git a/scripts/__tests__/EventTest/WheelEvent.test.js b/scripts/__tests__/EventTest/WheelEvent.test.js index e563d94c..13f8bada 100644 --- a/scripts/__tests__/EventTest/WheelEvent.test.js +++ b/scripts/__tests__/EventTest/WheelEvent.test.js @@ -13,14 +13,14 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from '../jest/testUtils'; describe('合成滚轮事件', () => { const LogUtils = getLogUtils(); it('onWheel', () => { - const realNode = Horizon.render( + const realNode = Inula.render(
LogUtils.log(`onWheel: ${event.type}`)} onWheelCapture={event => LogUtils.log(`onWheelCapture: ${event.type}`)} @@ -50,7 +50,7 @@ describe('合成滚轮事件', () => { expect(e.isPropagationStopped()).toBe(true); LogUtils.log(e.type + ' handle'); }; - const realNode = Horizon.render( + const realNode = Inula.render(
, container ); diff --git a/scripts/__tests__/HorizonIsTest/index.test.js b/scripts/__tests__/HorizonIsTest/index.test.js deleted file mode 100644 index ff33477d..00000000 --- a/scripts/__tests__/HorizonIsTest/index.test.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * InulaJS 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'; - -function App() { - return <>; -} - -describe('HorizonIs', () => { - it('should identify horizon elements', () => { - expect(Horizon.isElement(
)).toBe(true); - expect(Horizon.isElement('span')).toBe(false); - expect(Horizon.isElement(111)).toBe(false); - expect(Horizon.isElement(false)).toBe(false); - expect(Horizon.isElement(null)).toBe(false); - expect(Horizon.isElement([])).toBe(false); - expect(Horizon.isElement({})).toBe(false); - expect(Horizon.isElement(undefined)).toBe(false); - - const TestContext = Horizon.createContext(false); - expect(Horizon.isElement()).toBe(true); - expect(Horizon.isElement()).toBe(true); - expect(Horizon.isElement(<>)).toBe(true); - expect(Horizon.isElement()).toBe(true); - }); - - it('should identify Fragment', () => { - expect(Horizon.isFragment(<>)).toBe(true); - }); - - it('should identify memo component', () => { - const MemoComp = Horizon.memo(App); - expect(Horizon.isMemo()).toBe(true); - }); - - it('should identify forwardRef', () => { - const ForwardRefComp = Horizon.forwardRef(App); - expect(Horizon.isForwardRef()).toBe(true); - }); - - it('should identify lazy', () => { - const LazyComp = Horizon.lazy(() => App); - expect(Horizon.isLazy()).toBe(true); - }); - - it('should identify portal', () => { - const portal = Horizon.createPortal(
, container); - expect(Horizon.isPortal(portal)).toBe(true); - }); - - it('should identify ContextProvider', () => { - const TestContext = Horizon.createContext(false); - expect(Horizon.isContextProvider()).toBe(true); - expect(Horizon.isContextProvider()).toBe(false); - expect(Horizon.isContextConsumer()).toBe(false); - expect(Horizon.isContextConsumer()).toBe(true); - }); -}); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateArray.test.tsx similarity index 94% rename from scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateArray.test.tsx index 9ffad653..ca4a9e08 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateArray.test.tsx @@ -14,9 +14,9 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -49,7 +49,7 @@ const useUserStore = createStore({ }); describe('测试store中的Array', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -102,17 +102,17 @@ describe('测试store中的Array', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); // 在Array中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); // 在Array中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); @@ -138,7 +138,7 @@ describe('测试store中的Array', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // push @@ -188,7 +188,7 @@ describe('测试store中的Array', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // push diff --git a/scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateMap.test.tsx similarity index 88% rename from scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateMap.test.tsx index f327b09c..ddbf7589 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateMap.test.tsx @@ -14,9 +14,9 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -49,7 +49,7 @@ const useUserStore = createStore({ }); describe('测试store中的Map', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -73,13 +73,13 @@ describe('测试store中的Map', () => { function Parent(props) { const userStore = useUserStore(); - const addOnePerson = function() { + const addOnePerson = function () { userStore.addOnePerson(newPerson); }; - const delOnePerson = function() { + const delOnePerson = function () { userStore.delOnePerson(newPerson); }; - const clearPersons = function() { + const clearPersons = function () { userStore.clearPersons(); }; @@ -110,23 +110,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); @@ -149,23 +149,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -188,23 +188,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: '); @@ -227,23 +227,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -265,23 +265,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -298,11 +298,11 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); @@ -324,23 +324,23 @@ describe('测试store中的Map', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateMixType.test.tsx similarity index 93% rename from scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateMixType.test.tsx index 2bb360af..52e9b610 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateMixType.test.tsx @@ -14,13 +14,13 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; describe('测试store中的混合类型变化', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -98,10 +98,10 @@ describe('测试store中的混合类型变化', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5 7'); @@ -168,7 +168,7 @@ describe('测试store中的混合类型变化', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2'); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateSet.test.tsx similarity index 90% rename from scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateSet.test.tsx index 7c1c8656..84213b7d 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateSet.test.tsx @@ -14,9 +14,9 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -49,7 +49,7 @@ const useUserStore = createStore({ }); describe('测试store中的Set', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -116,24 +116,24 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); expect(container?.querySelector('#lastAge')?.innerHTML).toBe('last person age: 2'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在set中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear set - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); @@ -158,23 +158,23 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -197,23 +197,23 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -235,23 +235,23 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); @@ -268,11 +268,11 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); @@ -294,23 +294,23 @@ describe('测试store中的Set', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateWeakMap.test.tsx similarity index 91% rename from scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateWeakMap.test.tsx index c6e2fc09..9e847d18 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateWeakMap.test.tsx @@ -14,9 +14,9 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -49,7 +49,7 @@ const useUserStore = createStore({ }); describe('测试store中的WeakMap', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -110,17 +110,17 @@ describe('测试store中的WeakMap', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在WeakMap中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); // 在WeakMap中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); @@ -137,11 +137,11 @@ describe('测试store中的WeakMap', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: undefined'); // 在WeakMap中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx b/scripts/__tests__/HorizonXTest/StateManager/StateWeakSet.test.tsx similarity index 92% rename from scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx rename to scripts/__tests__/HorizonXTest/StateManager/StateWeakSet.test.tsx index 962e73c3..9986b941 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx +++ b/scripts/__tests__/HorizonXTest/StateManager/StateWeakSet.test.tsx @@ -14,9 +14,9 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -49,7 +49,7 @@ const useUserStore = createStore({ }); describe('测试store中的WeakSet', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -102,17 +102,17 @@ describe('测试store中的WeakSet', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在WeakSet中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); // 在WeakSet中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx b/scripts/__tests__/HorizonXTest/StoreFunctionality/async.test.tsx similarity index 93% rename from scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx rename to scripts/__tests__/HorizonXTest/StoreFunctionality/async.test.tsx index a215e3be..da9e1ca6 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/async.test.tsx @@ -14,12 +14,12 @@ */ //@ts-ignore -import * as Horizon from '../../../../libs/horizon'; -import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import * as Inula from '../../../../libs/inula/index'; +import { createStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; -const { unmountComponentAtNode } = Horizon; +const { unmountComponentAtNode } = Inula; function postpone(timer, func) { return new Promise(resolve => { diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx b/scripts/__tests__/HorizonXTest/StoreFunctionality/basicAccess.test.tsx similarity index 89% rename from scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx rename to scripts/__tests__/HorizonXTest/StoreFunctionality/basicAccess.test.tsx index a626551b..277a5bad 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/basicAccess.test.tsx @@ -14,13 +14,13 @@ */ //@ts-ignore -import Horizon from '@cloudsop/horizon/index.ts'; +import Inula from '../../../../libs/inula/index'; import { triggerClickEvent } from '../../jest/commonComponents'; import { useLogStore } from './store'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; -import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { createStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; -const { unmountComponentAtNode } = Horizon; +const { unmountComponentAtNode } = Inula; describe('Basic store manipulation', () => { let container: HTMLElement | null = null; @@ -46,7 +46,7 @@ describe('Basic store manipulation', () => { return
{logStore.length}
; } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1'); }); @@ -70,9 +70,9 @@ describe('Basic store manipulation', () => { ); } - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); @@ -98,9 +98,9 @@ describe('Basic store manipulation', () => { ); } - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); @@ -142,9 +142,9 @@ describe('Basic store manipulation', () => { ); } - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); @@ -187,9 +187,9 @@ describe('Basic store manipulation', () => { ); } - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/cloneDeep.test.js b/scripts/__tests__/HorizonXTest/StoreFunctionality/cloneDeep.test.js similarity index 81% rename from scripts/__tests__/HorizonXText/StoreFunctionality/cloneDeep.test.js rename to scripts/__tests__/HorizonXTest/StoreFunctionality/cloneDeep.test.js index d2b159d8..e1c0231d 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/cloneDeep.test.js +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/cloneDeep.test.js @@ -13,13 +13,13 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { OBSERVER_KEY } from '../../../../libs/horizon/src/horizonx/Constants'; +import * as Inula from '../../../../libs/inula/index'; +import { clearStore, createStore, useStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; +import { OBSERVER_KEY } from '../../../../libs/inula/src/inulax/Constants'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; describe('测试对store.state对象进行深度克隆', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -81,7 +81,7 @@ describe('测试对store.state对象进行深度克隆', () => { ); } - it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => { + it('The observer object of symbol (\'_inulaObserver\') cannot be accessed to from Proxy', () => { let userStore = null; function Child(props) { userStore = useStore('user'); @@ -93,13 +93,13 @@ describe('测试对store.state对象进行深度克隆', () => { ); } - Horizon.render(, container); + Inula.render(, container); - // The observer object of symbol ('_horizonObserver') cannot be accessed to from Proxy prevent errors caused by clonedeep. + // The observer object of symbol ('_inulaObserver') cannot be accessed to from Proxy prevent errors caused by clonedeep. expect(userStore.persons[0][OBSERVER_KEY]).toBe(undefined); }); - it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => { + it('The observer object of symbol (\'_inulaObserver\') cannot be accessed to from Proxy', () => { let userStore = null; function Child(props) { userStore = useStore('user'); @@ -111,7 +111,7 @@ describe('测试对store.state对象进行深度克隆', () => { ); } - Horizon.render(, container); + Inula.render(, container); // NO throw this Exception, TypeError: 'get' on proxy: property 'prototype' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value const proxyObj = userStore.persons[0].constructor; diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx b/scripts/__tests__/HorizonXTest/StoreFunctionality/dollarAccess.test.tsx similarity index 90% rename from scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx rename to scripts/__tests__/HorizonXTest/StoreFunctionality/dollarAccess.test.tsx index 0ecd9fca..54c211f5 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/dollarAccess.test.tsx @@ -14,12 +14,12 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { triggerClickEvent } from '../../jest/commonComponents'; import { useLogStore } from './store'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; -const { unmountComponentAtNode } = Horizon; +const { unmountComponentAtNode } = Inula; describe('Dollar store access', () => { let container: HTMLElement | null = null; @@ -45,7 +45,7 @@ describe('Dollar store access', () => { return
{logStore.$c.length()}
; } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1'); }); @@ -69,9 +69,9 @@ describe('Dollar store access', () => { ); } - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx b/scripts/__tests__/HorizonXTest/StoreFunctionality/otherCases.test.tsx similarity index 90% rename from scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx rename to scripts/__tests__/HorizonXTest/StoreFunctionality/otherCases.test.tsx index 46f5198b..8d473e8b 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/otherCases.test.tsx @@ -14,12 +14,12 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import * as Inula from '../../../../libs/inula/index'; +import { createStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; import { triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; -const { unmountComponentAtNode } = Horizon; +const { unmountComponentAtNode } = Inula; describe('Self referencing', () => { let container: HTMLElement | null = null; @@ -69,17 +69,17 @@ describe('Self referencing', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('4'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('6'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); @@ -110,11 +110,11 @@ describe('Self referencing', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); @@ -154,10 +154,10 @@ describe('Self referencing', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('abc'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('def'); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/reset.js b/scripts/__tests__/HorizonXTest/StoreFunctionality/reset.js similarity index 87% rename from scripts/__tests__/HorizonXText/StoreFunctionality/reset.js rename to scripts/__tests__/HorizonXTest/StoreFunctionality/reset.js index 6fa0a639..4df7add5 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/reset.js +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/reset.js @@ -13,11 +13,11 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import {createStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import * as Inula from '../../../../libs/inula/index'; +import {createStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {triggerClickEvent} from '../../jest/commonComponents'; -const {unmountComponentAtNode} = Horizon; +const {unmountComponentAtNode} = Inula; describe('Reset', () => { it('RESET NOT IMPLEMENTED', async () => { @@ -69,25 +69,25 @@ describe('Reset', () => {
} - Horizon.render(, container); + Inula.render(, container); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID).innerHTML).toBe('2'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, RESET_ID); }); expect(document.getElementById(RESULT_ID).innerHTML).toBe('0'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/store.ts b/scripts/__tests__/HorizonXTest/StoreFunctionality/store.ts similarity index 92% rename from scripts/__tests__/HorizonXText/StoreFunctionality/store.ts rename to scripts/__tests__/HorizonXTest/StoreFunctionality/store.ts index 4831afd3..f37d4547 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/store.ts +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/store.ts @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { createStore } from '../../../../libs/inula/src/inulax/store/StoreHandler'; export const useLogStore = createStore({ id: 'logStore', // you do not need to specify ID for local store diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/utils.test.js b/scripts/__tests__/HorizonXTest/StoreFunctionality/utils.test.js similarity index 85% rename from scripts/__tests__/HorizonXText/StoreFunctionality/utils.test.js rename to scripts/__tests__/HorizonXTest/StoreFunctionality/utils.test.js index 7d1f786f..ff9f0629 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/utils.test.js +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/utils.test.js @@ -1,4 +1,4 @@ -import { resolveMutation } from '../../../../libs/horizon/src/horizonx/CommonUtils'; +import { resolveMutation } from '../../../../libs/inula/src/inulax/CommonUtils'; describe('Mutation resolve', () => { it('should resolve mutation different types', () => { @@ -64,7 +64,6 @@ describe('Mutation resolve', () => { it('should resolve mutation same type types, same object', () => { const mutation = resolveMutation({ a: 1, b: 2 }, { a: 1, b: 2 }); - console.log(mutation); expect(mutation.mutation).toBe(false); }); @@ -78,3 +77,17 @@ describe('Mutation resolve', () => { expect(mutation.attributes.c.to).toBe(2); }); }); + +describe('Mutation collections', () => { + it('should resolve mutation of two sets', () => { + const values = [{ a: 1 }, { b: 2 }, { c: 3 }]; + + const source = new Set([values[0], values[1], values[2]]); + + const target = new Set([values[0], values[1]]); + + const mutation = resolveMutation(source, target); + + expect(mutation.mutation).toBe(true); + }); +}); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/watch.test.tsx b/scripts/__tests__/HorizonXTest/StoreFunctionality/watch.test.tsx similarity index 93% rename from scripts/__tests__/HorizonXText/StoreFunctionality/watch.test.tsx rename to scripts/__tests__/HorizonXTest/StoreFunctionality/watch.test.tsx index 1aa765c3..5d51aa76 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/watch.test.tsx +++ b/scripts/__tests__/HorizonXTest/StoreFunctionality/watch.test.tsx @@ -13,11 +13,11 @@ * See the Mulan PSL v2 for more details. */ -import { createStore } from '@cloudsop/horizon/src/horizonx/store/StoreHandler'; -import { watch } from '@cloudsop/horizon/src/horizonx/proxy/watch'; +import { createStore } from '../../../../libs/inula'; +import { watch } from '../../../../libs/inula'; describe('watch', () => { - it('shouhld watch promitive state variable', async () => { + it('shouhld watch primitive state variable', async () => { const useStore = createStore({ state: { variable: 'x', diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx b/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx similarity index 98% rename from scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx rename to scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx index 16e5bbe7..431719f5 100644 --- a/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx +++ b/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx @@ -14,13 +14,13 @@ */ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { createStore, applyMiddleware, combineReducers, bindActionCreators, -} from '../../../../libs/horizon/src/horizonx/adapters/redux'; +} from '../../../../libs/inula/src/inulax/adapters/redux'; import { describe, it, expect } from '@jest/globals'; describe('Redux adapter', () => { diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx b/scripts/__tests__/HorizonXTest/adapters/ReduxAdapterThunk.test.tsx similarity index 93% rename from scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx rename to scripts/__tests__/HorizonXTest/adapters/ReduxAdapterThunk.test.tsx index c7ccf8b2..2507f28a 100644 --- a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx +++ b/scripts/__tests__/HorizonXTest/adapters/ReduxAdapterThunk.test.tsx @@ -13,8 +13,8 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { createStore, applyMiddleware, thunk } from '../../../../libs/horizon/src/horizonx/adapters/redux'; +import * as Inula from '../../../../libs/inula/index'; +import { createStore, applyMiddleware, thunk } from '../../../../libs/inula/src/inulax/adapters/redux'; import {describe, it, expect} from '@jest/globals'; describe('Redux thunk', () => { diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx b/scripts/__tests__/HorizonXTest/adapters/ReduxReactAdapter.test.tsx similarity index 89% rename from scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx rename to scripts/__tests__/HorizonXTest/adapters/ReduxReactAdapter.test.tsx index bf31c13a..b2402c90 100644 --- a/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx +++ b/scripts/__tests__/HorizonXTest/adapters/ReduxReactAdapter.test.tsx @@ -14,7 +14,7 @@ */ //@ts-ignore -import horizon, * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import { batch, connect, @@ -25,10 +25,10 @@ import { useStore, createSelectorHook, createDispatchHook -} from '../../../../libs/horizon/src/horizonx/adapters/redux'; +} from '../../../../libs/inula/src/inulax/adapters/redux'; import {triggerClickEvent} from '../../jest/commonComponents'; import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; -import { ReduxStoreHandler } from '@cloudsop/horizon/src/horizonx/types'; +import { ReduxStoreHandler } from '../../../../libs/inula/src/inulax/adapters/redux'; const BUTTON = 'button'; const BUTTON2 = 'button2'; @@ -64,7 +64,7 @@ describe('Redux/React binding adapter', () => { ; }; - Horizon.render(, getE(CONTAINER)); + Inula.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('state'); }); @@ -92,11 +92,11 @@ describe('Redux/React binding adapter', () => { ; }; - Horizon.render(, getE(CONTAINER)); + Inula.render(, getE(CONTAINER)); expect(reduxStore.getState()).toBe(0); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(getE(CONTAINER), BUTTON); }); @@ -127,11 +127,11 @@ describe('Redux/React binding adapter', () => { ; }; - Horizon.render(, getE(CONTAINER)); + Inula.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('0'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(getE(CONTAINER), BUTTON); triggerClickEvent(getE(CONTAINER), BUTTON); }); @@ -181,7 +181,7 @@ describe('Redux/React binding adapter', () => { const Wrapper = () => { //@ts-ignore - const [amount, setAmount] = Horizon.useState(5); + const [amount, setAmount] = Inula.useState(5); return
; } - Horizon.render(, getE(CONTAINER)); + Inula.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('0'); expect(renderCounter).toBe(1); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(getE(CONTAINER), BUTTON); }); @@ -262,8 +262,8 @@ describe('Redux/React binding adapter', () => { return state; }); - const counterContext = horizon.createContext(); - const toggleContext = horizon.createContext(); + const counterContext = Inula.createContext(); + const toggleContext = Inula.createContext(); function Counter() { const count = createSelectorHook(counterContext)(); @@ -295,12 +295,12 @@ describe('Redux/React binding adapter', () => {
; } - Horizon.render(, getE(CONTAINER)); + Inula.render(, getE(CONTAINER)); expect(getE(BUTTON).innerHTML).toBe('0'); expect(getE(BUTTON2).innerHTML).toBe('false'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(getE(CONTAINER), BUTTON); triggerClickEvent(getE(CONTAINER), BUTTON2); }); diff --git a/scripts/__tests__/HorizonXText/adapters/connectTest.tsx b/scripts/__tests__/HorizonXTest/adapters/connectTest.tsx similarity index 72% rename from scripts/__tests__/HorizonXText/adapters/connectTest.tsx rename to scripts/__tests__/HorizonXTest/adapters/connectTest.tsx index a11b8249..59e42396 100644 --- a/scripts/__tests__/HorizonXText/adapters/connectTest.tsx +++ b/scripts/__tests__/HorizonXTest/adapters/connectTest.tsx @@ -1,7 +1,7 @@ -import { createElement } from '../../../../libs/horizon/src/external/JSXElement'; -import { createDomTextVNode } from '../../../../libs/horizon/src/renderer/vnode/VNodeCreator'; -import { createStore } from '../../../../libs/horizon/src/horizonx/adapters/redux'; -import { connect } from '../../../../libs/horizon/src/horizonx/adapters/reduxReact'; +import { createElement } from '../../../../libs/inula/src/external/JSXElement'; +import { createDomTextVNode } from '../../../../libs/inula/src/renderer/vnode/VNodeCreator'; +import { createStore } from '../../../../libs/inula/src/inulax/adapters/redux'; +import { connect } from '../../../../libs/inula/src/inulax/adapters/reduxReact'; createStore((state: number = 0, action): number => { if (action.type === 'add') return state + 1; diff --git a/scripts/__tests__/HorizonXText/class/ClassException.test.tsx b/scripts/__tests__/HorizonXTest/class/ClassException.test.tsx similarity index 88% rename from scripts/__tests__/HorizonXText/class/ClassException.test.tsx rename to scripts/__tests__/HorizonXTest/class/ClassException.test.tsx index 4feb4635..1a9aed46 100644 --- a/scripts/__tests__/HorizonXText/class/ClassException.test.tsx +++ b/scripts/__tests__/HorizonXTest/class/ClassException.test.tsx @@ -13,15 +13,15 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {clearStore, createStore, useStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {Text, triggerClickEvent} from '../../jest/commonComponents'; -import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {getObserver} from '../../../../libs/inula/src/inulax/proxy/ProxyHandler'; import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; describe('测试 Class VNode 清除时,对引用清除', () => { - const {unmountComponentAtNode} = Horizon; + const {unmountComponentAtNode} = Inula; let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', @@ -62,7 +62,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { }); it('test observer.clearByNode', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useStore('user'); render() { @@ -78,7 +78,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { } expect(() => { - Horizon.render(, container); + Inula.render(, container); }).toThrow('The number of updates exceeds the upper limit 50.\n' + ' A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.'); diff --git a/scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx b/scripts/__tests__/HorizonXTest/class/ClassStateArray.test.tsx similarity index 92% rename from scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx rename to scripts/__tests__/HorizonXTest/class/ClassStateArray.test.tsx index fccc922f..c1dd15a3 100644 --- a/scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx +++ b/scripts/__tests__/HorizonXTest/class/ClassStateArray.test.tsx @@ -13,9 +13,9 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {clearStore, createStore, useStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {App, Text, triggerClickEvent} from '../../jest/commonComponents'; import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; @@ -42,7 +42,7 @@ let useUserStore = createStore({ }); describe('在Class组件中,测试store中的Array', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container:HTMLElement|null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -61,7 +61,7 @@ describe('在Class组件中,测试store中的Array', () => { }); const newPerson = { name: 'p3', age: 3 }; - class Parent extends Horizon.Component { + class Parent extends Inula.Component { userStore = useUserStore(); props:{ children:any[] @@ -96,7 +96,7 @@ describe('在Class组件中,测试store中的Array', () => { } it('测试Array方法: push()、pop()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -108,17 +108,17 @@ describe('在Class组件中,测试store中的Array', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); // 在Array中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); // 在Array中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); @@ -127,7 +127,7 @@ describe('在Class组件中,测试store中的Array', () => { it('测试Array方法: entries()、push()、shift()、unshift、直接赋值', () => { let globalStore = useUserStore(); - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); constructor(props) { @@ -152,7 +152,7 @@ describe('在Class组件中,测试store中的Array', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // push @@ -188,7 +188,7 @@ describe('在Class组件中,测试store中的Array', () => { it('测试Array方法: forEach()', () => { let globalStore = useUserStore(); globalStore.$s.persons.push({ name: 'p2', age: 2 }); - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); constructor(props) { @@ -210,7 +210,7 @@ describe('在Class组件中,测试store中的Array', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // push diff --git a/scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx b/scripts/__tests__/HorizonXTest/class/ClassStateMap.test.tsx similarity index 86% rename from scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx rename to scripts/__tests__/HorizonXTest/class/ClassStateMap.test.tsx index 50504d38..1ba27076 100644 --- a/scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx +++ b/scripts/__tests__/HorizonXTest/class/ClassStateMap.test.tsx @@ -13,9 +13,9 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {clearStore, createStore, useStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {App, Text, triggerClickEvent} from '../../jest/commonComponents'; import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; @@ -42,7 +42,7 @@ const useUserStore = createStore({ }); describe('在Class组件中,测试store中的Map', () => { - const { unmountComponentAtNode } = Horizon; + const { unmountComponentAtNode } = Inula; let container:HTMLElement|null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 @@ -64,7 +64,7 @@ describe('在Class组件中,测试store中的Map', () => { const newPerson = { name: 'p3', age: 3 }; - class Parent extends Horizon.Component { + class Parent extends Inula.Component { userStore = useUserStore(); props = {children:[]} @@ -104,7 +104,7 @@ describe('在Class组件中,测试store中的Map', () => { } it('测试Map方法: set()、delete()、clear()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -116,30 +116,30 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); }); it('测试Map方法: keys()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -157,30 +157,30 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: values()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -198,30 +198,30 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: '); }); it('测试Map方法: entries()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -239,30 +239,30 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: forEach()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -279,30 +279,30 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: has()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -314,18 +314,18 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); }); it('测试Map方法: for of()', () => { - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useUserStore(); render() { @@ -342,23 +342,23 @@ describe('在Class组件中,测试store中的Map', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'addBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'delBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'clearBtn'); }); expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); diff --git a/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx b/scripts/__tests__/HorizonXTest/clear/ClassVNodeClear.test.tsx similarity index 87% rename from scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx rename to scripts/__tests__/HorizonXTest/clear/ClassVNodeClear.test.tsx index f87560c6..6de95373 100644 --- a/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx +++ b/scripts/__tests__/HorizonXTest/clear/ClassVNodeClear.test.tsx @@ -13,15 +13,15 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {clearStore, createStore, useStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {Text, triggerClickEvent} from '../../jest/commonComponents'; -import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {getObserver} from '../../../../libs/inula/src/inulax/proxy/ProxyHandler'; import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; describe('测试 Class VNode 清除时,对引用清除', () => { - const {unmountComponentAtNode} = Horizon; + const {unmountComponentAtNode} = Inula; let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', @@ -62,7 +62,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { }); it('test observer.clearByNode', () => { - class App extends Horizon.Component { + class App extends Inula.Component { userStore = useStore('user'); render() { @@ -75,7 +75,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { } } - class Parent extends Horizon.Component { + class Parent extends Inula.Component { userStore = useStore('user'); setWin = () => { @@ -92,7 +92,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { } } - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useStore('user'); render() { @@ -105,24 +105,24 @@ describe('测试 Class VNode 清除时,对引用清除', () => { } } - Horizon.render(, container); + Inula.render(, container); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'toggleBtn'); }); // Parent hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(1); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'toggleBtn'); }); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'hideBtn'); }); // no component hold the isWin key diff --git a/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx b/scripts/__tests__/HorizonXTest/clear/FunctionVNodeClear.test.tsx similarity index 86% rename from scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx rename to scripts/__tests__/HorizonXTest/clear/FunctionVNodeClear.test.tsx index d00752e6..37cdd2d0 100644 --- a/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx +++ b/scripts/__tests__/HorizonXTest/clear/FunctionVNodeClear.test.tsx @@ -13,15 +13,15 @@ * See the Mulan PSL v2 for more details. */ -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../../libs/inula/index'; import * as LogUtils from '../../jest/logUtils'; -import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {clearStore, createStore, useStore} from '../../../../libs/inula/src/inulax/store/StoreHandler'; import {Text, triggerClickEvent} from '../../jest/commonComponents'; -import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {getObserver} from '../../../../libs/inula/src/inulax/proxy/ProxyHandler'; import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; describe('测试VNode清除时,对引用清除', () => { - const {unmountComponentAtNode} = Horizon; + const {unmountComponentAtNode} = Inula; let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', @@ -59,7 +59,7 @@ describe('测试VNode清除时,对引用清除', () => { }); it('test observer.clearByNode', () => { - class App extends Horizon.Component { + class App extends Inula.Component { userStore = useStore('user'); render() { @@ -72,7 +72,7 @@ describe('测试VNode清除时,对引用清除', () => { } } - class Parent extends Horizon.Component { + class Parent extends Inula.Component { userStore = useStore('user'); setWin = () => { @@ -89,7 +89,7 @@ describe('测试VNode清除时,对引用清除', () => { } } - class Child extends Horizon.Component { + class Child extends Inula.Component { userStore = useStore('user'); render() { @@ -100,24 +100,24 @@ describe('测试VNode清除时,对引用清除', () => { } } - Horizon.render(, container); + Inula.render(, container); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'toggleBtn'); }); // Parent hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(1); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'toggleBtn'); }); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, 'hideBtn'); }); // no component hold the isWin key diff --git a/scripts/__tests__/HorizonXTest/edgeCases/deepVariableObserver.test.tsx b/scripts/__tests__/HorizonXTest/edgeCases/deepVariableObserver.test.tsx new file mode 100644 index 00000000..6e7495a1 --- /dev/null +++ b/scripts/__tests__/HorizonXTest/edgeCases/deepVariableObserver.test.tsx @@ -0,0 +1,155 @@ +import { createStore, useStore } from '../../../../libs/inula'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +describe('Using deep variables', () => { + it('should listen to object variable change', () => { + let counter = 0; + const useTestStore = createStore({ + state: { a: { b: { c: 1 } } }, + }); + const testStore = useTestStore(); + testStore.$subscribe(() => { + counter++; + }); + + testStore.a.b.c = 0; + + expect(counter).toBe(1); + }); + + it('should listen to deep variable change', () => { + let counter = 0; + const useTestStore = createStore({ + state: { color: [{ a: 1 }, 255, 255] }, + }); + const testStore = useTestStore(); + testStore.$subscribe(() => { + counter++; + }); + + for (let i = 0; i < 5; i++) { + testStore.color[0].a = i; + } + testStore.color = 'x'; + + expect(counter).toBe(6); + }); + + it('should use set', () => { + const useTestStore = createStore({ + state: { data: new Set() }, + }); + const testStore = useTestStore(); + + const a = { a: true }; + + testStore.data.add(a); + + expect(testStore.data.has(a)).toBe(true); + + testStore.data.add(a); + testStore.data.add(a); + testStore.data.delete(a); + + expect(testStore.data.has(a)).toBe(false); + + testStore.data.add(a); + + const values = Array.from(testStore.data.values()); + expect(values.length).toBe(1); + + let counter = 0; + testStore.$subscribe(mutation => { + counter++; + }); + + values.forEach(val => { + val.a = !val.a; + }); + + expect(testStore.data.has(a)).toBe(true); + + expect(counter).toBe(1); + }); + + it('should use map', () => { + const useTestStore = createStore({ + state: { data: new Map() }, + }); + const testStore = useTestStore(); + + const data = { key: { a: 1 }, value: { b: 2 } }; + + testStore.data.set(data.key, data.value); + + const key = Array.from(testStore.data.keys())[0]; + + expect(testStore.data.has(key)).toBe(true); + + testStore.data.set(data.key, data.value); + testStore.data.set(data.key, data.value); + testStore.data.delete(key); + + expect(testStore.data.get(key)).toBe(); + + testStore.data.set(data.key, data.value); + + const entries = Array.from(testStore.data.entries()); + expect(entries.length).toBe(1); + + let counter = 0; + testStore.$subscribe(mutation => { + counter++; + }); + + entries.forEach(([key, value]) => { + key.a++; + value.b++; + }); + + expect(counter).toBe(2); + }); + + it('should use weakSet', () => { + const useTestStore = createStore({ + state: { data: new WeakSet() }, + }); + const testStore = useTestStore(); + + const a = { a: true }; + + testStore.data.add(a); + + expect(testStore.data.has(a)).toBe(true); + + testStore.data.add(a); + testStore.data.add(a); + testStore.data.delete(a); + + expect(testStore.data.has(a)).toBe(false); + + testStore.data.add(a); + + expect(testStore.data.has(a)).toBe(true); + }); + + it('should use weakMap', () => { + const useTestStore = createStore({ + state: { data: new WeakMap() }, + }); + const testStore = useTestStore(); + + const data = { key: { a: 1 }, value: { b: 2 } }; + + testStore.data.set(data.key, data.value); + + let counter = 0; + testStore.$subscribe(mutation => { + counter++; + }); + + testStore.data.get(data.key).b++; + + expect(counter).toBe(1); + }); +}); diff --git a/scripts/__tests__/HorizonXText/edgeCases/multipleStores.test.tsx b/scripts/__tests__/HorizonXTest/edgeCases/multipleStores.test.tsx similarity index 91% rename from scripts/__tests__/HorizonXText/edgeCases/multipleStores.test.tsx rename to scripts/__tests__/HorizonXTest/edgeCases/multipleStores.test.tsx index 12599ba8..9373861d 100644 --- a/scripts/__tests__/HorizonXText/edgeCases/multipleStores.test.tsx +++ b/scripts/__tests__/HorizonXTest/edgeCases/multipleStores.test.tsx @@ -14,11 +14,11 @@ */ //@ts-ignore -import Horizon, { createStore } from '@cloudsop/horizon/index.ts'; +import Inula, { createStore } from '../../../../libs/inula/index'; import { triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; -const { unmountComponentAtNode } = Horizon; +const { unmountComponentAtNode } = Inula; const useStore1 = createStore({ state: { counter: 1 }, @@ -57,7 +57,7 @@ describe('Using multiple stores', () => { }); it('Should use multiple stores in class component', () => { - class App extends Horizon.Component { + class App extends Inula.Component { render() { const { counter, add } = useStore1(); const { counter2, add2 } = useStore2(); @@ -88,15 +88,15 @@ describe('Using multiple stores', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID2); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2'); @@ -107,7 +107,7 @@ describe('Using multiple stores', () => { store: any; store2: any; } - class App extends Horizon.Component { + class App extends Inula.Component { constructor() { super(); this.store = useStore1(); @@ -151,15 +151,15 @@ describe('Using multiple stores', () => { } } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID2); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2'); @@ -196,15 +196,15 @@ describe('Using multiple stores', () => { ); } - Horizon.render(, container); + Inula.render(, container); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1'); - Horizon.act(() => { + Inula.act(() => { triggerClickEvent(container, BUTTON_ID2); }); expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2'); diff --git a/scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx b/scripts/__tests__/HorizonXTest/edgeCases/proxy.test.tsx similarity index 89% rename from scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx rename to scripts/__tests__/HorizonXTest/edgeCases/proxy.test.tsx index e02f5967..ca3d8fa6 100644 --- a/scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx +++ b/scripts/__tests__/HorizonXTest/edgeCases/proxy.test.tsx @@ -13,8 +13,8 @@ * See the Mulan PSL v2 for more details. */ -import {createProxy} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; -import {readonlyProxy} from '../../../../libs/horizon/src/horizonx/proxy/readonlyProxy'; +import {createProxy} from '../../../../libs/inula/src/inulax/proxy/ProxyHandler'; +import {readonlyProxy} from '../../../../libs/inula/src/inulax/proxy/readonlyProxy'; import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; describe('Proxy', () => { diff --git a/scripts/__tests__/InulaIsTest/index.test.js b/scripts/__tests__/InulaIsTest/index.test.js new file mode 100644 index 00000000..5afbcc7c --- /dev/null +++ b/scripts/__tests__/InulaIsTest/index.test.js @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * InulaJS 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 Inula from '../../../libs/inula/index'; + +function App() { + return <>; +} + +describe('InulaIs', () => { + it('should identify inula elements', () => { + expect(Inula.isElement(
)).toBe(true); + expect(Inula.isElement('span')).toBe(false); + expect(Inula.isElement(111)).toBe(false); + expect(Inula.isElement(false)).toBe(false); + expect(Inula.isElement(null)).toBe(false); + expect(Inula.isElement([])).toBe(false); + expect(Inula.isElement({})).toBe(false); + expect(Inula.isElement(undefined)).toBe(false); + + const TestContext = Inula.createContext(false); + expect(Inula.isElement()).toBe(true); + expect(Inula.isElement()).toBe(true); + expect(Inula.isElement(<>)).toBe(true); + expect(Inula.isElement()).toBe(true); + }); + + it('should identify Fragment', () => { + expect(Inula.isFragment(<>)).toBe(true); + }); + + it('should identify memo component', () => { + const MemoComp = Inula.memo(App); + expect(Inula.isMemo()).toBe(true); + }); + + it('should identify forwardRef', () => { + const ForwardRefComp = Inula.forwardRef(App); + expect(Inula.isForwardRef()).toBe(true); + }); + + it('should identify lazy', () => { + const LazyComp = Inula.lazy(() => App); + expect(Inula.isLazy()).toBe(true); + }); + + it('should identify portal', () => { + const portal = Inula.createPortal(
, container); + expect(Inula.isPortal(portal)).toBe(true); + }); + + it('should identify ContextProvider', () => { + const TestContext = Inula.createContext(false); + expect(Inula.isContextProvider()).toBe(true); + expect(Inula.isContextProvider()).toBe(false); + expect(Inula.isContextConsumer()).toBe(false); + expect(Inula.isContextConsumer()).toBe(true); + }); +}); diff --git a/scripts/__tests__/jest/commonComponents.js b/scripts/__tests__/jest/commonComponents.js index 99832e5b..f9af6745 100644 --- a/scripts/__tests__/jest/commonComponents.js +++ b/scripts/__tests__/jest/commonComponents.js @@ -14,7 +14,7 @@ */ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Inula from '../../../libs/inula/index'; import { getLogUtils } from './testUtils'; export const App = props => { diff --git a/scripts/__tests__/jest/jestEnvironment.js b/scripts/__tests__/jest/jestEnvironment.js index 92d696b2..f4ed610c 100644 --- a/scripts/__tests__/jest/jestEnvironment.js +++ b/scripts/__tests__/jest/jestEnvironment.js @@ -20,4 +20,4 @@ global.MessageChannel = function MessageChannel() { postMessage() { } }; }; -global.__VERSION__ = require('../../../libs/horizon/package.json').version; +global.__VERSION__ = require('../../../libs/inula/package.json').version; diff --git a/scripts/__tests__/jest/jestSetting.js b/scripts/__tests__/jest/jestSetting.js index 8af40471..94f4ab6c 100644 --- a/scripts/__tests__/jest/jestSetting.js +++ b/scripts/__tests__/jest/jestSetting.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import { unmountComponentAtNode } from '../../../libs/horizon/src/dom/DOMExternal'; +import { unmountComponentAtNode } from '../../../libs/inula/src/dom/DOMExternal'; import { getLogUtils } from './testUtils'; const LogUtils = getLogUtils(); diff --git a/scripts/gen3rdLib.js b/scripts/gen3rdLib.js index 1f3c51d0..10ce63cd 100644 --- a/scripts/gen3rdLib.js +++ b/scripts/gen3rdLib.js @@ -18,16 +18,16 @@ const path = require('path'); const fs = require('fs'); const childProcess = require('child_process'); -const horizonEcoPath = path.resolve(__dirname, '../../horizon-ecosystem'); -if (!fs.existsSync(horizonEcoPath)) { - throw Error('horizon-ecosystem not found, put horizon-core and horizon-ecosystem in same folder plz!'); +const inulaEcoPath = path.resolve(__dirname, '../../inula-ecosystem'); +if (!fs.existsSync(inulaEcoPath)) { + throw Error('inula-ecosystem not found, put inula-core and inula-ecosystem in same folder plz!'); } const cmd = process.argv[2]; childProcess.exec( `npm run ${cmd}`, { - cwd: horizonEcoPath, + cwd: inulaEcoPath, }, function (error, stdout) { if (error) { diff --git a/scripts/rollup/rollup.config.js b/scripts/rollup/rollup.config.js index cf98694f..f9e41654 100644 --- a/scripts/rollup/rollup.config.js +++ b/scripts/rollup/rollup.config.js @@ -20,14 +20,14 @@ import fs from 'fs'; import replace from '@rollup/plugin-replace'; import copy from './copy-plugin'; import execute from 'rollup-plugin-execute'; -import { terser } from 'rollup-plugin-terser'; -import { version as horizonVersion } from '@cloudsop/horizon/package.json'; +import {terser} from 'rollup-plugin-terser'; +import {version as inulaVersion} from '../../package.json'; const extensions = ['.js', '.ts']; -const libDir = path.join(__dirname, '../../libs/horizon'); +const libDir = path.join(__dirname, '../../libs/inula'); const rootDir = path.join(__dirname, '../..'); -const outDir = path.join(rootDir, 'build', 'horizon'); +const outDir = path.join(rootDir, 'build', 'inula'); if (!fs.existsSync(path.join(rootDir, 'build'))) { fs.mkdirSync(path.join(rootDir, 'build')); @@ -38,13 +38,41 @@ if (!fs.existsSync(outDir)) { const outputResolve = (...p) => path.resolve(outDir, ...p); +const isDev = (mode) => { + return mode === 'development'; +} + +const getBasicPlugins = (mode) => { + return [ + nodeResolve({ + extensions, + modulesOnly: true, + }), + babel({ + exclude: 'node_modules/**', + configFile: path.join(__dirname, '../../babel.config.js'), + babelHelpers: 'runtime', + extensions, + }), + replace({ + values: { + 'process.env.NODE_ENV': `"${mode}"`, + isDev: isDev(mode).toString(), + isTest: false, + __VERSION__: `"${inulaVersion}"`, + }, + preventAssignment: true, + }), + ]; +} + + function getOutputName(mode) { - return mode === 'production' ? `horizon.${mode}.min.js` : `horizon.${mode}.js`; + return mode === 'production' ? `inula.${mode}.min.js` : `inula.${mode}.js`; } function genConfig(mode) { - const isDev = mode === 'development'; - const sourcemap = isDev ? 'inline' : false; + const sourcemap = isDev(mode) ? 'inline' : false; return { input: path.resolve(libDir, 'index.ts'), output: [ @@ -56,30 +84,12 @@ function genConfig(mode) { { file: outputResolve('umd', getOutputName(mode)), sourcemap, - name: 'Horizon', + name: 'Inula', format: 'umd', }, ], plugins: [ - nodeResolve({ - extensions, - modulesOnly: true, - }), - babel({ - exclude: 'node_modules/**', - configFile: path.join(__dirname, '../../babel.config.js'), - babelHelpers: 'runtime', - extensions, - }), - replace({ - values: { - 'process.env.NODE_ENV': `"${mode}"`, - isDev: isDev.toString(), - isTest: false, - __VERSION__: `"${horizonVersion}"`, - }, - preventAssignment: true, - }), + ...getBasicPlugins(mode), execute('npm run build-types'), mode === 'production' && terser(), copy([ @@ -96,4 +106,30 @@ function genConfig(mode) { }; } -export default [genConfig('development'), genConfig('production')]; +function genJSXRuntimeConfig(mode) { + return { + input: path.resolve(libDir, 'jsx-runtime.ts'), + output: { + file: outputResolve('jsx-runtime.js'), + format: 'cjs', + }, + plugins: [ + ...getBasicPlugins(mode) + ] + }; +} + +function genJSXDEVRuntimeConfig(mode) { + return { + input: path.resolve(libDir, 'jsx-dev-runtime.ts'), + output: { + file: outputResolve('jsx-dev-runtime.js'), + format: 'cjs', + }, + plugins: [ + ...getBasicPlugins(mode) + ] + }; +} + +export default [genConfig('development'), genConfig('production'), genJSXRuntimeConfig(''), genJSXDEVRuntimeConfig('')]; diff --git a/tsconfig.json b/tsconfig.json index dbd77f67..4c6fbd07 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,7 +35,7 @@ "include": [ "./libs/**/src/**/*.ts", "./libs/**/*.ts", - "./libs/horizon/global.d.ts" + "./libs/inula/global.d.ts" ], "exclude": ["node_modules", "**/*.spec.ts", "dev"], "types": ["node"]