diff --git a/libs/horizon/package.json b/libs/horizon/package.json index 0eff3ec8..b986d476 100644 --- a/libs/horizon/package.json +++ b/libs/horizon/package.json @@ -12,5 +12,6 @@ "engines": { "node": ">=0.10.0" }, - "dependencies": {} + "dependencies": {}, + "types": "@types/index.d.ts" } diff --git a/libs/horizon/src/horizonx/adapters/redux.ts b/libs/horizon/src/horizonx/adapters/redux.ts index fb19cf9a..fad3a6ef 100644 --- a/libs/horizon/src/horizonx/adapters/redux.ts +++ b/libs/horizon/src/horizonx/adapters/redux.ts @@ -52,7 +52,7 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): }, }, options: { - suppressHooks: true, + reduxAdapter: true, }, })(); diff --git a/libs/horizon/src/horizonx/adapters/reduxPromiseMiddleware.ts b/libs/horizon/src/horizonx/adapters/reduxPromiseMiddleware.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts index a540e199..f67dcc47 100644 --- a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts @@ -38,7 +38,9 @@ export function createProxy(rawObj: any, hookObserver = true): any { // 创建Proxy let proxyObj; - if (isArray(rawObj)) { + if (!hookObserver) { + proxyObj = createObjectProxy(rawObj,true); + } else if (isArray(rawObj)) { // 数组 proxyObj = createArrayProxy(rawObj as []); } else if (isCollection(rawObj)) { diff --git a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts index 28749a57..4dc27a0b 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts @@ -2,16 +2,16 @@ import { isSame } from '../../CommonUtils'; import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; import { OBSERVER_KEY } from '../../Constants'; -export function createObjectProxy(rawObj: T): ProxyHandler { +export function createObjectProxy(rawObj: T, singleLevel = false): ProxyHandler { const proxy = new Proxy(rawObj, { - get, + get: (...args) => get(...args, singleLevel), set, }); return proxy; } -export function get(rawObj: object, key: string | symbol, receiver: any): any { +export function get(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any { // The observer object of symbol ('_horizonObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep. if (key === OBSERVER_KEY) { return undefined; @@ -34,7 +34,7 @@ export function get(rawObj: object, key: string | symbol, receiver: any): any { // 对于prototype不做代理 if (key !== 'prototype') { // 对于value也需要进一步代理 - const valProxy = createProxy(value, hookObserverMap.get(rawObj)); + const valProxy = singleLevel ? value : createProxy(value, hookObserverMap.get(rawObj)); return valProxy; } diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index e85391d8..bdbf8f77 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -87,7 +87,7 @@ export function createStore, C extend throw new Error('store obj must be pure object'); } - const proxyObj = createProxy(config.state, !config.options?.suppressHooks); + const proxyObj = createProxy(config.state, !config.options?.reduxAdapter); proxyObj.$pending = false; diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx b/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx index 66c287e9..d7cab296 100644 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx +++ b/scripts/__tests__/HorizonXText/StoreFunctionality/async.test.tsx @@ -1,5 +1,5 @@ //@ts-ignore -import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as Horizon from '../../../../libs/horizon'; import { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler'; import { triggerClickEvent } from '../../jest/commonComponents'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; @@ -8,159 +8,90 @@ const { unmountComponentAtNode } = Horizon; function postpone(timer, func) { return new Promise(resolve => { - setTimeout(function() { + window.setTimeout(function () { + console.log('resolving postpone'); resolve(func()); }, timer); }); } -describe('Asynchronous functions', () => { - let container: HTMLElement | null = null; - - const COUNTER_ID = 'counter'; - const TOGGLE_ID = 'toggle'; - const TOGGLE_FAST_ID = 'toggleFast'; - const RESULT_ID = 'result'; - - let useAsyncCounter; +describe('Asynchronous store', () => { + const useAsyncCounter = createStore({ + state: { + counter: 0, + check: false, + }, + actions: { + increment: function (state) { + return new Promise(resolve => { + window.setTimeout(() => { + state.counter++; + resolve(true); + }, 10); + }); + }, + toggle: function (state) { + state.check = !state.check; + }, + reset: function (state) { + state.check = false; + state.counter = 0; + }, + }, + computed: { + value: state => { + return (state.check ? 'true' : 'false') + state.counter; + }, + }, + }); beforeEach(() => { - useAsyncCounter = createStore({ - state: { - counter: 0, - check: false, - }, - actions: { - increment: function(state) { - return new Promise(resolve => { - setTimeout(() => { - state.counter++; - resolve(true); - }, 100); - }); - }, - toggle: function(state) { - state.check = !state.check; - }, - }, - computed: { - value: state => { - return (state.check ? 'true' : 'false') + state.counter; - }, - }, - }); - container = document.createElement('div'); - document.body.appendChild(container); + useAsyncCounter().reset(); }); - afterEach(() => { - unmountComponentAtNode(container); - container?.remove(); - container = null; - }); - - it('Should wait for async actions', async () => { - // @ts-ignore - jest.useRealTimers(); - let globalStore; - - function App() { - const store = useAsyncCounter(); - globalStore = store; - - return ( -
-

{store.value}

- - - -
- ); - } - - Horizon.render(, container); - - // initial state - 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'); - - // counter increment is slow. slow toggle waits for result - Horizon.act(() => { - triggerClickEvent(container, COUNTER_ID); - }); - Horizon.act(() => { - triggerClickEvent(container, TOGGLE_ID); - }); - - 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'); - - // at 150ms counter increment will be resolved and slow toggle immediately after - const t150 = postpone(150, () => { - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('true1'); - }); - - // before that, two more actions are added to queue - another counter and slow toggle - Horizon.act(() => { - triggerClickEvent(container, COUNTER_ID); - }); - Horizon.act(() => { - triggerClickEvent(container, TOGGLE_ID); - }); - - // at 250ms they should be already resolved - const t250 = postpone(250, () => { - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false2'); - }); - - await Promise.all([t150, t250]); - }); - - it('call async action by then', async () => { - // @ts-ignore + it('should return promise when queued function is called', () => { jest.useFakeTimers(); - let globalStore; - function App() { - const store = useAsyncCounter(); - globalStore = store; + const store = useAsyncCounter(); - return ( -
-

