diff --git a/jest.config.js b/jest.config.js index 941aafa9..c0193a04 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,7 +11,8 @@ module.exports = { testEnvironment: 'jest-environment-jsdom-sixteen', testMatch: [ - '/scripts/__tests__/**/*.test.js' + '/scripts/__tests__/**/*.test.js', + '/scripts/__tests__/**/*.test.tsx' ], timers: 'fake', diff --git a/libs/horizon/src/horizonx/adapters/redux.ts b/libs/horizon/src/horizonx/adapters/redux.ts index 293700b6..fb19cf9a 100644 --- a/libs/horizon/src/horizonx/adapters/redux.ts +++ b/libs/horizon/src/horizonx/adapters/redux.ts @@ -1,14 +1,38 @@ import { createStore as createStoreX } from '../store/StoreHandler'; -import { ReduxStoreHandler, ReduxAction, ReduxMiddleware } from '../types'; +import { ReduxStoreHandler } from '../store/StoreHandler'; export { thunk } from './reduxThunk'; -export { Provider, useSelector, useStore, useDispatch, connect, createSelectorHook, createDispatchHook } from './reduxReact'; +export { + Provider, + useSelector, + useStore, + useDispatch, + connect, + createSelectorHook, + createDispatchHook, +} from './reduxReact'; + +export type ReduxAction = { + type: string; + [key: string]: any; +}; + +export type ReduxMiddleware = ( + store: ReduxStoreHandler, + extraArgument?: any +) => ( + next: (action: ReduxAction) => any +) => ( + action: + | ReduxAction + | ((dispatch: (action: ReduxAction) => void, store: ReduxStoreHandler, extraArgument?: any) => any) +) => ReduxStoreHandler; type Reducer = (state: any, action: ReduxAction) => any; -export function createStore(reducer: Reducer, preloadedState: any, enhancers): ReduxStoreHandler { +export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler { const store = createStoreX({ id: 'defaultStore', state: { stateWrapper: preloadedState }, @@ -35,7 +59,7 @@ export function createStore(reducer: Reducer, preloadedState: any, enhancers): R const result = { reducer, getState: function() { - return store.$state.stateWrapper; + return store.$s.stateWrapper; }, subscribe: listener => { store.$subscribe(listener); @@ -48,7 +72,7 @@ export function createStore(reducer: Reducer, preloadedState: any, enhancers): R reducer = newReducer; }, _horizonXstore: store, - dispatch: store.$actions.dispatch, + dispatch: store.$a.dispatch, }; enhancers && enhancers(result); @@ -117,7 +141,6 @@ export function compose(middlewares: ReduxMiddleware[]) { }; } - // HorizonX 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/horizon/src/horizonx/adapters/reduxReact.ts index da99ab52..8e205eb7 100644 --- a/libs/horizon/src/horizonx/adapters/reduxReact.ts +++ b/libs/horizon/src/horizonx/adapters/reduxReact.ts @@ -3,9 +3,10 @@ import { useState, useContext, useEffect, useRef } from '../../renderer/hooks/Ho import { createContext } from '../../renderer/components/context/CreateContext'; import { createElement } from '../../external/JSXElement'; import { BoundActionCreator } from './redux'; -import { ReduxAction, ReduxStoreHandler } from '../types'; +import { ReduxAction } from './redux'; +import { ReduxStoreHandler } from '../store/StoreHandler' -const DefaultContext = createContext(); +const DefaultContext = createContext(null); type Context = typeof DefaultContext; export function Provider({ @@ -27,8 +28,8 @@ export function createStoreHook(context: Context) { }; } -export function createSelectorHook(context: Context): (selector: (any) => any) => any { - const store = createStoreHook(context)(); +export function createSelectorHook(context: Context): (selector?: (any) => any) => any { + const store = (createStoreHook(context)() as unknown) as ReduxStoreHandler; return function(selector = state => state) { const [b, fr] = useState(false); @@ -37,22 +38,18 @@ export function createSelectorHook(context: Context): (selector: (any) => any) = }; useEffect(() => { - const unsubscribe = store.subscribe(listener); - - return () => { - unsubscribe(listener); - }; + return store.subscribe(listener); }); return selector(store.getState()); }; } -export function createDispatchHook(context: Context): BoundActionCreator { - const store = createStoreHook(context)(); +export function createDispatchHook(context: Context): ()=>BoundActionCreator { + const store = (createStoreHook(context)() as unknown) as ReduxStoreHandler; return function() { return action => { - this.dispatch(action); + store.dispatch(action); }; }.bind(store); } @@ -104,26 +101,32 @@ export function connect( } return Component => { - const useStore = createStoreHook(options.context || DefaultContext); + const useStore = createStoreHook(options?.context || DefaultContext); function Wrapper(props) { const [f, forceReload] = useState(true); - const store = useStore(); + const store = (useStore() as unknown) as ReduxStoreHandler; useEffect(() => { const unsubscribe = store.subscribe(() => forceReload(!f)); () => { - unsubscribe(() => forceReload(!f)); + unsubscribe(); }; }); const previous = useRef({ state: {}, - }); + mappedState: {}, + }) as { + current: { + state: {}; + mappedState: {}; + }; + }; let mappedState; - if (options.areStatesEqual) { + if (options?.areStatesEqual) { if (options.areStatesEqual(previous.current.state, store.getState())) { mappedState = previous.current.mappedState; } else { diff --git a/libs/horizon/src/horizonx/adapters/reduxThunk.ts b/libs/horizon/src/horizonx/adapters/reduxThunk.ts index 850f1b1c..28ce23e0 100644 --- a/libs/horizon/src/horizonx/adapters/reduxThunk.ts +++ b/libs/horizon/src/horizonx/adapters/reduxThunk.ts @@ -1,4 +1,5 @@ -import { ReduxStoreHandler, ReduxAction, ReduxMiddleware } from '../types'; +import { ReduxAction, ReduxMiddleware } from './redux'; +import { ReduxStoreHandler } from '../store/StoreHandler'; function createThunkMiddleware(extraArgument?: any): ReduxMiddleware { return (store: ReduxStoreHandler) => (next: (action: ReduxAction) => any) => ( diff --git a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts b/libs/horizon/src/horizonx/proxy/HooklessObserver.ts index 10fce509..59539000 100644 --- a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts +++ b/libs/horizon/src/horizonx/proxy/HooklessObserver.ts @@ -1,6 +1,6 @@ // TODO: implement vNode type -import {IObserver} from '../types'; +import {IObserver} from './Observer'; /** * 一个对象(对象、数组、集合)对应一个Observer diff --git a/libs/horizon/src/horizonx/proxy/Observer.ts b/libs/horizon/src/horizonx/proxy/Observer.ts index 1324c87a..d93c7a05 100644 --- a/libs/horizon/src/horizonx/proxy/Observer.ts +++ b/libs/horizon/src/horizonx/proxy/Observer.ts @@ -6,7 +6,24 @@ import { launchUpdateFromVNode } from '../../renderer/TreeBuilder'; import { getProcessingVNode } from '../../renderer/GlobalVar'; import { VNode } from '../../renderer/vnode/VNode'; -import { IObserver } from '../types'; +export interface IObserver { + + useProp: (key: string) => void; + + addListener: (listener: () => void) => void; + + removeListener: (listener: () => void) => void; + + setProp: (key: string) => void; + + triggerChangeListeners: () => void; + + triggerUpdate: (vNode: any) => void; + + allChange: () => void; + + clearByVNode: (vNode: any) => void; +} export class Observer implements IObserver { diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index 9b746c96..ffc67aa8 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -3,31 +3,88 @@ import { useEffect, useRef } from '../../renderer/hooks/HookExternal'; import { getProcessingVNode } from '../../renderer/GlobalVar'; import { createProxy } from '../proxy/ProxyHandler'; import readonlyProxy from '../proxy/readonlyProxy'; -import { StoreHandler, StoreConfig, UserActions, UserComputedValues, StoreActions, ComputedValues, ActionFunction, Action, QueuedStoreActions } from '../types'; import { Observer } from '../proxy/Observer'; import { FunctionComponent, ClassComponent } from '../Constants'; +import { VNode } from '../../renderer/Types'; -const storeMap = new Map>(); +const storeMap = new Map>(); function isPromise(obj: any): boolean { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; } -type PlannedAction>={ - action:string, - payload: any[], - resolve: ReturnType -} +type StoreConfig, C extends UserComputedValues> = { + state?: S; + options?: { suppressHooks?: boolean }; + actions?: A; + id?: string; + computed?: C; +}; -export function createStore,C extends UserComputedValues>(config: StoreConfig): () => StoreHandler { +export type ReduxStoreHandler = { + reducer: (state: any, action: { type: string }) => any; + dispatch: (action: { type: string }) => void; + getState: () => any; + subscribe: (listener: () => void) => () => void; + replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void; + _horizonXstore: StoreHandler; +}; + +type StoreHandler, C extends UserComputedValues> = { + $subscribe: (listener: () => void) => void; + $unsubscribe: (listener: () => void) => void; + $s: S; + $config: StoreConfig; + $queue: QueuedStoreActions; + $a: StoreActions; + $c: UserComputedValues; + reduxHandler?: ReduxStoreHandler; +} & { [K in keyof S]: S[K] } & + { [K in keyof A]: Action } & + { [K in keyof C]: ReturnType }; + +type PlannedAction> = { + action: string; + payload: any[]; + resolve: ReturnType; +}; +type RemoveFirstFromTuple = T['length'] extends 0 + ? [] + : ((...b: T) => void) extends (a, ...b: infer I) => void + ? I + : []; + +type UserActions = { [K: string]: ActionFunction }; +type UserComputedValues = { [K: string]: ComputedFunction }; + +type ActionFunction = (this: StoreHandler, state: S, ...args: any[]) => any; +type ComputedFunction = (state: S) => any; +type Action, S extends object> = ( + this: StoreHandler, + ...args: RemoveFirstFromTuple> +) => ReturnType; +type AsyncAction, S extends object> = ( + this: StoreHandler, + ...args: RemoveFirstFromTuple> +) => Promise>; + +type StoreActions> = { [K in keyof A]: Action }; +type QueuedStoreActions> = { [K in keyof A]: AsyncAction }; +type ComputedValues> = { [K in keyof C]: ReturnType }; +type PostponedAction = (state: object, ...args: any[]) => Promise; +type PostponedActions = { [key: string]: PostponedAction }; + +export function createStore, C extends UserComputedValues>( + config: StoreConfig +): () => StoreHandler { //create a local shalow copy to ensure consistency (if user would change the config object after store creation) config = { - id:config.id, + id: config.id, options: config.options, state: config.state, - actions: config.actions ? {...config.actions}:undefined, - computed: config.computed ? {...config.computed}:undefined - } + actions: config.actions ? { ...config.actions } : undefined, + computed: config.computed ? { ...config.computed } : undefined, + }; // 校验 if (Object.prototype.toString.call(config) !== '[object Object]') { @@ -35,30 +92,30 @@ export function createStore,C extends } const proxyObj = createProxy(config.state, !config.options?.suppressHooks); - + proxyObj.$pending = false; - - const $subscribe = (listener) => { + + const $subscribe = listener => { proxyObj.addListener(listener); }; - - const $unsubscribe = (listener) => { + + const $unsubscribe = listener => { proxyObj.removeListener(listener); }; - const plannedActions:PlannedAction>[] = []; - const $actions:Partial>={} - const $queue:Partial> = {}; - const $computed:Partial>={} - const handler = { + const plannedActions: PlannedAction>[] = []; + const $a: Partial> = {}; + const $queue: Partial> = {}; + const $c: Partial> = {}; + const handler = ({ $subscribe, $unsubscribe, - $actions:$actions as StoreActions, - $state:proxyObj, - $computed: $computed as ComputedValues, - $config:config, - $queue: $queue as QueuedStoreActions, - } as StoreHandler; + $a: $a as StoreActions, + $s: proxyObj, + $c: $c as ComputedValues, + $config: config, + $queue: $queue as QueuedStoreActions, + } as unknown) as StoreHandler; function tryNextAction() { if (!plannedActions.length) { @@ -67,7 +124,9 @@ export function createStore,C extends } const nextAction = plannedActions.shift()!; - const result = config.actions ? config.actions[nextAction.action].bind(self, proxyObj)(...nextAction.payload) : undefined; + const result = config.actions + ? config.actions[nextAction.action].bind(handler, proxyObj)(...nextAction.payload) + : undefined; if (isPromise(result)) { result.then(value => { @@ -81,16 +140,16 @@ export function createStore,C extends } // 包装actions - if(config.actions){ + if (config.actions) { Object.keys(config.actions).forEach(action => { ($queue as any)[action] = (...payload) => { - return new Promise((resolve) => { + return new Promise(resolve => { if (!proxyObj.$pending) { proxyObj.$pending = true; - const result = config.actions![action].bind(self, proxyObj)(...payload); - + const result = config.actions![action].bind(handler, proxyObj)(...payload); + if (isPromise(result)) { - result.then((value) => { + result.then(value => { resolve(value); tryNextAction(); }); @@ -102,40 +161,42 @@ export function createStore,C extends plannedActions.push({ action, payload, - resolve + resolve, }); } }); }; - - ($actions as any)[action] = function Wrapped(...payload) { - return config.actions![action].bind(self, proxyObj)(...payload); + + ($a as any)[action] = function Wrapped(...payload) { + return config.actions![action].bind(handler, proxyObj)(...payload); }; - // direct store access + // direct store access Object.defineProperty(handler, action, { writable: false, - value: $actions[action] + value: (...payload) => { + return config.actions![action].bind(handler, proxyObj)(...payload); + }, }); }); } - if (config.computed) { - Object.keys(config.computed).forEach((key) => { - ($computed as any)[key] = config.computed![key].bind(handler, readonlyProxy(proxyObj)); + if (config.computed) { + Object.keys(config.computed).forEach(key => { + ($c as any)[key] = config.computed![key].bind(handler, readonlyProxy(proxyObj)); - // direct store access + // direct store access Object.defineProperty(handler, key, { - get: $computed[key] as ()=>any + get: $c[key] as () => any, }); }); } // direct state access - if(config.state){ + if (config.state) { Object.keys(config.state).forEach(key => { Object.defineProperty(handler, key, { - get: () => proxyObj[key] + get: () => proxyObj[key], }); }); } @@ -172,7 +233,7 @@ function hookStore() { if (processingVNode.tag === FunctionComponent) { // from FunctionComponent - const vNodeRef = useRef(null); + const vNodeRef = (useRef(null) as unknown) as { current: VNode }; vNodeRef.current = processingVNode; useEffect(() => { @@ -211,9 +272,9 @@ export function useStore, C extends U if (storeObj && !storeObj.$config.options?.suppressHooks) hookStore(); - return storeObj as StoreHandler; + return storeObj as StoreHandler; } -export function clearStore(id:string):void { +export function clearStore(id: string): void { storeMap.delete(id); -} \ No newline at end of file +} diff --git a/package.json b/package.json index ed675dbd..e4b4229a 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ ], "scripts": { "lint": "eslint . --ext .ts", - "build": " rollup --config ./scripts/rollup/rollup.config.js", - "build:watch": " rollup --watch --config ./scripts/rollup/rollup.config.js", + "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-dev": "npm run build & node ./scripts/gen3rdLib.js --dev", "build-horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev --type horizon", + "build-types": "tsc -p ./libs/horizon/index.ts --emitDeclarationOnly --declaration --declarationDir ./build/horizon/@types --skipLibCheck", "debug-test": "yarn test --debug", "test": "jest --config=jest.config.js", "watch-test": "yarn test --watch --dev" @@ -56,6 +57,7 @@ "jest-environment-jsdom-sixteen": "^1.0.3", "prettier": "2.6.2", "rollup": "^2.75.5", + "rollup-plugin-execute": "^1.1.1", "rollup-plugin-terser": "^7.0.2", "typescript": "4.2.3" }, diff --git a/scripts/__tests__/HorizonXText/StateManager/StateArray.test.js b/scripts/__tests__/HorizonXText/StateManager/StateArray.test.js deleted file mode 100644 index 826d6dda..00000000 --- a/scripts/__tests__/HorizonXText/StateManager/StateArray.test.js +++ /dev/null @@ -1,201 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; - -describe('测试store中的Array', () => { - const { unmountComponentAtNode } = Horizon; - let container = null; - beforeEach(() => { - // 创建一个 DOM 元素作为渲染目标 - container = document.createElement('div'); - document.body.appendChild(container); - - const persons = [ - { name: 'p1', age: 1 }, - { name: 'p2', age: 2 }, - ]; - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.push(person); - }, - delOnePerson: state => { - state.persons.pop(); - }, - clearPersons: state => { - state.persons = null; - }, - }, - }); - }); - - afterEach(() => { - // 退出时进行清理 - unmountComponentAtNode(container); - container.remove(); - container = null; - - clearStore('user'); - }); - - const newPerson = { name: 'p3', age: 3 }; - - function Parent(props) { - const userStore = useStore('user'); - const addOnePerson = function() { - userStore.addOnePerson(newPerson); - }; - const delOnePerson = function() { - userStore.delOnePerson(); - }; - return ( -
- - -
{props.children}
-
- ); - } - - it('测试Array方法: push()、pop()', () => { - function Child(props) { - const userStore = useStore('user'); - - return ( -
- -
- ); - } - - Horizon.render(, container); - - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 2'); - // 在Array中增加一个对象 - Horizon.act(() => { - triggerClickEvent(container, 'addBtn'); - }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 3'); - - // 在Array中删除一个对象 - Horizon.act(() => { - triggerClickEvent(container, 'delBtn'); - }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 2'); - }); - - it('测试Array方法: entries()、push()、shift()、unshift、直接赋值', () => { - let globalStore = null; - - function Child(props) { - const userStore = useStore('user'); - globalStore = userStore; - - const nameList = []; - const entries = userStore.$state.persons?.entries(); - if (entries) { - for (const entry of entries) { - nameList.push(entry[1].name); - } - } - - return ( -
- -
- ); - } - - Horizon.render(, container); - - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); - // push - globalStore.$state.persons.push(newPerson); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); - - // shift - globalStore.$state.persons.shift({ name: 'p0', age: 0 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3'); - - // 赋值[2] - globalStore.$state.persons[2] = { name: 'p4', age: 4 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p4'); - - // 重新赋值[2] - globalStore.$state.persons[2] = { name: 'p5', age: 5 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p5'); - - // unshift - globalStore.$state.persons.unshift({ name: 'p1', age: 1 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3 p5'); - - // 重新赋值 null - globalStore.$state.persons = null; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); - - // 重新赋值 [{ name: 'p1', age: 1 }] - globalStore.$state.persons = [{ name: 'p1', age: 1 }]; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1'); - }); - - it('测试Array方法: forEach()', () => { - let globalStore = null; - - function Child(props) { - const userStore = useStore('user'); - globalStore = userStore; - - const nameList = []; - userStore.$state.persons?.forEach(per => { - nameList.push(per.name); - }); - - return ( -
- -
- ); - } - - Horizon.render(, container); - - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); - // push - globalStore.$state.persons.push(newPerson); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); - - // shift - globalStore.$state.persons.shift({ name: 'p0', age: 0 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3'); - - // 赋值[2] - globalStore.$state.persons[2] = { name: 'p4', age: 4 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p4'); - - // 重新赋值[2] - globalStore.$state.persons[2] = { name: 'p5', age: 5 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p5'); - - // unshift - globalStore.$state.persons.unshift({ name: 'p1', age: 1 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3 p5'); - - // 重新赋值 null - globalStore.$state.persons = null; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); - - // 重新赋值 [{ name: 'p1', age: 1 }] - globalStore.$state.persons = [{ name: 'p1', age: 1 }]; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1'); - }); -}); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx b/scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx new file mode 100644 index 00000000..fbe1de0b --- /dev/null +++ b/scripts/__tests__/HorizonXText/StateManager/StateArray.test.tsx @@ -0,0 +1,208 @@ +//@ts-ignore +import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; +import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: [ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ], + }, + actions: { + addOnePerson: (state, person) => { + state.persons.push(person); + }, + delOnePerson: state => { + state.persons.pop(); + }, + clearPersons: state => { + state.persons = []; + }, + reset: state => { + state.persons = [ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ]; + }, + }, +}); + +describe('测试store中的Array', () => { + const { unmountComponentAtNode } = Horizon; + let container: HTMLElement | null = null; + beforeEach(() => { + // 创建一个 DOM 元素作为渲染目标 + container = document.createElement('div'); + document.body.appendChild(container); + + useUserStore().reset(); + }); + + afterEach(() => { + // 退出时进行清理 + unmountComponentAtNode(container); + container?.remove(); + container = null; + LogUtils.clear(); + + clearStore('user'); + }); + + const newPerson = { name: 'p3', age: 3 }; + function Parent(props) { + const userStore = useUserStore(); + const addOnePerson = function() { + userStore.addOnePerson(newPerson); + }; + const delOnePerson = function() { + userStore.delOnePerson(); + }; + return ( +
+ + +
{props.children}
+
+ ); + } + + it('测试Array方法: push()、pop()', () => { + function Child(props) { + const userStore = useUserStore(); + + return ( +
+ +
+ ); + } + + Horizon.render(, container); + + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); + // 在Array中增加一个对象 + Horizon.act(() => { + triggerClickEvent(container, 'addBtn'); + }); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); + + // 在Array中删除一个对象 + Horizon.act(() => { + triggerClickEvent(container, 'delBtn'); + }); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); + }); + + it('测试Array方法: entries()、push()、shift()、unshift、直接赋值', () => { + let globalStore = useUserStore(); + function Child(props) { + const userStore = useUserStore(); + + const nameList: string[] = []; + const entries = userStore.$s.persons?.entries(); + if (entries) { + for (const entry of entries) { + nameList.push(entry[1].name); + } + } + + return ( +
+ +
+ ); + } + + Horizon.render(, container); + + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); + // push + globalStore.$s.persons.push(newPerson); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); + + // shift + //@ts-ignore TODO:why is this argument here? + globalStore.$s.persons.shift({ name: 'p0', age: 0 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3'); + + // 赋值[2] + globalStore.$s.persons[2] = { name: 'p4', age: 4 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p4'); + + // 重新赋值[2] + globalStore.$s.persons[2] = { name: 'p5', age: 5 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p5'); + + // unshift + globalStore.$s.persons.unshift({ name: 'p1', age: 1 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3 p5'); + + // 重新赋值 [] + globalStore.$s.persons = []; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); + + // 重新赋值 [{ name: 'p1', age: 1 }] + globalStore.$s.persons = [{ name: 'p1', age: 1 }]; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1'); + }); + + it('测试Array方法: forEach()', () => { + let globalStore = useUserStore(); + function Child(props) { + const userStore = useUserStore(); + + const nameList: string[] = []; + userStore.$s.persons?.forEach(per => { + nameList.push(per.name); + }); + + return ( +
+ +
+ ); + } + + Horizon.render(, container); + + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); + // push + globalStore.$s.persons.push(newPerson); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); + + // shift + //@ts-ignore TODO: why is this argument here? + globalStore.$s.persons.shift({ name: 'p0', age: 0 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3'); + + // 赋值[2] + globalStore.$s.persons[2] = { name: 'p4', age: 4 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p4'); + + // 重新赋值[2] + globalStore.$s.persons[2] = { name: 'p5', age: 5 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p5'); + + // unshift + globalStore.$s.persons.unshift({ name: 'p1', age: 1 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3 p5'); + + // 重新赋值 [] + globalStore.$s.persons = []; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); + + // 重新赋值 [{ name: 'p1', age: 1 }] + globalStore.$s.persons = [{ name: 'p1', age: 1 }]; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1'); + }); +}); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateMap.test.js b/scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx similarity index 60% rename from scripts/__tests__/HorizonXText/StateManager/StateMap.test.js rename to scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx index f7eaed9f..e5d1b141 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateMap.test.js +++ b/scripts/__tests__/HorizonXText/StateManager/StateMap.test.tsx @@ -1,45 +1,55 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: new Map([ + ['p1', 1], + ['p2', 2], + ]), + }, + actions: { + addOnePerson: (state, person) => { + state.persons.set(person.name, person.age); + }, + delOnePerson: (state, person) => { + state.persons.delete(person.name); + }, + clearPersons: state => { + state.persons.clear(); + }, + reset: state => { + state.persons = new Map([ + ['p1', 1], + ['p2', 2], + ]); + }, + }, +}); describe('测试store中的Map', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); document.body.appendChild(container); - const persons = new Map([ - ['p1', 1], - ['p2', 2], - ]); - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.set(person.name, person.age); - }, - delOnePerson: (state, person) => { - state.persons.delete(person.name); - }, - clearPersons: state => { - state.persons.clear(); - }, - }, - }); + useUserStore().reset(); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -47,7 +57,7 @@ describe('测试store中的Map', () => { const newPerson = { name: 'p3', age: 3 }; function Parent(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); const addOnePerson = function() { userStore.addOnePerson(newPerson); }; @@ -76,43 +86,43 @@ describe('测试store中的Map', () => { it('测试Map方法: set()、delete()、clear()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 3'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 0'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); }); it('测试Map方法: keys()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - const keys = userStore.$state.persons.keys(); + const nameList: string[] = []; + const keys = userStore.$s.persons.keys(); for (const key of keys) { nameList.push(key); } @@ -126,32 +136,32 @@ describe('测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: values()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const ageList = []; - const values = userStore.$state.persons.values(); + const ageList: number[] = []; + const values = userStore.$s.persons.values(); for (const val of values) { ageList.push(val); } @@ -165,32 +175,32 @@ describe('测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2 3'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: '); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: '); }); it('测试Map方法: entries()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - const entries = userStore.$state.persons.entries(); + const nameList: string[] = []; + const entries = userStore.$s.persons.entries(); for (const entry of entries) { nameList.push(entry[0]); } @@ -204,32 +214,32 @@ describe('测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: forEach()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - userStore.$state.persons.forEach((val, key) => { + const nameList: string[] = []; + userStore.$s.persons.forEach((val, key) => { nameList.push(key); }); @@ -242,53 +252,53 @@ describe('测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: has()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: true'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); }); it('测试Map方法: for of()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - for (const per of userStore.$state.persons) { + const nameList: string[] = []; + for (const per of userStore.$s.persons) { nameList.push(per[0]); } @@ -301,23 +311,23 @@ describe('测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); }); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.js b/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx similarity index 77% rename from scripts/__tests__/HorizonXText/StateManager/StateMixType.test.js rename to scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx index 870d7d26..bb38773d 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.js +++ b/scripts/__tests__/HorizonXText/StateManager/StateMixType.test.tsx @@ -1,10 +1,12 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; describe('测试store中的混合类型变化', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); @@ -42,8 +44,9 @@ describe('测试store中的混合类型变化', () => { afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + (container as HTMLElement).remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -68,7 +71,7 @@ describe('测试store中的混合类型变化', () => { function Child(props) { const userStore = useStore('user'); - const days = userStore.$state.persons + const days = userStore.persons .values() .next() .value.love.get('lanqiu').days; @@ -82,11 +85,11 @@ describe('测试store中的混合类型变化', () => { Horizon.render(, container); - expect(container.querySelector('#dayList').innerHTML).toBe('love: 1 3 5'); + expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5'); Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#dayList').innerHTML).toBe('love: 1 3 5 7'); + expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5 7'); }); it('属性是个class实例', () => { @@ -103,7 +106,6 @@ describe('测试store中的混合类型变化', () => { setName(name) { this.name = name; } - getName() { return this.name; } @@ -111,7 +113,6 @@ describe('测试store中的混合类型变化', () => { setAge(age) { this.age = age; } - getAge() { return this.age; } @@ -119,7 +120,6 @@ describe('测试store中的混合类型变化', () => { addLove(lv) { this.loves.add(lv); } - getLoves() { return this.loves; } @@ -127,14 +127,19 @@ describe('测试store中的混合类型变化', () => { let globalPerson; let globalStore; - function Child(props) { const userStore = useStore('user'); globalStore = userStore; - const nameList = []; - const valIterator = userStore.$state.persons.values(); - let per = valIterator.next(); + const nameList: string[] = []; + const valIterator = userStore.persons.values(); + let per = valIterator.next() as { + value: { + name: string; + getName: () => string; + }; + done: boolean; + }; while (!per.done) { nameList.push(per.value.name ?? per.value.getName()); globalPerson = per.value; @@ -150,15 +155,15 @@ describe('测试store中的混合类型变化', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2'); // 动态增加一个Person实例 - globalStore.$state.persons.add(new Person('ClassPerson', 5)); + globalStore.$s.persons.add(new Person('ClassPerson', 5)); - expect(container.querySelector('#nameList').innerHTML).toBe('p1 p2 ClassPerson'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2 ClassPerson'); globalPerson.setName('ClassPerson1'); - expect(container.querySelector('#nameList').innerHTML).toBe('p1 p2 ClassPerson1'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2 ClassPerson1'); }); }); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateSet.test.js b/scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx similarity index 58% rename from scripts/__tests__/HorizonXText/StateManager/StateSet.test.js rename to scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx index 4b6bdc7b..a1a245f0 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateSet.test.js +++ b/scripts/__tests__/HorizonXText/StateManager/StateSet.test.tsx @@ -1,53 +1,62 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: new Set([ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ]), + }, + actions: { + addOnePerson: (state, person) => { + state.persons.add(person); + }, + delOnePerson: (state, person) => { + state.persons.delete(person); + }, + clearPersons: state => { + state.persons.clear(); + }, + reset: state => { + state.persons = new Set([ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ]); + }, + }, +}); describe('测试store中的Set', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); document.body.appendChild(container); - const persons = new Set([ - { name: 'p1', age: 1 }, - { name: 'p2', age: 2 }, - ]); - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.add(person); - }, - delOnePerson: (state, person) => { - state.persons.delete(person); - }, - clearPersons: state => { - state.persons.clear(); - }, - }, - }); + useUserStore().reset(); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); const newPerson = { name: 'p3', age: 3 }; - function Parent(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); const addOnePerson = function() { userStore.addOnePerson(newPerson); }; @@ -76,17 +85,17 @@ describe('测试store中的Set', () => { it('测试Set方法: add()、delete()、clear()', () => { function Child(props) { - const userStore = useStore('user'); - const personArr = Array.from(userStore.$state.persons); - const nameList = []; - const keys = userStore.$state.persons.keys(); + const userStore = useUserStore(); + const personArr = Array.from(userStore.$s.persons); + const nameList: string[] = []; + const keys = userStore.$s.persons.keys(); for (const key of keys) { nameList.push(key.name); } return (
- +
); @@ -94,35 +103,35 @@ describe('测试store中的Set', () => { Horizon.render(, container); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); - expect(container.querySelector('#lastAge').innerHTML).toBe('last person age: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#lastAge')?.innerHTML).toBe('last person age: 2'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 3'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在set中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear set Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 0'); - expect(container.querySelector('#lastAge').innerHTML).toBe('last person age: 0'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); + expect(container?.querySelector('#lastAge')?.innerHTML).toBe('last person age: 0'); }); it('测试Set方法: keys()、values()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - const keys = userStore.$state.persons.keys(); - // const keys = userStore.$state.persons.values(); + const nameList: string[] = []; + const keys = userStore.$s.persons.keys(); + // const keys = userStore.$s.persons.values(); for (const key of keys) { nameList.push(key.name); } @@ -136,32 +145,32 @@ describe('测试store中的Set', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Set方法: entries()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - const entries = userStore.$state.persons.entries(); + const nameList: string[] = []; + const entries = userStore.$s.persons.entries(); for (const entry of entries) { nameList.push(entry[0].name); } @@ -175,32 +184,32 @@ describe('测试store中的Set', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Set方法: forEach()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - userStore.$state.persons.forEach(per => { + const nameList: string[] = []; + userStore.$s.persons.forEach(per => { nameList.push(per.name); }); @@ -213,53 +222,53 @@ describe('测试store中的Set', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Set方法: has()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: true'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); }); it('测试Set方法: for of()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); - const nameList = []; - for (const per of userStore.$state.persons) { + const nameList: string[] = []; + for (const per of userStore.$s.persons) { nameList.push(per.name); } @@ -272,23 +281,23 @@ describe('测试store中的Set', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在set中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在set中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear set Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); }); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.js b/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx similarity index 61% rename from scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.js rename to scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx index 28d4ff98..2d5a441f 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.js +++ b/scripts/__tests__/HorizonXText/StateManager/StateWeakMap.test.tsx @@ -1,45 +1,55 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: new WeakMap([ + [{ name: 'p1' }, 1], + [{ name: 'p2' }, 2], + ]), + }, + actions: { + addOnePerson: (state, person) => { + state.persons.set(person, 3); + }, + delOnePerson: (state, person) => { + state.persons.delete(person); + }, + clearPersons: state => { + state.persons = new WeakMap([]); + }, + reset: state => { + state.persons = new WeakMap([ + [{ name: 'p1' }, 1], + [{ name: 'p2' }, 2], + ]); + }, + }, +}); describe('测试store中的WeakMap', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); document.body.appendChild(container); - const persons = new WeakMap([ - [{ name: 'p1' }, 1], - [{ name: 'p2' }, 2], - ]); - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.set(person, 3); - }, - delOnePerson: (state, person) => { - state.persons.delete(person); - }, - clearPersons: state => { - state.persons.clear(); - }, - }, - }); + useUserStore().reset(); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -47,7 +57,7 @@ describe('测试store中的WeakMap', () => { const newPerson = { name: 'p3' }; function Parent(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); const addOnePerson = function() { userStore.addOnePerson(newPerson); }; @@ -76,49 +86,49 @@ describe('测试store中的WeakMap', () => { it('测试WeakMap方法: set()、delete()、has()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在WeakMap中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: true'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); // 在WeakMap中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); }); it('测试WeakMap方法: get()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: undefined'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: undefined'); // 在WeakMap中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 3'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); }); }); diff --git a/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.js b/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx similarity index 59% rename from scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.js rename to scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx index 4c6a8fca..ecbc632d 100644 --- a/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.js +++ b/scripts/__tests__/HorizonXText/StateManager/StateWeakSet.test.tsx @@ -1,53 +1,62 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: new WeakSet([ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ]), + }, + actions: { + addOnePerson: (state, person) => { + state.persons.add(person); + }, + delOnePerson: (state, person) => { + state.persons.delete(person); + }, + clearPersons: state => { + state.persons = new WeakSet([]); + }, + reset: state => { + state.persons = new WeakSet([ + { name: 'p1', age: 1 }, + { name: 'p2', age: 2 }, + ]); + }, + }, +}); describe('测试store中的WeakSet', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container: HTMLElement | null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); document.body.appendChild(container); - const persons = new WeakSet([ - { name: 'p1', age: 1 }, - { name: 'p2', age: 2 }, - ]); - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.add(person); - }, - delOnePerson: (state, person) => { - state.persons.delete(person); - }, - clearPersons: state => { - state.persons.clear(); - }, - }, - }); + useUserStore().reset(); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); const newPerson = { name: 'p3', age: 3 }; - function Parent(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); const addOnePerson = function() { userStore.addOnePerson(newPerson); }; @@ -69,28 +78,28 @@ describe('测试store中的WeakSet', () => { it('测试WeakSet方法: add()、delete()、has()', () => { function Child(props) { - const userStore = useStore('user'); + const userStore = useUserStore(); return (
- +
); } Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在WeakSet中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: true'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); // 在WeakSet中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); }); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.js b/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx similarity index 80% rename from scripts/__tests__/HorizonXText/StoreFunctionality/async.test.js rename to scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx index f45599d2..66c287e9 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.js +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx @@ -1,6 +1,8 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; const { unmountComponentAtNode } = Horizon; @@ -13,7 +15,7 @@ function postpone(timer, func) { } describe('Asynchronous functions', () => { - let container = null; + let container: HTMLElement | null = null; const COUNTER_ID = 'counter'; const TOGGLE_ID = 'toggle'; @@ -33,7 +35,7 @@ describe('Asynchronous functions', () => { return new Promise(resolve => { setTimeout(() => { state.counter++; - resolve(); + resolve(true); }, 100); }); }, @@ -53,11 +55,12 @@ describe('Asynchronous functions', () => { afterEach(() => { unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; }); it('Should wait for async actions', async () => { + // @ts-ignore jest.useRealTimers(); let globalStore; @@ -84,14 +87,14 @@ describe('Asynchronous functions', () => { Horizon.render(, container); // initial state - expect(document.getElementById(RESULT_ID).innerHTML).toBe('false0'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false0'); // slow toggle has nothing to wait for, it is resolved immediately Horizon.act(() => { triggerClickEvent(container, TOGGLE_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('true0'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('true0'); // counter increment is slow. slow toggle waits for result Horizon.act(() => { @@ -101,18 +104,18 @@ describe('Asynchronous functions', () => { triggerClickEvent(container, TOGGLE_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('true0'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('true0'); // fast toggle does not wait for counter and it is resolved immediately Horizon.act(() => { triggerClickEvent(container, TOGGLE_FAST_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('false0'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false0'); // at 150ms counter increment will be resolved and slow toggle immediately after const t150 = postpone(150, () => { - expect(document.getElementById(RESULT_ID).innerHTML).toBe('true1'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('true1'); }); // before that, two more actions are added to queue - another counter and slow toggle @@ -125,13 +128,14 @@ describe('Asynchronous functions', () => { // at 250ms they should be already resolved const t250 = postpone(250, () => { - expect(document.getElementById(RESULT_ID).innerHTML).toBe('false2'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false2'); }); await Promise.all([t150, t250]); }); it('call async action by then', async () => { + // @ts-ignore jest.useFakeTimers(); let globalStore; @@ -150,12 +154,13 @@ describe('Asynchronous functions', () => { // call async action by then globalStore.$queue.increment().then(() => { - expect(document.getElementById(RESULT_ID).innerHTML).toBe('false1'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false1'); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('false0'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false0'); // past 150 ms + // @ts-ignore jest.advanceTimersByTime(150); }); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.js b/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.js deleted file mode 100644 index e42feeda..00000000 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { triggerClickEvent } from '../../jest/commonComponents'; -import { useLogStore } from './store'; - -const { unmountComponentAtNode } = Horizon; - -describe('Basic store manipulation', () => { - let container = null; - - const BUTTON_ID = 'btn'; - const RESULT_ID = 'result'; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - - it('Should use getters', () => { - function App() { - const logStore = useLogStore(); - - return
{logStore.length}
; - } - - Horizon.render(, container); - - expect(document.getElementById(RESULT_ID).innerHTML).toBe('1'); - }); - - it('Should use actions and update components', () => { - function App() { - const logStore = useLogStore(); - - return ( -
- -

{logStore.length}

-
- ); - } - - Horizon.render(, container); - - Horizon.act(() => { - triggerClickEvent(container, BUTTON_ID); - }); - - expect(document.getElementById(RESULT_ID).innerHTML).toBe('2'); - }); -}); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx b/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx new file mode 100644 index 00000000..0a2c8584 --- /dev/null +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/basicAccess.test.tsx @@ -0,0 +1,110 @@ +//@ts-ignore +import Horizon from '@cloudsop/horizon/index.ts'; +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'; + +const { unmountComponentAtNode } = Horizon; + +describe('Basic store manipulation', () => { + let container: HTMLElement | null = null; + + const BUTTON_ID = 'btn'; + const RESULT_ID = 'result'; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + unmountComponentAtNode(container); + container?.remove(); + container = null; + }); + + it('Should use getters', () => { + function App() { + const logStore = useLogStore(); + + return
{logStore.length}
; + } + + Horizon.render(, container); + + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1'); + }); + + it('Should use actions and update components', () => { + function App() { + const logStore = useLogStore(); + + return ( +
+ +

