Match-id-30ebfb9de74da58e87335f3d031ca098a506122c

This commit is contained in:
* 2022-08-17 10:09:55 +08:00 committed by *
commit b415fd3346
8 changed files with 81 additions and 158 deletions

View File

@ -12,5 +12,6 @@
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
}, },
"dependencies": {} "dependencies": {},
"types": "@types/index.d.ts"
} }

View File

@ -52,7 +52,7 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
}, },
}, },
options: { options: {
suppressHooks: true, reduxAdapter: true,
}, },
})(); })();

View File

@ -38,7 +38,9 @@ export function createProxy(rawObj: any, hookObserver = true): any {
// 创建Proxy // 创建Proxy
let proxyObj; let proxyObj;
if (isArray(rawObj)) { if (!hookObserver) {
proxyObj = createObjectProxy(rawObj,true);
} else if (isArray(rawObj)) {
// 数组 // 数组
proxyObj = createArrayProxy(rawObj as []); proxyObj = createArrayProxy(rawObj as []);
} else if (isCollection(rawObj)) { } else if (isCollection(rawObj)) {

View File

@ -2,16 +2,16 @@ import { isSame } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY } from '../../Constants'; import { OBSERVER_KEY } from '../../Constants';
export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T> { export function createObjectProxy<T extends object>(rawObj: T, singleLevel = false): ProxyHandler<T> {
const proxy = new Proxy(rawObj, { const proxy = new Proxy(rawObj, {
get, get: (...args) => get(...args, singleLevel),
set, set,
}); });
return proxy; 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. // The observer object of symbol ('_horizonObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep.
if (key === OBSERVER_KEY) { if (key === OBSERVER_KEY) {
return undefined; return undefined;
@ -34,7 +34,7 @@ export function get(rawObj: object, key: string | symbol, receiver: any): any {
// 对于prototype不做代理 // 对于prototype不做代理
if (key !== 'prototype') { if (key !== 'prototype') {
// 对于value也需要进一步代理 // 对于value也需要进一步代理
const valProxy = createProxy(value, hookObserverMap.get(rawObj)); const valProxy = singleLevel ? value : createProxy(value, hookObserverMap.get(rawObj));
return valProxy; return valProxy;
} }

View File

@ -87,7 +87,7 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
throw new Error('store obj must be pure object'); 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; proxyObj.$pending = false;

View File

@ -1,5 +1,5 @@
//@ts-ignore //@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 { createStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler';
import { triggerClickEvent } from '../../jest/commonComponents'; import { triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
@ -8,40 +8,35 @@ const { unmountComponentAtNode } = Horizon;
function postpone(timer, func) { function postpone(timer, func) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(function() { window.setTimeout(function () {
console.log('resolving postpone');
resolve(func()); resolve(func());
}, timer); }, timer);
}); });
} }
describe('Asynchronous functions', () => { describe('Asynchronous store', () => {
let container: HTMLElement | null = null; const useAsyncCounter = createStore({
const COUNTER_ID = 'counter';
const TOGGLE_ID = 'toggle';
const TOGGLE_FAST_ID = 'toggleFast';
const RESULT_ID = 'result';
let useAsyncCounter;
beforeEach(() => {
useAsyncCounter = createStore({
state: { state: {
counter: 0, counter: 0,
check: false, check: false,
}, },
actions: { actions: {
increment: function(state) { increment: function (state) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(() => { window.setTimeout(() => {
state.counter++; state.counter++;
resolve(true); resolve(true);
}, 100); }, 10);
}); });
}, },
toggle: function(state) { toggle: function (state) {
state.check = !state.check; state.check = !state.check;
}, },
reset: function (state) {
state.check = false;
state.counter = 0;
},
}, },
computed: { computed: {
value: state => { value: state => {
@ -49,118 +44,54 @@ describe('Asynchronous functions', () => {
}, },
}, },
}); });
container = document.createElement('div');
document.body.appendChild(container); beforeEach(() => {
useAsyncCounter().reset();
}); });
afterEach(() => { it('should return promise when queued function is called', () => {
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 (
<div>
<p id={RESULT_ID}>{store.value}</p>
<button onClick={store.$queue.increment} id={COUNTER_ID}>
add 1
</button>
<button onClick={store.$queue.toggle} id={TOGGLE_ID}>
slow toggle
</button>
<button onClick={store.toggle} id={TOGGLE_FAST_ID}>
fast toggle
</button>
</div>
);
}
Horizon.render(<App />, 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
jest.useFakeTimers(); jest.useFakeTimers();
let globalStore;
function App() {
const store = useAsyncCounter(); const store = useAsyncCounter();
globalStore = store;
return ( return new Promise(resolve => {
<div> store.$queue.increment().then(() => {
<p id={RESULT_ID}>{store.value}</p> expect(store.counter == 1);
</div> resolve(true);
);
}
Horizon.render(<App />, container);
// call async action by then
globalStore.$queue.increment().then(() => {
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false1');
}); });
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('false0');
// past 150 ms
// @ts-ignore
jest.advanceTimersByTime(150); jest.advanceTimersByTime(150);
}); });
});
it('should queue async functions', () => {
jest.useFakeTimers();
return new Promise(resolve => {
const store = useAsyncCounter();
//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);
});
});
}); });

View File

@ -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;
};