{store.value}

-
- ); - } + return new Promise(resolve => { + store.$queue.increment().then(() => { + expect(store.counter == 1); + resolve(true); + }); - Horizon.render(, container); - - // call async action by then - globalStore.$queue.increment().then(() => { - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false1'); + jest.advanceTimersByTime(150); }); + }); - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false0'); + it('should queue async functions', () => { + jest.useFakeTimers(); + return new Promise(resolve => { + const store = useAsyncCounter(); - // past 150 ms - // @ts-ignore - jest.advanceTimersByTime(150); + //initial value + expect(store.value).toBe('false0'); + + // no blocking action action + store.$queue.toggle(); + expect(store.value).toBe('true0'); + + // store is not updated before blocking action is resolved + store.$queue.increment(); + const togglePromise = store.$queue.toggle(); + expect(store.value).toBe('true0'); + + // fast action is resolved immediatelly + store.toggle(); + expect(store.value).toBe('false0'); + + // queued action waits for blocking action to resolve + togglePromise.then(() => { + expect(store.value).toBe('true1'); + resolve(); + }); + + jest.advanceTimersByTime(150); + }); }); }); diff --git a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterPromiseMiddleware.js b/scripts/__tests__/HorizonXText/adapters/ReduxAdapterPromiseMiddleware.js deleted file mode 100644 index 3c4a8f11..00000000 --- a/scripts/__tests__/HorizonXText/adapters/ReduxAdapterPromiseMiddleware.js +++ /dev/null @@ -1,11 +0,0 @@ -export const ActionType = { - Pending: 'PENDING', - Fulfilled: 'FULFILLED', - Rejected: 'REJECTED', -}; - -export const promise = store => next => action => { - //let result = next(action); - store._horizonXstore.$queue.dispatch(action); - return result; -};