{logStore.length}

+
+ ); + } + + Horizon.render(, container); + + Horizon.act(() => { + triggerClickEvent(container, BUTTON_ID); + }); + + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2'); + }); + + it('should call actions from own actions', () => { + const useIncrementStore = createStore({ + id: 'incrementStore', + state: { + count: 2, + }, + actions: { + increment: state => { + state.count++; + }, + doublePlusOne: function(state) { + state.count = state.count * 2; + this.increment(); + }, + }, + }); + + function App() { + const incrementStore = useIncrementStore(); + + return ( +
+ +

{incrementStore.count}

+
+ ); + } + + Horizon.render(, container); + + Horizon.act(() => { + triggerClickEvent(container, BUTTON_ID); + }); + + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5'); + }); +}); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.js b/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx similarity index 64% rename from scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.js rename to scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx index 032234d0..94d149f0 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.js +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/dollarAccess.test.tsx @@ -1,11 +1,13 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; import { triggerClickEvent } from '../../jest/commonComponents'; import { useLogStore } from './store'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; const { unmountComponentAtNode } = Horizon; describe('Dollar store access', () => { - let container = null; + let container: HTMLElement | null = null; const BUTTON_ID = 'btn'; const RESULT_ID = 'result'; @@ -17,23 +19,23 @@ describe('Dollar store access', () => { afterEach(() => { unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; }); - it('Should use $state and $computed', () => { + it('Should use $s and $c', () => { function App() { const logStore = useLogStore(); - return
{logStore.$computed.length()}
; + return
{logStore.$c.length()}
; } Horizon.render(, container); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('1'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1'); }); - it('Should use $actions and update components', () => { + it('Should use $a and update components', () => { function App() { const logStore = useLogStore(); @@ -42,12 +44,12 @@ describe('Dollar store access', () => { -

{logStore.$computed.length()}

+

{logStore.$c.length()}

); } @@ -58,6 +60,6 @@ describe('Dollar store access', () => { triggerClickEvent(container, BUTTON_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('2'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2'); }); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.js b/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx similarity index 71% rename from scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.js rename to scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx index 115164c0..ad6a4028 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.js +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/otherCases.test.tsx @@ -1,11 +1,13 @@ +//@ts-ignore import * as Horizon from '@cloudsop/horizon/index.ts'; import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { triggerClickEvent } from '../../jest/commonComponents'; +import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; const { unmountComponentAtNode } = Horizon; describe('Self referencing', () => { - let container = null; + let container: HTMLElement | null = null; const BUTTON_ID = 'btn'; const RESULT_ID = 'result'; @@ -15,7 +17,7 @@ describe('Self referencing', () => { val: 2, }, actions: { - magic: function(state) { + increaseVal: function(state) { state.val = state.val * 2 - 1; }, }, @@ -34,7 +36,7 @@ describe('Self referencing', () => { afterEach(() => { unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; }); @@ -45,8 +47,8 @@ describe('Self referencing', () => { return (

{store.double}

-
); @@ -54,29 +56,29 @@ describe('Self referencing', () => { Horizon.render(, container); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('4'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('4'); Horizon.act(() => { triggerClickEvent(container, BUTTON_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('6'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('6'); Horizon.act(() => { triggerClickEvent(container, BUTTON_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('10'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('10'); }); it('should access other stores', () => { const useOtherStore = createStore({ state: {}, actions: { - doMagic: () => useSelfRefStore().magic(), + doIncreaseVal: () => useSelfRefStore().increaseVal(), }, computed: { - magicConstant: () => useSelfRefStore().value, + selfRefStoreValue: () => useSelfRefStore().value, }, }); @@ -85,9 +87,9 @@ describe('Self referencing', () => { return (
-

{store.magicConstant}

-
); @@ -95,13 +97,13 @@ describe('Self referencing', () => { Horizon.render(, container); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('5'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5'); Horizon.act(() => { triggerClickEvent(container, BUTTON_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('9'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('9'); }); it('should use parametric getters', () => { @@ -138,11 +140,11 @@ describe('Self referencing', () => { } Horizon.render(, container); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('abc'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('abc'); Horizon.act(() => { triggerClickEvent(container, BUTTON_ID); }); - expect(document.getElementById(RESULT_ID).innerHTML).toBe('def'); + expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('def'); }); }); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/reset.test.js b/scripts/__tests__/HorizonXText/StoreFunctionality/reset.js similarity index 65% rename from scripts/__tests__/HorizonXText/StoreFunctionality/reset.test.js rename to scripts/__tests__/HorizonXText/StoreFunctionality/reset.js index f0b349da..0cc4a4d3 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/reset.test.js +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/reset.js @@ -1,14 +1,14 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { triggerClickEvent } from '../../jest/commonComponents'; +import {createStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {triggerClickEvent} from '../../jest/commonComponents'; -const { unmountComponentAtNode } = Horizon; +const {unmountComponentAtNode} = Horizon; describe('Reset', () => { it('RESET NOT IMPLEMENTED', async () => { // console.log('reset functionality is not yet implemented') expect(true).toBe(true); - }); + }) return; let container = null; @@ -19,14 +19,14 @@ describe('Reset', () => { const useCounter = createStore({ state: { - counter: 0, + counter: 0 }, actions: { - increment: function(state) { + increment: function (state) { state.counter++; - }, + } }, - computed: {}, + computed: {} }); beforeEach(() => { @@ -44,25 +44,17 @@ describe('Reset', () => { function App() { const store = useCounter(); - return ( -
-

{store.$state.counter}

- - -
- ); + return
+

{store.$s.counter}

+ + +
} - Horizon.render(, container); + Horizon.render(, container); Horizon.act(() => { triggerClickEvent(container, BUTTON_ID); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/store.js b/scripts/__tests__/HorizonXText/StoreFunctionality/store.ts similarity index 100% rename from scripts/__tests__/HorizonXText/StoreFunctionality/store.js rename to scripts/__tests__/HorizonXText/StoreFunctionality/store.ts diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.js b/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx similarity index 96% rename from scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.js rename to scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx index 3683a596..e14c5d9e 100644 --- a/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.js +++ b/scripts/__tests__/HorizonXText/adapters/ReduxAdapter.test.tsx @@ -1,9 +1,12 @@ +//@ts-ignore +import * as Horizon from '@cloudsop/horizon/index.ts'; import { createStore, applyMiddleware, combineReducers, bindActionCreators, } from '../../../../libs/horizon/src/horizonx/adapters/redux'; +import { describe, it, expect } from '@jest/globals'; describe('Redux adapter', () => { it('should use getState()', async () => { @@ -142,7 +145,7 @@ describe('Redux adapter', () => { it('Should apply enhancers', async () => { let counter = 0; - let middlewareCallList = []; + let middlewareCallList: string[] = []; const callCounter = store => next => action => { middlewareCallList.push('callCounter'); @@ -175,7 +178,7 @@ describe('Redux adapter', () => { it('Should apply multiple enhancers', async () => { let counter = 0; let lastAction = ''; - let middlewareCallList = []; + let middlewareCallList: string[] = []; const callCounter = store => next => action => { middlewareCallList.push('callCounter'); diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.js b/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.js deleted file mode 100644 index 3785b92c..00000000 --- a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { createStore, applyMiddleware, thunk } from '../../../../libs/horizon/src/horizonx/adapters/redux'; - -describe('Redux thunk', () => { - it('should use apply thunk middleware', async () => { - const MAX_TODOS = 5; - - function addTodosIfAllowed(todoText) { - return (dispatch, getState) => { - const state = getState(); - - if (state.todos.length < MAX_TODOS) { - dispatch({ type: 'ADD_TODO', text: todoText }); - } - }; - } - - const todoStore = createStore( - (state = { todos: [] }, action) => { - if (action.type === 'ADD_TODO') { - return { todos: state.todos?.concat(action.text) }; - } - return state; - }, - null, - applyMiddleware(thunk) - ); - - for (let i = 0; i < 10; i++) { - todoStore.dispatch(addTodosIfAllowed('todo no.' + i)); - } - - expect(todoStore.getState().todos.length).toBe(5); - }); -}); diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx b/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx new file mode 100644 index 00000000..39e4fc83 --- /dev/null +++ b/scripts/__tests__/HorizonXText/adapters/ReduxAdapterThunk.test.tsx @@ -0,0 +1,33 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { createStore, applyMiddleware, thunk } from '../../../../libs/horizon/src/horizonx/adapters/redux'; +import {describe, it, expect} from '@jest/globals'; + +describe('Redux thunk', () => { + it('should use apply thunk middleware', async () => { + const MAX_TODOS = 5; + + function addTodosIfAllowed(todoText) { + return (dispatch, getState) => { + const state = getState(); + + if (state.todos.length < MAX_TODOS) { + dispatch({type: 'ADD_TODO', text: todoText}); + } + } + } + + const todoStore = createStore((state = {todos: []}, action) => { + if (action.type === 'ADD_TODO') { + return {todos: state.todos?.concat(action.text)}; + } + return state; + }, null, applyMiddleware(thunk)); + + for (let i = 0; i < 10; i++) { + //TODO: resolve thunk problems + (todoStore.dispatch as unknown as (delayedAction:(dispatch,getState)=>void)=>void)(addTodosIfAllowed('todo no.' + i)); + } + + expect(todoStore.getState().todos.length).toBe(5); + }); +}); diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.js b/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx similarity index 50% rename from scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.js rename to scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx index 5977671a..4f6feec6 100644 --- a/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.js +++ b/scripts/__tests__/HorizonXText/adapters/ReduxReactAdapter.test.tsx @@ -1,3 +1,4 @@ +//@ts-ignore import horizon, * as Horizon from '@cloudsop/horizon/index.ts'; import { batch, @@ -8,17 +9,19 @@ import { useSelector, useStore, createSelectorHook, - createDispatchHook, + createDispatchHook } from '../../../../libs/horizon/src/horizonx/adapters/redux'; -import { triggerClickEvent } from '../../jest/commonComponents'; +import {triggerClickEvent} from '../../jest/commonComponents'; +import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; +import { ReduxStoreHandler } from '@cloudsop/horizon/src/horizonx/types'; const BUTTON = 'button'; const BUTTON2 = 'button2'; const RESULT = 'result'; -const CONTAINER = 'container'; +const CONTAINER = 'container' -function getE(id) { - return document.getElementById(id); +function getE(id):HTMLElement { + return document.getElementById(id)||document.body; } describe('Redux/React binding adapter', () => { @@ -36,19 +39,17 @@ describe('Redux/React binding adapter', () => { const reduxStore = createStore((state = 'state', action) => state); const Child = () => { - const store = useStore(); + const store = useStore() as unknown as ReduxStoreHandler; return
{store.getState()}
; }; const Wrapper = () => { - return ( - - - - ); + return + + ; }; - Horizon.render(, getE(CONTAINER)); + Horizon.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('state'); }); @@ -60,30 +61,23 @@ describe('Redux/React binding adapter', () => { }); const Child = () => { - const store = useStore(); + const store = useStore() as unknown as ReduxStoreHandler; const dispatch = useDispatch(); - return ( -
-

{store.getState()}

- -
- ); + return
+

{store.getState()}

+ +
; }; const Wrapper = () => { - return ( - - - - ); + return + + ; }; - Horizon.render(, getE(CONTAINER)); + Horizon.render(, getE(CONTAINER)); expect(reduxStore.getState()).toBe(0); @@ -101,32 +95,24 @@ describe('Redux/React binding adapter', () => { }); const Child = () => { - const count = useSelector(state => state); + const count = useSelector((state) => state); const dispatch = useDispatch(); - return ( -
-

{count}

- -
- ); + return
+

{count}

+ +
; }; const Wrapper = () => { - return ( - - - - ); + return + + ; }; - Horizon.render(, getE(CONTAINER)); + Horizon.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('0'); @@ -139,80 +125,58 @@ describe('Redux/React binding adapter', () => { }); it('Should use connect', async () => { - const reduxStore = createStore( - (state, action) => { - switch (action.type) { - case 'INCREMENT': - return { - ...state, - value: state.negative ? state.value - action.amount : state.value + action.amount, - }; - case 'TOGGLE': - return { - ...state, - negative: !state.negative, - }; - default: - return state; - } - }, - { negative: false, value: 0 } - ); + const reduxStore = createStore((state, action) => { + switch (action.type) { + case('INCREMENT'): + return { + ...state, + value: state.negative ? state.value - action.amount : state.value + action.amount + }; + case('TOGGLE'): + return { + ...state, + negative: !state.negative + }; + default: + return state; + } + }, {negative: false, value: 0}); - const Child = connect( - (state, ownProps) => { - // map state to props - return { ...state, ...ownProps }; - }, - (dispatch, ownProps) => { - // map dispatch to props - return { - increment: () => dispatch({ type: 'INCREMENT', amount: ownProps.amount }), - }; - }, - (stateProps, dispatchProps, ownProps) => { - //merge props - return { stateProps, dispatchProps, ownProps }; - }, - {} - )(props => { + const Child = connect((state, ownProps) => { + // map state to props + return {...state, ...ownProps}; + }, (dispatch, ownProps) => { + // map dispatch to props + return { + // @ts-ignore + increment: () => dispatch({type: 'INCREMENT', amount: ownProps?.amount}) + }; + }, (stateProps, dispatchProps, ownProps) => { + //merge props + return {stateProps, dispatchProps, ownProps}; + }, {})((props) => { const n = props.stateProps.negative; - return ( -
-
- {n ? '-' : '+'} - {props.stateProps.value} -
- -
- ); + return
+
{n ? '-' : '+'}{props.stateProps.value}
+ +
; }); const Wrapper = () => { + //@ts-ignore const [amount, setAmount] = Horizon.useState(5); - return ( - - - - - ); + return + + + ; }; - Horizon.render(, getE(CONTAINER)); + Horizon.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('+0'); @@ -231,7 +195,7 @@ describe('Redux/React binding adapter', () => { }); expect(getE(RESULT).innerHTML).toBe('+8'); - }); + }) it('Should batch dispatches', async () => { const reduxStore = createStore((state = 0, action) => { @@ -244,32 +208,22 @@ describe('Redux/React binding adapter', () => { function Counter() { renderCounter++; - const value = useSelector(state => state); + const value = useSelector((state) => state); const dispatch = useDispatch(); - return ( -
-

{value}

- -
- ); + return
+

{value}

+ +
; } - Horizon.render( - - - , - getE(CONTAINER) - ); + Horizon.render(, getE(CONTAINER)); expect(getE(RESULT).innerHTML).toBe('0'); expect(renderCounter).toBe(1); @@ -300,49 +254,33 @@ describe('Redux/React binding adapter', () => { const count = createSelectorHook(counterContext)(); const dispatch = createDispatchHook(counterContext)(); - return ( - - ); + return ; } function Toggle() { const check = createSelectorHook(toggleContext)(); const dispatch = createDispatchHook(toggleContext)(); - return ( - - ); + return ; } function Wrapper() { - return ( -
- - - + return
+ + + - - - -
- ); + + + +
; } - Horizon.render(, getE(CONTAINER)); + Horizon.render(, getE(CONTAINER)); expect(getE(BUTTON).innerHTML).toBe('0'); expect(getE(BUTTON2).innerHTML).toBe('false'); diff --git a/scripts/__tests__/HorizonXText/class/ClassException.test.js b/scripts/__tests__/HorizonXText/class/ClassException.test.tsx similarity index 54% rename from scripts/__tests__/HorizonXText/class/ClassException.test.js rename to scripts/__tests__/HorizonXText/class/ClassException.test.tsx index 64cea5aa..7933d376 100644 --- a/scripts/__tests__/HorizonXText/class/ClassException.test.js +++ b/scripts/__tests__/HorizonXText/class/ClassException.test.tsx @@ -1,14 +1,17 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { Text } from '../../jest/commonComponents'; +import * as LogUtils from '../../jest/logUtils'; +import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {Text, triggerClickEvent} from '../../jest/commonComponents'; +import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; describe('测试 Class VNode 清除时,对引用清除', () => { - const { unmountComponentAtNode } = Horizon; - let container = null; + const {unmountComponentAtNode} = Horizon; + let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', isWin: true, - isShow: true, + isShow: true }; beforeEach(() => { @@ -23,7 +26,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { setWin: (state, val) => { state.isWin = val; }, - hide: state => { + hide: (state) => { state.isShow = false; }, updateName: (state, val) => { @@ -36,8 +39,9 @@ describe('测试 Class VNode 清除时,对引用清除', () => { afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -47,23 +51,21 @@ describe('测试 Class VNode 清除时,对引用清除', () => { userStore = useStore('user'); render() { + if(!this.userStore) return
; // Do not modify the store data in the render method. Otherwise, an infinite loop may occur. this.userStore.updateName(this.userStore.name === 'bing dun dun' ? 'huo dun dun' : 'bing dun dun'); - return ( -
- - -
- ); + return
+ + +
; } } expect(() => { - Horizon.render(, container); - }).toThrow( - 'The number of updates exceeds the upper limit 50.\n' + - ' A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.' - ); + Horizon.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.js b/scripts/__tests__/HorizonXText/class/ClassStateArray.test.js deleted file mode 100644 index c61aed9c..00000000 --- a/scripts/__tests__/HorizonXText/class/ClassStateArray.test.js +++ /dev/null @@ -1,220 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; - -describe('在Class组件中,测试store中的Array', () => { - const { unmountComponentAtNode } = Horizon; - let container = null; - beforeEach(() => { - // 创建一个 DOM 元素作为渲染目标 - container = document.createElement('div'); - document.body.appendChild(container); - - const persons = [ - { name: 'p1', age: 1 }, - { name: 'p2', age: 2 }, - ]; - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.push(person); - }, - delOnePerson: state => { - state.persons.pop(); - }, - clearPersons: state => { - state.persons = null; - }, - }, - }); - }); - - afterEach(() => { - // 退出时进行清理 - unmountComponentAtNode(container); - container.remove(); - container = null; - - clearStore('user'); - }); - - const newPerson = { name: 'p3', age: 3 }; - - class Parent extends Horizon.Component { - userStore = useStore('user'); - - addOnePerson = () => { - this.userStore.addOnePerson(newPerson); - }; - - delOnePerson = () => { - this.userStore.delOnePerson(); - }; - - render() { - return ( -
- - -
{this.props.children}
-
- ); - } - } - - it('测试Array方法: push()、pop()', () => { - class Child extends Horizon.Component { - userStore = useStore('user'); - - render() { - return ( -
- -
- ); - } - } - - Horizon.render(, container); - - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 2'); - // 在Array中增加一个对象 - Horizon.act(() => { - triggerClickEvent(container, 'addBtn'); - }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 3'); - - // 在Array中删除一个对象 - Horizon.act(() => { - triggerClickEvent(container, 'delBtn'); - }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: 2'); - }); - - it('测试Array方法: entries()、push()、shift()、unshift、直接赋值', () => { - let globalStore = null; - - class Child extends Horizon.Component { - userStore = useStore('user'); - - constructor(props) { - super(props); - globalStore = this.userStore; - } - - render() { - const nameList = []; - const entries = this.userStore.$state.persons?.entries(); - if (entries) { - for (const entry of entries) { - nameList.push(entry[1].name); - } - } - - return ( -
- -
- ); - } - } - - Horizon.render(, container); - - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); - // push - globalStore.$state.persons.push(newPerson); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); - - // shift - globalStore.$state.persons.shift({ name: 'p0', age: 0 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3'); - - // 赋值[2] - globalStore.$state.persons[2] = { name: 'p4', age: 4 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p4'); - - // 重新赋值[2] - globalStore.$state.persons[2] = { name: 'p5', age: 5 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p5'); - - // unshift - globalStore.$state.persons.unshift({ name: 'p1', age: 1 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3 p5'); - - // 重新赋值 null - globalStore.$state.persons = null; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); - - // 重新赋值 [{ name: 'p1', age: 1 }] - globalStore.$state.persons = [{ name: 'p1', age: 1 }]; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1'); - }); - - it('测试Array方法: forEach()', () => { - let globalStore = null; - - class Child extends Horizon.Component { - userStore = useStore('user'); - - constructor(props) { - super(props); - globalStore = this.userStore; - } - - render() { - const nameList = []; - this.userStore.$state.persons?.forEach(per => { - nameList.push(per.name); - }); - - return ( -
- -
- ); - } - } - - Horizon.render(, container); - - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); - // push - globalStore.$state.persons.push(newPerson); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); - - // shift - globalStore.$state.persons.shift({ name: 'p0', age: 0 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3'); - - // 赋值[2] - globalStore.$state.persons[2] = { name: 'p4', age: 4 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p4'); - - // 重新赋值[2] - globalStore.$state.persons[2] = { name: 'p5', age: 5 }; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p2 p3 p5'); - - // unshift - globalStore.$state.persons.unshift({ name: 'p1', age: 1 }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3 p5'); - - // 重新赋值 null - globalStore.$state.persons = null; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); - - // 重新赋值 [{ name: 'p1', age: 1 }] - globalStore.$state.persons = [{ name: 'p1', age: 1 }]; - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1'); - }); -}); diff --git a/scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx b/scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx new file mode 100644 index 00000000..2ca08ef5 --- /dev/null +++ b/scripts/__tests__/HorizonXText/class/ClassStateArray.test.tsx @@ -0,0 +1,231 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from '../../jest/logUtils'; +import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {App, Text, triggerClickEvent} from '../../jest/commonComponents'; +import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; + +type Person = {name:string,age:number}; + +const persons:Person[] = [{ name: 'p1', age: 1 }, { name: 'p2', age: 2 }]; +let useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: persons, + }, + actions: { + addOnePerson: (state, person) => { + state.persons.push(person); + }, + delOnePerson: (state) => { + state.persons.pop(); + }, + clearPersons: (state) => { + state.persons = []; + }, + }, +}); + +describe('在Class组件中,测试store中的Array', () => { + const { unmountComponentAtNode } = Horizon; + let container:HTMLElement|null = null; + beforeEach(() => { + // 创建一个 DOM 元素作为渲染目标 + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + // 退出时进行清理 + unmountComponentAtNode(container); + container?.remove(); + container = null; + LogUtils.clear(); + + clearStore('user'); + }); + + const newPerson = { name: 'p3', age: 3 }; + class Parent extends Horizon.Component { + userStore = useUserStore(); + props:{ + children:any[] + }; + + constructor(props){ + super(props); + this.props = props; + } + + addOnePerson = () => { + this.userStore.addOnePerson(newPerson); + } + + delOnePerson = () => { + this.userStore.delOnePerson(); + } + + render() { + return
+ + +
+ {this.props.children} +
+
+ } + } + + it('测试Array方法: push()、pop()', () => { + class Child extends Horizon.Component { + userStore = useUserStore(); + + render() { + return ( +
+ +
+ ); + } + } + + Horizon.render(, container); + + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); + // 在Array中增加一个对象 + Horizon.act(() => { + triggerClickEvent(container, 'addBtn'); + }); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3'); + + // 在Array中删除一个对象 + Horizon.act(() => { + triggerClickEvent(container, 'delBtn'); + }); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2'); + }); + + it('测试Array方法: entries()、push()、shift()、unshift、直接赋值', () => { + let globalStore = useUserStore(); + + class Child extends Horizon.Component { + userStore = useUserStore(); + + constructor(props) { + super(props); + globalStore = this.userStore; + } + + render() { + const nameList:string[] = []; + const entries = this.userStore.$s.persons?.entries(); + if (entries) { + for (const entry of entries) { + nameList.push(entry[1].name); + } + } + + return ( +
+ +
+ ); + } + } + + Horizon.render(, container); + + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); + // push + globalStore?.$s.persons.push(newPerson); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); + + // shift + // @ts-ignore TODO:why has this function argument? + globalStore.$s.persons.shift({ name: 'p0', age: 0 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3'); + + // 赋值[2] + globalStore.$s.persons[2] = { name: 'p4', age: 4 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p4'); + + // 重新赋值[2] + globalStore.$s.persons[2] = { name: 'p5', age: 5 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p5'); + + // unshift + globalStore.$s.persons.unshift({ name: 'p1', age: 1 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3 p5'); + + // 重新赋值 [] + globalStore.$s.persons = []; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); + + // 重新赋值 [{ name: 'p1', age: 1 }] + globalStore.$s.persons = [{ name: 'p1', age: 1 }]; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1'); + }); + + it('测试Array方法: forEach()', () => { + let globalStore = useUserStore(); + globalStore.$s.persons.push({ name: 'p2', age: 2 }); + class Child extends Horizon.Component { + userStore = useUserStore(); + + constructor(props) { + super(props); + globalStore = this.userStore; + } + + render() { + const nameList:string[] = []; + this.userStore.$s.persons.forEach((per:Person) => { + nameList.push(per.name); + }); + + return ( +
+ +
+ ); + } + } + + Horizon.render(, container); + + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); + // push + globalStore.$s.persons.push(newPerson); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); + + // shift + // @ts-ignore TODO:why has this function argument? + globalStore.$s.persons.shift({ name: 'p0', age: 0 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3'); + + // 赋值[2] + globalStore.$s.persons[2] = { name: 'p4', age: 4 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p4'); + + // 重新赋值[2] + globalStore.$s.persons[2] = { name: 'p5', age: 5 }; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p2 p3 p5'); + + // unshift + globalStore.$s.persons.unshift({ name: 'p1', age: 1 }); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3 p5'); + + // 重新赋值 [] + globalStore.$s.persons = []; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); + + // 重新赋值 [{ name: 'p1', age: 1 }] + globalStore.$s.persons = [{ name: 'p1', age: 1 }]; + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1'); + }); + +}); diff --git a/scripts/__tests__/HorizonXText/class/ClassStateMap.test.js b/scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx similarity index 58% rename from scripts/__tests__/HorizonXText/class/ClassStateMap.test.js rename to scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx index 071ad650..bb790d8a 100644 --- a/scripts/__tests__/HorizonXText/class/ClassStateMap.test.js +++ b/scripts/__tests__/HorizonXText/class/ClassStateMap.test.tsx @@ -1,45 +1,48 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { App, Text, triggerClickEvent } from '../../jest/commonComponents'; +import * as LogUtils from '../../jest/logUtils'; +import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {App, Text, triggerClickEvent} from '../../jest/commonComponents'; +import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; + +const useUserStore = createStore({ + id: 'user', + state: { + type: 'bing dun dun', + persons: new Map([['p1', 1], ['p2', 2]]), + }, + actions: { + addOnePerson: (state, person) => { + state.persons.set(person.name, person.age); + }, + delOnePerson: (state, person) => { + state.persons.delete(person.name); + }, + clearPersons: (state) => { + state.persons.clear(); + }, + reset: (state)=>{ + state.persons=new Map([['p1', 1], ['p2', 2]]); + } + }, +}); describe('在Class组件中,测试store中的Map', () => { const { unmountComponentAtNode } = Horizon; - let container = null; + let container:HTMLElement|null = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement('div'); document.body.appendChild(container); - const persons = new Map([ - ['p1', 1], - ['p2', 2], - ]); - - createStore({ - id: 'user', - state: { - type: 'bing dun dun', - persons: persons, - }, - actions: { - addOnePerson: (state, person) => { - state.persons.set(person.name, person.age); - }, - delOnePerson: (state, person) => { - state.persons.delete(person.name); - }, - clearPersons: state => { - state.persons.clear(); - }, - }, - }); + useUserStore().reset(); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -47,17 +50,23 @@ describe('在Class组件中,测试store中的Map', () => { const newPerson = { name: 'p3', age: 3 }; class Parent extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); + props = {children:[]} + + constructor(props){ + super(props); + this.props = props; + } addOnePerson = () => { this.userStore.addOnePerson(newPerson); - }; + } delOnePerson = () => { this.userStore.delOnePerson(newPerson); - }; + } clearPersons = () => { this.userStore.clearPersons(); - }; + } render() { return ( @@ -71,7 +80,9 @@ describe('在Class组件中,测试store中的Map', () => { -
{this.props.children}
+
+ {this.props.children} +
); } @@ -79,12 +90,12 @@ describe('在Class组件中,测试store中的Map', () => { it('测试Map方法: set()、delete()、clear()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { return (
- +
); } @@ -92,33 +103,33 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 3'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 2'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#size').innerHTML).toBe('persons number: 0'); + expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0'); }); it('测试Map方法: keys()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { - const nameList = []; - const keys = this.userStore.$state.persons.keys(); + const nameList:string[] = []; + const keys = this.userStore.$s.persons.keys(); for (const key of keys) { nameList.push(key); } @@ -133,33 +144,33 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: values()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { - const ageList = []; - const values = this.userStore.$state.persons.values(); + const ageList:number[] = []; + const values = this.userStore.$s.persons.values(); for (const val of values) { ageList.push(val); } @@ -174,33 +185,33 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2 3'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: 1 2'); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#ageList').innerHTML).toBe('age list: '); + expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: '); }); it('测试Map方法: entries()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { - const nameList = []; - const entries = this.userStore.$state.persons.entries(); + const nameList:string[] = []; + const entries = this.userStore.$s.persons.entries(); for (const entry of entries) { nameList.push(entry[0]); } @@ -215,33 +226,33 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: forEach()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { - const nameList = []; - this.userStore.$state.persons.forEach((val, key) => { + const nameList:string[] = []; + this.userStore.$s.persons.forEach((val, key) => { nameList.push(key); }); @@ -255,34 +266,34 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); it('测试Map方法: has()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { return (
- +
); } @@ -290,21 +301,21 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: false'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#hasPerson').innerHTML).toBe('has new person: true'); + expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true'); }); it('测试Map方法: for of()', () => { class Child extends Horizon.Component { - userStore = useStore('user'); + userStore = useUserStore(); render() { - const nameList = []; - for (const per of this.userStore.$state.persons) { + const nameList:string[] = []; + for (const per of this.userStore.$s.persons) { nameList.push(per[0]); } @@ -318,23 +329,23 @@ describe('在Class组件中,测试store中的Map', () => { Horizon.render(, container); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // 在Map中增加一个对象 Horizon.act(() => { triggerClickEvent(container, 'addBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2 p3'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3'); // 在Map中删除一个对象 Horizon.act(() => { triggerClickEvent(container, 'delBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: p1 p2'); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2'); // clear Map Horizon.act(() => { triggerClickEvent(container, 'clearBtn'); }); - expect(container.querySelector('#nameList').innerHTML).toBe('name list: '); + expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: '); }); }); diff --git a/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.js b/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx similarity index 62% rename from scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.js rename to scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx index a2226b1c..4fe4e719 100644 --- a/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.js +++ b/scripts/__tests__/HorizonXText/clear/ClassVNodeClear.test.tsx @@ -1,15 +1,17 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { Text, triggerClickEvent } from '../../jest/commonComponents'; -import { getObserver } from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import * as LogUtils from '../../jest/logUtils'; +import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {Text, triggerClickEvent} from '../../jest/commonComponents'; +import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; describe('测试 Class VNode 清除时,对引用清除', () => { - const { unmountComponentAtNode } = Horizon; - let container = null; + const {unmountComponentAtNode} = Horizon; + let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', isWin: true, - isShow: true, + isShow: true }; beforeEach(() => { @@ -24,7 +26,7 @@ describe('测试 Class VNode 清除时,对引用清除', () => { setWin: (state, val) => { state.isWin = val; }, - hide: state => { + hide: (state) => { state.isShow = false; }, updateName: (state, val) => { @@ -37,8 +39,9 @@ describe('测试 Class VNode 清除时,对引用清除', () => { afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -48,14 +51,12 @@ describe('测试 Class VNode 清除时,对引用清除', () => { userStore = useStore('user'); render() { - return ( -
- - {this.userStore.isShow && } -
- ); + return
+ + {this.userStore?.isShow && } +
; } } @@ -63,18 +64,16 @@ describe('测试 Class VNode 清除时,对引用清除', () => { userStore = useStore('user'); setWin = () => { - this.userStore.setWin(!this.userStore.isWin); - }; + this.userStore?.setWin(!this.userStore?.isWin); + } render() { - return ( -
- - {this.userStore.isWin && } -
- ); + return
+ + {this.userStore?.isWin && } +
; } } @@ -84,16 +83,14 @@ describe('测试 Class VNode 清除时,对引用清除', () => { render() { // this.userStore.updateName(this.userStore.name === 'bing dun dun' ? 'huo dun dun' : 'bing dun dun'); - return ( -
- - -
- ); + return
+ + +
; } } - Horizon.render(, container); + Horizon.render(, container); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); diff --git a/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.js b/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx similarity index 59% rename from scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.js rename to scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx index ead0453a..76633b76 100644 --- a/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.js +++ b/scripts/__tests__/HorizonXText/clear/FunctionVNodeClear.test.tsx @@ -1,15 +1,17 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; -import { Text, triggerClickEvent } from '../../jest/commonComponents'; -import { getObserver } from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import * as LogUtils from '../../jest/logUtils'; +import {clearStore, createStore, useStore} from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; +import {Text, triggerClickEvent} from '../../jest/commonComponents'; +import {getObserver} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {describe, it, beforeEach, afterEach, expect} from '@jest/globals'; describe('测试VNode清除时,对引用清除', () => { - const { unmountComponentAtNode } = Horizon; - let container = null; + const {unmountComponentAtNode} = Horizon; + let container:HTMLElement|null = null; let globalState = { name: 'bing dun dun', isWin: true, - isShow: true, + isShow: true }; beforeEach(() => { @@ -24,9 +26,9 @@ describe('测试VNode清除时,对引用清除', () => { setWin: (state, val) => { state.isWin = val; }, - hide: state => { + hide: (state) => { state.isShow = false; - }, + } }, }); }); @@ -34,8 +36,9 @@ describe('测试VNode清除时,对引用清除', () => { afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); - container.remove(); + container?.remove(); container = null; + LogUtils.clear(); clearStore('user'); }); @@ -45,14 +48,12 @@ describe('测试VNode清除时,对引用清除', () => { userStore = useStore('user'); render() { - return ( -
- - {this.userStore.isShow && } -
- ); + return
+ + {this.userStore?.isShow && } +
; } } @@ -60,18 +61,16 @@ describe('测试VNode清除时,对引用清除', () => { userStore = useStore('user'); setWin = () => { - this.userStore.setWin(!this.userStore.isWin); - }; + this.userStore?.setWin(!this.userStore.isWin); + } render() { - return ( -
- - {this.userStore.isWin && } -
- ); + return
+ + {this.userStore?.isWin && } +
; } } @@ -79,16 +78,14 @@ describe('测试VNode清除时,对引用清除', () => { userStore = useStore('user'); render() { - return ( -
- - -
- ); + return
+ + +
; } } - Horizon.render(, container); + Horizon.render(, container); // Parent and Child hold the isWin key expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2); diff --git a/scripts/__tests__/HorizonXText/edgeCases/proxy.test.js b/scripts/__tests__/HorizonXText/edgeCases/proxy.test.js deleted file mode 100644 index a643f311..00000000 --- a/scripts/__tests__/HorizonXText/edgeCases/proxy.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { createProxy } from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; - -describe('Proxy', () => { - const arr = []; - - it('Should not double wrap proxies', async () => { - const proxy1 = createProxy(arr); - - const proxy2 = createProxy(proxy1); - - expect(proxy1 === proxy2).toBe(true); - }); - - it('Should re-use existing proxy of same object', async () => { - const proxy1 = createProxy(arr); - - const proxy2 = createProxy(arr); - - expect(proxy1 === proxy2).toBe(true); - }); -}); diff --git a/scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx b/scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx new file mode 100644 index 00000000..d402e35c --- /dev/null +++ b/scripts/__tests__/HorizonXText/edgeCases/proxy.test.tsx @@ -0,0 +1,48 @@ +import {createProxy} from '../../../../libs/horizon/src/horizonx/proxy/ProxyHandler'; +import {readonlyProxy} from '../../../../libs/horizon/src/horizonx/proxy/readonlyProxy'; +import {describe, beforeEach, afterEach, it, expect} from '@jest/globals'; + +describe('Proxy', () => { + const arr = []; + + it('Should not double wrap proxies', async () => { + const proxy1 = createProxy(arr); + + const proxy2 = createProxy(proxy1); + + expect(proxy1 === proxy2).toBe(true); + }); + + it('Should re-use existing proxy of same object', async () => { + const proxy1 = createProxy(arr); + + const proxy2 = createProxy(arr); + + expect(proxy1 === proxy2).toBe(true); + }); + + it('Readonly proxy should prevent changes', async () => { + const proxy1 = readonlyProxy([1]); + + try{ + proxy1.push('a'); + expect(true).toBe(false);//we expect exception above + }catch(e){ + //expected + } + + try{ + proxy1[0]=null; + expect(true).toBe(false);//we expect exception above + }catch(e){ + //expected + } + + try{ + delete proxy1[0]; + expect(true).toBe(false);//we expect exception above + }catch(e){ + //expected + } + }); +}); diff --git a/scripts/__tests__/jest/logUtils.js b/scripts/__tests__/jest/logUtils.js new file mode 100644 index 00000000..ecb32c98 --- /dev/null +++ b/scripts/__tests__/jest/logUtils.js @@ -0,0 +1,26 @@ +let dataArray = null; + +const log = value => { + if (dataArray === null) { + dataArray = [value]; + } else { + dataArray.push(value); + } +}; + +const getAndClear = () => { + if (dataArray === null) { + return []; + } + const values = dataArray; + dataArray = null; + return values; +}; + +const clear = () => { + dataArray = dataArray ? null : dataArray; +}; + +exports.clear = clear; +exports.log = log; +exports.getAndClear = getAndClear; diff --git a/scripts/rollup/rollup.config.js b/scripts/rollup/rollup.config.js index 290490e5..05c7bf81 100644 --- a/scripts/rollup/rollup.config.js +++ b/scripts/rollup/rollup.config.js @@ -4,6 +4,7 @@ import path from 'path'; 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'; @@ -60,6 +61,7 @@ function genConfig(mode) { }, preventAssignment: true, }), + execute('npm run build-types'), mode === 'production' && terser(), copy([ {