Match-id-0b029a7f0823812017af1a6f83f4b34a1bcff165

This commit is contained in:
* 2022-07-15 22:50:24 +08:00 committed by *
parent d820471f30
commit 8bf64ab1c1
36 changed files with 1459 additions and 1240 deletions

View File

@ -11,7 +11,8 @@ module.exports = {
testEnvironment: 'jest-environment-jsdom-sixteen',
testMatch: [
'<rootDir>/scripts/__tests__/**/*.test.js'
'<rootDir>/scripts/__tests__/**/*.test.js',
'<rootDir>/scripts/__tests__/**/*.test.tsx'
],
timers: 'fake',

View File

@ -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();

View File

@ -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 {

View File

@ -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) => (

View File

@ -1,6 +1,6 @@
// TODO: implement vNode type
import {IObserver} from '../types';
import {IObserver} from './Observer';
/**
* Observer

View File

@ -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 {

View File

@ -3,9 +3,9 @@ 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<string, StoreHandler<any, any, any>>();
@ -13,21 +13,78 @@ function isPromise(obj: any): boolean {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
type PlannedAction<S extends object,F extends ActionFunction<S>>={
action:string,
payload: any[],
resolve: ReturnType<F>
}
type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
state?: S;
options?: { suppressHooks?: boolean };
actions?: A;
id?: string;
computed?: C;
};
export function createStore<S extends object,A extends UserActions<S>,C extends UserComputedValues<S>>(config: StoreConfig<S,A,C>): () => StoreHandler<S,A,C> {
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<any, any, any>;
};
type StoreHandler<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
$subscribe: (listener: () => void) => void;
$unsubscribe: (listener: () => void) => void;
$s: S;
$config: StoreConfig<S, A, C>;
$queue: QueuedStoreActions<S, A>;
$a: StoreActions<S, A>;
$c: UserComputedValues<S>;
reduxHandler?: ReduxStoreHandler;
} & { [K in keyof S]: S[K] } &
{ [K in keyof A]: Action<A[K], S> } &
{ [K in keyof C]: ReturnType<C[K]> };
type PlannedAction<S extends object, F extends ActionFunction<S>> = {
action: string;
payload: any[];
resolve: ReturnType<F>;
};
type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
? []
: ((...b: T) => void) extends (a, ...b: infer I) => void
? I
: [];
type UserActions<S extends object> = { [K: string]: ActionFunction<S> };
type UserComputedValues<S extends object> = { [K: string]: ComputedFunction<S> };
type ActionFunction<S extends object> = (this: StoreHandler<S, any, any>, state: S, ...args: any[]) => any;
type ComputedFunction<S extends object> = (state: S) => any;
type Action<T extends ActionFunction<any>, S extends object> = (
this: StoreHandler<S, any, any>,
...args: RemoveFirstFromTuple<Parameters<T>>
) => ReturnType<T>;
type AsyncAction<T extends ActionFunction<any>, S extends object> = (
this: StoreHandler<S, any, any>,
...args: RemoveFirstFromTuple<Parameters<T>>
) => Promise<ReturnType<T>>;
type StoreActions<S extends object, A extends UserActions<S>> = { [K in keyof A]: Action<A[K], S> };
type QueuedStoreActions<S extends object, A extends UserActions<S>> = { [K in keyof A]: AsyncAction<A[K], S> };
type ComputedValues<S extends object, C extends UserComputedValues<S>> = { [K in keyof C]: ReturnType<C[K]> };
type PostponedAction = (state: object, ...args: any[]) => Promise<any>;
type PostponedActions = { [key: string]: PostponedAction };
export function createStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
config: StoreConfig<S, A, C>
): () => StoreHandler<S, A, C> {
//create a local shalow copy to ensure consistency (if user would change the config object after store creation)
config = {
id: config.id,
options: config.options,
state: config.state,
actions: config.actions ? { ...config.actions } : undefined,
computed: config.computed ? {...config.computed}:undefined
}
computed: config.computed ? { ...config.computed } : undefined,
};
// 校验
if (Object.prototype.toString.call(config) !== '[object Object]') {
@ -38,27 +95,27 @@ export function createStore<S extends object,A extends UserActions<S>,C extends
proxyObj.$pending = false;
const $subscribe = (listener) => {
const $subscribe = listener => {
proxyObj.addListener(listener);
};
const $unsubscribe = (listener) => {
const $unsubscribe = listener => {
proxyObj.removeListener(listener);
};
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
const $actions:Partial<StoreActions<S,A>>={}
const $a: Partial<StoreActions<S, A>> = {};
const $queue: Partial<StoreActions<S, A>> = {};
const $computed:Partial<ComputedValues<S,C>>={}
const handler = {
const $c: Partial<ComputedValues<S, C>> = {};
const handler = ({
$subscribe,
$unsubscribe,
$actions:$actions as StoreActions<S,A>,
$state:proxyObj,
$computed: $computed as ComputedValues<S,C>,
$a: $a as StoreActions<S, A>,
$s: proxyObj,
$c: $c as ComputedValues<S, C>,
$config: config,
$queue: $queue as QueuedStoreActions<S, A>,
} as StoreHandler<S,A,C>;
} as unknown) as StoreHandler<S, A, C>;
function tryNextAction() {
if (!plannedActions.length) {
@ -67,7 +124,9 @@ export function createStore<S extends object,A extends UserActions<S>,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 => {
@ -84,13 +143,13 @@ export function createStore<S extends object,A extends UserActions<S>,C extends
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,31 +161,33 @@ export function createStore<S extends object,A extends UserActions<S>,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
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));
Object.keys(config.computed).forEach(key => {
($c as any)[key] = config.computed![key].bind(handler, readonlyProxy(proxyObj));
// direct store access
Object.defineProperty(handler, key, {
get: $computed[key] as ()=>any
get: $c[key] as () => any,
});
});
}
@ -135,7 +196,7 @@ export function createStore<S extends object,A extends UserActions<S>,C extends
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(() => {

View File

@ -10,6 +10,7 @@
"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"
},

View File

@ -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 (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<div>{props.children}</div>
</div>
);
}
it('测试Array方法: push()、pop()', () => {
function Child(props) {
const userStore = useStore('user');
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.length}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -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 (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<div>{props.children}</div>
</div>
);
}
it('测试Array方法: push()、pop()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.length}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -1,25 +1,18 @@
//@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';
describe('测试store中的Map', () => {
const { unmountComponentAtNode } = Horizon;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new Map([
['p1', 1],
['p2', 2],
]);
createStore({
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
persons: new Map([
['p1', 1],
['p2', 2],
]),
},
actions: {
addOnePerson: (state, person) => {
@ -31,15 +24,32 @@ describe('测试store中的Map', () => {
clearPersons: state => {
state.persons.clear();
},
reset: state => {
state.persons = new Map([
['p1', 1],
['p2', 2],
]);
},
},
});
describe('测试store中的Map', () => {
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?.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 (
<div>
<Text id={'size'} text={`persons number: ${userStore.$state.persons.size}`} />
<Text id={'size'} text={`persons number: ${userStore.$s.persons.size}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.has(newPerson.name)}`} />
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson.name)}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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: ');
});
});

View File

@ -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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -1,25 +1,18 @@
//@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';
describe('测试store中的Set', () => {
const { unmountComponentAtNode } = Horizon;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new Set([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]);
createStore({
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
persons: new Set([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]),
},
actions: {
addOnePerson: (state, person) => {
@ -31,23 +24,39 @@ describe('测试store中的Set', () => {
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: HTMLElement | null = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
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 (
<div>
<Text id={'size'} text={`persons number: ${userStore.$state.persons.size}`} />
<Text id={'size'} text={`persons number: ${userStore.$s.persons.size}`} />
<Text id={'lastAge'} text={`last person age: ${personArr[personArr.length - 1]?.age ?? 0}`} />
</div>
);
@ -94,35 +103,35 @@ describe('测试store中的Set', () => {
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.has(newPerson)}`} />
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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: ');
});
});

View File

@ -1,25 +1,18 @@
//@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';
describe('测试store中的WeakMap', () => {
const { unmountComponentAtNode } = Horizon;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new WeakMap([
[{ name: 'p1' }, 1],
[{ name: 'p2' }, 2],
]);
createStore({
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
persons: new WeakMap([
[{ name: 'p1' }, 1],
[{ name: 'p2' }, 2],
]),
},
actions: {
addOnePerson: (state, person) => {
@ -29,17 +22,34 @@ describe('测试store中的WeakMap', () => {
state.persons.delete(person);
},
clearPersons: state => {
state.persons.clear();
state.persons = new WeakMap([]);
},
reset: state => {
state.persons = new WeakMap([
[{ name: 'p1' }, 1],
[{ name: 'p2' }, 2],
]);
},
},
});
describe('测试store中的WeakMap', () => {
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?.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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.has(newPerson)}`} />
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.get(newPerson)}`} />
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.get(newPerson)}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -1,25 +1,18 @@
//@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';
describe('测试store中的WeakSet', () => {
const { unmountComponentAtNode } = Horizon;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new WeakSet([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]);
createStore({
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
persons: new WeakSet([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]),
},
actions: {
addOnePerson: (state, person) => {
@ -29,25 +22,41 @@ describe('测试store中的WeakSet', () => {
state.persons.delete(person);
},
clearPersons: state => {
state.persons.clear();
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: HTMLElement | null = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$state.persons.has(newPerson)}`} />
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -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(<App />, 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);
});
});

View File

@ -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 <div id={RESULT_ID}>{logStore.length}</div>;
}
Horizon.render(<App />, container);
expect(document.getElementById(RESULT_ID).innerHTML).toBe('1');
});
it('Should use actions and update components', () => {
function App() {
const logStore = useLogStore();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
logStore.addLog('a');
}}
>
add
</button>
<p id={RESULT_ID}>{logStore.length}</p>
</div>
);
}
Horizon.render(<App />, container);
Horizon.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID).innerHTML).toBe('2');
});
});

View File

@ -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 <div id={RESULT_ID}>{logStore.length}</div>;
}
Horizon.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1');
});
it('Should use actions and update components', () => {
function App() {
const logStore = useLogStore();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
logStore.addLog('a');
}}
>
add
</button>
<p id={RESULT_ID}>{logStore.length}</p>
</div>
);
}
Horizon.render(<App />, 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 (
<div>
<button
id={BUTTON_ID}
onClick={() => {
incrementStore.doublePlusOne();
}}
>
+
</button>
<p id={RESULT_ID}>{incrementStore.count}</p>
</div>
);
}
Horizon.render(<App />, container);
Horizon.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5');
});
});

View File

@ -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 <div id={RESULT_ID}>{logStore.$computed.length()}</div>;
return <div id={RESULT_ID}>{logStore.$c.length()}</div>;
}
Horizon.render(<App />, 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', () => {
<button
id={BUTTON_ID}
onClick={() => {
logStore.$actions.addLog();
logStore.$a.addLog('data');
}}
>
add
</button>
<p id={RESULT_ID}>{logStore.$computed.length()}</p>
<p id={RESULT_ID}>{logStore.$c.length()}</p>
</div>
);
}
@ -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');
});
});

View File

@ -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 (
<div>
<p id={RESULT_ID}>{store.double}</p>
<button onClick={store.magic} id={BUTTON_ID}>
do magic
<button onClick={store.increaseVal} id={BUTTON_ID}>
increase value
</button>
</div>
);
@ -54,29 +56,29 @@ describe('Self referencing', () => {
Horizon.render(<App />, 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 (
<div>
<p id={RESULT_ID}>{store.magicConstant}</p>
<button onClick={store.doMagic} id={BUTTON_ID}>
do magic
<p id={RESULT_ID}>{store.selfRefStoreValue}</p>
<button onClick={store.doIncreaseVal} id={BUTTON_ID}>
increase value in other store
</button>
</div>
);
@ -95,13 +97,13 @@ describe('Self referencing', () => {
Horizon.render(<App />, 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(<App />, 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');
});
});

View File

@ -8,7 +8,7 @@ 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) {
state.counter++;
}
},
},
computed: {},
computed: {}
});
beforeEach(() => {
@ -44,22 +44,14 @@ describe('Reset', () => {
function App() {
const store = useCounter();
return (
<div>
<p id={RESULT_ID}>{store.$state.counter}</p>
<button onClick={store.increment} id={BUTTON_ID}>
add
</button>
<button
onClick={() => {
return <div>
<p id={RESULT_ID}>{store.$s.counter}</p>
<button onClick={store.increment} id={BUTTON_ID}>add</button>
<button onClick={() => {
store.$reset();
}}
id={RESET_ID}
>
reset
}} id={RESET_ID}>reset
</button>
</div>
);
}
Horizon.render(<App/>, container);

View File

@ -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');

View File

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

View File

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

View File

@ -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 {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,16 +39,14 @@ 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 <div id={RESULT}>{store.getState()}</div>;
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
return <Provider store={reduxStore}>
<Child/>
</Provider>
);
</Provider>;
};
Horizon.render(<Wrapper/>, getE(CONTAINER));
@ -60,27 +61,20 @@ describe('Redux/React binding adapter', () => {
});
const Child = () => {
const store = useStore();
const store = useStore() as unknown as ReduxStoreHandler;
const dispatch = useDispatch();
return (
<div>
return <div>
<p id={RESULT}>{store.getState()}</p>
<button
id={BUTTON}
onClick={() => {
<button id={BUTTON} onClick={() => {
dispatch({type: 'ADD'});
}}
></button>
</div>
);
}}></button>
</div>;
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
return <Provider store={reduxStore}>
<Child/>
</Provider>
);
</Provider>;
};
Horizon.render(<Wrapper/>, getE(CONTAINER));
@ -101,29 +95,21 @@ describe('Redux/React binding adapter', () => {
});
const Child = () => {
const count = useSelector(state => state);
const count = useSelector((state) => state);
const dispatch = useDispatch();
return (
<div>
return <div>
<p id={RESULT}>{count}</p>
<button
id={BUTTON}
onClick={() => {
<button id={BUTTON} onClick={() => {
dispatch({type: 'ADD'});
}}
>
click
}}>click
</button>
</div>
);
</div>;
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
return <Provider store={reduxStore}>
<Child/>
</Provider>
);
</Provider>;
};
Horizon.render(<Wrapper/>, getE(CONTAINER));
@ -139,77 +125,55 @@ describe('Redux/React binding adapter', () => {
});
it('Should use connect', async () => {
const reduxStore = createStore(
(state, action) => {
const reduxStore = createStore((state, action) => {
switch (action.type) {
case 'INCREMENT':
case('INCREMENT'):
return {
...state,
value: state.negative ? state.value - action.amount : state.value + action.amount,
value: state.negative ? state.value - action.amount : state.value + action.amount
};
case 'TOGGLE':
case('TOGGLE'):
return {
...state,
negative: !state.negative,
negative: !state.negative
};
default:
return state;
}
},
{ negative: false, value: 0 }
);
}, {negative: false, value: 0});
const Child = connect(
(state, ownProps) => {
const Child = connect((state, ownProps) => {
// map state to props
return {...state, ...ownProps};
},
(dispatch, ownProps) => {
}, (dispatch, ownProps) => {
// map dispatch to props
return {
increment: () => dispatch({ type: 'INCREMENT', amount: ownProps.amount }),
// @ts-ignore
increment: () => dispatch({type: 'INCREMENT', amount: ownProps?.amount})
};
},
(stateProps, dispatchProps, ownProps) => {
}, (stateProps, dispatchProps, ownProps) => {
//merge props
return {stateProps, dispatchProps, ownProps};
},
{}
)(props => {
}, {})((props) => {
const n = props.stateProps.negative;
return (
<div>
<div id={RESULT}>
{n ? '-' : '+'}
{props.stateProps.value}
</div>
<button
id={BUTTON}
onClick={() => {
return <div>
<div id={RESULT}>{n ? '-' : '+'}{props.stateProps.value}</div>
<button id={BUTTON} onClick={() => {
props.dispatchProps.increment();
}}
>
add {props.ownProps.amount}
</button>
</div>
);
}}>add {props.ownProps.amount}</button>
</div>;
});
const Wrapper = () => {
//@ts-ignore
const [amount, setAmount] = Horizon.useState(5);
return (
<Provider store={reduxStore}>
return <Provider store={reduxStore}>
<Child amount={amount}/>
<button
id={BUTTON2}
onClick={() => {
<button id={BUTTON2} onClick={() => {
setAmount(3);
}}
>
change amount
}}>change amount
</button>
</Provider>
);
</Provider>;
};
Horizon.render(<Wrapper/>, getE(CONTAINER));
@ -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 (
<div>
return <div>
<p id={RESULT}>{value}</p>
<button
id={BUTTON}
onClick={() => {
<button id={BUTTON} onClick={() => {
batch(() => {
for (let i = 0; i < 10; i++) {
dispatch({type: 'ADD'});
}
});
}}
></button>
</div>
);
}}></button>
</div>;
}
Horizon.render(
<Provider store={reduxStore}>
<Counter />
</Provider>,
getE(CONTAINER)
);
Horizon.render(<Provider store={reduxStore}><Counter/></Provider>, getE(CONTAINER));
expect(getE(RESULT).innerHTML).toBe('0');
expect(renderCounter).toBe(1);
@ -300,37 +254,22 @@ describe('Redux/React binding adapter', () => {
const count = createSelectorHook(counterContext)();
const dispatch = createDispatchHook(counterContext)();
return (
<button
id={BUTTON}
onClick={() => {
return <button id={BUTTON} onClick={() => {
dispatch({type: 'ADD'});
}}
>
{count}
</button>
);
}}>{count}</button>;
}
function Toggle() {
const check = createSelectorHook(toggleContext)();
const dispatch = createDispatchHook(toggleContext)();
return (
<button
id={BUTTON2}
onClick={() => {
return <button id={BUTTON2} onClick={() => {
dispatch({type: 'TOGGLE'});
}}
>
{check ? 'true' : 'false'}
</button>
);
}}>{check ? 'true' : 'false'}</button>;
}
function Wrapper() {
return (
<div>
return <div>
<Provider store={counterStore} context={counterContext}>
<Counter/>
</Provider>
@ -338,8 +277,7 @@ describe('Redux/React binding adapter', () => {
<Provider store={toggleStore} context={toggleContext}>
<Toggle/>
</Provider>
</div>
);
</div>;
}
Horizon.render(<Wrapper/>, getE(CONTAINER));

View File

@ -1,14 +1,17 @@
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 { Text } from '../../jest/commonComponents';
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;
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 <div />;
// 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 (
<div>
return <div>
<Text id={'name'} text={`name: ${this.userStore.name}`}/>
<Text id={'isWin'} text={`isWin: ${this.userStore.isWin}`}/>
</div>
);
</div>;
}
}
expect(() => {
Horizon.render(<Child/>, container);
}).toThrow(
'The number of updates exceeds the upper limit 50.\n' +
' A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.'
);
}).toThrow('The number of updates exceeds the upper limit 50.\n' +
' A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.');
});
});

View File

@ -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 (
<div>
<button id={'addBtn'} onClick={this.addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={this.delOnePerson}>
delete person
</button>
<div>{this.props.children}</div>
</div>
);
}
}
it('测试Array方法: push()、pop()', () => {
class Child extends Horizon.Component {
userStore = useStore('user');
render() {
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${this.userStore.persons.length}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -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 <div>
<button id={'addBtn'} onClick={this.addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={this.delOnePerson}>
delete person
</button>
<div>
{this.props.children}
</div>
</div>
}
}
it('测试Array方法: push()、pop()', () => {
class Child extends Horizon.Component {
userStore = useUserStore();
render() {
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${this.userStore.persons.length}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Horizon.render(<App parent={Parent} child={Child} />, 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');
});
});

View File

@ -1,25 +1,14 @@
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';
describe('在Class组件中测试store中的Map', () => {
const { unmountComponentAtNode } = Horizon;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new Map([
['p1', 1],
['p2', 2],
]);
createStore({
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
persons: new Map([['p1', 1], ['p2', 2]]),
},
actions: {
addOnePerson: (state, person) => {
@ -28,18 +17,32 @@ describe('在Class组件中测试store中的Map', () => {
delOnePerson: (state, person) => {
state.persons.delete(person.name);
},
clearPersons: state => {
clearPersons: (state) => {
state.persons.clear();
},
reset: (state)=>{
state.persons=new Map([['p1', 1], ['p2', 2]]);
}
},
});
describe('在Class组件中测试store中的Map', () => {
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?.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', () => {
<button id={'clearBtn'} onClick={this.clearPersons}>
clear persons
</button>
<div>{this.props.children}</div>
<div>
{this.props.children}
</div>
</div>
);
}
@ -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 (
<div>
<Text id={'size'} text={`persons number: ${this.userStore.$state.persons.size}`} />
<Text id={'size'} text={`persons number: ${this.userStore.$s.persons.size}`} />
</div>
);
}
@ -92,33 +103,33 @@ describe('在Class组件中测试store中的Map', () => {
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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 (
<div>
<Text id={'hasPerson'} text={`has new person: ${this.userStore.$state.persons.has(newPerson.name)}`} />
<Text id={'hasPerson'} text={`has new person: ${this.userStore.$s.persons.has(newPerson.name)}`} />
</div>
);
}
@ -290,21 +301,21 @@ describe('在Class组件中测试store中的Map', () => {
Horizon.render(<App parent={Parent} child={Child} />, 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(<App parent={Parent} child={Child} />, 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: ');
});
});

View File

@ -1,15 +1,17 @@
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 {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;
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 (
<div>
<button id={'hideBtn'} onClick={this.userStore.hide}>
return <div>
<button id={'hideBtn'} onClick={this.userStore?.hide}>
toggle
</button>
{this.userStore.isShow && <Parent />}
</div>
);
{this.userStore?.isShow && <Parent/>}
</div>;
}
}
@ -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 (
<div>
return <div>
<button id={'toggleBtn'} onClick={this.setWin}>
toggle
</button>
{this.userStore.isWin && <Child />}
</div>
);
{this.userStore?.isWin && <Child/>}
</div>;
}
}
@ -84,12 +83,10 @@ describe('测试 Class VNode 清除时,对引用清除', () => {
render() {
// this.userStore.updateName(this.userStore.name === 'bing dun dun' ? 'huo dun dun' : 'bing dun dun');
return (
<div>
<Text id={'name'} text={`name: ${this.userStore.name}`} />
<Text id={'isWin'} text={`isWin: ${this.userStore.isWin}`} />
</div>
);
return <div>
<Text id={'name'} text={`name: ${this.userStore?.name}`}/>
<Text id={'isWin'} text={`isWin: ${this.userStore?.isWin}`}/>
</div>;
}
}

View File

@ -1,15 +1,17 @@
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 {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;
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 (
<div>
<button id={'hideBtn'} onClick={this.userStore.hide}>
return <div>
<button id={'hideBtn'} onClick={this.userStore?.hide}>
toggle
</button>
{this.userStore.isShow && <Parent />}
</div>
);
{this.userStore?.isShow && <Parent/>}
</div>;
}
}
@ -60,18 +61,16 @@ describe('测试VNode清除时对引用清除', () => {
userStore = useStore('user');
setWin = () => {
this.userStore.setWin(!this.userStore.isWin);
};
this.userStore?.setWin(!this.userStore.isWin);
}
render() {
return (
<div>
return <div>
<button id={'toggleBtn'} onClick={this.setWin}>
toggle
</button>
{this.userStore.isWin && <Child />}
</div>
);
{this.userStore?.isWin && <Child/>}
</div>;
}
}
@ -79,12 +78,10 @@ describe('测试VNode清除时对引用清除', () => {
userStore = useStore('user');
render() {
return (
<div>
<Text id={'name'} text={`name: ${this.userStore.name}`} />
<Text id={'isWin'} text={`isWin: ${this.userStore.isWin}`} />
</div>
);
return <div>
<Text id={'name'} text={`name: ${this.userStore?.name}`}/>
<Text id={'isWin'} text={`isWin: ${this.userStore?.isWin}`}/>
</div>;
}
}

View File

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

View File

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

View File

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

View File

@ -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([
{