Match-id-7acb2be199fe6acf434203e79bb147823f4b3d8a
This commit is contained in:
commit
2f5d5fa80e
|
@ -45,6 +45,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
isDev: true,
|
isDev: true,
|
||||||
|
isTest: true,
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,3 +4,4 @@ build/
|
||||||
.vscode
|
.vscode
|
||||||
package-lock.json
|
package-lock.json
|
||||||
libs/**/dist
|
libs/**/dist
|
||||||
|
scripts/*.ejs
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
## 0.0.10 (2022-07-14)
|
||||||
|
- **core**: #24 修复lazy包裹memo时,卸载错误
|
||||||
|
- **core**: #21 修复异步更新时路径错误
|
||||||
|
|
||||||
|
## 0.0.9 (2022-07-12)
|
||||||
|
### Bug Fixes
|
||||||
|
- **horizonX**: 修复对store进行deepClone时循环克隆
|
||||||
|
|
||||||
## 0.0.8 (2022-07-08)
|
## 0.0.8 (2022-07-08)
|
||||||
### Features
|
### Features
|
||||||
- 增加HorizonX提供状态管理能力
|
- 增加HorizonX提供状态管理能力
|
||||||
|
|
|
@ -6,7 +6,6 @@ module.exports = {
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||||
'syntax-trailing-function-commas',
|
|
||||||
[
|
[
|
||||||
'@babel/plugin-proposal-object-rest-spread',
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
{ loose: true, useBuiltIns: true },
|
{ loose: true, useBuiltIns: true },
|
||||||
|
@ -34,6 +33,5 @@ module.exports = {
|
||||||
pragma: 'Horizon.createElement',
|
pragma: 'Horizon.createElement',
|
||||||
pragmaFrag: 'Horizon.Fragment'
|
pragmaFrag: 'Horizon.Fragment'
|
||||||
}],
|
}],
|
||||||
'@babel/plugin-transform-flow-strip-types',
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
165
jest.config.js
165
jest.config.js
|
@ -1,183 +1,18 @@
|
||||||
// For a detailed explanation regarding each configuration property, visit:
|
|
||||||
// https://jestjs.io/docs/en/configuration.html
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// All imported modules in your tests should be mocked automatically
|
|
||||||
// automock: false,
|
|
||||||
|
|
||||||
// Stop running tests after `n` failures
|
|
||||||
// bail: 0,
|
|
||||||
|
|
||||||
// Respect "browser" field in package.json when resolving modules
|
|
||||||
// browser: false,
|
|
||||||
|
|
||||||
// The directory where Jest should store its cached dependency information
|
|
||||||
// cacheDirectory: "",
|
|
||||||
|
|
||||||
// Automatically clear mock calls and instances between every test
|
|
||||||
// clearMocks: false,
|
|
||||||
|
|
||||||
// Indicates whether the coverage information should be collected while executing the test
|
|
||||||
// collectCoverage: false,
|
|
||||||
|
|
||||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
|
||||||
// collectCoverageFrom: undefined,
|
|
||||||
|
|
||||||
// The directory where Jest should output its coverage files
|
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
|
|
||||||
// An array of regexp pattern strings used to skip coverage collection
|
|
||||||
// coveragePathIgnorePatterns: [
|
|
||||||
// "\\\\node_modules\\\\"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// A list of reporter names that Jest uses when writing coverage reports
|
|
||||||
// coverageReporters: [
|
|
||||||
// "json",
|
|
||||||
// "text",
|
|
||||||
// "lcov",
|
|
||||||
// "clover"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An object that configures minimum threshold enforcement for coverage results
|
|
||||||
// coverageThreshold: undefined,
|
|
||||||
|
|
||||||
// A path to a custom dependency extractor
|
|
||||||
// dependencyExtractor: undefined,
|
|
||||||
|
|
||||||
// Make calling deprecated APIs throw helpful error messages
|
|
||||||
// errorOnDeprecated: false,
|
|
||||||
|
|
||||||
// Force coverage collection from ignored files using an array of glob patterns
|
|
||||||
// forceCoverageMatch: [],
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once before all test suites
|
|
||||||
// globalSetup: undefined,
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once after all test suites
|
|
||||||
// globalTeardown: undefined,
|
|
||||||
|
|
||||||
// A set of global variables that need to be available in all test environments
|
|
||||||
// globals: {},
|
|
||||||
|
|
||||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
|
||||||
// maxWorkers: "50%",
|
|
||||||
|
|
||||||
// An array of directory names to be searched recursively up from the requiring module's location
|
|
||||||
// moduleDirectories: [
|
|
||||||
// "node_modules"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An array of file extensions your modules use
|
|
||||||
// moduleFileExtensions: [
|
|
||||||
// 'js',
|
|
||||||
// 'ts'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
|
||||||
// moduleNameMapper: {},
|
|
||||||
|
|
||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
|
||||||
// modulePathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Activates notifications for test results
|
|
||||||
// notify: false,
|
|
||||||
|
|
||||||
// An enum that specifies notification mode. Requires { notify: true }
|
|
||||||
// notifyMode: "failure-change",
|
|
||||||
|
|
||||||
// A preset that is used as a base for Jest's configuration
|
|
||||||
// preset: undefined,
|
|
||||||
|
|
||||||
// Run tests from one or more projects
|
|
||||||
// projects: undefined,
|
|
||||||
|
|
||||||
// Use this configuration option to add custom reporters to Jest
|
|
||||||
// reporters: undefined,
|
|
||||||
|
|
||||||
// Automatically reset mock state between every test
|
|
||||||
// resetMocks: false,
|
|
||||||
|
|
||||||
// Reset the module registry before running each individual test
|
|
||||||
resetModules: true,
|
resetModules: true,
|
||||||
|
|
||||||
// A path to a custom resolver
|
|
||||||
// resolver: undefined,
|
|
||||||
|
|
||||||
// Automatically restore mock state between every test
|
|
||||||
// restoreMocks: false,
|
|
||||||
|
|
||||||
// The root directory that Jest should scan for tests and modules within
|
|
||||||
rootDir: process.cwd(),
|
rootDir: process.cwd(),
|
||||||
|
|
||||||
// A list of paths to directories that Jest should use to search for files in
|
|
||||||
// roots: [
|
|
||||||
// '<rootDir>/scripts'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// Allows you to use a custom runner instead of Jest's default test runner
|
|
||||||
// runner: "jest-runner",
|
|
||||||
|
|
||||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
|
||||||
setupFiles: [require.resolve('./scripts/__tests__/jest/jestEnvironment.js')],
|
setupFiles: [require.resolve('./scripts/__tests__/jest/jestEnvironment.js')],
|
||||||
|
|
||||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
|
||||||
setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/jestSetting.js')],
|
setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/jestSetting.js')],
|
||||||
|
|
||||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
|
||||||
// snapshotSerializers: [],
|
|
||||||
|
|
||||||
// The test environment that will be used for testing
|
|
||||||
testEnvironment: 'jest-environment-jsdom-sixteen',
|
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
|
||||||
// testEnvironmentOptions: {},
|
|
||||||
|
|
||||||
// Adds a location field to test results
|
|
||||||
// testLocationInResults: false,
|
|
||||||
|
|
||||||
// The glob patterns Jest uses to detect test files
|
|
||||||
testMatch: [
|
testMatch: [
|
||||||
'<rootDir>/scripts/__tests__/**/*.test.js'
|
'<rootDir>/scripts/__tests__/**/*.test.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
|
||||||
// testPathIgnorePatterns: [
|
|
||||||
// "\\\\node_modules\\\\"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
|
||||||
//testRegex: ['/scripts/jest/dont-run-jest-directly\\.js$'],
|
|
||||||
|
|
||||||
// This option allows the use of a custom results processor
|
|
||||||
// testResultsProcessor: undefined,
|
|
||||||
|
|
||||||
// This option allows use of a custom test runner
|
|
||||||
// testRunner: "jasmine2",
|
|
||||||
|
|
||||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
|
||||||
// testURL: "http://localhost",
|
|
||||||
|
|
||||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
|
||||||
timers: 'fake',
|
timers: 'fake',
|
||||||
|
|
||||||
// A map from regular expressions to paths to transformers
|
|
||||||
// transform: undefined,
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
|
||||||
// transformIgnorePatterns: [
|
|
||||||
// "\\\\node_modules\\\\"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
|
||||||
// unmockedModulePathPatterns: undefined,
|
|
||||||
|
|
||||||
// Indicates whether each individual test should be reported during the run
|
|
||||||
// verbose: undefined,
|
|
||||||
|
|
||||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
|
||||||
// watchPathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Whether to use watchman for file crawling
|
|
||||||
// watchman: true,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"@babel/plugin-syntax-import-meta": "^7.2.0",
|
"@babel/plugin-syntax-import-meta": "^7.2.0",
|
||||||
"@babel/plugin-syntax-jsx": "^7.2.0",
|
"@babel/plugin-syntax-jsx": "^7.2.0",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
|
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
|
||||||
|
"@babel/traverse": "^7.11.0",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"@babel/types": "^7.0.0",
|
"@babel/types": "^7.0.0",
|
||||||
"babel-plugin-tester": "^10.1.0"
|
"babel-plugin-tester": "^10.1.0"
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
区分是否开发者模式
|
区分是否开发者模式
|
||||||
*/
|
*/
|
||||||
declare var isDev: boolean;
|
declare var isDev: boolean;
|
||||||
|
declare var isTest: boolean;
|
||||||
declare const __VERSION__: string;
|
declare const __VERSION__: string;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"horizon"
|
"horizon"
|
||||||
],
|
],
|
||||||
"version": "0.0.8",
|
"version": "0.0.10",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"bugs": "",
|
"bugs": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
// The two constants must be the same as those in horizon.
|
// The two constants must be the same as those in horizon.
|
||||||
export const FunctionComponent = 'FunctionComponent';
|
export const FunctionComponent = 'FunctionComponent';
|
||||||
export const ClassComponent = 'ClassComponent';
|
export const ClassComponent = 'ClassComponent';
|
||||||
|
|
||||||
|
export const OBSERVER_KEY = Symbol('_horizonObserver');
|
||||||
|
|
|
@ -10,7 +10,7 @@ export class HooklessObserver implements IObserver {
|
||||||
|
|
||||||
listeners:(() => void)[] = [];
|
listeners:(() => void)[] = [];
|
||||||
|
|
||||||
useProp(key: string): void {
|
useProp(key: string | symbol): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
addListener(listener: () => void) {
|
addListener(listener: () => void) {
|
||||||
|
@ -21,7 +21,7 @@ export class HooklessObserver implements IObserver {
|
||||||
this.listeners = this.listeners.filter(item => item != listener);
|
this.listeners = this.listeners.filter(item => item != listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
setProp(key: string): void {
|
setProp(key: string | symbol): void {
|
||||||
this.triggerChangeListeners();
|
this.triggerChangeListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class Observer implements IObserver {
|
||||||
|
|
||||||
listeners:(()=>void)[] = [];
|
listeners:(()=>void)[] = [];
|
||||||
|
|
||||||
useProp(key: string): void {
|
useProp(key: string | symbol): void {
|
||||||
const processingVNode = getProcessingVNode();
|
const processingVNode = getProcessingVNode();
|
||||||
if (processingVNode === null || !processingVNode.observers) {
|
if (processingVNode === null || !processingVNode.observers) {
|
||||||
return;
|
return;
|
||||||
|
@ -50,7 +50,7 @@ export class Observer implements IObserver {
|
||||||
this.listeners = this.listeners.filter(item => item != listener);
|
this.listeners = this.listeners.filter(item => item != listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
setProp(key: string): void {
|
setProp(key: string | symbol): void {
|
||||||
const vNodes = this.keyVNodes.get(key);
|
const vNodes = this.keyVNodes.get(key);
|
||||||
vNodes?.forEach((vNode: VNode) => {
|
vNodes?.forEach((vNode: VNode) => {
|
||||||
if (vNode.isStoreChange) {
|
if (vNode.isStoreChange) {
|
||||||
|
@ -85,7 +85,7 @@ export class Observer implements IObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearByVNode(vNode: Vnode): void {
|
clearByVNode(vNode: VNode): void {
|
||||||
const keys = this.vNodeKeys.get(vNode);
|
const keys = this.vNodeKeys.get(vNode);
|
||||||
if (keys) {
|
if (keys) {
|
||||||
keys.forEach((key: any) => {
|
keys.forEach((key: any) => {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import {createObjectProxy} from './handlers/ObjectProxyHandler';
|
import { createObjectProxy } from './handlers/ObjectProxyHandler';
|
||||||
import {Observer} from './Observer';
|
import { Observer } from './Observer';
|
||||||
import {HooklessObserver} from './HooklessObserver';
|
import { HooklessObserver } from './HooklessObserver';
|
||||||
import {isArray, isCollection, isObject} from '../CommonUtils';
|
import { isArray, isCollection, isObject } from '../CommonUtils';
|
||||||
import {createArrayProxy} from './handlers/ArrayProxyHandler';
|
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
||||||
import {createCollectionProxy} from './handlers/CollectionProxyHandler';
|
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
||||||
import { IObserver } from '../types';
|
import { IObserver } from '../types';
|
||||||
|
import { OBSERVER_KEY } from '../Constants';
|
||||||
const OBSERVER_KEY = Symbol('_horizonObserver');
|
|
||||||
|
|
||||||
const proxyMap = new WeakMap();
|
const proxyMap = new WeakMap();
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ export function createProxy(rawObj: any, hookObserver = true): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Observer
|
// 创建Observer
|
||||||
let observer:IObserver = getObserver(rawObj);
|
let observer: IObserver = getObserver(rawObj);
|
||||||
if (!observer) {
|
if (!observer) {
|
||||||
observer = hookObserver ? new Observer() : new HooklessObserver();
|
observer = hookObserver ? new Observer() : new HooklessObserver();
|
||||||
rawObj[OBSERVER_KEY] = observer;
|
rawObj[OBSERVER_KEY] = observer;
|
||||||
|
@ -59,4 +58,3 @@ export function createProxy(rawObj: any, hookObserver = true): any {
|
||||||
export function getObserver(rawObj: any): Observer {
|
export function getObserver(rawObj: any): Observer {
|
||||||
return rawObj[OBSERVER_KEY];
|
return rawObj[OBSERVER_KEY];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,14 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
|
||||||
const ret = Reflect.set(rawObj, key, newValue, receiver);
|
const ret = Reflect.set(rawObj, key, newValue, receiver);
|
||||||
|
|
||||||
const newLength = rawObj.length;
|
const newLength = rawObj.length;
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
|
|
||||||
if (!isSame(newValue, oldValue)) {
|
if (!isSame(newValue, oldValue)) {
|
||||||
tracker.setProp(key);
|
observer.setProp(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldLength !== newLength) {
|
if (oldLength !== newLength) {
|
||||||
tracker.setProp('length');
|
observer.setProp('length');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -40,8 +40,8 @@ function get(rawObj: { size: number }, key: any, receiver: any): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFun(rawObj: { get: (key: any) => any }, key: any) {
|
function getFun(rawObj: { get: (key: any) => any }, key: any) {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.useProp(key);
|
observer.useProp(key);
|
||||||
|
|
||||||
const value = rawObj.get(key);
|
const value = rawObj.get(key);
|
||||||
// 对于value也需要进一步代理
|
// 对于value也需要进一步代理
|
||||||
|
@ -60,14 +60,14 @@ function set(
|
||||||
const newValue = value;
|
const newValue = value;
|
||||||
rawObj.set(key, newValue);
|
rawObj.set(key, newValue);
|
||||||
const valChange = !isSame(newValue, oldValue);
|
const valChange = !isSame(newValue, oldValue);
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
|
|
||||||
if (valChange || !rawObj.has(key)) {
|
if (valChange || !rawObj.has(key)) {
|
||||||
tracker.setProp(COLLECTION_CHANGE);
|
observer.setProp(COLLECTION_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valChange) {
|
if (valChange) {
|
||||||
tracker.setProp(key);
|
observer.setProp(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawObj;
|
return rawObj;
|
||||||
|
@ -78,17 +78,17 @@ function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any)
|
||||||
if (!rawObj.has(value)) {
|
if (!rawObj.has(value)) {
|
||||||
rawObj.add(value);
|
rawObj.add(value);
|
||||||
|
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.setProp(value);
|
observer.setProp(value);
|
||||||
tracker.setProp(COLLECTION_CHANGE);
|
observer.setProp(COLLECTION_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawObj;
|
return rawObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
|
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.useProp(key);
|
observer.useProp(key);
|
||||||
|
|
||||||
return rawObj.has(key);
|
return rawObj.has(key);
|
||||||
}
|
}
|
||||||
|
@ -98,8 +98,8 @@ function clear(rawObj: { size: number; clear: () => void }) {
|
||||||
rawObj.clear();
|
rawObj.clear();
|
||||||
|
|
||||||
if (oldSize > 0) {
|
if (oldSize > 0) {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.allChange();
|
observer.allChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +107,9 @@ function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => v
|
||||||
if (rawObj.has(key)) {
|
if (rawObj.has(key)) {
|
||||||
rawObj.delete(key);
|
rawObj.delete(key);
|
||||||
|
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.setProp(key);
|
observer.setProp(key);
|
||||||
tracker.setProp(COLLECTION_CHANGE);
|
observer.setProp(COLLECTION_CHANGE);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,8 @@ function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => v
|
||||||
}
|
}
|
||||||
|
|
||||||
function size(rawObj: { size: number }) {
|
function size(rawObj: { size: number }) {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.useProp(COLLECTION_CHANGE);
|
observer.useProp(COLLECTION_CHANGE);
|
||||||
return rawObj.size;
|
return rawObj.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,8 +148,8 @@ function forEach(
|
||||||
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
|
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
|
||||||
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
|
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
|
||||||
) {
|
) {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
tracker.useProp(COLLECTION_CHANGE);
|
observer.useProp(COLLECTION_CHANGE);
|
||||||
rawObj.forEach((value, key) => {
|
rawObj.forEach((value, key) => {
|
||||||
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
||||||
const keyProxy = createProxy(key, hookObserverMap.get(rawObj));
|
const keyProxy = createProxy(key, hookObserverMap.get(rawObj));
|
||||||
|
@ -159,9 +159,9 @@ function forEach(
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
|
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
|
||||||
const tracker = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
const hookObserver = hookObserverMap.get(rawObj);
|
const hookObserver = hookObserverMap.get(rawObj);
|
||||||
tracker.useProp(COLLECTION_CHANGE);
|
observer.useProp(COLLECTION_CHANGE);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
next() {
|
next() {
|
||||||
|
@ -170,7 +170,7 @@ function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: b
|
||||||
return { value: createProxy(value, hookObserver), done };
|
return { value: createProxy(value, hookObserver), done };
|
||||||
}
|
}
|
||||||
|
|
||||||
tracker.useProp(COLLECTION_CHANGE);
|
observer.useProp(COLLECTION_CHANGE);
|
||||||
|
|
||||||
let newVal;
|
let newVal;
|
||||||
if (isPair) {
|
if (isPair) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isSame } from '../../CommonUtils';
|
import { isSame } from '../../CommonUtils';
|
||||||
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
|
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
|
||||||
|
import { OBSERVER_KEY } from '../../Constants';
|
||||||
|
|
||||||
export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T> {
|
export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T> {
|
||||||
const proxy = new Proxy(rawObj, {
|
const proxy = new Proxy(rawObj, {
|
||||||
|
@ -10,7 +11,12 @@ export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T>
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get(rawObj: object, key: string, receiver: any): any {
|
export function get(rawObj: object, key: string | symbol, receiver: any): any {
|
||||||
|
// The observer object of symbol ('_horizonObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep.
|
||||||
|
if (key === OBSERVER_KEY) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const observer = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
|
|
||||||
if (key === 'addListener') {
|
if (key === 'addListener') {
|
||||||
|
@ -25,10 +31,15 @@ export function get(rawObj: object, key: string, receiver: any): any {
|
||||||
|
|
||||||
const value = Reflect.get(rawObj, key, receiver);
|
const value = Reflect.get(rawObj, key, receiver);
|
||||||
|
|
||||||
// 对于value也需要进一步代理
|
// 对于prototype不做代理
|
||||||
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
if (key !== 'prototype') {
|
||||||
|
// 对于value也需要进一步代理
|
||||||
|
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
||||||
|
|
||||||
return valProxy;
|
return valProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
|
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
export interface IObserver {
|
export interface IObserver {
|
||||||
|
|
||||||
useProp: (key: string) => void;
|
useProp: (key: string | symbol) => void;
|
||||||
|
|
||||||
addListener: (listener: () => void) => void;
|
addListener: (listener: () => void) => void;
|
||||||
|
|
||||||
removeListener: (listener: () => void) => void;
|
removeListener: (listener: () => void) => void;
|
||||||
|
|
||||||
setProp: (key: string) => void;
|
setProp: (key: string | symbol) => void;
|
||||||
|
|
||||||
triggerChangeListeners: () => void;
|
triggerChangeListeners: () => void;
|
||||||
|
|
||||||
triggerUpdate: (vNode: any) => void;
|
triggerUpdate: (vNode: any) => void;
|
||||||
|
|
||||||
allChange: () => void;
|
allChange: () => void;
|
||||||
|
|
||||||
clearByVNode: (vNode: any) => void;
|
clearByVNode: (vNode: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveFirstFromTuple<T extends any[]> =
|
type RemoveFirstFromTuple<T extends any[]> =
|
||||||
T['length'] extends 0 ? [] :
|
T['length'] extends 0 ? [] :
|
||||||
(((...b: T) => void) extends (a, ...b: infer I) => void ? I : [])
|
(((...b: T) => void) extends (a, ...b: infer I) => void ? I : [])
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ type ComputedValues<S extends object,C extends UserComputedValues<S>> = { [K in
|
||||||
type PostponedAction = (state: object, ...args: any[]) => Promise<any>;
|
type PostponedAction = (state: object, ...args: any[]) => Promise<any>;
|
||||||
type PostponedActions = { [key:string]: PostponedAction }
|
type PostponedActions = { [key:string]: PostponedAction }
|
||||||
|
|
||||||
export type StoreHandler<S extends object,A extends UserActions<S>,C extends UserComputedValues<S>> =
|
export type StoreHandler<S extends object,A extends UserActions<S>,C extends UserComputedValues<S>> =
|
||||||
{$subscribe: ((listener: () => void) => void),
|
{$subscribe: ((listener: () => void) => void),
|
||||||
$unsubscribe: ((listener: () => void) => void),
|
$unsubscribe: ((listener: () => void) => void),
|
||||||
$state: S,
|
$state: S,
|
||||||
|
@ -78,4 +78,4 @@ type ReduxMiddleware = (store:ReduxStoreHandler, extraArgument?:any) =>
|
||||||
(action:(
|
(action:(
|
||||||
ReduxAction|
|
ReduxAction|
|
||||||
((dispatch:(action:ReduxAction)=>void,store:ReduxStoreHandler,extraArgument?:any)=>any)
|
((dispatch:(action:ReduxAction)=>void,store:ReduxStoreHandler,extraArgument?:any)=>any)
|
||||||
)) => ReduxStoreHandler
|
)) => ReduxStoreHandler
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate';
|
||||||
import {BuildErrored, setBuildResult} from './GlobalVar';
|
import {BuildErrored, setBuildResult} from './GlobalVar';
|
||||||
|
|
||||||
function consoleError(error: any): void {
|
function consoleError(error: any): void {
|
||||||
if (isDev) {
|
if (isTest) {
|
||||||
// 只打印message为了让测试用例能pass
|
// 只打印message为了让测试用例能pass
|
||||||
console['error']('The codes throw the error: ' + error.message);
|
console['error']('The codes throw the error: ' + error.message);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -199,6 +199,10 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
|
||||||
for (let i = 1; i < startNodePath.length; i++) {
|
for (let i = 1; i < startNodePath.length; i++) {
|
||||||
const pathIndex = Number(startNodePath[i]);
|
const pathIndex = Number(startNodePath[i]);
|
||||||
node = getChildByIndex(node, pathIndex)!;
|
node = getChildByIndex(node, pathIndex)!;
|
||||||
|
// 路径错误时,回退到从根更新
|
||||||
|
if (node == null) {
|
||||||
|
return treeRoot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
|
|
|
@ -61,6 +61,7 @@ function captureLazyComponent(
|
||||||
if (lazyRender) {
|
if (lazyRender) {
|
||||||
if (lazyVNodeTag === MemoComponent) {
|
if (lazyVNodeTag === MemoComponent) {
|
||||||
// Memo要特殊处理
|
// Memo要特殊处理
|
||||||
|
processing.effectList = null;
|
||||||
const memoVNodeProps = mergeDefaultProps(Component.type, lazyVNodeProps); // 需要整合defaultProps
|
const memoVNodeProps = mergeDefaultProps(Component.type, lazyVNodeProps); // 需要整合defaultProps
|
||||||
return lazyRender(processing, Component, memoVNodeProps, shouldUpdate);
|
return lazyRender(processing, Component, memoVNodeProps, shouldUpdate);
|
||||||
} else {
|
} else {
|
||||||
|
|
94
package.json
94
package.json
|
@ -15,85 +15,49 @@
|
||||||
"watch-test": "yarn test --watch --dev"
|
"watch-test": "yarn test --watch --dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.10.5",
|
"@babel/core": "7.16.7",
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||||
"@babel/core": "^7.11.1",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
|
||||||
"@babel/eslint-parser": "^7.11.4",
|
"@babel/plugin-proposal-object-rest-spread": "7.16.7",
|
||||||
"@babel/helper-module-imports": "^7.10.4",
|
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
||||||
"@babel/parser": "^7.11.3",
|
"@babel/plugin-syntax-jsx": "7.16.7",
|
||||||
"@babel/plugin-external-helpers": "^7.10.4",
|
"@babel/plugin-transform-arrow-functions": "7.16.7",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@babel/plugin-transform-block-scoped-functions": "7.16.7",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0",
|
"@babel/plugin-transform-block-scoping": "7.16.7",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
|
"@babel/plugin-transform-classes": "7.16.7",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.16.0",
|
"@babel/plugin-transform-computed-properties": "7.16.7",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
"@babel/plugin-transform-destructuring": "7.16.7",
|
||||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
"@babel/plugin-transform-for-of": "7.16.7",
|
||||||
"@babel/plugin-transform-arrow-functions": "^7.10.4",
|
"@babel/plugin-transform-literals": "7.16.7",
|
||||||
"@babel/plugin-transform-async-to-generator": "^7.10.4",
|
"@babel/plugin-transform-object-super": "7.16.7",
|
||||||
"@babel/plugin-transform-block-scoped-functions": "^7.10.4",
|
"@babel/plugin-transform-parameters": "7.16.7",
|
||||||
"@babel/plugin-transform-block-scoping": "^7.11.1",
|
"@babel/plugin-transform-runtime": "7.16.7",
|
||||||
"@babel/plugin-transform-classes": "^7.14.2",
|
"@babel/plugin-transform-shorthand-properties": "7.16.7",
|
||||||
"@babel/plugin-transform-computed-properties": "^7.10.4",
|
"@babel/plugin-transform-spread": "7.16.7",
|
||||||
"@babel/plugin-transform-destructuring": "^7.10.4",
|
"@babel/plugin-transform-template-literals": "7.16.7",
|
||||||
"@babel/plugin-transform-for-of": "^7.10.4",
|
"@babel/preset-env": "7.16.7",
|
||||||
"@babel/plugin-transform-literals": "^7.10.4",
|
"@babel/preset-react": "7.16.7",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
"@babel/preset-typescript": "7.16.7",
|
||||||
"@babel/plugin-transform-object-super": "^7.10.4",
|
|
||||||
"@babel/plugin-transform-parameters": "^7.10.5",
|
|
||||||
"@babel/plugin-transform-react-jsx-source": "^7.10.5",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
|
||||||
"@babel/plugin-transform-shorthand-properties": "^7.10.4",
|
|
||||||
"@babel/plugin-transform-spread": "^7.11.0",
|
|
||||||
"@babel/plugin-transform-template-literals": "^7.10.5",
|
|
||||||
"@babel/preset-env": "^7.16.11",
|
|
||||||
"@babel/preset-flow": "^7.10.4",
|
|
||||||
"@babel/preset-react": "^7.16.7",
|
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
|
||||||
"@babel/register": "^7.14.5",
|
|
||||||
"@babel/traverse": "^7.11.0",
|
|
||||||
"@mattiasbuelens/web-streams-polyfill": "^0.3.2",
|
|
||||||
"@rollup/plugin-babel": "^5.3.1",
|
"@rollup/plugin-babel": "^5.3.1",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^17.0.18",
|
"@types/node": "^17.0.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
"@typescript-eslint/eslint-plugin": "4.8.0",
|
||||||
"@typescript-eslint/parser": "^5.15.0",
|
"@typescript-eslint/parser": "4.8.0",
|
||||||
"babel-core": "^6.26.3",
|
|
||||||
"babel-eslint": "^10.0.3",
|
|
||||||
"babel-jest": "^27.5.1",
|
"babel-jest": "^27.5.1",
|
||||||
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
"eslint": "7.13.0",
|
||||||
"babel-preset-env": "^1.7.0",
|
|
||||||
"babel-preset-react": "^6.24.1",
|
|
||||||
"chalk": "^3.0.0",
|
|
||||||
"confusing-browser-globals": "^1.0.9",
|
|
||||||
"core-js": "^3.6.4",
|
|
||||||
"danger": "^9.2.10",
|
|
||||||
"ejs": "^3.1.6",
|
|
||||||
"error-stack-parser": "^2.0.6",
|
|
||||||
"eslint": "^7.32.0",
|
|
||||||
"eslint-config-prettier": "^6.9.0",
|
"eslint-config-prettier": "^6.9.0",
|
||||||
"eslint-plugin-babel": "^5.3.0",
|
|
||||||
"eslint-plugin-flowtype": "^2.25.0",
|
|
||||||
"eslint-plugin-jest": "^22.15.0",
|
"eslint-plugin-jest": "^22.15.0",
|
||||||
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
||||||
"eslint-plugin-no-function-declare-after-return": "^1.0.0",
|
"eslint-plugin-no-function-declare-after-return": "^1.0.0",
|
||||||
"eslint-plugin-react": "^6.7.1",
|
"eslint-plugin-react": "7.14.3",
|
||||||
"jest": "^25.5.4",
|
"jest": "^25.5.4",
|
||||||
"jest-cli": "^25.2.7",
|
|
||||||
"jest-diff": "^25.2.6",
|
|
||||||
"jest-environment-jsdom-sixteen": "^1.0.3",
|
"jest-environment-jsdom-sixteen": "^1.0.3",
|
||||||
"jest-react": "^0.12.0",
|
"prettier": "2.6.2",
|
||||||
"jest-snapshot-serializer-raw": "^1.1.0",
|
|
||||||
"minimist": "^1.2.3",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prettier": "1.19.1",
|
|
||||||
"react-lifecycles-compat": "^3.0.4",
|
|
||||||
"regenerator-runtime": "^0.13.9",
|
|
||||||
"rimraf": "^3.0.0",
|
|
||||||
"rollup": "^2.75.5",
|
"rollup": "^2.75.5",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"typescript": "^3.9.7"
|
"typescript": "4.2.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.x",
|
"node": ">=10.x",
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
|
import { getLogUtils } from '../jest/testUtils';
|
||||||
|
|
||||||
|
describe('ForwardRef', () => {
|
||||||
|
const LogUtils = getLogUtils();
|
||||||
|
it('ForwardRef包裹的函数组件应该正常触发effect', () => {
|
||||||
|
function App(props, ref) {
|
||||||
|
Horizon.useEffect(() => {
|
||||||
|
LogUtils.log('effect');
|
||||||
|
return () => {
|
||||||
|
LogUtils.log('effect remove');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return <button ref={ref}></button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = Horizon.forwardRef(App);
|
||||||
|
|
||||||
|
Horizon.act(() => {
|
||||||
|
Horizon.render(<Wrapper />, container);
|
||||||
|
});
|
||||||
|
expect(LogUtils.getAndClear()).toEqual(['effect']);
|
||||||
|
Horizon.act(() => {
|
||||||
|
Horizon.render(<Wrapper />, container);
|
||||||
|
});
|
||||||
|
expect(LogUtils.getAndClear()).toEqual(['effect remove', 'effect']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('memo组件包裹的类组件', () => {
|
||||||
|
class Component extends Horizon.Component {
|
||||||
|
render() {
|
||||||
|
return <button>123</button>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = Horizon.memo(Component);
|
||||||
|
|
||||||
|
Horizon.act(() => {
|
||||||
|
Horizon.render(<Wrapper />, container);
|
||||||
|
});
|
||||||
|
Horizon.act(() => {
|
||||||
|
Horizon.render(<Wrapper />, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -184,4 +184,30 @@ describe('LazyComponent Test', () => {
|
||||||
container.querySelector('button').click();
|
container.querySelector('button').click();
|
||||||
expect(container.textContent).toBe('Error: num is 2');
|
expect(container.textContent).toBe('Error: num is 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#24 配合memo', async () => {
|
||||||
|
const fnComp = () => {
|
||||||
|
return <h1>horizon</h1>;
|
||||||
|
};
|
||||||
|
const LazyApp = Horizon.lazy(() => ({
|
||||||
|
then(cb) {
|
||||||
|
cb({ default: Horizon.memo(() => fnComp, false) });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
expect(() => {
|
||||||
|
Horizon.render(
|
||||||
|
<Horizon.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<LazyApp text="Lazy" />
|
||||||
|
</Horizon.Suspense>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
|
||||||
|
Horizon.render(
|
||||||
|
<Horizon.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<LazyApp text="Lazy" />
|
||||||
|
</Horizon.Suspense>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,14 +10,6 @@ function dispatchChangeEvent(input) {
|
||||||
|
|
||||||
describe('事件', () => {
|
describe('事件', () => {
|
||||||
const LogUtils = TestUtils.getLogUtils();
|
const LogUtils = TestUtils.getLogUtils();
|
||||||
it('根节点挂载全量事件', () => {
|
|
||||||
const App = () => {
|
|
||||||
return <div />;
|
|
||||||
};
|
|
||||||
Horizon.render(<App />, container);
|
|
||||||
console.log(TestUtils.getEventListeners(container));
|
|
||||||
//expect(TestUtils.getEventListeners(container)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('事件捕获与冒泡', () => {
|
it('事件捕获与冒泡', () => {
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
|
import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler';
|
||||||
|
import { OBSERVER_KEY } from '../../../../libs/horizon/src/horizonx/Constants';
|
||||||
|
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
|
||||||
|
|
||||||
|
describe('测试对store.state对象进行深度克隆', () => {
|
||||||
|
const { unmountComponentAtNode } = Horizon;
|
||||||
|
let container = null;
|
||||||
|
beforeEach(() => {
|
||||||
|
// 创建一个 DOM 元素作为渲染目标
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
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 = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// 退出时进行清理
|
||||||
|
unmountComponentAtNode(container);
|
||||||
|
container.remove();
|
||||||
|
container = null;
|
||||||
|
|
||||||
|
clearStore('user');
|
||||||
|
});
|
||||||
|
|
||||||
|
const newPerson = { name: 'p3', age: 3 };
|
||||||
|
|
||||||
|
function Parent({ children }) {
|
||||||
|
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>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => {
|
||||||
|
let userStore = null;
|
||||||
|
function Child(props) {
|
||||||
|
userStore = useStore('user');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Text id={'hasPerson'} text={`has new person: ${userStore.persons.length}`} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App parent={Parent} child={Child} />, container);
|
||||||
|
|
||||||
|
// The observer object of symbol ('_horizonObserver') cannot be accessed to from Proxy prevent errors caused by clonedeep.
|
||||||
|
expect(userStore.persons[0][OBSERVER_KEY]).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => {
|
||||||
|
let userStore = null;
|
||||||
|
function Child(props) {
|
||||||
|
userStore = useStore('user');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Text id={'hasPerson'} text={`has new person: ${userStore.persons.length}`} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App parent={Parent} child={Child} />, container);
|
||||||
|
|
||||||
|
// NO throw this Exception, TypeError: 'get' on proxy: property 'prototype' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value
|
||||||
|
const proxyObj = userStore.persons[0].constructor;
|
||||||
|
expect(proxyObj.prototype !== undefined).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -2,7 +2,7 @@ import {
|
||||||
createStore,
|
createStore,
|
||||||
applyMiddleware,
|
applyMiddleware,
|
||||||
combineReducers,
|
combineReducers,
|
||||||
bindActionCreators
|
bindActionCreators,
|
||||||
} from '../../../../libs/horizon/src/horizonx/adapters/redux';
|
} from '../../../../libs/horizon/src/horizonx/adapters/redux';
|
||||||
|
|
||||||
describe('Redux adapter', () => {
|
describe('Redux adapter', () => {
|
||||||
|
@ -12,21 +12,21 @@ describe('Redux adapter', () => {
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
expect(reduxStore.getState()).toBe(0);
|
expect(reduxStore.getState()).toBe(0);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('Should use default state, dispatch action and update state', async () => {
|
it('Should use default state, dispatch action and update state', async () => {
|
||||||
const reduxStore = createStore((state, action) => {
|
const reduxStore = createStore((state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case('ADD'):
|
case 'ADD':
|
||||||
return {counter: state.counter + 1}
|
return { counter: state.counter + 1 };
|
||||||
default:
|
default:
|
||||||
return {counter: 0};
|
return { counter: 0 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(0);
|
expect(reduxStore.getState().counter).toBe(0);
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(1);
|
expect(reduxStore.getState().counter).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -35,37 +35,37 @@ describe('Redux adapter', () => {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const reduxStore = createStore((state = 0, action) => {
|
const reduxStore = createStore((state = 0, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case('ADD'):
|
case 'ADD':
|
||||||
return state + 1
|
return state + 1;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
expect(counter).toBe(0);
|
expect(counter).toBe(0);
|
||||||
expect(reduxStore.getState()).toBe(1);
|
expect(reduxStore.getState()).toBe(1);
|
||||||
const unsubscribe = reduxStore.subscribe(() => {
|
const unsubscribe = reduxStore.subscribe(() => {
|
||||||
counter++;
|
counter++;
|
||||||
});
|
});
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
expect(counter).toBe(2);
|
expect(counter).toBe(2);
|
||||||
expect(reduxStore.getState()).toBe(3);
|
expect(reduxStore.getState()).toBe(3);
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
expect(counter).toBe(2);
|
expect(counter).toBe(2);
|
||||||
expect(reduxStore.getState()).toBe(5);
|
expect(reduxStore.getState()).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should bind action creators', async () => {
|
it('Should bind action creators', async () => {
|
||||||
const addTodo = (text) => {
|
const addTodo = text => {
|
||||||
return {
|
return {
|
||||||
type: 'ADD_TODO',
|
type: 'ADD_TODO',
|
||||||
text
|
text,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const reduxStore = createStore((state = [], action) => {
|
const reduxStore = createStore((state = [], action) => {
|
||||||
if (action.type === 'ADD_TODO') {
|
if (action.type === 'ADD_TODO') {
|
||||||
|
@ -74,7 +74,7 @@ describe('Redux adapter', () => {
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = bindActionCreators({addTodo}, reduxStore.dispatch);
|
const actions = bindActionCreators({ addTodo }, reduxStore.dispatch);
|
||||||
|
|
||||||
actions.addTodo('todo');
|
actions.addTodo('todo');
|
||||||
|
|
||||||
|
@ -84,57 +84,57 @@ describe('Redux adapter', () => {
|
||||||
it('Should replace reducer', async () => {
|
it('Should replace reducer', async () => {
|
||||||
const reduxStore = createStore((state, action) => {
|
const reduxStore = createStore((state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case('ADD'):
|
case 'ADD':
|
||||||
return {counter: state.counter + 1}
|
return { counter: state.counter + 1 };
|
||||||
default:
|
default:
|
||||||
return {counter: 0};
|
return { counter: 0 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(1);
|
expect(reduxStore.getState().counter).toBe(1);
|
||||||
|
|
||||||
reduxStore.replaceReducer((state, action) => {
|
reduxStore.replaceReducer((state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case('SUB'):
|
case 'SUB':
|
||||||
return {counter: state.counter - 1}
|
return { counter: state.counter - 1 };
|
||||||
default:
|
default:
|
||||||
return {counter: 0};
|
return { counter: 0 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'SUB'});
|
reduxStore.dispatch({ type: 'SUB' });
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(0);
|
expect(reduxStore.getState().counter).toBe(0);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('Should combine reducers', async () => {
|
it('Should combine reducers', async () => {
|
||||||
const booleanReducer = (state = false, action) => {
|
const booleanReducer = (state = false, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case('TOGGLE'):
|
case 'TOGGLE':
|
||||||
return !state
|
return !state;
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addReducer = (state = 0, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case('ADD'):
|
|
||||||
return state + 1
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reduxStore = createStore(combineReducers({check: booleanReducer, counter: addReducer}));
|
const addReducer = (state = 0, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'ADD':
|
||||||
|
return state + 1;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reduxStore = createStore(combineReducers({ check: booleanReducer, counter: addReducer }));
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(0);
|
expect(reduxStore.getState().counter).toBe(0);
|
||||||
expect(reduxStore.getState().check).toBe(false);
|
expect(reduxStore.getState().check).toBe(false);
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'ADD'});
|
reduxStore.dispatch({ type: 'ADD' });
|
||||||
reduxStore.dispatch({type: 'TOGGLE'});
|
reduxStore.dispatch({ type: 'TOGGLE' });
|
||||||
|
|
||||||
expect(reduxStore.getState().counter).toBe(1);
|
expect(reduxStore.getState().counter).toBe(1);
|
||||||
expect(reduxStore.getState().check).toBe(true);
|
expect(reduxStore.getState().check).toBe(true);
|
||||||
|
@ -149,21 +149,25 @@ describe('Redux adapter', () => {
|
||||||
counter++;
|
counter++;
|
||||||
let result = next(action);
|
let result = next(action);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
const reduxStore = createStore((state, action) => {
|
const reduxStore = createStore(
|
||||||
switch (action.type) {
|
(state, action) => {
|
||||||
case('toggle'):
|
switch (action.type) {
|
||||||
return {
|
case 'toggle':
|
||||||
check: !state.check
|
return {
|
||||||
}
|
check: !state.check,
|
||||||
default:
|
};
|
||||||
return state;
|
default:
|
||||||
}
|
return state;
|
||||||
}, {check: false}, applyMiddleware(callCounter));
|
}
|
||||||
|
},
|
||||||
|
{ check: false },
|
||||||
|
applyMiddleware(callCounter)
|
||||||
|
);
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'toggle'});
|
reduxStore.dispatch({ type: 'toggle' });
|
||||||
reduxStore.dispatch({type: 'toggle'});
|
reduxStore.dispatch({ type: 'toggle' });
|
||||||
|
|
||||||
expect(counter).toBe(3); // NOTE: first action is always store initialization
|
expect(counter).toBe(3); // NOTE: first action is always store initialization
|
||||||
});
|
});
|
||||||
|
@ -178,31 +182,35 @@ describe('Redux adapter', () => {
|
||||||
counter++;
|
counter++;
|
||||||
let result = next(action);
|
let result = next(action);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
const lastFunctionStorage = store => next => action => {
|
const lastFunctionStorage = store => next => action => {
|
||||||
middlewareCallList.push('lastFunctionStorage');
|
middlewareCallList.push('lastFunctionStorage');
|
||||||
lastAction = action.type;
|
lastAction = action.type;
|
||||||
let result = next(action);
|
let result = next(action);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
const reduxStore = createStore((state, action) => {
|
const reduxStore = createStore(
|
||||||
switch (action.type) {
|
(state, action) => {
|
||||||
case('toggle'):
|
switch (action.type) {
|
||||||
return {
|
case 'toggle':
|
||||||
check: !state.check
|
return {
|
||||||
}
|
check: !state.check,
|
||||||
default:
|
};
|
||||||
return state;
|
default:
|
||||||
}
|
return state;
|
||||||
}, {check: false}, applyMiddleware(callCounter, lastFunctionStorage));
|
}
|
||||||
|
},
|
||||||
|
{ check: false },
|
||||||
|
applyMiddleware(callCounter, lastFunctionStorage)
|
||||||
|
);
|
||||||
|
|
||||||
reduxStore.dispatch({type: 'toggle'});
|
reduxStore.dispatch({ type: 'toggle' });
|
||||||
|
|
||||||
expect(counter).toBe(2); // NOTE: first action is always store initialization
|
expect(counter).toBe(2); // NOTE: first action is always store initialization
|
||||||
expect(lastAction).toBe('toggle');
|
expect(lastAction).toBe('toggle');
|
||||||
expect(middlewareCallList[0]).toBe("callCounter");
|
expect(middlewareCallList[0]).toBe('callCounter');
|
||||||
expect(middlewareCallList[1]).toBe("lastFunctionStorage");
|
expect(middlewareCallList[1]).toBe('lastFunctionStorage');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { unmountComponentAtNode } from '../../../libs/horizon/src/dom/DOMExternal';
|
import { unmountComponentAtNode } from '../../../libs/horizon/src/dom/DOMExternal';
|
||||||
import { getLogUtils } from './testUtils';
|
import { getLogUtils } from './testUtils';
|
||||||
//import failOnConsole from 'jest-fail-on-console';
|
|
||||||
|
|
||||||
//failOnConsole();
|
|
||||||
const LogUtils = getLogUtils();
|
const LogUtils = getLogUtils();
|
||||||
global.isDev = process.env.NODE_ENV === 'development';
|
global.isDev = process.env.NODE_ENV === 'development';
|
||||||
|
global.isTest = true;
|
||||||
global.container = null;
|
global.container = null;
|
||||||
global.beforeEach(() => {
|
global.beforeEach(() => {
|
||||||
LogUtils.clear();
|
LogUtils.clear();
|
||||||
|
|
|
@ -1,68 +1,9 @@
|
||||||
import { allDelegatedNativeEvents } from '@cloudsop/horizon/src/event/EventHub';
|
|
||||||
//import * as LogUtils from './logUtils';
|
|
||||||
|
|
||||||
export const stopBubbleOrCapture = (e, value) => {
|
export const stopBubbleOrCapture = (e, value) => {
|
||||||
const LogUtils = getLogUtils();
|
const LogUtils = getLogUtils();
|
||||||
LogUtils.log(value);
|
LogUtils.log(value);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
function listAllEventListeners() {
|
|
||||||
const allElements = Array.prototype.slice.call(document.querySelectorAll('*'));
|
|
||||||
allElements.push(document);
|
|
||||||
allElements.push(window);
|
|
||||||
|
|
||||||
const types = [];
|
|
||||||
|
|
||||||
for (let ev in window) {
|
|
||||||
if (/^on/.test(ev)) types[types.length] = ev;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elements = [];
|
|
||||||
for (let i = 0; i < allElements.length; i++) {
|
|
||||||
const currentElement = allElements[i];
|
|
||||||
for (let j = 0; j < types.length; j++) {
|
|
||||||
if (typeof currentElement[types[j]] === 'function') {
|
|
||||||
elements.push({
|
|
||||||
'node': currentElement,
|
|
||||||
'type': types[j],
|
|
||||||
'func': currentElement[types[j]].toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements.sort(function(a,b) {
|
|
||||||
return a.type.localeCompare(b.type);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getEventListeners = (dom) => {
|
|
||||||
console.table(listAllEventListeners());
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// let ret = true;
|
|
||||||
// let keyArray = [];
|
|
||||||
// for (let key in dom) {
|
|
||||||
// if (/^on/.test(key)) keyArray.push(key);
|
|
||||||
// }
|
|
||||||
// console.log(getEventListeners);
|
|
||||||
// console.log('---------------------------------');
|
|
||||||
// console.log(allDelegatedNativeEvents);
|
|
||||||
// try {
|
|
||||||
// allDelegatedNativeEvents.forEach(event => {
|
|
||||||
// if (!keyArray.includes(event)) {
|
|
||||||
// ret = false;
|
|
||||||
// throw new Error('没有挂载全量事件');
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function triggerClickEvent(container, id) {
|
export function triggerClickEvent(container, id) {
|
||||||
const event = new MouseEvent('click', {
|
const event = new MouseEvent('click', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
|
|
@ -10,6 +10,11 @@ const argv = require('minimist')(process.argv.slice(2));
|
||||||
const libPathPrefix = '../build';
|
const libPathPrefix = '../build';
|
||||||
const suffix = argv.dev ? 'development.js' : 'production.js';
|
const suffix = argv.dev ? 'development.js' : 'production.js';
|
||||||
const template = argv.type === 'horizon' ? 'horizon3rdTemplate.ejs' : 'template.ejs';
|
const template = argv.type === 'horizon' ? 'horizon3rdTemplate.ejs' : 'template.ejs';
|
||||||
|
const templatePath = path.resolve(__dirname, `./${template}`);
|
||||||
|
if (!fs.existsSync(templatePath)) {
|
||||||
|
console.log(chalk.yellow('Failed: Template file not exist'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const readLib = lib => {
|
const readLib = lib => {
|
||||||
const libName = lib.split('.')[0];
|
const libName = lib.split('.')[0];
|
||||||
const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`);
|
const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,7 +23,8 @@ if (!fs.existsSync(outDir)) {
|
||||||
const outputResolve = (...p) => path.resolve(outDir, ...p);
|
const outputResolve = (...p) => path.resolve(outDir, ...p);
|
||||||
|
|
||||||
function genConfig(mode) {
|
function genConfig(mode) {
|
||||||
const sourcemap = mode === 'development' ? 'inline' : false;
|
const isDev = mode === 'development';
|
||||||
|
const sourcemap = isDev ? 'inline' : false;
|
||||||
return {
|
return {
|
||||||
input: path.resolve(libDir, 'index.ts'),
|
input: path.resolve(libDir, 'index.ts'),
|
||||||
output: [
|
output: [
|
||||||
|
@ -53,7 +54,8 @@ function genConfig(mode) {
|
||||||
replace({
|
replace({
|
||||||
values: {
|
values: {
|
||||||
'process.env.NODE_ENV': `"${mode}"`,
|
'process.env.NODE_ENV': `"${mode}"`,
|
||||||
isDev: 'true',
|
isDev: isDev.toString(),
|
||||||
|
isTest: false,
|
||||||
__VERSION__: `"${horizonVersion}"`,
|
__VERSION__: `"${horizonVersion}"`,
|
||||||
},
|
},
|
||||||
preventAssignment: true,
|
preventAssignment: true,
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue