Match-id-0bbf86a1dee488bce8ef0f3767dd5798f4c61fca

This commit is contained in:
* 2024-01-16 19:26:30 +08:00
commit 791f65ec3d
260 changed files with 76 additions and 33344 deletions

View File

@ -1,10 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_size = 2
indent_style = space
trim_trailing_whitespace = true

View File

@ -1,6 +0,0 @@
node_modules
.idea
.vscode
package-lock.json
scripts/*.ejs
build

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
'use strict';
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -1,174 +0,0 @@
# 欢迎使用 InulaJS!
## 项目介绍:
InulaJS 是一款用于构建用户界面的Javascript库。
InulaJS 提供响应式API相比virtual dom方式提升渲染效率30%以上。
InulaJS 提供了5大常用核心组件状态管理器、路由、国际化、请求组件、应用脚手架帮助开发者高效、高质量的构筑产品前端。
InulaJS 同时兼容了React API和相关生态react-redux、react-router、react-intl、axios等
## 安装指南
欢迎使用响应式前端框架 InulaJS本指南将为您提供详细的安装步骤以便您可以开始在前端项目中使用该框架。
### 步骤1安装InulaJS
您可以通过以下几种方式安装InulaJS
#### 使用npm安装
首先,确保您已经安装了 Node.js。你可以在终端中运行以下命令来检查是否已经安装
```shell
node -v
```
如果成功显示 Node.js 的版本号,则说明已经安装。
在命令行中运行以下命令来通过npm安装 InulaJS
```shell
npm install inulaJS
```
#### 使用yarn安装
首先,确保您已经安装了 Node.js。具体操作可参考使用 npm 安装第一步
借来确保您已经安装了 yarn您可以通过以下命令来安装 Yarn全局安装
```shell
npm install -g yarn
```
安装完成后,你可以在终端中运行以下命令来验证 yarn 是否成功安装:
```shell
yarn --version
```
如果成功显示 yarn 的版本号,则说明安装成功。
最后在命令行中运行以下命令来通过yarn安装InulaJS
```shell
yarn add inulaJS
```
注意yarn 和 npm 是两个独立的包管理器,您可以根据自己的喜好选择使用哪个。它们可以在同一个项目中共存,但建议在一个项目中只使用其中一个来管理依赖。
### 步骤2开始使用InulaJS
恭喜您已经成功安装了InulaJS。现在您可以根据您的项目需求自由使用InulaJS提供的组件和功能。
请查阅InulaJS的用户使用指南文档以了解更多关于如何使用和配置框架的详细信息。
## 贡献指南
本指南会指导你如何为InulaJS贡献自己的一份力量请你在提出issue或pull request前花费几分钟来了解InulaJS社区的贡献指南。
### 行为准则
我们有一份**行为准则**,希望所有的贡献者都能遵守,请花时间阅读一遍全文以确保你能明白哪些是可以做的,哪些是不可以做的。
#### 我们的承诺
身为社区成员、贡献者和领袖,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。
我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。
#### 我们的准则
**有助于为我们的社区创造积极环境的行为例子包括但不限于:**
- 表现出对他人的同情和善意
- 尊重不同的主张、观点和感受
- 提出和大方接受建设性意见
- 承担责任并向受我们错误影响的人道歉
- 注重社区共同诉求,而非个人得失
**不当行为例子包括:**
- 使用情色化的语言或图像,及性引诱或挑逗
- 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击
- 公开或私下的骚扰行为
- 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址
- 其他有理由认定为违反职业操守的不当行为
#### 责任和权力
社区领袖有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。
社区领导有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论comment、提交commits、代码、维基wiki编辑、议题issues或其他贡献并在适当时机知采取措施的理由。
#### 适用范围
本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。
代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。
#### 监督
辱骂、骚扰或其他不可接受的行为可通过 XX@XXX.com 向负责监督的社区领袖报告。 所有投诉都将得到及时和公平的审查和调查。
所有社区领袖都有义务尊重任何事件报告者的隐私和安全。
#### 参见
本行为准则改编自 Contributor Covenant 2.1 版, 参见 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html。
### 公正透明的开发流程
我们所有的工作都会放在 [Gitee](https://www.gitee.com) 上。不管是核心团队的成员还是外部贡献者的 pull request 都需要经过同样流程的 review。
### 分支管理
InulaJS长期维护XX分支。如果你要修复一个Bug或增加一个新的功能那么请Pull Request到XX分支上
### Bug提交
我们使用 Gitee Issues来进行Bug跟踪。在你发现Bug后请通过我们提供的模板来提Issue以便你发现的Bug能被快速解决。
在你报告一个 bug 之前请先确保不和已有Issue重复以及查阅了我们的用户使用指南。
### 新增功能
如果你有帮助我们改进API或者新增功能的想法我们同样推荐你使用我们提供Issue模板来新建一个添加新功能的 Issue。
### 第一次贡献
如果你还不清楚怎么在 Gitee 上提交 Pull Request你可以通过[这篇文章](https://oschina.gitee.io/opensource-guide/guide/%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%EF%BC%9A%E5%B0%9D%E8%AF%95%E5%8F%82%E4%B8%8E%E5%BC%80%E6%BA%90/%E7%AC%AC%207%20%E5%B0%8F%E8%8A%82%EF%BC%9A%E6%8F%90%E4%BA%A4%E7%AC%AC%E4%B8%80%E4%B8%AA%20Pull%20Request/#%E4%BB%80%E4%B9%88%E6%98%AF-pull-request)学习
当你想开始处理一个 issue 时,先检查一下 issue 下面的留言,确保没有其他人正在处理。如果没有,你可以留言告知其他人你将处理这个 issue避免重复劳动。
### 开发指南
InulaJS团队会关注所有Pull Request我们会review以及合入你的代码也有可能要求你做一些修改或者告诉你我们我们为什么不能接受你的修改。
在你发送 Pull Request 之前,请确认你是按照下面的步骤来做的:
1. 确保基于正确的分支进行修改,详细信息请参考[这里](#分支管理)。
2. 在项目根目录下运行了 `npm install`
3. 如果你修复了一个 bug 或者新增了一个功能,请确保新增或完善了相应的测试,这很重要。
4. 确认所有的测试都是通过的 `npm run test`
5. 确保你的代码通过了 lint 检查 `npm run lint`.
#### 常用命令介绍
1. `npm run build` 同时构建InulaJS UMD的prod版本和dev版本
2. `build-types` 单独构建InulaJS的类型提示@types目录
#### 配套开发工具
- [InulaJS-devtool](https://www.XXXX.com) 可视化InulaJS项目页面的vDom树
## 开源许可协议
请查阅 License 获取开源许可协议的更多信息.
版权说明:
InulaJS 前端框架,版权所有 © 2023-InulaJS开发团队。保留一切权利。
除非另有明示本网站上的内容采用以下许可证进行许可Creative Commons Attribution 4.0 International License。
如需了解更多信息请查看完整的许可证协议https://creativecommons.org/licenses/by/4.0/legalcode

View File

@ -14,36 +14,12 @@
*/
module.exports = {
presets: ['@babel/preset-typescript', ['@babel/preset-env', { targets: { node: 'current' } }]],
plugins: [
'@babel/plugin-syntax-jsx',
presets: [
[
'@babel/plugin-transform-react-jsx',
{
pragma: 'Inula.createElement',
pragmaFrag: 'Inula.Fragment',
},
'@babel/preset-env',
],
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-proposal-private-methods', { loose: true }],
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
'@babel/plugin-transform-object-assign',
'@babel/plugin-transform-object-super',
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
['@babel/plugin-transform-template-literals', { loose: true }],
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-literals',
'@babel/plugin-transform-for-of',
'@babel/plugin-transform-block-scoped-functions',
'@babel/plugin-transform-classes',
'@babel/plugin-transform-shorthand-properties',
'@babel/plugin-transform-computed-properties',
'@babel/plugin-transform-parameters',
['@babel/plugin-transform-spread', { loose: true, useBuiltIns: true }],
['@babel/plugin-transform-block-scoping', { throwIfClosureRequired: false }],
['@babel/plugin-transform-destructuring', { loose: true, useBuiltIns: true }],
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
],
[
'@babel/preset-typescript',
]
]
};

View File

@ -1,23 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/*
*/
declare var isDev: boolean;
declare var isTest: boolean;
declare const __VERSION__: string;
declare var setImmediate: Function;
declare var __INULA_DEV_HOOK__: any;

View File

@ -19,17 +19,12 @@ module.exports = {
rootDir: process.cwd(),
setupFiles: [require.resolve('./scripts/__tests__/jest/jestEnvironment.js')],
setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/jestSetting.js')],
testEnvironment: 'jest-environment-jsdom-sixteen',
testMatch: [
// '<rootDir>/scripts/__tests__/InulaXTest/edgeCases/deepVariableObserver.test.tsx',
// '<rootDir>/scripts/__tests__/InulaXTest/StateManager/StateMap.test.tsx',
'<rootDir>/scripts/__tests__/**/*.test.js',
'<rootDir>/scripts/__tests__/**/*.test.tsx',
'<rootDir>/tests/**/*.test.js',
'<rootDir>/tests/**/*.test.ts',
'<rootDir>/tests/**/*.test.tsx',
],
timers: 'fake',

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/inula.production.min.js');
} else {
module.exports = require('./cjs/inula.development.js');
}

View File

@ -1,26 +1,9 @@
{
"name": "inulajs-reactive",
"description": "Inulajs is a JavaScript framework library.",
"keywords": [
"inulajs"
],
"version": "0.0.11",
"homepage": "",
"bugs": "",
"name": "inula-reactive",
"version": "0.0.1",
"description": "reactive core",
"main": "index.js",
"repository": {},
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "rollup --config ./scripts/rollup/rollup.config.js",
"build-types": "tsc -p tsconfig.build.json || echo \"WARNING: TSC exited with status $?\" && rollup -c ./scripts/rollup/build-types.js",
"build:watch": "rollup --watch --config ./scripts/rollup/rollup.config.js",
"debug-test": "yarn test --debug",
"lint": "eslint . --ext .ts --fix",
"prettier": "prettier -w libs/**/*.ts",
"test": "jest --config=jest.config.js",
"watch-test": "yarn test --watch --dev"
},
"types": "@types/index.d.ts"
"test": "jest --config=jest.config.js"
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, useState, act, useEffect } from '../../../src/index';
describe('Inula.act function Test', () => {
it('The act can wait for the useEffect update to complete.', function () {
const Parent = props => {
const [buttonOptions, setBtn] = useState([]);
const [checkedRows, setCheckedRows] = useState([]);
useEffect(() => {
setBtn([1, 2, 3]);
}, [checkedRows.length]);
return (
<div>
<Child buttonOptions={buttonOptions}></Child>
</div>
);
};
const Child = props => {
const { buttonOptions } = props;
const [btnList, setBtnList] = useState(0);
useEffect(() => {
setBtnList(buttonOptions.length);
}, [buttonOptions]);
return <div id="childDiv">{btnList}</div>;
};
act(() => {
render(<Parent />, container);
});
// act能够等待useEffect触发的update完成
expect(container.querySelector('#childDiv').innerHTML).toEqual('3');
});
});

View File

@ -1,51 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Class refs Test', () => {
it('Parent can get Child instance by refs', function () {
let pInst;
class Parent extends Inula.Component {
componentDidMount() {
pInst = this;
}
render() {
return (
<div>
<Child ref="child">
<div ref="childDiv">childDiv</div>
</Child>
</div>
);
}
}
class Child extends Inula.Component {
state = { y: 0 };
render() {
return <div>{this.props.children}</div>;
}
}
Inula.render(<Parent />, container);
expect(pInst.refs['child'].state.y).toEqual(0);
expect(pInst.refs['childDiv'].innerHTML).toEqual('childDiv');
});
});

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('Component Error Test', () => {
const LogUtils = getLogUtils();
it('createElement不能为null或undefined', () => {
const NullElement = null;
const UndefinedElement = undefined;
jest.spyOn(console, 'error').mockImplementation();
expect(() => {
Inula.render(<NullElement />, document.createElement('div'));
}).toThrow('Component type is invalid, got: null');
expect(() => {
Inula.render(<UndefinedElement />, document.createElement('div'));
}).toThrow('Component type is invalid, got: undefined');
const App = () => {
return <AppChild />;
};
let AppChild = () => {
return <NullElement />;
};
expect(() => {
Inula.render(<App />, document.createElement('div'));
}).toThrow('Component type is invalid, got: null');
AppChild = () => {
return <UndefinedElement />;
};
expect(() => {
Inula.render(<App />, document.createElement('div'));
}).toThrow('Component type is invalid, got: undefined');
});
});

View File

@ -1,414 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('Context Test', () => {
const LogUtils = getLogUtils();
it('Provider及其内部consumer组件都不受制于shouldComponentUpdate函数或者Inula.memo()', () => {
const LanguageTypes = {
JAVA: 'Java',
JAVASCRIPT: 'JavaScript',
};
const defaultValue = { type: LanguageTypes.JAVASCRIPT };
const SystemLanguageContext = Inula.createContext(defaultValue);
const SystemLanguageConsumer = SystemLanguageContext.Consumer;
const SystemLanguageProvider = props => {
LogUtils.log('SystemLanguageProvider');
return <SystemLanguageContext.Provider value={props.type}>{props.children}</SystemLanguageContext.Provider>;
};
const Consumer = () => {
LogUtils.log('Consumer');
return (
<SystemLanguageConsumer>
{type => {
LogUtils.log('Consumer DOM mutations');
return <p>{type}</p>;
}}
</SystemLanguageConsumer>
);
};
class Middle extends Inula.Component {
shouldComponentUpdate() {
return false;
}
render() {
LogUtils.log('Middle');
return this.props.children;
}
}
const App = props => {
LogUtils.log('App');
return (
<SystemLanguageProvider type={props.value}>
<Middle>
<Middle>
<Consumer />
</Middle>
</Middle>
</SystemLanguageProvider>
);
};
Inula.render(<App value={LanguageTypes.JAVA} />, container);
expect(container.querySelector('p').innerHTML).toBe('Java');
expect(LogUtils.getAndClear()).toEqual([
'App',
'SystemLanguageProvider',
'Middle',
'Middle',
'Consumer',
'Consumer DOM mutations',
]);
// 组件不变Middle没有更新消费者也不会执行
Inula.render(<App value={LanguageTypes.JAVA} />, container);
expect(container.querySelector('p').innerHTML).toBe('Java');
expect(LogUtils.getAndClear()).toEqual(['App', 'SystemLanguageProvider']);
Inula.render(<App value={LanguageTypes.JAVASCRIPT} />, container);
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
// 组件更新但是Middle没有更新会绕过Middle
expect(LogUtils.getAndClear()).toEqual(['App', 'SystemLanguageProvider', 'Consumer DOM mutations']);
});
it('嵌套consumer provider', () => {
const Num = {
ONE: 1,
TWO: 2,
};
const NumberContext = Inula.createContext(0);
const NumberConsumer = NumberContext.Consumer;
const NumberProvider = props => {
LogUtils.log(`SystemLanguageProvider: ${props.type}`);
return <NumberContext.Provider value={props.type}>{props.children}</NumberContext.Provider>;
};
const Consumer = () => {
LogUtils.log('Consumer');
return (
<NumberConsumer>
{type => {
LogUtils.log('Consumer DOM mutations');
return <p>{type}</p>;
}}
</NumberConsumer>
);
};
class Middle extends Inula.Component {
shouldComponentUpdate() {
return false;
}
render() {
LogUtils.log('Middle');
return this.props.children;
}
}
const App = props => {
LogUtils.log('App');
return (
<NumberProvider type={props.value}>
<NumberProvider type={props.value + 1}>
<Middle>
<Consumer />
</Middle>
</NumberProvider>
</NumberProvider>
);
};
// Consumer决定于距离它最近的provider
Inula.render(<App value={Num.ONE} />, container);
expect(container.querySelector('p').innerHTML).toBe('2');
expect(LogUtils.getAndClear()).toEqual([
'App',
'SystemLanguageProvider: 1',
'SystemLanguageProvider: 2',
'Middle',
'Consumer',
'Consumer DOM mutations',
]);
// 更新
Inula.render(<App value={Num.TWO} />, container);
expect(container.querySelector('p').innerHTML).toBe('3');
expect(LogUtils.getAndClear()).toEqual([
'App',
'SystemLanguageProvider: 2',
'SystemLanguageProvider: 3',
'Consumer DOM mutations',
]);
});
it('设置defaultValue', () => {
const Num = {
ONE: 1,
TWO: 2,
};
const NumberContext = Inula.createContext(0);
const NewNumberContext = Inula.createContext(1);
const NumberConsumer = NumberContext.Consumer;
const NumberProvider = props => {
return <NumberContext.Provider value={props.type}>{props.children}</NumberContext.Provider>;
};
const NewNumberProvider = props => {
return <NewNumberContext.Provider value={props.type}>{props.children}</NewNumberContext.Provider>;
};
class Middle extends Inula.Component {
shouldComponentUpdate() {
return false;
}
render() {
return this.props.children;
}
}
const NewApp = props => {
return (
<NewNumberProvider value={props.value}>
<Middle>
<NumberConsumer>
{type => {
LogUtils.log('Consumer DOM mutations');
return <p>{type}</p>;
}}
</NumberConsumer>
</Middle>
</NewNumberProvider>
);
};
const App = props => {
return (
<NumberProvider value={props.value}>
<Middle>
<NumberConsumer>
{type => {
LogUtils.log('Consumer DOM mutations');
return <p>{type}</p>;
}}
</NumberConsumer>
</Middle>
</NumberProvider>
);
};
Inula.render(<NewApp value={Num.ONE} />, container);
// 没有匹配到Provider,会使用defaultValue
expect(container.querySelector('p').innerHTML).toBe('0');
// 更新,设置value为undefined
Inula.render(<App value={undefined} />, container);
// 设置value为undefined时defaultValue不生效
expect(container.querySelector('p').innerHTML).toBe('');
});
it('不同provider下的多个consumer', () => {
const NumContext = Inula.createContext(1);
const Consumer = NumContext.Consumer;
function Provider(props) {
return (
<Consumer>
{value => <NumContext.Provider value={props.value || value * 2}>{props.children}</NumContext.Provider>}
</Consumer>
);
}
class Middle extends Inula.Component {
shouldComponentUpdate() {
return false;
}
render() {
return this.props.children;
}
}
const App = props => {
return (
<Provider value={props.value}>
<Middle>
<Middle>
<Provider>
<Consumer>{value => <p>{value}</p>}</Consumer>
</Provider>
</Middle>
<Middle>
<Consumer>{value => <p id="p">{value}</p>}</Consumer>
</Middle>
</Middle>
</Provider>
);
};
Inula.render(<App value={2} />, container);
expect(container.querySelector('p').innerHTML).toBe('4');
expect(container.querySelector('#p').innerHTML).toBe('2');
Inula.render(<App value={3} />, container);
expect(container.querySelector('p').innerHTML).toBe('6');
expect(container.querySelector('#p').innerHTML).toBe('3');
});
it('consumer里的child更新是不会重新渲染', () => {
const NumContext = Inula.createContext(1);
const Consumer = NumContext.Consumer;
let setNum;
const ReturnDom = props => {
const [num, _setNum] = Inula.useState(0);
setNum = _setNum;
LogUtils.log('ReturnDom');
return <p>{`Context: ${props.context}, Num: ${num}`}</p>;
};
const App = props => {
return (
<NumContext.Provider value={props.value}>
<Consumer>
{value => {
LogUtils.log('Consumer');
return <ReturnDom context={value} />;
}}
</Consumer>
</NumContext.Provider>
);
};
Inula.render(<App value={2} />, container);
expect(container.querySelector('p').innerHTML).toBe('Context: 2, Num: 0');
expect(LogUtils.getAndClear()).toEqual(['Consumer', 'ReturnDom']);
setNum(3);
expect(container.querySelector('p').innerHTML).toBe('Context: 2, Num: 3');
expect(LogUtils.getAndClear()).toEqual(['ReturnDom']);
});
it('consumer可以拿到其他context的值', () => {
const NumContext = Inula.createContext(1);
const TypeContext = Inula.createContext('typeA');
const NumAndType = () => {
const type = Inula.useContext(TypeContext);
return (
<NumContext.Consumer>
{value => {
LogUtils.log('Consumer');
return <p>{`Num: ${value}, Type: ${type}`}</p>;
}}
</NumContext.Consumer>
);
};
const App = props => {
return (
<NumContext.Provider value={props.num}>
<TypeContext.Provider value={props.type}>
<NumAndType />
</TypeContext.Provider>
</NumContext.Provider>
);
};
Inula.render(<App num={2} type={'typeB'} />, container);
expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeB');
Inula.render(<App num={2} type={'typeR'} />, container);
expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeR');
Inula.render(<App num={8} type={'typeR'} />, container);
expect(container.querySelector('p').innerHTML).toBe('Num: 8, Type: typeR');
});
// antd menu 级连context场景menu路径使用级联context实现
it('nested context', () => {
const NestedContext = Inula.createContext([]);
let updateContext;
function App() {
const [state, useState] = Inula.useState([]);
updateContext = useState;
return (
<NestedContext.Provider value={state}>
<Sub1 />
<Sub2 />
</NestedContext.Provider>
);
}
const div1Ref = Inula.createRef();
const div2Ref = Inula.createRef();
let updateSub1;
function Sub1() {
const path = Inula.useContext(NestedContext);
const [_, setState] = Inula.useState({});
updateSub1 = () => setState({});
return (
<NestedContext.Provider value={[...path, 1]}>
<Son divRef={div1Ref} />
</NestedContext.Provider>
);
}
function Sub2() {
const path = Inula.useContext(NestedContext);
return (
<NestedContext.Provider value={[...path, 2]}>
<Sub3 />
</NestedContext.Provider>
);
}
function Sub3() {
const path = Inula.useContext(NestedContext);
return (
<NestedContext.Provider value={[...path, 3]}>
<Son divRef={div2Ref} />
</NestedContext.Provider>
);
}
function Son({ divRef }) {
const path = Inula.useContext(NestedContext);
return (
<NestedContext.Provider value={path}>
<div ref={divRef}>{path.join(',')}</div>
</NestedContext.Provider>
);
}
Inula.render(<App />, container);
updateSub1();
expect(div1Ref.current.innerHTML).toEqual('1');
expect(div2Ref.current.innerHTML).toEqual('2,3');
updateContext([0]);
expect(div1Ref.current.innerHTML).toEqual('0,1');
expect(div2Ref.current.innerHTML).toEqual('0,2,3');
// 局部更新Sub1
updateSub1();
expect(div1Ref.current.innerHTML).toEqual('0,1');
expect(div2Ref.current.innerHTML).toEqual('0,2,3');
});
});

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Diff Algorithm', () => {
it('null should diff correctly', () => {
const fn = jest.fn();
class C extends Inula.Component {
constructor() {
super();
fn();
}
render() {
return 1;
}
}
let update;
function App() {
const [current, setCurrent] = Inula.useState(1);
update = setCurrent;
return (
<>
{current === 1 ? <C /> : null}
{current === 2 ? <C /> : null}
{current === 3 ? <C /> : null}
</>
);
}
Inula.render(<App text="app" />, container);
expect(fn).toHaveBeenCalledTimes(1);
update(2);
expect(fn).toHaveBeenCalledTimes(2);
update(3);
expect(fn).toHaveBeenCalledTimes(3);
update(1);
expect(fn).toHaveBeenCalledTimes(4);
});
});

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('ForwardRef', () => {
const LogUtils = getLogUtils();
it('ForwardRef包裹的函数组件应该正常触发effect', () => {
function App(props, ref) {
Inula.useEffect(() => {
LogUtils.log('effect');
return () => {
LogUtils.log('effect remove');
};
});
return <button ref={ref}></button>;
}
const Wrapper = Inula.forwardRef(App);
Inula.act(() => {
Inula.render(<Wrapper />, container);
});
expect(LogUtils.getAndClear()).toEqual(['effect']);
Inula.act(() => {
Inula.render(<Wrapper />, container);
});
expect(LogUtils.getAndClear()).toEqual(['effect remove', 'effect']);
});
it('memo组件包裹的类组件', () => {
class Component extends Inula.Component {
render() {
return <button>123</button>;
}
}
const Wrapper = Inula.memo(Component);
Inula.act(() => {
Inula.render(<Wrapper />, container);
});
Inula.act(() => {
Inula.render(<Wrapper />, container);
});
});
});

View File

@ -1,476 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { Text } from '../jest/commonComponents';
import { getLogUtils } from '../jest/testUtils';
describe('Fragment', () => {
const LogUtils = getLogUtils();
const { useEffect, useRef, act } = Inula;
it('可以渲染空元素', () => {
const element = <Inula.Fragment />;
Inula.render(element, container);
expect(container.textContent).toBe('');
});
it('可以渲染单个元素', () => {
const element = (
<Inula.Fragment>
<Text text="Fragment" />
</Inula.Fragment>
);
Inula.render(element, container);
expect(LogUtils.getAndClear()).toEqual(['Fragment']);
expect(container.textContent).toBe('Fragment');
});
it('可以渲染混合元素', () => {
const element = (
<Inula.Fragment>
Java and <Text text="JavaScript" />
</Inula.Fragment>
);
Inula.render(element, container);
expect(LogUtils.getAndClear()).toEqual(['JavaScript']);
expect(container.textContent).toBe('Java and JavaScript');
});
it('可以渲染集合元素', () => {
const App = [<Text text="Java" />, <Text text="JavaScript" />];
const element = <>{App}</>;
Inula.render(element, container);
expect(LogUtils.getAndClear()).toEqual(['Java', 'JavaScript']);
expect(container.textContent).toBe('JavaJavaScript');
});
it('元素被放进不同层级Fragment里时,状态不会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<>
<ChildApp logo={1} />
</>
) : (
<>
<>
<ChildApp logo={2} />
</>
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 切换到不同层级Fragment时副作用状态不会保留
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('1');
});
it('元素被放进单层Fragment里,且在Fragment的顶部时,状态会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<ChildApp logo={1} />
) : (
<>
<ChildApp logo={2} />
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态会保留
expect(LogUtils.getNotClear()).toEqual(['useEffect']);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']);
expect(container.textContent).toBe('1');
});
it('元素被放进单层Fragment里,但不在Fragment的顶部时,状态不会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<ChildApp logo={1} />
) : (
<>
<div>123</div>
<ChildApp logo={2} />
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态不会保留
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('1232');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('1');
});
it('元素被放进多层Fragment里时,状态不会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<ChildApp logo={1} />
) : (
<>
<>
<>
<ChildApp logo={2} />
</>
</>
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态不会保留
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('1');
});
it('元素被切换放进同级Fragment里时,状态会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<>
<>
<>
<ChildApp logo={1} />
</>
</>
</>
) : (
<>
<>
<>
<ChildApp logo={2} />
</>
</>
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态会保留
expect(LogUtils.getNotClear()).toEqual(['useEffect']);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']);
expect(container.textContent).toBe('1');
});
it('元素被切换放进同级Fragment,且在数组顶层时,状态会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<>
<>
<>
<ChildApp logo={1} />
</>
</>
</>
) : (
<>
<>
<>{[<ChildApp logo={2} />]}</>
</>
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态会保留
expect(LogUtils.getNotClear()).toEqual(['useEffect']);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']);
expect(container.textContent).toBe('1');
});
it('数组里的顶层元素被切换放进单级Fragment时,状态会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
[<ChildApp logo={1} />]
) : (
<>
<ChildApp logo={2} />
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态会保留
expect(LogUtils.getNotClear()).toEqual(['useEffect']);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']);
expect(container.textContent).toBe('1');
});
it('Fragment里的顶层数组里的顶层元素被切换放进不同级Fragment时,状态不会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<>
[<ChildApp logo={1} />]
</>
) : (
<>
<>
<ChildApp logo={2} />
</>
</>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态会保留
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('[1]');
});
it('Fragment的key值不同时,状态不会保留', () => {
const ChildApp = props => {
const flag = useRef(true);
useEffect(() => {
if (flag.current) {
flag.current = false;
} else {
LogUtils.log('useEffect');
}
});
return <p>{props.logo}</p>;
};
const App = props => {
return props.change ? (
<Inula.Fragment key="hf">
<ChildApp logo={1} />
</Inula.Fragment>
) : (
<Inula.Fragment key="nhf">
<ChildApp logo={2} />
</Inula.Fragment>
);
};
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
act(() => {
Inula.render(<App change={false} />, container);
});
// 状态不会保留
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('2');
act(() => {
Inula.render(<App change={true} />, container);
});
expect(LogUtils.getNotClear()).toEqual([]);
expect(container.textContent).toBe('1');
});
});

View File

@ -1,92 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('FunctionComponent Test', () => {
it('渲染无状态组件', () => {
const App = props => {
return <p>{props.text}</p>;
};
Inula.render(<App text="app" />, container);
expect(container.querySelector('p').innerHTML).toBe('app');
});
it('更新无状态组件', () => {
const App = props => {
return <p>{props.text}</p>;
};
Inula.render(<App text="app" />, container);
expect(container.querySelector('p').innerHTML).toBe('app');
Inula.render(<App text="ABC" />, container);
expect(container.querySelector('p').innerHTML).toBe('ABC');
Inula.render(<App text="abc" />, container);
expect(container.querySelector('p').innerHTML).toBe('abc');
});
it('卸载无状态组件', () => {
const App = props => {
return <p>{props.text}</p>;
};
Inula.render(<App text="app" />, container);
expect(container.querySelector('p').innerHTML).toBe('app');
Inula.unmountComponentAtNode(container);
expect(container.querySelector('p')).toBe(null);
});
it('渲染空组件返回空子节点', () => {
const App = () => {
return <div />;
};
const realNode = Inula.render(<App />, container);
expect(realNode).toBe(null);
});
it('测试函数组件的defaultPropsInula.memo(Inula.forwardRef(()=>{}))两层包装的场景后defaultProps依然正常', () => {
const App = () => {
return <DefaultPropsCompMemo />;
};
const DefaultPropsComp = Inula.forwardRef(props => {
return <div>{props.name}</div>;
});
DefaultPropsComp.defaultProps = {
name: 'Hello!',
};
const DefaultPropsCompMemo = Inula.memo(DefaultPropsComp);
Inula.render(<App />, container);
expect(container.querySelector('div').innerHTML).toBe('Hello!');
});
it('测试', () => {
const App = () => {
return <StyleComp />;
};
const StyleComp = props => {
return <div style={{ '--max-segment-num': 10 }}>{props.name}</div>;
};
Inula.render(<App />, container);
expect(container.querySelector('div').style['_values']['--max-segment-num']).toBe(10);
});
});

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
describe('useCallback Hook Test', () => {
const { useState, useCallback } = Inula;
it('测试useCallback', () => {
const App = props => {
const [num, setNum] = useState(0);
const NumUseCallback = useCallback(() => {
setNum(num + props.text);
}, [props]);
return (
<>
<p>{num}</p>
<button onClick={NumUseCallback} />
</>
);
};
Inula.render(<App text={1} />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
// 再次点击依赖项没变num不增加
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App text={2} />, container);
expect(container.querySelector('p').innerHTML).toBe('1');
// 依赖项有变化点击按钮num增加
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('3');
});
});

View File

@ -1,93 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
describe('useContext Hook Test', () => {
const { useState, useContext, createContext, act, unmountComponentAtNode } = Inula;
it('简单使用useContext', () => {
const LanguageTypes = {
JAVA: 'Java',
JAVASCRIPT: 'JavaScript',
};
const defaultValue = { type: LanguageTypes.JAVASCRIPT };
const SystemLanguageContext = Inula.createContext(defaultValue);
const SystemLanguageProvider = ({ type, children }) => {
return <SystemLanguageContext.Provider value={{ type }}>{children}</SystemLanguageContext.Provider>;
};
const TestFunction = () => {
const context = useContext(SystemLanguageContext);
return <p id="p">{context.type}</p>;
};
let setValue;
const App = () => {
const [value, _setValue] = useState(LanguageTypes.JAVA);
setValue = _setValue;
return (
<div className="App">
<SystemLanguageProvider type={value}>
<TestFunction />
</SystemLanguageProvider>
</div>
);
};
Inula.render(<TestFunction />, container);
// 测试当Provider未提供时获取到的默认值'JavaScript'。
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
unmountComponentAtNode(container);
Inula.render(<App />, container);
// 测试当Provider提供时可以获取到Provider的值'Java'。
expect(container.querySelector('p').innerHTML).toBe('Java');
// 测试当Provider改变时可以获取到最新Provider的值。
act(() => setValue(LanguageTypes.JAVASCRIPT));
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
});
it('更新后useContext仍能获取到context', () => {
const Context = createContext({});
const ref = Inula.createRef();
function App() {
return (
<Context.Provider
value={{
text: 'context',
}}
>
<Child />
</Context.Provider>
);
}
let update;
function Child() {
const context = useContext(Context);
const [_, setState] = useState({});
update = () => setState({});
return <div ref={ref}>{context.text}</div>;
}
Inula.render(<App />, container);
expect(ref.current.innerHTML).toBe('context');
update();
expect(ref.current.innerHTML).toBe('context');
});
});

View File

@ -1,681 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useEffect Hook Test', () => {
const { useEffect, useLayoutEffect, useState, memo, forwardRef, act } = Inula;
const LogUtils = getLogUtils();
it('简单使用useEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">
{num}
</p>
<button onClick={() => setNum(num + 1)} />
</>
);
};
Inula.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('block');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
it('act方法', () => {
const App = () => {
return <Text text={'op'} />;
};
act(() => {
Inula.render(<App />, container, () => {
LogUtils.log('num effect');
});
// 第一次渲染为同步所以同步执行的可以写在act里做判断
expect(LogUtils.getAndClear()).toEqual(['op', 'num effect']);
expect(container.textContent).toBe('op');
});
act(() => {
Inula.render(null, container, () => {
LogUtils.log('num effect89');
});
// 第二次渲染为异步所以同步执行的不可以写在act里做判断act里拿到的为空数组
expect(LogUtils.getAndClear()).toEqual([]);
});
expect(LogUtils.getAndClear()).toEqual(['num effect89']);
expect(container.textContent).toBe('');
});
it('兄弟节点被删除useEffect依然正常', () => {
const App = () => {
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
const na = <NewApp />;
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Inula.render([<App key="app" />, na], container);
expect(LogUtils.getAndClear()).toEqual(['App', 'NewApp']);
expect(container.textContent).toBe('AppNewApp');
expect(LogUtils.getAndClear()).toEqual([]);
// 在执行新的render前会执行完上一次render的useEffect所以LogUtils会加入'NewApp effect'。
Inula.render([na], container);
expect(LogUtils.getAndClear()).toEqual(['NewApp effect', 'NewApp']);
expect(container.textContent).toBe('NewApp');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('兄弟节点更新useEffect依然正常', () => {
const App = () => {
const [num, setNum] = useState(0);
useLayoutEffect(() => {
if (num === 0) {
setNum(1);
}
LogUtils.log('App Layout effect ' + num);
});
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Inula.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect 0',
// 在App更新前会执行完NewApp的useEffect
'NewApp effect',
'App',
'App Layout effect 1',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('兄弟节点执行新的挂载动作useEffect依然正常', () => {
const newContainer = document.createElement('div');
const App = () => {
useLayoutEffect(() => {
LogUtils.log('App Layout effect');
Inula.render(<Text text="NewContainer" />, newContainer);
});
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Inula.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect',
// 在执行useLayoutEffectApp的render前会执行完NewApp的useEffect
'NewApp effect',
'NewContainer',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('执行新render的useEffect前会先执行旧render的useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`First effect [${props.num}]`);
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['First effect [0]']);
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
// 此时异步执行act执行完后会执行新render的useEffect
expect(LogUtils.getAndClear()).toEqual(['num: 1', 'callback effect', 'First effect [1]']);
expect(container.textContent).toEqual('num: 1');
});
it('混合使用useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`First effect [${props.num}]`);
});
useEffect(() => {
LogUtils.log(`Second effect [${props.num}]`);
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['First effect [0]', 'Second effect [0]']);
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
// 第二次render时异步执行act保证所有效果都已更新所以先常规记录日志
// 然后记录useEffect的日志
expect(LogUtils.getAndClear()).toEqual(['num: 1', 'callback effect', 'First effect [1]', 'Second effect [1]']);
expect(container.textContent).toEqual('num: 1');
});
it('创建销毁useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, [props.num]);
useEffect(() => {
LogUtils.log(`word effect [${props.word}]`);
return () => {
LogUtils.log('word effect destroy');
};
}, [props.word]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${props.num}]`);
return () => {
LogUtils.log('num Layouteffect destroy');
};
}, [props.num]);
useLayoutEffect(() => {
LogUtils.log(`word Layouteffect [${props.word}]`);
return () => {
LogUtils.log('word Layouteffect destroy');
};
}, [props.word]);
return <Text text={'num: ' + props.num + ',word: ' + props.word} />;
};
act(() => {
Inula.render(<App num={0} word={'App'} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0,word: App',
'num Layouteffect [0]',
'word Layouteffect [App]',
'callback effect',
]);
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]', 'word effect [App]']);
act(() => {
// 此时word改变num不变
Inula.render(<App num={0} word={'Inula'} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 0,word: Inula',
'word Layouteffect destroy',
'word Layouteffect [Inula]',
'callback effect',
// 最后执行异步的
'word effect destroy',
'word effect [Inula]',
]);
act(() => {
// 此时num和word的所有effect都销毁
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num Layouteffect destroy',
'word Layouteffect destroy',
'callback effect',
// 最后执行异步useEffect
'num effect destroy',
'word effect destroy',
]);
});
it('销毁不含依赖数组的useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect',
// 最后执行异步
'num effect destroy',
'num effect [1]',
]);
expect(container.textContent).toEqual('num: 1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy']);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('销毁依赖空数组的useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, []);
return <Text text={'num: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect',
// 依赖空数组没有执行useEffect
]);
expect(container.textContent).toEqual('num: 1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy']);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(1', () => {
let setNum;
const App = () => {
const [num, _setNum] = Inula.useState(0);
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
setNum = () => _setNum(1);
}, [num]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${num}]`);
return () => {
LogUtils.log('num Layouteffect destroy');
};
}, []);
return <Text text={'num: ' + num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'num Layouteffect [0]', 'callback effect']);
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
act(() => {
setNum();
});
expect(LogUtils.getAndClear()).toEqual(['num: 1', 'num effect [1]']);
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(2', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`App effect`);
setNum(1);
}, []);
return <Text text={'Num: ' + num} />;
};
Inula.render(<App />, container, () => LogUtils.log('App callback effect'));
expect(LogUtils.getAndClear()).toEqual(['Num: 0', 'App callback effect']);
expect(container.textContent).toEqual('Num: 0');
act(() => {
setNum(2);
});
// 虽然执行了setNum(2)但执行到setNum(1)所以最终num为1
expect(LogUtils.getAndClear()).toEqual(['App effect', 'Num: 1']);
expect(container.textContent).toEqual('Num: 1');
});
it('useEffect与memo一起使用(1', () => {
let setNum;
const App = memo(() => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
return () => {
LogUtils.log(`num effect destroy ${num}`);
};
});
return <Text text={num} />;
});
act(() => {
Inula.render(<App />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([0, 'callback effect']);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
// 不会重新渲染
act(() => {
Inula.render(<App />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect']);
expect(container.textContent).toEqual('0');
expect(LogUtils.getAndClear()).toEqual([]);
// 会重新渲染
act(() => {
setNum(1);
});
expect(LogUtils.getAndClear()).toEqual([1, 'num effect destroy 0', 'num effect [1]']);
expect(container.textContent).toEqual('1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy 1']);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect与memo一起使用(2', () => {
const compare = (prevProps, nextProps) => prevProps.num === nextProps.num;
const App = memo(props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log(`num effect destroy ${props.num}`);
};
});
return <Text text={props.num} />;
}, compare);
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([0, 'callback effect']);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
// 不会重新渲染
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect']);
expect(container.textContent).toEqual('0');
expect(LogUtils.getAndClear()).toEqual([]);
// 会重新渲染
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
1,
'callback effect',
// 执行异步,先清除旧的,再执行新的
'num effect destroy 0',
'num effect [1]',
]);
expect(container.textContent).toEqual('1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy 1']);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect处理错误', () => {
const App = props => {
useEffect(() => {
LogUtils.log('throw Error');
throw new Error('mistake');
// eslint-disable-next-line no-unreachable
LogUtils.log(`Mount with [${props.num}]`);
return () => {
LogUtils.log(`Unmount with [${props.num}]`);
};
});
return <Text text={'Number: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('App callback effect'));
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'App callback effect']);
expect(container.textContent).toEqual('Number: 0');
});
// 处理错误不会向下执行LogUtils.log(`Mount with [${props.num}]`);
expect(LogUtils.getAndClear()).toEqual(['throw Error']);
act(() => {
Inula.render(null, container, () => LogUtils.log('App callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'App callback effect',
// 不会处理卸载部分 LogUtils.log(`Unmount with [${props.num}]`);
]);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('卸载useEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log(`num effect destroy ${props.num}`);
};
}, []);
if (props.num < 0) {
useEffect(() => {
LogUtils.log(`New num effect [${props.num}]`);
return () => {
LogUtils.log(`New num effect destroy ${props.num}`);
};
}, []);
}
return <Text text={`Number: ${props.num}`} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']);
expect(container.textContent).toBe('Number: 0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
act(() => {
Inula.render(null, container, () => LogUtils.log('num effect'));
});
expect(LogUtils.getAndClear()).toEqual(['num effect', 'num effect destroy 0']);
expect(container.textContent).toBe('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('同步刷新不会导致effect执行', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
_setNum(1);
}, []);
return <Text text={`Number: ${num}`} />;
};
Inula.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']);
expect(container.textContent).toBe('Number: 0');
act(() => {
// 模拟同步刷新
(function () {
setNum(2);
})();
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]', 'Number: 1']);
expect(container.textContent).toBe('Number: 1');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('当组件的更新方法在卸载函数中,组件更新不会告警', () => {
const App = () => {
LogUtils.log('useEffect');
const [num, setNum] = useState(0);
useEffect(() => {
LogUtils.log('effect');
return () => {
setNum(1);
LogUtils.log('effect destroy');
};
}, []);
return num;
};
act(() => {
Inula.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['useEffect', 'num effect']);
});
expect(LogUtils.getAndClear()).toEqual(['effect']);
act(() => {
Inula.render(null, container);
});
// 不会处理setNum(1)
expect(LogUtils.getAndClear()).toEqual(['effect destroy']);
});
it('当组件的更新方法在卸载函数中,组件的子组件更新不会告警', () => {
const App = () => {
LogUtils.log('App');
const appRef = Inula.createRef(null);
useEffect(() => {
LogUtils.log('App effect');
return () => {
appRef.current(1);
LogUtils.log('App effect destroy');
};
}, []);
return <AppChild ref={appRef} />;
};
let AppChild = (props, ref) => {
LogUtils.log('AppChild');
const [num, setNum] = useState(0);
useEffect(() => {
LogUtils.log('Child effect');
ref.current = setNum;
}, []);
return num;
};
AppChild = forwardRef(AppChild);
act(() => {
Inula.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['App', 'AppChild', 'num effect']);
});
expect(LogUtils.getAndClear()).toEqual(['Child effect', 'App effect']);
act(() => {
Inula.render(null, container);
});
// 销毁时执行appRef.current(1)不会报错
expect(LogUtils.getAndClear()).toEqual(['App effect destroy']);
});
it('当组件的更新方法在卸载函数中,组件的父组件更新不会告警', () => {
const App = () => {
LogUtils.log('App');
const [num, setNum] = useState(0);
return <AppChild num={num} setNum={setNum} />;
};
let AppChild = props => {
LogUtils.log('AppChild');
useEffect(() => {
LogUtils.log('Child effect');
return () => {
LogUtils.log('Child effect destroy');
props.setNum(1);
};
}, []);
return props.num;
};
act(() => {
Inula.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['App', 'AppChild', 'num effect']);
});
expect(LogUtils.getAndClear()).toEqual(['Child effect']);
act(() => {
Inula.render(null, container);
});
// 销毁时执行 props.setNum(1);不会报错
expect(LogUtils.getAndClear()).toEqual(['Child effect destroy']);
});
});

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { Text } from '../../jest/commonComponents';
import { getLogUtils } from '../../jest/testUtils';
describe('useImperativeHandle Hook Test', () => {
const { useState, useImperativeHandle, forwardRef, act } = Inula;
const { unmountComponentAtNode } = Inula;
const LogUtils = getLogUtils();
it('测试useImperativeHandle', () => {
let App = (props, ref) => {
const [num, setNum] = useState(0);
useImperativeHandle(ref, () => ({ num, setNum }), []);
return <p>{num}</p>;
};
let App1 = (props, ref) => {
const [num1, setNum1] = useState(0);
useImperativeHandle(ref, () => ({ num1, setNum1 }), [num1]);
return <p>{num1}</p>;
};
App = forwardRef(App);
App1 = forwardRef(App1);
const counter = Inula.createRef(null);
const counter1 = Inula.createRef(null);
Inula.render(<App ref={counter} />, container);
expect(counter.current.num).toBe(0);
act(() => {
counter.current.setNum(1);
});
// useImperativeHandle的dep为[],所以不会变
expect(counter.current.num).toBe(0);
// 清空container
unmountComponentAtNode(container);
Inula.render(<App1 ref={counter1} />, container);
expect(counter1.current.num1).toBe(0);
act(() => {
counter1.current.setNum1(1);
});
// useImperativeHandle的dep为[num1],所以会变
expect(counter1.current.num1).toBe(1);
});
it('useImperativeHandle没有配置dep时自动更新', () => {
let App = (props, ref) => {
const [num, setNum] = useState(0);
useImperativeHandle(ref, () => ({ num, setNum }));
return <Text text={num} />;
};
let App1 = (props, ref) => {
const [num1, setNum1] = useState(0);
useImperativeHandle(ref, () => ({ num1, setNum1 }), []);
return <Text text={num1} />;
};
App = forwardRef(App);
App1 = forwardRef(App1);
const counter = Inula.createRef(null);
const counter1 = Inula.createRef(null);
Inula.render(<App ref={counter} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter.current.num).toBe(0);
act(() => {
counter.current.setNum(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle没有配置的dep,所以会自动更新
expect(counter.current.num).toBe(1);
// 清空container
unmountComponentAtNode(container);
Inula.render(<App1 ref={counter1} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter1.current.num1).toBe(0);
act(() => {
counter1.current.setNum1(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle的dep为[],所以不会变
expect(counter1.current.num1).toBe(0);
});
});

View File

@ -1,118 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useLayoutEffect Hook Test', () => {
const { useState, useEffect, useLayoutEffect, act } = Inula;
const LogUtils = getLogUtils();
it('简单使用useLayoutEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useLayoutEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">
{num}
</p>
<button onClick={() => setNum(num + 1)} />
</>
);
};
Inula.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
it('useLayoutEffect的触发时序', () => {
const App = props => {
useLayoutEffect(() => {
LogUtils.log('LayoutEffect');
});
return <Text text={props.num} />;
};
Inula.render(<App num={1} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([
1,
// 同步在渲染之后
'LayoutEffect',
'Sync effect',
]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 更新
Inula.render(<App num={2} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([2, 'LayoutEffect', 'Sync effect']);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('创建销毁useLayoutEffect', () => {
const App = props => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, [props.num]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${props.num}]`);
return () => {
LogUtils.log(`num [${props.num}] Layouteffect destroy`);
};
}, [props.num]);
return <Text text={'num: ' + props.num} />;
};
act(() => {
Inula.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'num Layouteffect [0]', 'callback effect']);
expect(container.textContent).toBe('num: 0');
});
// 更新
act(() => {
Inula.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
// 异步effect
'num effect [0]',
'num: 1',
// 旧Layouteffect销毁
'num [0] Layouteffect destroy',
// 新Layouteffect建立
'num Layouteffect [1]',
'callback effect',
// 异步旧的effect销毁
'num effect destroy',
// 异步新的effect建立
'num effect [1]',
]);
act(() => {
Inula.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
// 同步Layouteffect销毁
'num [1] Layouteffect destroy',
'callback effect',
// 最后执行异步effect销毁
'num effect destroy',
]);
});
});

View File

@ -1,112 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useMemo Hook Test', () => {
const { useMemo, useState } = Inula;
const LogUtils = getLogUtils();
it('测试useMemo', () => {
let setMemo;
const App = () => {
const [num, setNum] = useState(0);
const [memoDependent, _setMemo] = useState('App');
setMemo = _setMemo;
const text = useMemo(() => {
setNum(num + 1);
return memoDependent;
}, [memoDependent]);
return (
<>
<p>{text}</p>
<p id="p">{num}</p>
</>
);
};
Inula.render(<App words="App" />, container);
expect(container.querySelector('p').innerHTML).toBe('App');
expect(container.querySelector('#p').innerHTML).toBe('1');
// 修改useMemo的依赖项num会加一text会改变。
setMemo('Apps');
expect(container.querySelector('p').innerHTML).toBe('Apps');
expect(container.querySelector('#p').innerHTML).toBe('2');
// useMemo的依赖项不变num不会加一text不会改变。
setMemo('Apps');
expect(container.querySelector('p').innerHTML).toBe('Apps');
expect(container.querySelector('#p').innerHTML).toBe('2');
// 修改useMemo的依赖项num会加一text会改变。
setMemo('App');
expect(container.querySelector('p').innerHTML).toBe('App');
expect(container.querySelector('#p').innerHTML).toBe('3');
});
it('触发useMemo', () => {
const App = props => {
const num = useMemo(() => {
LogUtils.log(props._num);
return props._num + 1;
}, [props._num]);
return <Text text={num} />;
};
Inula.render(<App _num={0} />, container);
expect(LogUtils.getAndClear()).toEqual([0, 1]);
expect(container.textContent).toBe('1');
Inula.render(<App _num={1} />, container);
expect(LogUtils.getAndClear()).toEqual([1, 2]);
expect(container.textContent).toBe('2');
Inula.render(<App _num={1} />, container);
// 不会触发useMemo
expect(LogUtils.getAndClear()).toEqual([2]);
expect(container.textContent).toBe('2');
Inula.render(<App _num={2} />, container);
expect(LogUtils.getAndClear()).toEqual([2, 3]);
expect(container.textContent).toBe('3');
});
it('输入不变重新渲染也会触发useMemo', () => {
const App = props => {
const num = useMemo(props._num);
return <Text text={num} />;
};
const num1 = () => {
LogUtils.log('num 1');
return 1;
};
const num2 = () => {
LogUtils.log('num 2');
return 2;
};
Inula.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
Inula.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
Inula.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
Inula.render(<App _num={num2} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 2', 2]);
});
});

View File

@ -1,104 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
describe('useReducer Hook Test', () => {
const { useReducer } = Inula;
it('简单使用useReducer', () => {
const intlCar = { logo: '', price: 0 };
let dispatch;
const App = () => {
const carReducer = (state, action) => {
switch (action.logo) {
case 'ford':
return {
...intlCar,
logo: 'ford',
price: 76,
};
case 'bmw':
return {
...intlCar,
logo: 'bmw',
price: 100,
};
case 'benz':
return {
...intlCar,
logo: 'benz',
price: 80,
};
default:
return {
...intlCar,
logo: 'audi',
price: 88,
};
}
};
const [car, carDispatch] = useReducer(carReducer, intlCar);
dispatch = carDispatch;
return (
<div>
<p>{car.logo}</p>
<p id={'senP'}>{car.price}</p>
</div>
);
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('');
expect(container.querySelector('#senP').innerHTML).toBe('0');
// 触发bmw
dispatch({ logo: 'bmw' });
expect(container.querySelector('p').innerHTML).toBe('bmw');
expect(container.querySelector('#senP').innerHTML).toBe('100');
// 触发carReducer里的switch的default项
dispatch({ logo: 'wrong logo' });
expect(container.querySelector('p').innerHTML).toBe('audi');
expect(container.querySelector('#senP').innerHTML).toBe('88');
});
it('dispatch只触发一次', () => {
let nextId = 1;
const reducer = () => {
return { data: nextId++ };
};
const btnRef = Inula.createRef();
const Main = () => {
const [{ data }, dispatch] = useReducer(reducer, { data: 0 });
const dispatchLogging = () => {
console.log('dispatch is called once');
dispatch();
};
return (
<div>
<button ref={btnRef} onClick={() => dispatchLogging()}>
increment
</button>
<div>{data}</div>
</div>
);
};
Inula.render(<Main />, container);
Inula.act(() => {
btnRef.current.click();
});
expect(nextId).toBe(2);
});
});

View File

@ -1,73 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useRef Hook Test', () => {
const { useState, useRef } = Inula;
const LogUtils = getLogUtils();
it('测试useRef', () => {
const App = () => {
const [num, setNum] = useState(1);
const ref = useRef();
if (!ref.current) {
ref.current = num;
}
return (
<>
<p>{num}</p>
<p id="sp">{ref.current}</p>
<button onClick={() => setNum(num + 1)} />
</>
);
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('1');
expect(container.querySelector('#sp').innerHTML).toBe('1');
// 点击按钮触发num加1,ref不变
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('2');
expect(container.querySelector('#sp').innerHTML).toBe('1');
});
it('更新current值时不会re-render', () => {
const App = () => {
const ref = useRef(1);
return (
<>
<button
onClick={() => {
ref.current += 1;
}}
>
button
</button>
<Text text={ref.current} />;
</>
);
};
Inula.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 点击按钮触发ref.current加1
container.querySelector('button').click();
// ref.current改变不会触发重新渲染
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
});

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useState Hook Test', () => {
const { useState, forwardRef, useImperativeHandle, memo, act } = Inula;
const LogUtils = getLogUtils();
it('简单使用useState', () => {
const App = () => {
const [num, setNum] = useState(0);
return (
<>
<p>{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
);
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
});
it('多个useState', () => {
const App = () => {
const [num, setNum] = useState(0);
const [count, setCount] = useState(0);
return (
<p
onClick={() => {
setNum(num + 1);
setCount(count + 2);
}}
>
{num}
{count}
</p>
);
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('00');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('12');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('24');
});
it('同一个useState声明的状态会被覆盖处理', () => {
const App = () => {
const [num, setNum] = useState(0);
return (
<p
onClick={() => {
setNum(num + 1);
setNum(num + 2);
}}
>
{num}
</p>
);
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('2');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('4');
});
it('useState设置相同的值时不会重新渲染', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
return <Text text={num} />;
};
Inula.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
expect(LogUtils.getAndClear()).toEqual([0]);
// useState修改state 时,设置相同的值,函数组件不会重新渲染
setNum(0);
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('0');
});
it('useState的惰性初始化', () => {
const App = forwardRef((props, ref) => {
const [num, setNum] = useState(() => {
LogUtils.log(props.initNum);
return props.initNum;
});
useImperativeHandle(ref, () => ({ setNum }));
return <p>{num}</p>;
});
const ref = Inula.createRef(null);
Inula.render(<App initNum={1} ref={ref} />, container);
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 设置num为3
ref.current.setNum(3);
// 初始化函数只在初始渲染时被调用,所以Scheduler里的dataArray清空后没有新增。
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('3');
});
it('useState与memo一起使用', () => {
let setNum;
const App = memo(() => {
const [num, _setNum] = useState(0);
setNum = _setNum;
return <Text text={num} />;
});
Inula.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(container.querySelector('p').innerHTML).toBe('0');
// 不会重新渲染
Inula.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('0');
// 会重新渲染
setNum(1);
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
it('卸载useState', () => {
let setNum;
let setCount;
const App = props => {
const [num, setNum_1] = useState(0);
setNum = setNum_1;
let count;
if (props.hasCount) {
const [count_1, setCount_1] = useState(0);
count = count_1;
setCount = setCount_1;
} else {
count = 'null';
}
return <Text text={`Number: ${num}, Count: ${count}`} />;
};
Inula.render(<App hasCount={true} />, container);
expect(LogUtils.getAndClear()).toEqual(['Number: 0, Count: 0']);
expect(container.textContent).toBe('Number: 0, Count: 0');
act(() => {
setNum(1);
setCount(2);
});
expect(LogUtils.getAndClear()).toEqual(['Number: 1, Count: 2']);
expect(container.textContent).toBe('Number: 1, Count: 2');
jest.spyOn(console, 'error').mockImplementation();
expect(() => {
Inula.render(<App hasCount={false} />, container);
}).toThrow('Hooks are less than expected, please check whether the hook is written in the condition.');
});
});

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('JSX Element test', () => {
it('symbol attribute prevent cloneDeep unlimited loop', function () {
function cloneDeep(obj) {
const result = {};
Object.keys(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
result[key] = cloneDeep(obj[key]);
} else {
result[key] = obj[key];
}
});
return result;
}
class Demo extends Inula.Component {
render() {
return <div>hello</div>;
}
}
const ele = Inula.createElement(Demo);
const copy = cloneDeep(ele);
expect(copy.vtype).toEqual(ele.vtype);
expect(Object.getOwnPropertySymbols(copy).length).toEqual(0);
});
});

View File

@ -1,223 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { Text } from '../jest/commonComponents';
import { getLogUtils } from '../jest/testUtils';
describe('LazyComponent Test', () => {
const LogUtils = getLogUtils();
const mockImport = jest.fn(async component => {
return { default: component };
});
it('Inula.lazy()', async () => {
class LazyComponent extends Inula.Component {
static defaultProps = { language: 'Java' };
render() {
const text = `${this.props.greeting}: ${this.props.language}`;
return <span>{text}</span>;
}
}
const Lazy = Inula.lazy(() => mockImport(LazyComponent));
Inula.render(
<Inula.Suspense fallback={<Text text="Loading..." />}>
<Lazy greeting="Hi" />
</Inula.Suspense>,
container
);
expect(LogUtils.getAndClear()).toEqual(['Loading...']);
expect(container.textContent).toBe('Loading...');
expect(container.querySelector('span')).toBe(null);
await Promise.resolve();
Inula.render(
<Inula.Suspense fallback={<Text text="Loading..." />}>
<Lazy greeting="Goodbye" />
</Inula.Suspense>,
container
);
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('span').innerHTML).toBe('Goodbye: Java');
});
it('同步解析', async () => {
const LazyApp = Inula.lazy(() => ({
then(cb) {
cb({ default: Text });
},
}));
Inula.render(
<Inula.Suspense fallback={<div>Loading...</div>}>
<LazyApp text="Lazy" />
</Inula.Suspense>,
container
);
expect(LogUtils.getAndClear()).toEqual(['Lazy']);
expect(container.textContent).toBe('Lazy');
});
it('异常捕获边界', async () => {
class ErrorBoundary extends Inula.Component {
state = {};
static getDerivedStateFromError(error) {
return { message: error.message };
}
render() {
return this.state.message ? <h2>Error: {this.state.message}</h2> : this.props.children;
}
}
const LazyComponent = () => {
const [num, setNum] = Inula.useState(0);
if (num === 2) {
throw new Error('num is 2');
} else {
return (
<>
<p id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
);
}
};
const LazyApp = Inula.lazy(() => mockImport(LazyComponent));
Inula.render(
<ErrorBoundary>
<Inula.Suspense fallback={<div>Loading...</div>}>
<LazyApp />
</Inula.Suspense>
</ErrorBoundary>,
container
);
expect(container.textContent).toBe('Loading...');
await Promise.resolve();
Inula.render(
<ErrorBoundary>
<Inula.Suspense fallback={<Text text="Loading..." />}>
<LazyApp />
</Inula.Suspense>
</ErrorBoundary>,
container
);
expect(container.textContent).toBe('0');
container.querySelector('button').click();
expect(container.textContent).toBe('1');
jest.spyOn(console, 'error').mockImplementation();
container.querySelector('button').click();
expect(container.textContent).toBe('Error: num is 2');
});
it('componentDidCatch捕获异常', async () => {
class ErrorBoundary extends Inula.Component {
state = {
catchError: false,
error: null,
componentStack: null,
};
componentDidCatch(error, info) {
if (error) {
this.setState({
catchError: true,
error,
componentStack: info.componentStack,
});
}
}
render() {
return this.state.catchError ? <h2>Error: {this.state.error.message}</h2> : this.props.children;
}
}
const LazyComponent = () => {
const [num, setNum] = Inula.useState(0);
if (num === 2) {
throw new Error('num is 2');
} else {
return (
<>
<p id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
);
}
};
const LazyApp = Inula.lazy(() => mockImport(LazyComponent));
Inula.render(
<ErrorBoundary>
<Inula.Suspense fallback={<div>Loading...</div>}>
<LazyApp />
</Inula.Suspense>
</ErrorBoundary>,
container
);
expect(container.textContent).toBe('Loading...');
await Promise.resolve();
Inula.render(
<ErrorBoundary>
<Inula.Suspense fallback={<Text text="Loading..." />}>
<LazyApp />
</Inula.Suspense>
</ErrorBoundary>,
container
);
expect(container.textContent).toBe('0');
container.querySelector('button').click();
expect(container.textContent).toBe('1');
jest.spyOn(console, 'error').mockImplementation();
container.querySelector('button').click();
expect(container.textContent).toBe('Error: num is 2');
});
it('#24 配合memo', async () => {
const fnComp = () => {
return <h1>inula</h1>;
};
const LazyApp = Inula.lazy(() => ({
then(cb) {
cb({ default: Inula.memo(() => fnComp, false) });
},
}));
expect(() => {
Inula.render(
<Inula.Suspense fallback={<div>Loading...</div>}>
<LazyApp text="Lazy" />
</Inula.Suspense>,
container
);
Inula.render(
<Inula.Suspense fallback={<div>Loading...</div>}>
<LazyApp text="Lazy" />
</Inula.Suspense>,
container
);
}).not.toThrow();
});
});

View File

@ -1,467 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('LifeCycle Test', () => {
const LogUtils = getLogUtils();
describe('LifeCycle function', () => {
it('不能在componentWillMount里setState', () => {
class App extends Inula.Component {
state = {};
UNSAFE_componentWillMount() {
this.setState = {
num: 1,
};
}
render() {
return <p>{this.state.num}</p>;
}
}
const realNode = Inula.render(<App />, container);
// 不能在componentWillMount里setState
expect(realNode.textContent).toBe(undefined);
});
it('componentDidMount里调用setState()将触发额外渲染', () => {
class ChildApp extends Inula.Component {
constructor(props) {
super(props);
}
componentDidMount() {
LogUtils.log(this.props.isShow);
}
render() {
return <p>{this.props.isShow}</p>;
}
}
class App extends Inula.Component {
constructor(props) {
super(props);
LogUtils.log('constructor');
this.state = { shouldShowChild: false };
}
componentDidMount() {
LogUtils.log('componentDidMount');
this.setState({ shouldShowChild: true });
}
render() {
return <div>{this.state.shouldShowChild ? <ChildApp isShow={this.state.shouldShowChild} /> : <div />}</div>;
}
}
const realNode = Inula.render(<App />, container);
// 确实触发了额外渲染
expect(LogUtils.getAndClear()).toEqual(['constructor', 'componentDidMount', true]);
// 可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前
expect(container.querySelector('p').innerHTML).toBe('');
// 在 componentDidMount() 里可以更新state
expect(realNode.state).toStrictEqual({ shouldShowChild: true });
});
it('调用 this.setState() 通常不会触发 UNSAFE_componentWillReceiveProps()', () => {
class App extends Inula.Component {
state = {};
update = () => {
this.setState({ num: 4 });
};
UNSAFE_componentWillReceiveProps() {
LogUtils.log('componentWillReceiveProps');
this.setState = {
num: 1,
};
}
render() {
return <p>{this.state.num}</p>;
}
}
const realNode = Inula.render(<App />, container);
expect(realNode.textContent).toBe(undefined);
realNode.update();
expect(LogUtils.getAndClear()).toEqual([]);
});
it('不能在componentWillReceiveProps里setState', () => {
class ChildApp extends Inula.Component {
state = {};
UNSAFE_componentWillReceiveProps() {
this.state = { text: 'text' };
}
render() {
LogUtils.log(this.state.text);
return <div>{this.state.text}</div>;
}
}
class App extends Inula.Component {
state = {};
update = () => {
this.setState({ num: 4 });
};
render() {
return <ChildApp num={this.state.num} />;
}
}
const realNode = Inula.render(<App />, container);
expect(realNode.textContent).toBe(undefined);
realNode.update();
expect(LogUtils.getAndClear()).toEqual([undefined, 'text']);
// 不能在componentWillMount里setState
expect(realNode.textContent).toBe(undefined);
});
it('shouldComponentUpdate与getDerivedStateFromProps', () => {
class App extends Inula.Component {
constructor(props) {
super(props);
this.state = {
num: props.num,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.num === prevState.num) {
return null;
}
return { num: nextProps.num };
}
shouldComponentUpdate(nextProps, nextState) {
LogUtils.log('shouldComponentUpdate');
return nextState.num !== this.state.num;
}
render() {
return <p>{this.state.num}</p>;
}
}
Inula.render(<App num={1} />, container);
// 初次渲染不会调用shouldComponentUpdate
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={1} />, container);
// getDerivedStateFromProps判断state没有变化时会调用shouldComponentUpdate
expect(LogUtils.getAndClear()).toEqual(['shouldComponentUpdate']);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={2} />, container);
// getDerivedStateFromProps判断state变化时会调用shouldComponentUpdate
expect(LogUtils.getAndClear()).toEqual(['shouldComponentUpdate']);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()', () => {
class App extends Inula.Component {
constructor(props) {
super(props);
this.state = {
num: props.num,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.num === prevState.num) {
return null;
}
return { num: nextProps.num };
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.num !== this.state.num;
}
componentDidUpdate() {
LogUtils.log('componentDidUpdate');
}
render() {
return <p>{this.state.num}</p>;
}
}
Inula.render(<App num={1} />, container);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={1} />, container);
// 不会调用componentDidUpdate()
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={2} />, container);
// 调用componentDidUpdate()
expect(LogUtils.getAndClear()).toEqual(['componentDidUpdate']);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('getSnapshotBeforeUpdate()的返回值会作为componentDidUpdate()的第三个参数', () => {
class App extends Inula.Component {
constructor(props) {
super(props);
this.state = {
num: 0,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.num === prevState.num) {
return null;
}
return { num: nextProps.num };
}
getSnapshotBeforeUpdate(prevProps, prevState) {
LogUtils.log(`getSnapshotBeforeUpdate prevProps:${prevProps.num} prevState:${prevState.num}`);
return 'Snapshot';
}
componentDidUpdate(prevProps, prevState, snapshot) {
LogUtils.log(`componentDidUpdate prevProps:${prevProps.num} prevState:${prevState.num} snapshot:${snapshot}`);
}
render() {
return <p>{this.state.num}</p>;
}
}
Inula.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('');
Inula.render(<App num={1} />, container);
// Snapshot作为componentDidUpdate()的第三个参数
expect(LogUtils.getAndClear()).toEqual([
'getSnapshotBeforeUpdate prevProps:undefined prevState:undefined',
'componentDidUpdate prevProps:undefined prevState:undefined snapshot:Snapshot',
]);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={1} />, container);
expect(LogUtils.getAndClear()).toEqual([
'getSnapshotBeforeUpdate prevProps:1 prevState:1',
'componentDidUpdate prevProps:1 prevState:1 snapshot:Snapshot',
]);
expect(container.querySelector('p').innerHTML).toBe('1');
Inula.render(<App num={2} />, container);
expect(LogUtils.getAndClear()).toEqual([
'getSnapshotBeforeUpdate prevProps:1 prevState:1',
'componentDidUpdate prevProps:1 prevState:1 snapshot:Snapshot',
]);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('无论什么原因触发了渲染,只要有渲染就会触发getDerivedStateFromProps', () => {
class App extends Inula.Component {
constructor(props) {
super(props);
this.state = {
num: 0,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
LogUtils.log(`getDerivedStateFromProps nextProps:${nextProps.num} prevState:${prevState.num}`);
}
render() {
return <p>{this.state.num}</p>;
}
}
let realNode = Inula.render(<App />, container);
realNode = Inula.render(<App num={1} />, container);
realNode.forceUpdate();
// 触发了3次渲染
expect(LogUtils.getAndClear()).toEqual([
'getDerivedStateFromProps nextProps:undefined prevState:0',
'getDerivedStateFromProps nextProps:1 prevState:0',
'getDerivedStateFromProps nextProps:1 prevState:0',
]);
});
});
it('生命周期执行顺序', () => {
class ChildApp extends Inula.Component {
UNSAFE_componentWillMount() {
LogUtils.log('Child componentWillMount');
}
componentDidMount() {
LogUtils.log('Child componentDidMount');
}
UNSAFE_componentWillReceiveProps() {
LogUtils.log('Child componentWillReceiveProps');
}
shouldComponentUpdate(nextProps, nextState) {
LogUtils.log('Child shouldComponentUpdates');
return this.props.number !== nextProps.number;
}
UNSAFE_componentWillUpdate() {
LogUtils.log('Child componentWillUpdate');
}
componentDidUpdate() {
LogUtils.log('Child componentDidUpdate');
}
componentWillUnmount() {
LogUtils.log('Child componentWillUnmount');
}
render() {
return <p>{this.props.number}</p>;
}
}
class App extends Inula.Component {
UNSAFE_componentWillMount() {
LogUtils.log('componentWillMount');
}
componentDidMount() {
LogUtils.log('componentDidMount');
}
UNSAFE_componentWillReceiveProps() {
LogUtils.log('componentWillReceiveProps');
}
shouldComponentUpdate(nextProps, nextState) {
LogUtils.log('shouldComponentUpdates');
return this.props.num !== nextProps.num;
}
UNSAFE_componentWillUpdate() {
LogUtils.log('componentWillUpdate');
}
componentDidUpdate() {
LogUtils.log('componentDidUpdate');
}
componentWillUnmount() {
LogUtils.log('componentWillUnmount');
}
render() {
return <ChildApp number={this.props.num} />;
}
}
Inula.render(<App num={1} />, container);
expect(container.textContent).toBe('1');
expect(LogUtils.getAndClear()).toEqual([
'componentWillMount',
'Child componentWillMount',
'Child componentDidMount',
'componentDidMount',
]);
Inula.render(<App num={2} />, container);
expect(container.textContent).toBe('2');
expect(LogUtils.getAndClear()).toEqual([
'componentWillReceiveProps',
'shouldComponentUpdates',
'componentWillUpdate',
'Child componentWillReceiveProps',
'Child shouldComponentUpdates',
'Child componentWillUpdate',
'Child componentDidUpdate',
'componentDidUpdate',
]);
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(LogUtils.getAndClear()).toEqual(['componentWillUnmount', 'Child componentWillUnmount']);
});
it('新生命周期执行顺序', () => {
class ChildApp extends Inula.Component {
static getDerivedStateFromProps(props, state) {
LogUtils.log('Child getDerivedStateFromProps');
}
componentDidMount() {
LogUtils.log('Child componentDidMount');
}
shouldComponentUpdate(nextProps, nextState) {
LogUtils.log('Child shouldComponentUpdates');
return this.props.number !== nextProps.number;
}
componentDidUpdate() {
LogUtils.log('Child componentDidUpdate');
}
getSnapshotBeforeUpdate() {
LogUtils.log('Child getSnapshotBeforeUpdate');
}
componentWillUnmount() {
LogUtils.log('Child componentWillUnmount');
}
render() {
return <p>{this.props.number}</p>;
}
}
class App extends Inula.Component {
static getDerivedStateFromProps(props, state) {
LogUtils.log('getDerivedStateFromProps');
}
componentDidMount() {
LogUtils.log('componentDidMount');
}
shouldComponentUpdate(nextProps, nextState) {
LogUtils.log('shouldComponentUpdates');
return this.props.num !== nextProps.num;
}
getSnapshotBeforeUpdate() {
LogUtils.log('getSnapshotBeforeUpdate');
}
componentDidUpdate() {
LogUtils.log('componentDidUpdate');
}
componentWillUnmount() {
LogUtils.log('componentWillUnmount');
}
render() {
return <ChildApp number={this.props.num} />;
}
}
Inula.render(<App num={1} />, container);
expect(container.textContent).toBe('1');
expect(LogUtils.getAndClear()).toEqual([
'getDerivedStateFromProps',
'Child getDerivedStateFromProps',
'Child componentDidMount',
'componentDidMount',
]);
Inula.render(<App num={2} />, container);
expect(container.textContent).toBe('2');
expect(LogUtils.getAndClear()).toEqual([
'getDerivedStateFromProps',
'shouldComponentUpdates',
'Child getDerivedStateFromProps',
'Child shouldComponentUpdates',
'Child getSnapshotBeforeUpdate',
'getSnapshotBeforeUpdate',
'Child componentDidUpdate',
'componentDidUpdate',
]);
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(LogUtils.getAndClear()).toEqual(['componentWillUnmount', 'Child componentWillUnmount']);
});
});

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Memo Test', () => {
it('Memo should not make the path wrong', function () {
let updateApp;
function Child() {
const [_, update] = Inula.useState({});
updateApp = () => update({});
return <div></div>;
}
const MemoChild = Inula.memo(Child);
function App() {
return (
<div>
<MemoChild />
</div>
);
}
const MemoApp = Inula.memo(App);
Inula.render(
<div>
<MemoApp key="1" />
</div>,
container
);
Inula.render(
<div>
<span></span>
<MemoApp key="1" />
</div>,
container
);
expect(() => updateApp()).not.toThrow();
});
});

View File

@ -1,289 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
import dispatchChangeEvent from '../utils/dispatchChangeEvent';
describe('PortalComponent Test', () => {
const LogUtils = getLogUtils();
it('将子节点渲染到存在于父组件以外的 DOM 节点', () => {
const portalRoot = document.createElement('div');
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
this.element = portalRoot;
}
render() {
return Inula.createPortal(this.props.child, this.element);
}
}
Inula.render(<PortalApp child={<div>PortalApp</div>} />, container);
expect(container.textContent).toBe('');
// <div>PortalApp</div>被渲染到了portalRoot而非container
expect(portalRoot.textContent).toBe('PortalApp');
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('');
});
it('渲染多个Portal', () => {
const portalRoot1st = document.createElement('div');
const portalRoot2nd = document.createElement('div');
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
this.element = portalRoot1st;
this.newElement = portalRoot2nd;
}
render() {
return [
Inula.createPortal(this.props.child, this.element),
Inula.createPortal(this.props.child, this.newElement),
];
}
}
Inula.render(<PortalApp child={<div>PortalApp</div>} />, container);
expect(container.textContent).toBe('');
// <div>PortalApp</div>被渲染到了portalRoot而非container
expect(portalRoot1st.textContent).toBe('PortalApp');
expect(portalRoot2nd.textContent).toBe('PortalApp');
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(portalRoot1st.textContent).toBe('');
expect(portalRoot2nd.textContent).toBe('');
});
it('渲染最近的Portal', () => {
const portalRoot1st = document.createElement('div');
const portalRoot2nd = document.createElement('div');
const portalRoot3rd = document.createElement('div');
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
this.element = portalRoot1st;
this.newElement = portalRoot2nd;
this.element3rd = portalRoot3rd;
}
render() {
return [
<div>PortalApp1st</div>,
Inula.createPortal(
[<div>PortalApp4</div>, Inula.createPortal(this.props.child, this.element3rd)],
this.element
),
<div>PortalApp2nd</div>,
Inula.createPortal(this.props.child, this.newElement),
];
}
}
Inula.render(<PortalApp child={<div>PortalApp</div>} />, container);
expect(container.textContent).toBe('PortalApp1stPortalApp2nd');
// <div>PortalApp4</div>会挂载在this.element上
expect(portalRoot1st.textContent).toBe('PortalApp4');
expect(portalRoot2nd.textContent).toBe('PortalApp');
expect(portalRoot3rd.textContent).toBe('PortalApp');
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(portalRoot1st.textContent).toBe('');
expect(portalRoot2nd.textContent).toBe('');
});
it('改变Portal的参数', () => {
const portalRoot = document.createElement('div');
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
this.element = portalRoot;
}
render() {
return Inula.createPortal(this.props.child, this.element);
}
}
Inula.render(<PortalApp key="portal" child={<div>PortalApp</div>} />, container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('PortalApp');
Inula.render(<PortalApp key="portal" child={<div>AppPortal</div>} />, container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('AppPortal');
Inula.render(<PortalApp key="portal" child={['por', 'tal']} />, container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('portal');
Inula.render(<PortalApp key="portal" child={null} />, container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('');
Inula.unmountComponentAtNode(container);
expect(container.textContent).toBe('');
expect(portalRoot.textContent).toBe('');
});
it('通过Portal进行事件冒泡', () => {
const portalRoot = document.createElement('div');
const buttonRef = Inula.createRef();
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
this.element = portalRoot;
}
render() {
return Inula.createPortal(this.props.child, this.element);
}
}
const Child = () => {
return (
<div>
<button ref={buttonRef}>Click</button>
</div>
);
};
const App = () => {
const handleClick = () => {
LogUtils.log('bubble click event');
};
const handleCaptureClick = () => {
LogUtils.log('capture click event');
};
return (
<div onClickCapture={handleCaptureClick()} onClick={handleClick()}>
<PortalApp child={<Child />}></PortalApp>
</div>
);
};
Inula.render(<App />, container);
const event = document.createEvent('Event');
event.initEvent('click', true, true);
buttonRef.current.dispatchEvent(event);
expect(LogUtils.getAndClear()).toEqual([
// 从外到内先捕获再冒泡
'capture click event',
'bubble click event',
]);
});
it('Create portal at app root should not add event listener multiple times', () => {
const btnRef = Inula.createRef();
class PortalApp extends Inula.Component {
constructor(props) {
super(props);
}
render() {
return Inula.createPortal(this.props.child, container);
}
}
const onClick = jest.fn();
class App extends Inula.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button onClick={onClick} ref={btnRef}></button>
<PortalApp />
</div>
);
}
}
Inula.render(<App />, container);
btnRef.current.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
it('#76 Portal onChange should activate', () => {
class Dialog extends Inula.Component {
node;
constructor(props) {
super(props);
this.node = window.document.createElement('div');
window.document.body.appendChild(this.node);
}
render() {
return Inula.createPortal(this.props.children, this.node);
}
}
let showPortalInput;
const fn = jest.fn();
const inputRef = Inula.createRef();
function App() {
const Input = () => {
const [show, setShow] = Inula.useState(false);
showPortalInput = setShow;
Inula.useEffect(() => {
setTimeout(() => {
setShow(true);
}, 0);
}, []);
if (!show) {
return null;
}
return <input onChange={fn} ref={inputRef} />;
};
return (
<div>
<Dialog>
<Input />
</Dialog>
</div>
);
}
Inula.render(<App />, container);
showPortalInput(true);
jest.advanceTimersToNextTimer();
dispatchChangeEvent(inputRef.current, 'test');
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,73 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { Text } from '../jest/commonComponents';
import { getLogUtils } from '../jest/testUtils';
describe('SuspenseComponent Test', () => {
const LogUtils = getLogUtils();
const mockImport = jest.fn(async component => {
return { default: component };
});
// var EMPTY_OBJECT = {};
// const mockCreateResource = jest.fn((component) => {
// let result = EMPTY_OBJECT;
// return () =>{
// component().then(res => {
// LogUtils.log(res);
// result = res;
// }, reason => {
// LogUtils.log(reason);
// });
// if(result === EMPTY_OBJECT){
// throw component();
// }
// return result;
// };
// });
it('挂载lazy组件', async () => {
// 用同步的代码来实现异步操作
class LazyComponent extends Inula.Component {
render() {
return <Text text={this.props.num} />;
}
}
const Lazy = Inula.lazy(() => mockImport(LazyComponent));
Inula.render(
<Inula.Suspense fallback={<Text text="Loading..." />}>
<Lazy num={5} />
</Inula.Suspense>,
container
);
expect(LogUtils.getAndClear()).toEqual(['Loading...']);
expect(container.textContent).toBe('Loading...');
await Promise.resolve();
Inula.render(
<Inula.Suspense fallback={<Text text="Loading..." />}>
<Lazy num={5} />
</Inula.Suspense>,
container
);
expect(LogUtils.getAndClear()).toEqual([5]);
expect(container.querySelector('p').innerHTML).toBe('5');
});
});

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Dom Attribute', () => {
it('属性值为null或undefined时不会设置此属性', () => {
Inula.render(<div id="div" />, container);
expect(container.querySelector('div').getAttribute('id')).toBe('div');
Inula.render(<div id={null} />, container);
expect(container.querySelector('div').hasAttribute('id')).toBe(false);
Inula.render(<div id={undefined} />, container);
expect(container.querySelector('div').hasAttribute('id')).toBe(false);
});
it('可以设置未知的属性', () => {
Inula.render(<div abcd="abcd" />, container);
expect(container.querySelector('div').hasAttribute('abcd')).toBe(true);
expect(container.querySelector('div').getAttribute('abcd')).toBe('abcd');
});
it('未知属性的值为null或undefined时不会设置此属性', () => {
Inula.render(<div abcd={null} />, container);
expect(container.querySelector('div').hasAttribute('abcd')).toBe(false);
Inula.render(<div abcd={undefined} />, container);
expect(container.querySelector('div').hasAttribute('abcd')).toBe(false);
});
it('未知属性的值为数字时,属性值会转为字符串', () => {
Inula.render(<div abcd={0} />, container);
expect(container.querySelector('div').getAttribute('abcd')).toBe('0');
Inula.render(<div abcd={-3} />, container);
expect(container.querySelector('div').getAttribute('abcd')).toBe('-3');
Inula.render(<div abcd={123.45} />, container);
expect(container.querySelector('div').getAttribute('abcd')).toBe('123.45');
});
it('访问节点的标准属性时可以拿到属性值访问节点的非标准属性时会得到undefined', () => {
Inula.render(<div id={'div'} abcd={0} />, container);
expect(container.querySelector('div').id).toBe('div');
expect(container.querySelector('div').abcd).toBe(undefined);
});
it('特性方法', () => {
Inula.render(<div id={'div'} abcd={0} />, container);
expect(container.querySelector('div').hasAttribute('abcd')).toBe(true);
expect(container.querySelector('div').getAttribute('abcd')).toBe('0');
container.querySelector('div').setAttribute('abcd', 4);
expect(container.querySelector('div').getAttribute('abcd')).toBe('4');
container.querySelector('div').removeAttribute('abcd');
expect(container.querySelector('div').hasAttribute('abcd')).toBe(false);
});
it('特性大小写不敏感', () => {
Inula.render(<div id={'div'} abcd={0} />, container);
expect(container.querySelector('div').hasAttribute('abcd')).toBe(true);
expect(container.querySelector('div').hasAttribute('ABCD')).toBe(true);
expect(container.querySelector('div').getAttribute('abcd')).toBe('0');
expect(container.querySelector('div').getAttribute('ABCD')).toBe('0');
});
it('使用 data- 开头的特性时会映射到DOM的dataset属性且中划线格式会变成驼峰格式', () => {
Inula.render(<div />, container);
container.querySelector('div').setAttribute('data-first-name', 'Tom');
expect(container.querySelector('div').dataset.firstName).toBe('Tom');
});
it('style 自动加px', () => {
const div = Inula.render(<div style={{ width: 10, height: 20 }} />, container);
expect(window.getComputedStyle(div).getPropertyValue('width')).toBe('10px');
expect(window.getComputedStyle(div).getPropertyValue('height')).toBe('20px');
});
it('WebkitLineClamp和lineClamp样式不会把数字转换成字符串或者追加"px"', () => {
Inula.render(<div id={'div'} style={{ WebkitLineClamp: 2 }} />, container);
// 浏览器可以将WebkitLineClamp识别为-webkit-line-clamp,测试框架不可以
expect(container.querySelector('div').style.WebkitLineClamp).toBe(2);
});
it('空字符串做属性名', () => {
const emptyStringProps = { '': '' };
expect(() => {
Inula.render(<div {...emptyStringProps} />, container);
}).not.toThrow();
});
});

View File

@ -1,362 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/* eslint-disable @typescript-eslint/no-empty-function */
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('Dom Input', () => {
const { act } = Inula;
const LogUtils = getLogUtils();
describe('type checkbox', () => {
it('没有设置checked属性时控制台不会报错', () => {
expect(() => Inula.render(<input type="checkbox" value={false} />, container)).not.toThrow();
});
it('checked属性为undefined或null时且没有onChange属性或没有readOnly={true},控制台不会报错', () => {
expect(() => Inula.render(<input type="checkbox" checked={undefined} />, container)).not.toThrow();
expect(() => Inula.render(<input type="checkbox" checked={null} />, container)).not.toThrow();
});
it('复选框的value属性值可以改变', () => {
Inula.render(
<input
type="checkbox"
value=""
onChange={() => {
LogUtils.log('checkbox click');
}}
/>,
container
);
Inula.render(
<input
type="checkbox"
value={0}
onChange={() => {
LogUtils.log('checkbox click');
}}
/>,
container
);
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('input').value).toBe('0');
expect(container.querySelector('input').getAttribute('value')).toBe('0');
});
it('复选框不设置value属性值时会设置value为"on"', () => {
Inula.render(<input type="checkbox" defaultChecked={true} />, container);
expect(container.querySelector('input').value).toBe('on');
expect(container.querySelector('input').hasAttribute('value')).toBe(false);
});
it('测试defaultChecked与更改defaultChecked', () => {
Inula.render(<input type="checkbox" defaultChecked={0} />, container);
expect(container.querySelector('input').value).toBe('on');
expect(container.querySelector('input').checked).toBe(false);
Inula.render(<input type="checkbox" defaultChecked={1} />, container);
expect(container.querySelector('input').value).toBe('on');
expect(container.querySelector('input').checked).toBe(true);
Inula.render(<input type="checkbox" defaultChecked={'1'} />, container);
expect(container.querySelector('input').value).toBe('on');
expect(container.querySelector('input').checked).toBe(true);
});
});
describe('type text', () => {
it('value属性为undefined或null时且没有onChange属性或没有readOnly={true},控制台不会报错', () => {
expect(() => Inula.render(<input type="text" value={undefined} />, container)).not.toThrow();
expect(() => Inula.render(<input type="text" value={null} />, container)).not.toThrow();
expect(() => Inula.render(<input type="text" />, container)).not.toThrow();
});
it('value值会转为字符串', () => {
const realNode = Inula.render(<input type="text" value={1} />, container);
expect(realNode.value).toBe('1');
});
it('value值可以被设置为true/false', () => {
let realNode = Inula.render(<input type="text" value={1} onChange={jest.fn()} />, container);
expect(realNode.value).toBe('1');
realNode = Inula.render(<input type="text" value={true} onChange={jest.fn()} />, container);
expect(realNode.value).toBe('true');
realNode = Inula.render(<input type="text" value={false} onChange={jest.fn()} />, container);
expect(realNode.value).toBe('false');
});
it('value值可以被设置为object', () => {
let realNode = Inula.render(<input type="text" value={1} onChange={jest.fn()} />, container);
expect(realNode.value).toBe('1');
const value = {
toString: () => {
return 'value';
},
};
realNode = Inula.render(<input type="text" value={value} onChange={jest.fn()} />, container);
expect(realNode.value).toBe('value');
});
it('设置defaultValue', () => {
let realNode = Inula.render(<input type="text" defaultValue={1} />, container);
expect(realNode.value).toBe('1');
expect(realNode.getAttribute('value')).toBe('1');
Inula.unmountComponentAtNode(container);
// 测试defaultValue为boolean类型
realNode = Inula.render(<input type="text" defaultValue={true} />, container);
expect(realNode.value).toBe('true');
expect(realNode.getAttribute('value')).toBe('true');
Inula.unmountComponentAtNode(container);
realNode = Inula.render(<input type="text" defaultValue={false} />, container);
expect(realNode.value).toBe('false');
expect(realNode.getAttribute('value')).toBe('false');
Inula.unmountComponentAtNode(container);
const value = {
toString: () => {
return 'default';
},
};
realNode = Inula.render(<input type="text" defaultValue={value} />, container);
expect(realNode.value).toBe('default');
expect(realNode.getAttribute('value')).toBe('default');
});
it('value为0、defaultValue为1input 的value应该为0', () => {
const input = Inula.render(<input defaultValue={1} value={0} />, container);
expect(input.getAttribute('value')).toBe('0');
});
it('name属性', () => {
let realNode = Inula.render(<input type="text" name={'name'} />, container);
expect(realNode.name).toBe('name');
expect(realNode.getAttribute('name')).toBe('name');
Inula.unmountComponentAtNode(container);
// 没有设置name属性
realNode = Inula.render(<input type="text" defaultValue={true} />, container);
expect(realNode.name).toBe('');
expect(realNode.getAttribute('name')).toBe(null);
});
it('受控input可以触发onChange', () => {
let realNode = Inula.render(
<input type="text" value={'name'} onChange={LogUtils.log('text change')} />,
container
);
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(realNode, 'abcd');
// 再触发事件
realNode.dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true,
})
);
// 触发onChange
expect(LogUtils.getAndClear()).toEqual(['text change']);
});
});
describe('type radio', () => {
it('radio的value可以更新', () => {
let realNode = Inula.render(<input type="radio" value={''} />, container);
expect(realNode.value).toBe('');
expect(realNode.getAttribute('value')).toBe('');
realNode = Inula.render(<input type="radio" value={false} />, container);
expect(realNode.value).toBe('false');
expect(realNode.getAttribute('value')).toBe('false');
realNode = Inula.render(<input type="radio" value={true} />, container);
expect(realNode.value).toBe('true');
expect(realNode.getAttribute('value')).toBe('true');
});
it('相同name且在同一表单的radio互斥', () => {
Inula.render(
<>
<input id="a" type="radio" name="num" onChange={LogUtils.log('a change')} defaultChecked={true} />
<input id="b" type="radio" name="num" onChange={LogUtils.log('b change')} />
<input id="c" type="radio" name="num" onChange={LogUtils.log('c change')} />
<form>
<input id="d" type="radio" name="num" onChange={() => {}} defaultChecked={true} />
</form>
</>,
container
);
expect(container.querySelector('input').checked).toBe(true);
expect(document.getElementById('b').checked).toBe(false);
expect(document.getElementById('c').checked).toBe(false);
expect(document.getElementById('d').checked).toBe(true);
// 模拟点击id为b的单选框b为选中状态ac为非选中状态
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'checked').set.call(
document.getElementById('b'),
true
);
expect(LogUtils.getAndClear()).toEqual(['a change', 'b change', 'c change']);
expect(container.querySelector('input').checked).toBe(false);
expect(document.getElementById('b').checked).toBe(true);
expect(document.getElementById('c').checked).toBe(false);
expect(document.getElementById('d').checked).toBe(true);
});
it('name改变不影响相同name的radio', () => {
const inputRef = Inula.createRef();
const App = () => {
const [isNum, setNum] = Inula.useState(false);
const inputName = isNum ? 'secondName' : 'firstName';
const buttonClick = () => {
setNum(true);
};
return (
<div>
<button type="button" onClick={() => buttonClick()} />
<input type="radio" name={inputName} onChange={() => {}} checked={isNum === true} />
<input ref={inputRef} type="radio" name={inputName} onChange={() => {}} checked={isNum === false} />
</div>
);
};
Inula.render(<App />, container);
expect(container.querySelector('input').checked).toBe(false);
expect(inputRef.current.checked).toBe(true);
// 点击button触发setNum
container.querySelector('button').click();
expect(container.querySelector('input').checked).toBe(true);
expect(inputRef.current.checked).toBe(false);
});
});
describe('type submit', () => {
it('type submit value', () => {
Inula.render(<input type="submit" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(false);
Inula.unmountComponentAtNode(container);
Inula.render(<input type="submit" value="" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('');
Inula.render(<input type="submit" value="submit" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('submit');
});
});
describe('type reset', () => {
it('type reset value', () => {
Inula.render(<input type="reset" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(false);
Inula.unmountComponentAtNode(container);
Inula.render(<input type="reset" value="" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('');
Inula.render(<input type="reset" value="reset" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('reset');
});
});
describe('type number', () => {
it('value值会把number类型转为字符串且.xx转为0.xx', () => {
Inula.render(<input type="number" value={0.12} />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('0.12');
});
it('value值会把number类型转为字符串且.xx转为0.xx', () => {
Inula.render(<input type="number" value={0.12} />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('0.12');
});
it('改变node.value值', () => {
let setNum;
const App = () => {
const [num, _setNum] = Inula.useState('');
setNum = _setNum;
return <input type="number" value={num} readOnly={true} />;
};
Inula.render(<App />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('');
act(() => {
setNum(0);
});
expect(container.querySelector('input').value).toBe('0');
});
it('node.value精度', () => {
let setNum;
const App = () => {
const [num, _setNum] = Inula.useState(0.0);
setNum = _setNum;
return <input type="number" value={num} readOnly={true} />;
};
Inula.render(<App />, container);
expect(container.querySelector('input').getAttribute('value')).toBe('0');
act(() => {
setNum(1.0);
});
expect(container.querySelector('input').getAttribute('value')).toBe('0');
expect(container.querySelector('input').value).toBe('1');
});
it('node.value与Attrubute value', () => {
const App = () => {
return <input type="number" defaultValue={1} />;
};
Inula.render(<App />, container);
expect(container.querySelector('input').getAttribute('value')).toBe('1');
expect(container.querySelector('input').value).toBe('1');
// 先修改
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(
container.querySelector('input'),
'8'
);
// 再触发事件
container.querySelector('input').dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true,
})
);
// Attrubute value不会改变node.value会改变
expect(container.querySelector('input').getAttribute('value')).toBe('1');
expect(container.querySelector('input').value).toBe('8');
});
});
describe('type reset', () => {
it('type reset的value值', () => {
Inula.render(<input type="reset" value={0.12} />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('0.12');
Inula.unmountComponentAtNode(container);
Inula.render(<input type="reset" value={''} />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(true);
expect(container.querySelector('input').getAttribute('value')).toBe('');
Inula.unmountComponentAtNode(container);
Inula.render(<input type="reset" />, container);
expect(container.querySelector('input').hasAttribute('value')).toBe(false);
});
});
});

View File

@ -1,336 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Dom Select', () => {
it('设置value', () => {
const selectNode = (
<select value="Vue">
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
const realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
realNode.value = 'React';
// 改变value会影响select的状态
Inula.render(selectNode, container);
expect(realNode.options[0].selected).toBe(true);
expect(realNode.value).toBe('React');
});
it('设置value为对象', () => {
let selectValue = {
toString: () => {
return 'Vue';
},
};
const selectNode = (
<select value={selectValue}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
const realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
selectValue = {
toString: () => {
return 'React';
},
};
const newSelectNode = (
<select value={selectValue}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
// 改变value会影响select的状态
Inula.render(newSelectNode, container);
expect(realNode.options[0].selected).toBe(true);
expect(realNode.value).toBe('React');
});
it('受控select转为不受控会保存原来select', () => {
const selectNode = (
<select value="Vue">
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
const realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
const newSelectNode = (
<select>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(newSelectNode, container);
// selected不变
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(true);
expect(realNode.value).toBe('Vue');
});
it('设置defaultValue', () => {
let defaultVal = 'Vue';
const selectNode = (
<select defaultValue={defaultVal}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
let realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
defaultVal = 'React';
// 改变defaultValue没有影响
realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(true);
});
it('设置defaultValue后,select不受控', () => {
const selectNode = (
<select defaultValue={'Vue'}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
let realNode = Inula.render(selectNode, container);
expect(realNode.value).toBe('Vue');
expect(realNode.options[1].selected).toBe(true);
// 先修改
Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value').set.call(realNode, 'React');
// 再触发事件
container.querySelector('select').dispatchEvent(
new Event('change', {
bubbles: true,
cancelable: true,
})
);
// 鼠标改变受控select生效,select不受控
Inula.render(selectNode, container);
// 'React'项没被选中
expect(realNode.options[0].selected).toBe(true);
expect(realNode.options[1].selected).toBe(false);
expect(realNode.value).toBe('React');
});
xit('设置multiple(一)', () => {
jest.spyOn(console, 'error').mockImplementation();
const selectNode = (
<select multiple="multiple" defaultValue={'Vue'}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
expect(() => Inula.render(selectNode, container)).toThrowError('newValues.forEach is not a function');
});
it('设置multiple(二)', () => {
let selectNode = (
<select id="se" multiple="multiple" defaultValue={['Vue', 'Angular']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
expect(() => Inula.render(selectNode, container)).not.toThrow();
expect(document.getElementById('se').options[0].selected).toBe(false);
expect(document.getElementById('se').options[1].selected).toBe(true);
expect(document.getElementById('se').options[2].selected).toBe(true);
// 改变defaultValue没有影响
selectNode = (
<select id="se" multiple="multiple" defaultValue={['React']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(false);
expect(document.getElementById('se').options[1].selected).toBe(true);
expect(document.getElementById('se').options[2].selected).toBe(true);
});
it('设置multiple(三)', () => {
let selectNode = (
<select id="se" multiple="multiple" value={['Vue', 'Angular']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
expect(() => Inula.render(selectNode, container)).not.toThrow();
expect(document.getElementById('se').options[0].selected).toBe(false);
expect(document.getElementById('se').options[1].selected).toBe(true);
expect(document.getElementById('se').options[2].selected).toBe(true);
// 改变value有影响
selectNode = (
<select id="se" multiple="multiple" value={['React']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(true);
expect(document.getElementById('se').options[1].selected).toBe(false);
expect(document.getElementById('se').options[2].selected).toBe(false);
});
it('defaultValue设置multiple与非multiple切换(一)', () => {
let selectNode = (
<select id="se" multiple="multiple" defaultValue={['Vue', 'Angular']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(false);
expect(document.getElementById('se').options[1].selected).toBe(true);
expect(document.getElementById('se').options[2].selected).toBe(true);
// 改变value有影响
selectNode = (
<select id="se" defaultValue="React">
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(true);
expect(document.getElementById('se').options[1].selected).toBe(false);
expect(document.getElementById('se').options[2].selected).toBe(false);
});
it('defaultValue设置multiple与非multiple切换(二)', () => {
let selectNode = (
<select id="se" defaultValue="React">
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(true);
expect(document.getElementById('se').options[1].selected).toBe(false);
expect(document.getElementById('se').options[2].selected).toBe(false);
// 改变value有影响
selectNode = (
<select id="se" multiple="multiple" defaultValue={['Vue', 'Angular']}>
<option value="React">React.js</option>
<option value="Vue">Vue.js</option>
<option value="Angular">Angular.js</option>
</select>
);
Inula.render(selectNode, container);
expect(document.getElementById('se').options[0].selected).toBe(false);
expect(document.getElementById('se').options[1].selected).toBe(true);
expect(document.getElementById('se').options[2].selected).toBe(true);
});
it('未指定value或者defaultValue时默认选择第一个可选的', () => {
const selectNode = (
<select id="se">
<option disabled={true} value="React">
React.js
</option>
<option value="Vue">Vue.js</option>
<option disabled={true} value="Angular">
Angular.js
</option>
</select>
);
const realNode = Inula.render(selectNode, container);
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(true);
expect(realNode.options[2].selected).toBe(false);
});
it('删除添加option', () => {
const selectNode = (
<select multiple={true} defaultValue={['Vue']}>
<option key="React" value="React">
React.js
</option>
<option key="Vue" value="Vue">
Vue.js
</option>
<option key="Angular" value="Angular">
Angular.js
</option>
</select>
);
const realNode = Inula.render(selectNode, container);
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(true);
expect(realNode.options[2].selected).toBe(false);
const newNode = (
<select multiple={true} defaultValue={['Vue']}>
<option key="React" value="React">
React.js
</option>
<option key="Angular" value="Angular">
Angular.js
</option>
</select>
);
Inula.render(newNode, container);
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(false);
const newSelectNode = (
<select multiple={true} defaultValue={['Vue']}>
<option key="React" value="React">
React.js
</option>
<option key="Vue" value="Vue">
Vue.js
</option>
<option key="Angular" value="Angular">
Angular.js
</option>
</select>
);
// 重新添加不会影响
Inula.render(newSelectNode, container);
expect(realNode.options[0].selected).toBe(false);
expect(realNode.options[1].selected).toBe(false);
expect(realNode.options[2].selected).toBe(false);
});
});

View File

@ -1,136 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('Dom Textarea', () => {
it('设置value', () => {
let realNode = Inula.render(<textarea value="text" />, container);
expect(realNode.getAttribute('value')).toBe(null);
expect(realNode.value).toBe('text');
realNode = Inula.render(<textarea value={0} />, container);
expect(realNode.getAttribute('value')).toBe(null);
expect(realNode.value).toBe('0');
realNode = Inula.render(<textarea value={true} />, container);
expect(realNode.getAttribute('value')).toBe(null);
expect(realNode.value).toBe('true');
realNode = Inula.render(<textarea value={false} />, container);
expect(realNode.getAttribute('value')).toBe(null);
expect(realNode.value).toBe('false');
});
it('设置value为对象', () => {
let textareaValue = {
toString: () => {
return 'Vue';
},
};
const textareaNode = <textarea value={textareaValue} />;
const realNode = Inula.render(textareaNode, container);
expect(realNode.value).toBe('Vue');
textareaValue = {
toString: () => {
return 'React';
},
};
const newTextareaNode = <textarea value={textareaValue} />;
// 改变value会影响select的状态
Inula.render(newTextareaNode, container);
expect(realNode.value).toBe('React');
});
it('设置defaultValue', () => {
let defaultVal = 'Vue';
const textareaNode = <textarea defaultValue={defaultVal} />;
let realNode = Inula.render(textareaNode, container);
expect(realNode.value).toBe('Vue');
defaultVal = 'React';
// 改变defaultValue没有影响
realNode = Inula.render(textareaNode, container);
expect(realNode.value).toBe('Vue');
Inula.unmountComponentAtNode(container);
defaultVal = 0;
realNode = Inula.render(<textarea defaultValue={defaultVal} />, container);
expect(realNode.value).toBe('0');
Inula.unmountComponentAtNode(container);
defaultVal = true;
realNode = Inula.render(<textarea defaultValue={defaultVal} />, container);
expect(realNode.value).toBe('true');
Inula.unmountComponentAtNode(container);
defaultVal = false;
realNode = Inula.render(<textarea defaultValue={defaultVal} />, container);
expect(realNode.value).toBe('false');
Inula.render(<textarea>123</textarea>, container);
expect(realNode.value).toBe('false');
});
it('设置defaultValue为对象', () => {
let textareaValue = {
toString: () => {
return 'Vue';
},
};
const textareaNode = <textarea defaultValue={textareaValue} />;
const realNode = Inula.render(textareaNode, container);
expect(realNode.value).toBe('Vue');
});
it('设置defaultValue后,select不受控', () => {
const textareaNode = <textarea defaultValue={'text'} />;
let realNode = Inula.render(textareaNode, container);
expect(realNode.value).toBe('text');
// 先修改
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(realNode, 'ABC');
// 再触发事件
container.querySelector('textarea').dispatchEvent(
new Event('change', {
bubbles: true,
cancelable: true,
})
);
// 鼠标改变textarea生效
Inula.render(textareaNode, container);
expect(realNode.value).toBe('ABC');
});
it('受控与非受控切换', () => {
// 非受控切换为受控
let realNode = Inula.render(<textarea defaultValue="text" />, container);
expect(realNode.value).toBe('text');
Inula.render(<textarea value="newtext" onChange={() => {}} />, container);
expect(realNode.value).toBe('newtext');
Inula.unmountComponentAtNode(container);
// 受控切换为非受控
realNode = Inula.render(<textarea value="text" onChange={() => {}} />, container);
expect(realNode.value).toBe('text');
Inula.render(<textarea defaultValue="newtext" onChange={() => {}} />, container);
expect(realNode.value).toBe('text');
});
it('textarea的孩子', () => {
let realNode = Inula.render(<textarea>{1234}</textarea>, container);
expect(realNode.value).toBe('1234');
realNode = Inula.render(<textarea>{5678}</textarea>, container);
// realNode.value依旧为1234
expect(realNode.value).toBe('1234');
});
});

View File

@ -1,281 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import * as TestUtils from '../jest/testUtils';
function dispatchChangeEvent(input) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputValueSetter.call(input, 'test');
input.dispatchEvent(new Event('input', { bubbles: true }));
}
describe('事件', () => {
const LogUtils = TestUtils.getLogUtils();
it('事件捕获与冒泡', () => {
const App = () => {
return (
<>
<div onClickCapture={() => LogUtils.log('div capture')} onClick={() => LogUtils.log('div bubble')}>
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={() => LogUtils.log('btn bubble')} />
</p>
</div>
</>
);
};
Inula.render(<App />, container);
const a = container.querySelector('button');
a.click();
expect(LogUtils.getAndClear()).toEqual([
// 从外到内先捕获再冒泡
'div capture',
'p capture',
'btn capture',
'btn bubble',
'p bubble',
'div bubble',
]);
});
it('returns 0', () => {
let keyCode = null;
const node = Inula.render(
<input
onKeyPress={e => {
keyCode = e.keyCode;
}}
/>,
container
);
node.dispatchEvent(
new KeyboardEvent('keypress', {
keyCode: 65,
bubbles: true,
cancelable: true,
})
);
expect(keyCode).toBe(65);
});
it('阻止事件冒泡', () => {
const App = () => {
return (
<>
<div onClickCapture={() => LogUtils.log('div capture')} onClick={() => LogUtils.log('div bubble')}>
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
<button
onClickCapture={() => LogUtils.log('btn capture')}
onClick={e => TestUtils.stopBubbleOrCapture(e, 'btn bubble')}
/>
</p>
</div>
</>
);
};
Inula.render(<App />, container);
container.querySelector('button').click();
expect(LogUtils.getAndClear()).toEqual([
// 到button时停止冒泡
'div capture',
'p capture',
'btn capture',
'btn bubble',
]);
});
it('阻止事件捕获', () => {
const App = () => {
return (
<>
<div
onClickCapture={e => TestUtils.stopBubbleOrCapture(e, 'div capture')}
onClick={() => LogUtils.log('div bubble')}
>
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={() => LogUtils.log('btn bubble')} />
</p>
</div>
</>
);
};
Inula.render(<App />, container);
container.querySelector('button').click();
expect(LogUtils.getAndClear()).toEqual([
// 阻止捕获,不再继续向下执行
'div capture',
]);
});
it('阻止原生事件冒泡', () => {
const App = () => {
return (
<div>
<p>
<button />
</p>
</div>
);
};
Inula.render(<App />, container);
container.querySelector('div').addEventListener(
'click',
() => {
LogUtils.log('div bubble');
},
false
);
container.querySelector('p').addEventListener(
'click',
() => {
LogUtils.log('p bubble');
},
false
);
container.querySelector('button').addEventListener(
'click',
e => {
LogUtils.log('btn bubble');
e.stopPropagation();
},
false
);
container.querySelector('button').click();
expect(LogUtils.getAndClear()).toEqual(['btn bubble']);
});
it('动态增加事件', () => {
let update;
let inputRef = Inula.createRef();
function Test() {
const [inputProps, setProps] = Inula.useState({});
update = setProps;
return <input ref={inputRef} {...inputProps} />;
}
Inula.render(<Test />, container);
update({
onChange: () => {
LogUtils.log('change');
},
});
dispatchChangeEvent(inputRef.current);
expect(LogUtils.getAndClear()).toEqual(['change']);
});
it('Radio change事件', () => {
let radio1Called = 0;
let radio2Called = 0;
function onChange1() {
radio1Called++;
}
function onChange2() {
radio2Called++;
}
const radio1Ref = Inula.createRef();
const radio2Ref = Inula.createRef();
Inula.render(
<>
<input type="radio" ref={radio1Ref} name="name" onChange={onChange1} />
<input type="radio" ref={radio2Ref} name="name" onChange={onChange2} />
</>,
container
);
function clickRadioAndExpect(radio, [expect1, expect2]) {
radio.click();
expect(radio1Called).toBe(expect1);
expect(radio2Called).toBe(expect2);
}
// 先选择选项1
clickRadioAndExpect(radio1Ref.current, [1, 0]);
// 再选择选项1
clickRadioAndExpect(radio2Ref.current, [1, 1]);
// 先选择选项1radio1应该重新触发onchange
clickRadioAndExpect(radio1Ref.current, [2, 1]);
});
it('多根节点下,事件挂载正确', () => {
const root1 = document.createElement('div');
const root2 = document.createElement('div');
root1.key = 'root1';
root2.key = 'root2';
let input1, input2, update1, update2;
function App1() {
const [props, setProps] = Inula.useState({});
update1 = setProps;
return (
<input
{...props}
ref={n => (input1 = n)}
onChange={() => {
LogUtils.log('input1 changed');
}}
/>
);
}
function App2() {
const [props, setProps] = Inula.useState({});
update2 = setProps;
return (
<input
{...props}
ref={n => (input2 = n)}
onChange={() => {
LogUtils.log('input2 changed');
}}
/>
);
}
// 多根mount阶段挂载onChange事件
Inula.render(<App1 key={1} />, root1);
Inula.render(<App2 key={2} />, root2);
dispatchChangeEvent(input1);
expect(LogUtils.getAndClear()).toEqual(['input1 changed']);
dispatchChangeEvent(input2);
expect(LogUtils.getAndClear()).toEqual(['input2 changed']);
// 多根update阶段挂载onClick事件
update1({
onClick: () => LogUtils.log('input1 clicked'),
});
update2({
onClick: () => LogUtils.log('input2 clicked'),
});
input1.click();
expect(LogUtils.getAndClear()).toEqual(['input1 clicked']);
input2.click();
expect(LogUtils.getAndClear()).toEqual(['input2 clicked']);
});
});

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('合成焦点事件', () => {
const LogUtils = getLogUtils();
it('onFocus', () => {
const realNode = Inula.render(
<input
onFocus={event => LogUtils.log(`onFocus: ${event.type}`)}
onFocusCapture={event => LogUtils.log(`onFocusCapture: ${event.type}`)}
/>,
container
);
realNode.dispatchEvent(
new FocusEvent('focusin', {
bubbles: true,
cancelable: false,
})
);
expect(LogUtils.getAndClear()).toEqual(['onFocusCapture: focus', 'onFocus: focus']);
});
it('onBlur', () => {
const realNode = Inula.render(
<input
onBlur={event => LogUtils.log(`onBlur: ${event.type}`)}
onBlurCapture={event => LogUtils.log(`onBlurCapture: ${event.type}`)}
/>,
container
);
realNode.dispatchEvent(
new FocusEvent('focusout', {
bubbles: true,
cancelable: false,
})
);
expect(LogUtils.getAndClear()).toEqual(['onBlurCapture: blur', 'onBlur: blur']);
});
});

View File

@ -1,130 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('Keyboard Event', () => {
const LogUtils = getLogUtils();
const getKeyboardEvent = (type, keyCode, code, charCode) => {
return new KeyboardEvent(type, {
keyCode: keyCode ?? undefined,
code: code ?? undefined,
charCode: charCode ?? undefined,
bubbles: true,
cancelable: true,
});
};
it('keydown,keypress,keyup的keycode,charcode', () => {
const node = Inula.render(
<input
onKeyUp={e => {
LogUtils.log('onKeyUp: keycode: ' + e.keyCode + ',charcode: ' + e.charCode);
}}
onKeyDown={e => {
LogUtils.log('onKeyDown: keycode: ' + e.keyCode + ',charcode: ' + e.charCode);
}}
/>,
container
);
node.dispatchEvent(getKeyboardEvent('keydown', 50, 'Digit2'));
node.dispatchEvent(getKeyboardEvent('keyup', 50, 'Digit2'));
expect(LogUtils.getAndClear()).toEqual(['onKeyDown: keycode: 50,charcode: 0', 'onKeyUp: keycode: 50,charcode: 0']);
});
it('keypress的keycode,charcode', () => {
const node = Inula.render(
<input
onKeyPress={e => {
LogUtils.log('onKeyPress: keycode: ' + e.keyCode + ',charcode: ' + e.charCode);
}}
/>,
container
);
node.dispatchEvent(getKeyboardEvent('keypress', undefined, 'Digit2', 50));
expect(LogUtils.getAndClear()).toEqual(['onKeyPress: keycode: 0,charcode: 50']);
});
it('当charcode为13,且不设置keycode的时候', () => {
const node = Inula.render(
<input
onKeyPress={e => {
LogUtils.log('onKeyPress: keycode: ' + e.keyCode + ',charcode: ' + e.charCode);
}}
/>,
container
);
node.dispatchEvent(getKeyboardEvent('keypress', undefined, undefined, 13));
expect(LogUtils.getAndClear()).toEqual(['onKeyPress: keycode: 0,charcode: 13']);
});
it('keydown,keypress,keyup的code', () => {
const node = Inula.render(
<input
onKeyUp={e => {
LogUtils.log('onKeyUp: code: ' + e.code);
}}
onKeyPress={e => {
LogUtils.log('onKeyPress: code: ' + e.code);
}}
onKeyDown={e => {
LogUtils.log('onKeyDown: code: ' + e.code);
}}
/>,
container
);
node.dispatchEvent(getKeyboardEvent('keydown', undefined, 'Digit2'));
node.dispatchEvent(getKeyboardEvent('keypress', undefined, 'Digit2', 50));
node.dispatchEvent(
new KeyboardEvent('keyup', {
code: 'Digit2',
bubbles: true,
cancelable: true,
})
);
expect(LogUtils.getAndClear()).toEqual([
'onKeyDown: code: Digit2',
'onKeyPress: code: Digit2',
'onKeyUp: code: Digit2',
]);
});
it('可以执行preventDefault和 stopPropagation', () => {
const keyboardProcessing = e => {
expect(e.isDefaultPrevented()).toBe(false);
e.preventDefault();
expect(e.isDefaultPrevented()).toBe(true);
expect(e.isPropagationStopped()).toBe(false);
e.stopPropagation();
expect(e.isPropagationStopped()).toBe(true);
LogUtils.log(e.type + ' handle');
};
const div = Inula.render(
<div onKeyDown={keyboardProcessing} onKeyUp={keyboardProcessing} onKeyPress={keyboardProcessing} />,
container
);
div.dispatchEvent(getKeyboardEvent('keydown', 40));
div.dispatchEvent(getKeyboardEvent('keyup', 40));
div.dispatchEvent(getKeyboardEvent('keypress', 40));
expect(LogUtils.getAndClear()).toEqual(['keydown handle', 'keyup handle', 'keypress handle']);
});
});

View File

@ -1,278 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
describe('mouseenter和mouseleave事件测试', () => {
let container;
beforeEach(() => {
jest.resetModules();
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('在iframe中mouseleave事件的relateTarget属性', () => {
const iframe = document.createElement('iframe');
container.appendChild(iframe);
const iframeDocument = iframe.contentDocument;
iframeDocument.write('<!DOCTYPE html><html><head></head><body><div></div></body></html>');
iframeDocument.close();
const leaveEvents = [];
const node = Inula.render(
<div
onMouseLeave={e => {
e.persist();
leaveEvents.push(e);
}}
/>,
iframeDocument.body.getElementsByTagName('div')[0]
);
node.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: iframe.contentWindow,
})
);
expect(leaveEvents.length).toBe(1);
expect(leaveEvents[0].target).toBe(node);
expect(leaveEvents[0].relatedTarget).toBe(iframe.contentWindow);
});
it('在iframe中mouseenter事件的relateTarget属性', () => {
const iframe = document.createElement('iframe');
container.appendChild(iframe);
const iframeDocument = iframe.contentDocument;
iframeDocument.write('<!DOCTYPE html><html><head></head><body><div></div></body></html>');
iframeDocument.close();
const enterEvents = [];
const node = Inula.render(
<div
onMouseEnter={e => {
e.persist();
enterEvents.push(e);
}}
/>,
iframeDocument.body.getElementsByTagName('div')[0]
);
node.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
cancelable: true,
relatedTarget: null,
})
);
expect(enterEvents.length).toBe(1);
expect(enterEvents[0].target).toBe(node);
expect(enterEvents[0].relatedTarget).toBe(iframe.contentWindow);
});
it('从新渲染的子组件触发mouseout事件子组件响应mouseenter事件父节点不响应', () => {
let parentEnterCalls = 0;
let childEnterCalls = 0;
let parent = null;
class Parent extends Inula.Component {
render() {
return (
<div onMouseEnter={() => parentEnterCalls++} ref={node => (parent = node)}>
{this.props.showChild && <div onMouseEnter={() => childEnterCalls++} />}
</div>
);
}
}
Inula.render(<Parent />, container);
Inula.render(<Parent showChild={true} />, container);
parent.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: parent.firstChild,
})
);
expect(childEnterCalls).toBe(1);
expect(parentEnterCalls).toBe(0);
});
it('render一个新组件兄弟节点触发mouseout事件mouseenter事件响应一次', done => {
const mockFn1 = jest.fn();
const mockFn2 = jest.fn();
const mockFn3 = jest.fn();
class Parent extends Inula.Component {
constructor(props) {
super(props);
this.parentEl = Inula.createRef();
}
componentDidMount() {
Inula.render(<MouseEnterDetect />, this.parentEl.current);
}
render() {
return <div ref={this.parentEl} id="parent" onMouseLeave={mockFn3} />;
}
}
class MouseEnterDetect extends Inula.Component {
constructor(props) {
super(props);
this.firstEl = Inula.createRef();
this.siblingEl = Inula.createRef();
}
componentDidMount() {
this.siblingEl.current.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: this.firstEl.current,
})
);
expect(mockFn1.mock.calls.length).toBe(1);
expect(mockFn2.mock.calls.length).toBe(1);
expect(mockFn3.mock.calls.length).toBe(0);
done();
}
render() {
return (
<Inula.Fragment>
<div ref={this.firstEl} id="first" onMouseEnter={mockFn1} />
<div ref={this.siblingEl} id="sibling" onMouseLeave={mockFn2} />
</Inula.Fragment>
);
}
}
Inula.render(<Parent />, container);
});
it('未被inula管理的节点触发mouseout事件mouseenter事件也能正常触发', done => {
const mockFn = jest.fn();
class Parent extends Inula.Component {
constructor(props) {
super(props);
this.parentEl = Inula.createRef();
}
componentDidMount() {
Inula.render(<MouseEnterDetect />, this.parentEl.current);
}
render() {
return <div ref={this.parentEl} />;
}
}
class MouseEnterDetect extends Inula.Component {
constructor(props) {
super(props);
this.divRef = Inula.createRef();
this.siblingEl = Inula.createRef();
}
componentDidMount() {
const attachedNode = document.createElement('div');
this.divRef.current.appendChild(attachedNode);
attachedNode.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: this.siblingEl.current,
})
);
expect(mockFn.mock.calls.length).toBe(1);
done();
}
render() {
return (
<div ref={this.divRef}>
<div ref={this.siblingEl} onMouseEnter={mockFn} />
</div>
);
}
}
Inula.render(<Parent />, container);
});
it('外部portal节点触发的mouseout事件根节点的mouseleave事件也能响应', () => {
const divRef = Inula.createRef();
const onMouseLeave = jest.fn();
function Component() {
return (
<div onMouseLeave={onMouseLeave} id="parent">
{Inula.createPortal(<div ref={divRef} id="sub" />, document.body)}
</div>
);
}
Inula.render(<Component />, container);
divRef.current.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: document.body,
})
);
expect(onMouseLeave).toHaveBeenCalledTimes(1);
});
it('外部portal节点触发的mouseout事件根节点的mouseEnter事件也能响应', () => {
const divRef = Inula.createRef();
const otherDivRef = Inula.createRef();
const onMouseEnter = jest.fn();
function Component() {
return (
<div ref={divRef}>
{Inula.createPortal(<div ref={otherDivRef} onMouseEnter={onMouseEnter} />, document.body)}
</div>
);
}
Inula.render(<Component />, container);
divRef.current.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: otherDivRef.current,
})
);
expect(onMouseEnter).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,185 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('MouseEvent Test', () => {
const LogUtils = getLogUtils();
describe('onClick Test', () => {
it('绑定this', () => {
class App extends Inula.Component {
constructor(props) {
super(props);
this.state = {
num: this.props.num,
price: this.props.price,
};
}
setNum() {
this.setState({ num: this.state.num + 1 });
}
setPrice = e => {
this.setState({ num: this.state.price + 1 });
};
render() {
return (
<>
<p>{this.state.num}</p>
<p id="p">{this.state.price}</p>
<button onClick={this.setNum.bind(this)}>button</button>
<button id="btn" onClick={() => this.setPrice()}>
button
</button>
</>
);
}
}
Inula.render(<App num={0} price={100} />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
expect(container.querySelector('#p').innerHTML).toBe('100');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
container.querySelector('#btn').click();
expect(container.querySelector('p').innerHTML).toBe('101');
});
it('点击触发', () => {
const handleClick = jest.fn();
Inula.render(<button onClick={handleClick}>Click Me</button>, container);
container.querySelector('button').click();
expect(handleClick).toHaveBeenCalledTimes(1);
for (let i = 0; i < 5; i++) {
container.querySelector('button').click();
}
expect(handleClick).toHaveBeenCalledTimes(6);
});
it('disable不触发click', () => {
const handleClick = jest.fn();
const spanRef = Inula.createRef();
Inula.render(
<button onClick={handleClick} disabled={true}>
<span ref={spanRef}>Click Me</span>
</button>,
container
);
spanRef.current.click();
expect(handleClick).toHaveBeenCalledTimes(0);
});
});
const test = (name, config) => {
const node = Inula.render(config, container);
let event = new MouseEvent(name, {
relatedTarget: null,
bubbles: true,
screenX: 1,
});
node.dispatchEvent(event);
expect(LogUtils.getAndClear()).toEqual([`${name} capture`, `${name} bubble`]);
event = new MouseEvent(name, {
relatedTarget: null,
bubbles: true,
screenX: 2,
});
node.dispatchEvent(event);
// 再次触发新事件
expect(LogUtils.getAndClear()).toEqual([`${name} capture`, `${name} bubble`]);
};
describe('合成鼠标事件', () => {
it('onMouseMove', () => {
const onMouseMove = () => {
LogUtils.log('mousemove bubble');
};
const onMouseMoveCapture = () => {
LogUtils.log('mousemove capture');
};
test('mousemove', <div onMouseMove={onMouseMove} onMouseMoveCapture={onMouseMoveCapture} />);
});
it('onMouseDown', () => {
const onMousedown = () => {
LogUtils.log('mousedown bubble');
};
const onMousedownCapture = () => {
LogUtils.log('mousedown capture');
};
test('mousedown', <div onMousedown={onMousedown} onMousedownCapture={onMousedownCapture} />);
});
it('onMouseUp', () => {
const onMouseUp = () => {
LogUtils.log('mouseup bubble');
};
const onMouseUpCapture = () => {
LogUtils.log('mouseup capture');
};
test('mouseup', <div onMouseUp={onMouseUp} onMouseUpCapture={onMouseUpCapture} />);
});
it('onMouseOut', () => {
const onMouseOut = () => {
LogUtils.log('mouseout bubble');
};
const onMouseOutCapture = () => {
LogUtils.log('mouseout capture');
};
test('mouseout', <div onMouseOut={onMouseOut} onMouseOutCapture={onMouseOutCapture} />);
});
it('onMouseOver', () => {
const onMouseOver = () => {
LogUtils.log('mouseover bubble');
};
const onMouseOverCapture = () => {
LogUtils.log('mouseover capture');
};
test('mouseover', <div onMouseOver={onMouseOver} onMouseOverCapture={onMouseOverCapture} />);
});
it('KeyboardEvent.getModifierState should not fail', () => {
const input = Inula.render(
<input
onMouseDown={e => {
e.getModifierState('CapsLock');
}}
/>,
container
);
const event = new MouseEvent('mousedown', {
relatedTarget: null,
bubbles: true,
screenX: 1,
});
expect(() => {
input.dispatchEvent(event);
}).not.toThrow();
});
});
});

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
import { getLogUtils } from '../jest/testUtils';
describe('合成滚轮事件', () => {
const LogUtils = getLogUtils();
it('onWheel', () => {
const realNode = Inula.render(
<div
onWheel={event => LogUtils.log(`onWheel: ${event.type}`)}
onWheelCapture={event => LogUtils.log(`onWheelCapture: ${event.type}`)}
/>,
container
);
realNode.dispatchEvent(
new MouseEvent('wheel', {
bubbles: true,
cancelable: false,
})
);
expect(LogUtils.getAndClear()).toEqual(['onWheelCapture: wheel', 'onWheel: wheel']);
});
it('可以执行preventDefault和stopPropagation', () => {
const eventHandler = e => {
expect(e.isDefaultPrevented()).toBe(false);
e.preventDefault();
expect(e.isDefaultPrevented()).toBe(true);
expect(e.isPropagationStopped()).toBe(false);
e.stopPropagation();
expect(e.isPropagationStopped()).toBe(true);
LogUtils.log(e.type + ' handle');
};
const realNode = Inula.render(<div onWheel={eventHandler} />, container);
realNode.dispatchEvent(
new MouseEvent('wheel', {
bubbles: true,
cancelable: true,
})
);
expect(LogUtils.getAndClear()).toEqual(['wheel handle']);
});
});

View File

@ -1,223 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/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 } = Inula;
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>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2');
// 在Array中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3');
// 在Array中删除一个对象
Inula.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>
);
}
Inula.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>
);
}
Inula.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,348 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: new Map([
['p1', 1],
['p2', 2],
]),
},
actions: {
addOnePerson: (state, person) => {
state.persons.set(person.name, person.age);
},
delOnePerson: (state, person) => {
state.persons.delete(person.name);
},
clearPersons: state => {
state.persons.clear();
},
reset: state => {
state.persons = new Map([
['p1', 1],
['p2', 2],
]);
},
},
});
describe('测试store中的Map', () => {
const { unmountComponentAtNode } = Inula;
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(newPerson);
};
const clearPersons = function () {
userStore.clearPersons();
};
return (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<button id={'clearBtn'} onClick={clearPersons}>
clear persons
</button>
<div>{props.children}</div>
</div>
);
}
it('测试Map方法: set()、delete()、clear()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'size'} text={`persons number: ${userStore.$s.persons.size}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0');
});
it('测试Map方法: keys()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
const keys = userStore.$s.persons.keys();
for (const key of keys) {
nameList.push(key);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: values()', () => {
function Child(props) {
const userStore = useUserStore();
const ageList: number[] = [];
const values = userStore.$s.persons.values();
for (const val of values) {
ageList.push(val);
}
return (
<div>
<Text id={'ageList'} text={`age list: ${ageList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: ');
});
it('测试Map方法: entries()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
const entries = userStore.$s.persons.entries();
for (const entry of entries) {
nameList.push(entry[0]);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: forEach()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
userStore.$s.persons.forEach((val, key) => {
nameList.push(key);
});
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: has()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson.name)}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true');
});
it('测试Map方法: for of()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
for (const per of userStore.$s.persons) {
nameList.push(per[0]);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
});

View File

@ -1,177 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
describe('测试store中的混合类型变化', () => {
const { unmountComponentAtNode } = Inula;
let container: HTMLElement | null = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
const persons = new Set([{ name: 'p1', age: 1, love: new Map() }]);
persons.add({
name: 'p2',
age: 2,
love: new Map(),
});
persons
.values()
.next()
.value.love.set('lanqiu', { moneny: 100, days: [1, 3, 5] });
createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: persons,
},
actions: {
addDay: (state, day) => {
state.persons.values().next().value.love.get('lanqiu').days.push(day);
},
},
});
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
(container as HTMLElement).remove();
container = null;
LogUtils.clear();
clearStore('user');
});
function Parent(props) {
const userStore = useStore('user');
const addDay = function () {
userStore.addDay(7);
};
return (
<div>
<button id={'addBtn'} onClick={addDay}>
add day
</button>
<div>{props.children}</div>
</div>
);
}
it('测试state -> set -> map -> array的数据变化', () => {
function Child(props) {
const userStore = useStore('user');
const days = userStore.persons.values().next().value.love.get('lanqiu').days;
return (
<div>
<Text id={'dayList'} text={`love: ${days.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5');
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#dayList')?.innerHTML).toBe('love: 1 3 5 7');
});
it('属性是个class实例', () => {
class Person {
name;
age;
loves = new Set();
constructor(name, age) {
this.name = name;
this.age = age;
}
setName(name) {
this.name = name;
}
getName() {
return this.name;
}
setAge(age) {
this.age = age;
}
getAge() {
return this.age;
}
addLove(lv) {
this.loves.add(lv);
}
getLoves() {
return this.loves;
}
}
let globalPerson;
let globalStore;
function Child(props) {
const userStore = useStore('user');
globalStore = userStore;
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;
per = valIterator.next();
}
return (
<div>
<Text id={'nameList'} text={nameList.join(' ')} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2');
// 动态增加一个Person实例
globalStore.$s.persons.add(new Person('ClassPerson', 5));
expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2 ClassPerson');
globalPerson.setName('ClassPerson1');
expect(container?.querySelector('#nameList')?.innerHTML).toBe('p1 p2 ClassPerson1');
});
});

View File

@ -1,318 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: new Set([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]),
},
actions: {
addOnePerson: (state, person) => {
state.persons.add(person);
},
delOnePerson: (state, person) => {
state.persons.delete(person);
},
clearPersons: state => {
state.persons.clear();
},
reset: state => {
state.persons = new Set([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]);
},
},
});
describe('测试store中的Set', () => {
const { unmountComponentAtNode } = Inula;
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(newPerson);
};
const clearPersons = function () {
userStore.clearPersons();
};
return (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<button id={'clearBtn'} onClick={clearPersons}>
clear persons
</button>
<div>{props.children}</div>
</div>
);
}
it('测试Set方法: add()、delete()、clear()', () => {
function Child(props) {
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.$s.persons.size}`} />
<Text id={'lastAge'} text={`last person age: ${personArr[personArr.length - 1]?.age ?? 0}`} />
</div>
);
}
Inula.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');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3');
// 在set中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2');
// clear set
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
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 = useUserStore();
const nameList: string[] = [];
const keys = userStore.$s.persons.keys();
// const keys = userStore.$s.persons.values();
for (const key of keys) {
nameList.push(key.name);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在set中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear set
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Set方法: entries()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
const entries = userStore.$s.persons.entries();
for (const entry of entries) {
nameList.push(entry[0].name);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在set中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear set
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Set方法: forEach()', () => {
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>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在set中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear set
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Set方法: has()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true');
});
it('测试Set方法: for of()', () => {
function Child(props) {
const userStore = useUserStore();
const nameList: string[] = [];
for (const per of userStore.$s.persons) {
nameList.push(per.name);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在set中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在set中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear set
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
});

View File

@ -1,149 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: new WeakMap([
[{ name: 'p1' }, 1],
[{ name: 'p2' }, 2],
]),
},
actions: {
addOnePerson: (state, person) => {
state.persons.set(person, 3);
},
delOnePerson: (state, person) => {
state.persons.delete(person);
},
clearPersons: state => {
state.persons = new WeakMap([]);
},
reset: state => {
state.persons = new WeakMap([
[{ name: 'p1' }, 1],
[{ name: 'p2' }, 2],
]);
},
},
});
describe('测试store中的WeakMap', () => {
const { unmountComponentAtNode } = Inula;
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' };
function Parent(props) {
const userStore = useUserStore();
const addOnePerson = function () {
userStore.addOnePerson(newPerson);
};
const delOnePerson = function () {
userStore.delOnePerson(newPerson);
};
const clearPersons = function () {
userStore.clearPersons();
};
return (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<button id={'clearBtn'} onClick={clearPersons}>
clear persons
</button>
<div>{props.children}</div>
</div>
);
}
it('测试WeakMap方法: set()、delete()、has()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
// 在WeakMap中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true');
// 在WeakMap中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
});
it('测试WeakMap方法: get()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.get(newPerson)}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: undefined');
// 在WeakMap中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3');
});
});

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: new WeakSet([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]),
},
actions: {
addOnePerson: (state, person) => {
state.persons.add(person);
},
delOnePerson: (state, person) => {
state.persons.delete(person);
},
clearPersons: state => {
state.persons = new WeakSet([]);
},
reset: state => {
state.persons = new WeakSet([
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
]);
},
},
});
describe('测试store中的WeakSet', () => {
const { unmountComponentAtNode } = Inula;
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(newPerson);
};
return (
<div>
<button id={'addBtn'} onClick={addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={delOnePerson}>
delete person
</button>
<div>{props.children}</div>
</div>
);
}
it('测试WeakSet方法: add()、delete()、has()', () => {
function Child(props) {
const userStore = useUserStore();
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${userStore.$s.persons.has(newPerson)}`} />
</div>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
// 在WeakSet中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true');
// 在WeakSet中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
});
});

View File

@ -1,112 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import { createStore } from '../../../../src/inulax/store/StoreHandler';
import { triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const { unmountComponentAtNode } = Inula;
function postpone(timer, func) {
return new Promise(resolve => {
window.setTimeout(function () {
console.log('resolving postpone');
resolve(func());
}, timer);
});
}
describe('Asynchronous store', () => {
const getStore = createStore({
state: {
counter: 0,
check: false,
},
actions: {
increment: function (state) {
return new Promise(resolve => {
window.setTimeout(() => {
state.counter++;
resolve(true);
}, 10);
});
},
toggle: function (state) {
state.check = !state.check;
},
reset: function (state) {
state.check = false;
state.counter = 0;
},
},
computed: {
value: state => {
return (state.check ? 'true' : 'false') + state.counter;
},
},
});
beforeEach(() => {
getStore().reset();
});
it('should return promise when queued function is called', () => {
jest.useFakeTimers();
const store = getStore();
return new Promise(resolve => {
store.$queue.increment().then(() => {
expect(store.counter == 1);
resolve(true);
});
jest.advanceTimersByTime(150);
});
});
it('should queue async functions', () => {
jest.useFakeTimers();
return new Promise(resolve => {
const store = getStore();
// initial value
expect(store.value).toBe('false0');
// no blocking action action
store.$queue.toggle();
expect(store.value).toBe('true0');
// store is not updated before blocking action is resolved
store.$queue.increment();
const togglePromise = store.$queue.toggle();
expect(store.value).toBe('true0');
// fast action is resolved immediatelly
store.toggle();
expect(store.value).toBe('false0');
// queued action waits for blocking action to resolve
togglePromise.then(() => {
expect(store.value).toBe('true1');
resolve();
});
jest.advanceTimersByTime(150);
});
});
});

View File

@ -1,198 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import Inula from '../../../../src/index';
import { triggerClickEvent } from '../../jest/commonComponents';
import { useLogStore } from './store';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
import { createStore } from '../../../../src/inulax/store/StoreHandler';
const { unmountComponentAtNode } = Inula;
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>;
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1');
});
it('Should use direct setters', () => {
function App() {
const logStore = useLogStore();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
logStore.logs = ['q'];
}}
>
add
</button>
<p id={RESULT_ID}>{logStore.logs[0]}</p>
</div>
);
}
Inula.render(<App />, container);
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('q');
});
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>
);
}
Inula.render(<App />, container);
Inula.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>
);
}
Inula.render(<App />, container);
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5');
});
it('should call computed from own actions', () => {
const useIncrementStore = createStore({
id: 'incrementStore',
state: {
count: 2,
},
actions: {
doublePlusOne: function (state) {
state.count = this.double + 1;
},
},
computed: {
double: state => {
return state.count * 2;
},
},
});
function App() {
const incrementStore = useIncrementStore();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
incrementStore.doublePlusOne();
}}
>
+
</button>
<p id={RESULT_ID}>{incrementStore.count}</p>
</div>
);
}
Inula.render(<App />, container);
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5');
});
});

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { OBSERVER_KEY } from '../../../../src/inulax/Constants';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
describe('测试对store.state对象进行深度克隆', () => {
const { unmountComponentAtNode } = Inula;
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 ('_inulaObserver') 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>
);
}
Inula.render(<App parent={Parent} child={Child} />, container);
// The observer object of symbol ('_inulaObserver') 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 ('_inulaObserver') 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>
);
}
Inula.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();
});
});

View File

@ -1,80 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import { triggerClickEvent } from '../../jest/commonComponents';
import { useLogStore } from './store';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const { unmountComponentAtNode } = Inula;
describe('Dollar store access', () => {
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 $s and $c', () => {
function App() {
const logStore = useLogStore();
return <div id={RESULT_ID}>{logStore.$c.length()}</div>;
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1');
});
it('Should use $a and update components', () => {
function App() {
const logStore = useLogStore();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
logStore.$a.addLog('data');
}}
>
add
</button>
<p id={RESULT_ID}>{logStore.$c.length()}</p>
</div>
);
}
Inula.render(<App />, container);
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2');
});
});

View File

@ -1,165 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import { createStore } from '../../../../src/inulax/store/StoreHandler';
import { triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const { unmountComponentAtNode } = Inula;
describe('Self referencing', () => {
let container: HTMLElement | null = null;
const BUTTON_ID = 'btn';
const RESULT_ID = 'result';
const useSelfRefStore = createStore({
state: {
val: 2,
},
actions: {
increaseVal: function (state) {
state.val = state.val * 2 - 1;
},
},
computed: {
value: state => state.val,
double: function () {
return this.value * 2;
},
},
});
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container?.remove();
container = null;
});
it('Should use own getters', () => {
function App() {
const store = useSelfRefStore();
return (
<div>
<p id={RESULT_ID}>{store.double}</p>
<button onClick={store.increaseVal} id={BUTTON_ID}>
increase value
</button>
</div>
);
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('4');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('6');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('10');
});
it('should access other stores', () => {
const useOtherStore = createStore({
state: {},
actions: {
doIncreaseVal: () => useSelfRefStore().increaseVal(),
},
computed: {
selfRefStoreValue: () => useSelfRefStore().value,
},
});
function App() {
const store = useOtherStore();
return (
<div>
<p id={RESULT_ID}>{store.selfRefStoreValue}</p>
<button onClick={store.doIncreaseVal} id={BUTTON_ID}>
increase value in other store
</button>
</div>
);
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('5');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('9');
});
it('should use parametric getters', () => {
const useArrayStore = createStore({
state: {
items: ['a', 'b', 'c'],
},
actions: {
setItem: (state, index, value) => (state.items[index] = value),
},
computed: {
getItem: state => index => state.items[index],
},
});
function App() {
const store = useArrayStore();
return (
<div>
<p id={RESULT_ID}>{store.getItem(0) + store.getItem(1) + store.getItem(2)}</p>
<button
id={BUTTON_ID}
onClick={() => {
store.setItem(0, 'd');
store.setItem(1, 'e');
store.setItem(2, 'f');
}}
>
change
</button>
</div>
);
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('abc');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('def');
});
});

View File

@ -1,104 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { createStore } from '../../../../src/inulax/store/StoreHandler';
import { triggerClickEvent } from '../../jest/commonComponents';
const { unmountComponentAtNode } = Inula;
describe('Reset', () => {
it('RESET NOT IMPLEMENTED', async () => {
// console.log('reset functionality is not yet implemented')
expect(true).toBe(true);
});
return;
let container = null;
const BUTTON_ID = 'btn';
const RESET_ID = 'reset';
const RESULT_ID = 'result';
const useCounter = createStore({
state: {
counter: 0,
},
actions: {
increment: function (state) {
state.counter++;
},
},
computed: {},
});
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it('Should reset to default state', async () => {
function App() {
const store = useCounter();
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
</button>
</div>
);
}
Inula.render(<App />, container);
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID).innerHTML).toBe('2');
Inula.act(() => {
triggerClickEvent(container, RESET_ID);
});
expect(document.getElementById(RESULT_ID).innerHTML).toBe('0');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID).innerHTML).toBe('1');
});
});

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createStore } from '../../../../src/inulax/store/StoreHandler';
export const useLogStore = createStore({
id: 'logStore', // you do not need to specify ID for local store
state: {
logs: ['log'],
},
actions: {
addLog: (state, data) => {
state.logs.push(data);
},
removeLog: (state, index) => {
state.logs.splice(index, 1);
},
cleanLog: state => {
state.logs.length = 0;
},
},
computed: {
length: state => {
return state.logs.length;
},
log: state => index => state.logs[index],
},
});

View File

@ -1,108 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { resolveMutation } from '../../../../src/inulax/CommonUtils';
describe('Mutation resolve', () => {
it('should resolve mutation different types', () => {
const mutation = resolveMutation(null, 42);
expect(mutation.mutation).toBe(true);
expect(mutation.from).toBe(null);
expect(mutation.to).toBe(42);
});
it('should resolve mutation same type types, different values', () => {
const mutation = resolveMutation(13, 42);
expect(mutation.mutation).toBe(true);
expect(mutation.from).toBe(13);
expect(mutation.to).toBe(42);
});
it('should resolve mutation same type types, same values', () => {
const mutation = resolveMutation(42, 42);
expect(mutation.mutation).toBe(false);
expect(Object.keys(mutation).length).toBe(1);
});
it('should resolve mutation same type types, same objects', () => {
const mutation = resolveMutation({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, same array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]);
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, longer array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]);
expect(mutation.mutation).toBe(true);
expect(mutation.items[5].mutation).toBe(true);
expect(mutation.items[5].to).toBe(6);
});
it('should resolve mutation same type types, shorter array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4]);
expect(mutation.mutation).toBe(true);
expect(mutation.items[4].mutation).toBe(true);
expect(mutation.items[4].from).toBe(5);
});
it('should resolve mutation same type types, changed array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 'a']);
expect(mutation.mutation).toBe(true);
expect(mutation.items[4].mutation).toBe(true);
expect(mutation.items[4].from).toBe(5);
expect(mutation.items[4].to).toBe('a');
});
it('should resolve mutation same type types, same object', () => {
const mutation = resolveMutation({ a: 1, b: 2 }, { a: 1, b: 2 });
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, changed object', () => {
const mutation = resolveMutation({ a: 1, b: 2, c: 3 }, { a: 1, c: 2 });
expect(mutation.mutation).toBe(true);
expect(mutation.attributes.a.mutation).toBe(false);
expect(mutation.attributes.b.mutation).toBe(true);
expect(mutation.attributes.b.from).toBe(2);
expect(mutation.attributes.c.to).toBe(2);
});
});
describe('Mutation collections', () => {
it('should resolve mutation of two sets', () => {
const values = [{ a: 1 }, { b: 2 }, { c: 3 }];
const source = new Set([values[0], values[1], values[2]]);
const target = new Set([values[0], values[1]]);
const mutation = resolveMutation(source, target);
expect(mutation.mutation).toBe(true);
});
});

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createStore } from '../../../../src/index';
import { watch } from '../../../../src/index';
describe('watch', () => {
it('shouhld watch primitive state variable', async () => {
const useStore = createStore({
state: {
variable: 'x',
},
actions: {
change: state => (state.variable = 'a'),
},
});
const store = useStore();
let counter = 0;
watch(store.$s, state => {
counter++;
expect(state.variable).toBe('a');
});
store.change();
expect(counter).toBe(1);
});
it('shouhld watch object variable', async () => {
const useStore = createStore({
state: {
variable: 'x',
},
actions: {
change: state => (state.variable = 'a'),
},
});
const store = useStore();
let counter = 0;
store.$s.watch('variable', () => {
counter++;
});
store.change();
expect(counter).toBe(1);
});
it('shouhld watch array item', async () => {
const useStore = createStore({
state: {
arr: ['x'],
},
actions: {
change: state => (state.arr[0] = 'a'),
},
});
const store = useStore();
let counter = 0;
store.arr.watch('0', () => {
counter++;
});
store.change();
expect(counter).toBe(1);
});
it('shouhld watch collection item', async () => {
const useStore = createStore({
state: {
collection: new Map([['a', 'a']]),
},
actions: {
change: state => state.collection.set('a', 'x'),
},
});
const store = useStore();
let counter = 0;
store.collection.watch('a', () => {
counter++;
});
store.change();
expect(counter).toBe(1);
});
it('should watch multiple variables independedntly', async () => {
const useStore = createStore({
state: {
bool1: true,
bool2: false,
},
actions: {
toggle1: state => (state.bool1 = !state.bool1),
toggle2: state => (state.bool2 = !state.bool2),
},
});
let counter1 = 0;
let counterAll = 0;
const store = useStore();
watch(store.$s, () => {
counterAll++;
});
store.$s.watch('bool1', () => {
counter1++;
});
store.toggle1();
store.toggle1();
store.toggle2();
store.toggle1();
store.toggle2();
store.toggle2();
expect(counter1).toBe(3);
expect(counterAll).toBe(6);
});
});

View File

@ -1,234 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import {
createStore,
applyMiddleware,
combineReducers,
bindActionCreators,
} from '../../../../src/inulax/adapters/redux';
import { describe, it, expect } from '@jest/globals';
describe('Redux adapter', () => {
it('should use getState()', async () => {
const reduxStore = createStore((state, action) => {
return state;
}, 0);
expect(reduxStore.getState()).toBe(0);
});
it('Should use default state, dispatch action and update state', async () => {
const reduxStore = createStore((state, action) => {
switch (action.type) {
case 'ADD':
return { counter: state.counter + 1 };
default:
return { counter: 0 };
}
});
expect(reduxStore.getState().counter).toBe(0);
reduxStore.dispatch({ type: 'ADD' });
expect(reduxStore.getState().counter).toBe(1);
});
it('Should attach and detach listeners', async () => {
let counter = 0;
const reduxStore = createStore((state = 0, action) => {
switch (action.type) {
case 'ADD':
return state + 1;
default:
return state;
}
});
reduxStore.dispatch({ type: 'ADD' });
expect(counter).toBe(0);
expect(reduxStore.getState()).toBe(1);
const unsubscribe = reduxStore.subscribe(() => {
counter++;
});
reduxStore.dispatch({ type: 'ADD' });
reduxStore.dispatch({ type: 'ADD' });
expect(counter).toBe(2);
expect(reduxStore.getState()).toBe(3);
unsubscribe();
reduxStore.dispatch({ type: 'ADD' });
reduxStore.dispatch({ type: 'ADD' });
expect(counter).toBe(2);
expect(reduxStore.getState()).toBe(5);
});
it('Should bind action creators', async () => {
const addTodo = text => {
return {
type: 'ADD_TODO',
text,
};
};
const reduxStore = createStore((state = [], action) => {
if (action.type === 'ADD_TODO') {
return [...state, action.text];
}
return state;
});
const actions = bindActionCreators({ addTodo }, reduxStore.dispatch);
actions.addTodo('todo');
expect(reduxStore.getState()[0]).toBe('todo');
});
it('Should replace reducer', async () => {
const reduxStore = createStore((state, action) => {
switch (action.type) {
case 'ADD':
return { counter: state.counter + 1 };
default:
return { counter: 0 };
}
});
reduxStore.dispatch({ type: 'ADD' });
expect(reduxStore.getState().counter).toBe(1);
reduxStore.replaceReducer((state, action) => {
switch (action.type) {
case 'SUB':
return { counter: state.counter - 1 };
default:
return { counter: 0 };
}
});
reduxStore.dispatch({ type: 'SUB' });
expect(reduxStore.getState().counter).toBe(0);
});
it('Should combine reducers', async () => {
const booleanReducer = (state = false, action) => {
switch (action.type) {
case 'TOGGLE':
return !state;
default:
return state;
}
};
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().check).toBe(false);
reduxStore.dispatch({ type: 'ADD' });
reduxStore.dispatch({ type: 'TOGGLE' });
expect(reduxStore.getState().counter).toBe(1);
expect(reduxStore.getState().check).toBe(true);
});
it('Should apply enhancers', async () => {
let counter = 0;
let middlewareCallList: string[] = [];
const callCounter = store => next => action => {
middlewareCallList.push('callCounter');
counter++;
let result = next(action);
return result;
};
const reduxStore = createStore(
(state, action) => {
switch (action.type) {
case 'toggle':
return {
check: !state.check,
};
default:
return state;
}
},
{ check: false },
applyMiddleware(callCounter)
);
reduxStore.dispatch({ type: 'toggle' });
reduxStore.dispatch({ type: 'toggle' });
expect(counter).toBe(3); // NOTE: first action is always store initialization
});
it('Should apply multiple enhancers', async () => {
let counter = 0;
let lastAction = '';
let middlewareCallList: string[] = [];
const callCounter = store => next => action => {
middlewareCallList.push('callCounter');
counter++;
let result = next(action);
return result;
};
const lastFunctionStorage = store => next => action => {
middlewareCallList.push('lastFunctionStorage');
lastAction = action.type;
let result = next(action);
return result;
};
const reduxStore = createStore(
(state, action) => {
switch (action.type) {
case 'toggle':
return {
check: !state.check,
};
default:
return state;
}
},
{ check: false },
applyMiddleware(callCounter, lastFunctionStorage)
);
reduxStore.dispatch({ type: 'toggle' });
expect(counter).toBe(2); // NOTE: first action is always store initialization
expect(lastAction).toBe('toggle');
expect(middlewareCallList[0]).toBe('callCounter');
expect(middlewareCallList[1]).toBe('lastFunctionStorage');
});
});

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import { createStore, applyMiddleware, thunk } from '../../../../src/inulax/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,378 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import * as Inula from '../../../../src/index';
import {
batch,
connect,
createStore,
Provider,
useDispatch,
useSelector,
useStore,
createSelectorHook,
createDispatchHook,
} from '../../../../src/inulax/adapters/redux';
import { triggerClickEvent } from '../../jest/commonComponents';
import { describe, it, beforeEach, afterEach, expect } from '@jest/globals';
import { ReduxStoreHandler } from '../../../../src/inulax/adapters/redux';
const BUTTON = 'button';
const BUTTON2 = 'button2';
const RESULT = 'result';
const CONTAINER = 'container';
function getE(id): HTMLElement {
return document.getElementById(id) || document.body;
}
describe('Redux/React binding adapter', () => {
beforeEach(() => {
const container = document.createElement('div');
container.id = CONTAINER;
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(getE(CONTAINER));
});
it('Should create provider context', async () => {
const reduxStore = createStore((state = 'state', action) => state);
const Child = () => {
const store = useStore() as unknown as ReduxStoreHandler;
return <div id={RESULT}>{store.getState()}</div>;
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
<Child />
</Provider>
);
};
Inula.render(<Wrapper />, getE(CONTAINER));
expect(getE(RESULT).innerHTML).toBe('state');
});
it('Should use dispatch', async () => {
const reduxStore = createStore((state = 0, action) => {
if (action.type === 'ADD') return state + 1;
return state;
});
const Child = () => {
const store = useStore() as unknown as ReduxStoreHandler;
const dispatch = useDispatch();
return (
<div>
<p id={RESULT}>{store.getState()}</p>
<button
id={BUTTON}
onClick={() => {
dispatch({ type: 'ADD' });
}}
></button>
</div>
);
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
<Child />
</Provider>
);
};
Inula.render(<Wrapper />, getE(CONTAINER));
expect(reduxStore.getState()).toBe(0);
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
});
expect(reduxStore.getState()).toBe(1);
});
it('Should use selector', async () => {
const reduxStore = createStore((state = 0, action) => {
if (action.type === 'ADD') return state + 1;
return state;
});
const Child = () => {
const count = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>
<p id={RESULT}>{count}</p>
<button
id={BUTTON}
onClick={() => {
dispatch({ type: 'ADD' });
}}
>
click
</button>
</div>
);
};
const Wrapper = () => {
return (
<Provider store={reduxStore}>
<Child />
</Provider>
);
};
Inula.render(<Wrapper />, getE(CONTAINER));
expect(getE(RESULT).innerHTML).toBe('0');
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
triggerClickEvent(getE(CONTAINER), BUTTON);
});
expect(getE(RESULT).innerHTML).toBe('2');
});
it('Should use connect', async () => {
const reduxStore = createStore(
(state, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
value: state.negative ? state.value - action.amount : state.value + action.amount,
};
case 'TOGGLE':
return {
...state,
negative: !state.negative,
};
default:
return state;
}
},
{ negative: false, value: 0 }
);
const Child = connect(
(state, ownProps) => {
// map state to props
return { ...state, ...ownProps };
},
(dispatch, ownProps) => {
// map dispatch to props
return {
// @ts-ignore
increment: () => dispatch({ type: 'INCREMENT', amount: ownProps?.amount }),
};
},
(stateProps, dispatchProps, ownProps) => {
//merge props
return { stateProps, dispatchProps, ownProps };
},
{}
)(props => {
const n = props.stateProps.negative;
return (
<div>
<div id={RESULT}>
{n ? '-' : '+'}
{props.stateProps.value}
</div>
<button
id={BUTTON}
onClick={() => {
props.dispatchProps.increment();
}}
>
add {props.ownProps.amount}
</button>
</div>
);
});
const Wrapper = () => {
//@ts-ignore
const [amount, setAmount] = Inula.useState(5);
return (
<Provider store={reduxStore}>
<Child amount={amount} />
<button
id={BUTTON2}
onClick={() => {
setAmount(3);
}}
>
change amount
</button>
</Provider>
);
};
Inula.render(<Wrapper />, getE(CONTAINER));
expect(getE(RESULT).innerHTML).toBe('+0');
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
});
expect(getE(RESULT).innerHTML).toBe('+5');
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON2);
});
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
});
expect(getE(RESULT).innerHTML).toBe('+8');
});
it('Should batch dispatches', async () => {
const reduxStore = createStore((state = 0, action) => {
if (action.type == 'ADD') return state + 1;
return state;
});
let renderCounter = 0;
function Counter() {
renderCounter++;
const value = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>
<p id={RESULT}>{value}</p>
<button
id={BUTTON}
onClick={() => {
batch(() => {
for (let i = 0; i < 10; i++) {
dispatch({ type: 'ADD' });
}
});
}}
></button>
</div>
);
}
Inula.render(
<Provider store={reduxStore}>
<Counter />
</Provider>,
getE(CONTAINER)
);
expect(getE(RESULT).innerHTML).toBe('0');
expect(renderCounter).toBe(1);
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
});
expect(getE(RESULT).innerHTML).toBe('10');
expect(renderCounter).toBe(2);
});
it('Should use multiple contexts', async () => {
const counterStore = createStore((state = 0, action) => {
if (action.type === 'ADD') return state + 1;
return state;
});
const toggleStore = createStore((state = false, action) => {
if (action.type === 'TOGGLE') return !state;
return state;
});
const counterContext = Inula.createContext();
const toggleContext = Inula.createContext();
function Counter() {
const count = createSelectorHook(counterContext)();
const dispatch = createDispatchHook(counterContext)();
return (
<button
id={BUTTON}
onClick={() => {
dispatch({ type: 'ADD' });
}}
>
{count}
</button>
);
}
function Toggle() {
const check = createSelectorHook(toggleContext)();
const dispatch = createDispatchHook(toggleContext)();
return (
<button
id={BUTTON2}
onClick={() => {
dispatch({ type: 'TOGGLE' });
}}
>
{check ? 'true' : 'false'}
</button>
);
}
function Wrapper() {
return (
<div>
<Provider store={counterStore} context={counterContext}>
<Counter />
</Provider>
<Provider store={toggleStore} context={toggleContext}>
<Toggle />
</Provider>
</div>
);
}
Inula.render(<Wrapper />, getE(CONTAINER));
expect(getE(BUTTON).innerHTML).toBe('0');
expect(getE(BUTTON2).innerHTML).toBe('false');
Inula.act(() => {
triggerClickEvent(getE(CONTAINER), BUTTON);
triggerClickEvent(getE(CONTAINER), BUTTON2);
});
expect(getE(BUTTON).innerHTML).toBe('1');
expect(getE(BUTTON2).innerHTML).toBe('true');
});
});

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createElement } from '../../../../src/external/JSXElement';
import { createDomTextVNode } from '../../../../src/renderer/vnode/VNodeCreator';
import { createStore } from '../../../../src/inulax/adapters/redux';
import { connect } from '../../../../src/inulax/adapters/reduxReact';
createStore((state: number = 0, action): number => {
if (action.type === 'add') return state + 1;
return 0;
});
type WrappedButtonProps = { add: () => void; count: number; text: string };
function Button(props: WrappedButtonProps) {
const { add, count, text } = props;
return createElement(
'button',
{
onClick: add,
},
createDomTextVNode(text),
createDomTextVNode(': '),
createDomTextVNode(count)
);
}
const connector = connect(
state => ({ count: state }),
dispatch => ({
add: (): void => {
dispatch({ type: 'add' });
},
}),
(stateProps, dispatchProps, ownProps: { text: string }) => ({
add: dispatchProps.add,
count: stateProps.count,
text: ownProps.text,
})
);
const ConnectedButton = connector(Button);
function App() {
return createElement('div', {}, createElement(ConnectedButton, { text: 'click' }));
}
export default App;

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { Text, triggerClickEvent } from '../../jest/commonComponents';
import { getObserver } from '../../../../src/inulax/proxy/ProxyHandler';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
describe('测试 Class VNode 清除时,对引用清除', () => {
const { unmountComponentAtNode } = Inula;
let container: HTMLElement | null = null;
let globalState = {
name: 'bing dun dun',
isWin: true,
isShow: true,
};
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
createStore({
id: 'user',
state: globalState,
actions: {
setWin: (state, val) => {
state.isWin = val;
},
hide: state => {
state.isShow = false;
},
updateName: (state, val) => {
state.name = val;
},
},
});
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container?.remove();
container = null;
LogUtils.clear();
clearStore('user');
});
it('test observer.clearByNode', () => {
class Child extends Inula.Component {
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>
<Text id={'name'} text={`name: ${this.userStore.name}`} />
<Text id={'isWin'} text={`isWin: ${this.userStore.isWin}`} />
</div>
);
}
}
expect(() => {
Inula.render(<Child />, container);
}).toThrow(
'The number of updates exceeds the upper limit 50.\n' +
' A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.'
);
});
});

View File

@ -1,248 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/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 } = Inula;
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 Inula.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 Inula.Component {
userStore = useUserStore();
render() {
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${this.userStore.persons.length}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 2');
// 在Array中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: 3');
// 在Array中删除一个对象
Inula.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 Inula.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>
);
}
}
Inula.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 Inula.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>
);
}
}
Inula.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,370 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const useUserStore = createStore({
id: 'user',
state: {
type: 'bing dun dun',
persons: new Map([
['p1', 1],
['p2', 2],
]),
},
actions: {
addOnePerson: (state, person) => {
state.persons.set(person.name, person.age);
},
delOnePerson: (state, person) => {
state.persons.delete(person.name);
},
clearPersons: state => {
state.persons.clear();
},
reset: state => {
state.persons = new Map([
['p1', 1],
['p2', 2],
]);
},
},
});
describe('在Class组件中测试store中的Map', () => {
const { unmountComponentAtNode } = Inula;
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 };
class Parent extends Inula.Component {
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 (
<div>
<button id={'addBtn'} onClick={this.addOnePerson}>
add person
</button>
<button id={'delBtn'} onClick={this.delOnePerson}>
delete person
</button>
<button id={'clearBtn'} onClick={this.clearPersons}>
clear persons
</button>
<div>{this.props.children}</div>
</div>
);
}
}
it('测试Map方法: set()、delete()、clear()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
return (
<div>
<Text id={'size'} text={`persons number: ${this.userStore.$s.persons.size}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#size')?.innerHTML).toBe('persons number: 0');
});
it('测试Map方法: keys()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
const nameList: string[] = [];
const keys = this.userStore.$s.persons.keys();
for (const key of keys) {
nameList.push(key);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: values()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
const ageList: number[] = [];
const values = this.userStore.$s.persons.values();
for (const val of values) {
ageList.push(val);
}
return (
<div>
<Text id={'ageList'} text={`age list: ${ageList.join(' ')}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2 3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: 1 2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#ageList')?.innerHTML).toBe('age list: ');
});
it('测试Map方法: entries()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
const nameList: string[] = [];
const entries = this.userStore.$s.persons.entries();
for (const entry of entries) {
nameList.push(entry[0]);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: forEach()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
const nameList: string[] = [];
this.userStore.$s.persons.forEach((val, key) => {
nameList.push(key);
});
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
it('测试Map方法: has()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
return (
<div>
<Text id={'hasPerson'} text={`has new person: ${this.userStore.$s.persons.has(newPerson.name)}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: false');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#hasPerson')?.innerHTML).toBe('has new person: true');
});
it('测试Map方法: for of()', () => {
class Child extends Inula.Component {
userStore = useUserStore();
render() {
const nameList: string[] = [];
for (const per of this.userStore.$s.persons) {
nameList.push(per[0]);
}
return (
<div>
<Text id={'nameList'} text={`name list: ${nameList.join(' ')}`} />
</div>
);
}
}
Inula.render(<App parent={Parent} child={Child} />, container);
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// 在Map中增加一个对象
Inula.act(() => {
triggerClickEvent(container, 'addBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2 p3');
// 在Map中删除一个对象
Inula.act(() => {
triggerClickEvent(container, 'delBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: p1 p2');
// clear Map
Inula.act(() => {
triggerClickEvent(container, 'clearBtn');
});
expect(container?.querySelector('#nameList')?.innerHTML).toBe('name list: ');
});
});

View File

@ -1,137 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { Text, triggerClickEvent } from '../../jest/commonComponents';
import { getObserver } from '../../../../src/inulax/proxy/ProxyHandler';
import { describe, it, beforeEach, afterEach, expect } from '@jest/globals';
describe('测试 Class VNode 清除时,对引用清除', () => {
const { unmountComponentAtNode } = Inula;
let container: HTMLElement | null = null;
let globalState = {
name: 'bing dun dun',
isWin: true,
isShow: true,
};
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
createStore({
id: 'user',
state: globalState,
actions: {
setWin: (state, val) => {
state.isWin = val;
},
hide: state => {
state.isShow = false;
},
updateName: (state, val) => {
state.name = val;
},
},
});
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container?.remove();
container = null;
LogUtils.clear();
clearStore('user');
});
it('test observer.clearByNode', () => {
class App extends Inula.Component {
userStore = useStore('user');
render() {
return (
<div>
<button id={'hideBtn'} onClick={this.userStore?.hide}>
toggle
</button>
{this.userStore?.isShow && <Parent />}
</div>
);
}
}
class Parent extends Inula.Component {
userStore = useStore('user');
setWin = () => {
this.userStore?.setWin(!this.userStore?.isWin);
};
render() {
return (
<div>
<button id={'toggleBtn'} onClick={this.setWin}>
toggle
</button>
{this.userStore?.isWin && <Child />}
</div>
);
}
}
class Child extends Inula.Component {
userStore = useStore('user');
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>
);
}
}
Inula.render(<App />, container);
// Parent and Child hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2);
Inula.act(() => {
triggerClickEvent(container, 'toggleBtn');
});
// Parent hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(1);
Inula.act(() => {
triggerClickEvent(container, 'toggleBtn');
});
// Parent and Child hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2);
Inula.act(() => {
triggerClickEvent(container, 'hideBtn');
});
// no component hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin')).toBe(undefined);
});
});

View File

@ -1,132 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../../src/index';
import * as LogUtils from '../../jest/logUtils';
import { clearStore, createStore, useStore } from '../../../../src/inulax/store/StoreHandler';
import { Text, triggerClickEvent } from '../../jest/commonComponents';
import { getObserver } from '../../../../src/inulax/proxy/ProxyHandler';
import { describe, it, beforeEach, afterEach, expect } from '@jest/globals';
describe('测试VNode清除时对引用清除', () => {
const { unmountComponentAtNode } = Inula;
let container: HTMLElement | null = null;
let globalState = {
name: 'bing dun dun',
isWin: true,
isShow: true,
};
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
createStore({
id: 'user',
state: globalState,
actions: {
setWin: (state, val) => {
state.isWin = val;
},
hide: state => {
state.isShow = false;
},
},
});
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container?.remove();
container = null;
LogUtils.clear();
clearStore('user');
});
it('test observer.clearByNode', () => {
class App extends Inula.Component {
userStore = useStore('user');
render() {
return (
<div>
<button id={'hideBtn'} onClick={this.userStore?.hide}>
toggle
</button>
{this.userStore?.isShow && <Parent />}
</div>
);
}
}
class Parent extends Inula.Component {
userStore = useStore('user');
setWin = () => {
this.userStore?.setWin(!this.userStore.isWin);
};
render() {
return (
<div>
<button id={'toggleBtn'} onClick={this.setWin}>
toggle
</button>
{this.userStore?.isWin && <Child />}
</div>
);
}
}
class Child extends Inula.Component {
userStore = useStore('user');
render() {
return (
<div>
<Text id={'name'} text={`name: ${this.userStore?.name}`} />
<Text id={'isWin'} text={`isWin: ${this.userStore?.isWin}`} />
</div>
);
}
}
Inula.render(<App />, container);
// Parent and Child hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2);
Inula.act(() => {
triggerClickEvent(container, 'toggleBtn');
});
// Parent hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(1);
Inula.act(() => {
triggerClickEvent(container, 'toggleBtn');
});
// Parent and Child hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin').size).toBe(2);
Inula.act(() => {
triggerClickEvent(container, 'hideBtn');
});
// no component hold the isWin key
expect(getObserver(globalState).keyVNodes.get('isWin')).toBe(undefined);
});
});

View File

@ -1,170 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createStore, useStore } from '../../../../src/index';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
describe('Using deep variables', () => {
it('should listen to object variable change', () => {
let counter = 0;
const useTestStore = createStore({
state: { a: { b: { c: 1 } } },
});
const testStore = useTestStore();
testStore.$subscribe(() => {
counter++;
});
testStore.a.b.c = 0;
expect(counter).toBe(1);
});
it('should listen to deep variable change', () => {
let counter = 0;
const useTestStore = createStore({
state: { color: [{ a: 1 }, 255, 255] },
});
const testStore = useTestStore();
testStore.$subscribe(() => {
counter++;
});
for (let i = 0; i < 5; i++) {
testStore.color[0].a = i;
}
testStore.color = 'x';
expect(counter).toBe(6);
});
it('should use set', () => {
const useTestStore = createStore({
state: { data: new Set() },
});
const testStore = useTestStore();
const a = { a: true };
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
testStore.data.add(a);
testStore.data.add(a);
testStore.data.delete(a);
expect(testStore.data.has(a)).toBe(false);
testStore.data.add(a);
const values = Array.from(testStore.data.values());
expect(values.length).toBe(1);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
values.forEach(val => {
val.a = !val.a;
});
expect(testStore.data.has(a)).toBe(true);
expect(counter).toBe(1);
});
it('should use map', () => {
const useTestStore = createStore({
state: { data: new Map() },
});
const testStore = useTestStore();
const data = { key: { a: 1 }, value: { b: 2 } };
testStore.data.set(data.key, data.value);
const key = Array.from(testStore.data.keys())[0];
expect(testStore.data.has(key)).toBe(true);
testStore.data.set(data.key, data.value);
testStore.data.set(data.key, data.value);
testStore.data.delete(key);
expect(testStore.data.get(key)).toBe();
testStore.data.set(data.key, data.value);
const entries = Array.from(testStore.data.entries());
expect(entries.length).toBe(1);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
entries.forEach(([key, value]) => {
key.a++;
value.b++;
});
expect(counter).toBe(2);
});
it('should use weakSet', () => {
const useTestStore = createStore({
state: { data: new WeakSet() },
});
const testStore = useTestStore();
const a = { a: true };
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
testStore.data.add(a);
testStore.data.add(a);
testStore.data.delete(a);
expect(testStore.data.has(a)).toBe(false);
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
});
it('should use weakMap', () => {
const useTestStore = createStore({
state: { data: new WeakMap() },
});
const testStore = useTestStore();
const data = { key: { a: 1 }, value: { b: 2 } };
testStore.data.set(data.key, data.value);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
testStore.data.get(data.key).b++;
expect(counter).toBe(1);
});
});

View File

@ -1,212 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
//@ts-ignore
import Inula, { createStore } from '../../../../src/index';
import { triggerClickEvent } from '../../jest/commonComponents';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
const { unmountComponentAtNode } = Inula;
const useStore1 = createStore({
state: { counter: 1 },
actions: {
add: state => state.counter++,
reset: state => (state.counter = 1),
},
});
const useStore2 = createStore({
state: { counter2: 1 },
actions: {
add2: state => state.counter2++,
reset: state => (state.counter2 = 1),
},
});
describe('Using multiple stores', () => {
let container: HTMLElement | null = null;
const BUTTON_ID = 'btn';
const BUTTON_ID2 = 'btn2';
const RESULT_ID = 'result';
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
useStore1().reset();
useStore2().reset();
});
afterEach(() => {
unmountComponentAtNode(container);
container?.remove();
container = null;
});
it('Should use multiple stores in class component', () => {
class App extends Inula.Component {
render() {
const { counter, add } = useStore1();
const { counter2, add2 } = useStore2();
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
add();
}}
>
add
</button>
<button
id={BUTTON_ID2}
onClick={() => {
add2();
}}
>
add
</button>
<p id={RESULT_ID}>
{counter} {counter2}
</p>
</div>
);
}
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID2);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
});
it('Should use use stores in cycles and multiple methods', () => {
interface App {
store: any;
store2: any;
}
class App extends Inula.Component {
constructor() {
super();
this.store = useStore1();
this.store2 = useStore2();
}
render() {
const { counter, add } = useStore1();
const store2 = useStore2();
const { counter2, add2 } = store2;
for (let i = 0; i < 100; i++) {
const { counter, add } = useStore1();
const store2 = useStore2();
const { counter2, add2 } = store2;
}
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
add();
}}
>
add
</button>
<button
id={BUTTON_ID2}
onClick={() => {
this.store2.add2();
}}
>
add
</button>
<p id={RESULT_ID}>
{counter} {counter2}
</p>
</div>
);
}
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID2);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
});
it('Should use multiple stores in function component', () => {
function App() {
const { counter, add } = useStore1();
const store2 = useStore2();
const { counter2, add2 } = store2;
return (
<div>
<button
id={BUTTON_ID}
onClick={() => {
add();
}}
>
add
</button>
<button
id={BUTTON_ID2}
onClick={() => {
add2();
}}
>
add
</button>
<p id={RESULT_ID}>
{counter} {counter2}
</p>
</div>
);
}
Inula.render(<App />, container);
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
Inula.act(() => {
triggerClickEvent(container, BUTTON_ID2);
});
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
});
});

View File

@ -1,63 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createProxy } from '../../../../src/inulax/proxy/ProxyHandler';
import { readonlyProxy } from '../../../../src/inulax/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

@ -1,71 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Inula from '../../../src/index';
function App() {
return <></>;
}
describe('InulaIs', () => {
it('should identify inula elements', () => {
expect(Inula.isElement(<div />)).toBe(true);
expect(Inula.isElement('span')).toBe(false);
expect(Inula.isElement(111)).toBe(false);
expect(Inula.isElement(false)).toBe(false);
expect(Inula.isElement(null)).toBe(false);
expect(Inula.isElement([])).toBe(false);
expect(Inula.isElement({})).toBe(false);
expect(Inula.isElement(undefined)).toBe(false);
const TestContext = Inula.createContext(false);
expect(Inula.isElement(<TestContext.Provider />)).toBe(true);
expect(Inula.isElement(<TestContext.Consumer />)).toBe(true);
expect(Inula.isElement(<></>)).toBe(true);
expect(Inula.isElement(<Inula.Suspense />)).toBe(true);
});
it('should identify Fragment', () => {
expect(Inula.isFragment(<></>)).toBe(true);
});
it('should identify memo component', () => {
const MemoComp = Inula.memo(App);
expect(Inula.isMemo(<MemoComp />)).toBe(true);
});
it('should identify forwardRef', () => {
const ForwardRefComp = Inula.forwardRef(App);
expect(Inula.isForwardRef(<ForwardRefComp />)).toBe(true);
});
it('should identify lazy', () => {
const LazyComp = Inula.lazy(() => App);
expect(Inula.isLazy(<LazyComp />)).toBe(true);
});
it('should identify portal', () => {
const portal = Inula.createPortal(<div />, container);
expect(Inula.isPortal(portal)).toBe(true);
});
it('should identify ContextProvider', () => {
const TestContext = Inula.createContext(false);
expect(Inula.isContextProvider(<TestContext.Provider />)).toBe(true);
expect(Inula.isContextProvider(<TestContext.Consumer />)).toBe(false);
expect(Inula.isContextConsumer(<TestContext.Provider />)).toBe(false);
expect(Inula.isContextConsumer(<TestContext.Consumer />)).toBe(true);
});
});

View File

@ -1,162 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, useReactive, For } from '../../../../../src/index';
import { beforeEach } from '@jest/globals';
const Row = ({ item }) => {
return <li id={item.id} key={item.id}>{item.name}</li>;
};
let rObj;
let ref;
let appFn;
let App;
let itemFn;
describe('测试 For 组件的新增', () => {
beforeEach(() => {
ref = createRef();
appFn = jest.fn();
itemFn = jest.fn();
App = () => {
const _rObj = useReactive({
items: [
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
],
});
rObj = _rObj;
appFn();
return (
<div ref={ref}>
<For each={_rObj.items}>
{item => {
itemFn();
return <Row item={item} />;
}}
</For>
</div>
);
};
});
it('通过 push 在后面添加1行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
// 在后面添加一行
rObj.items.push({ id: 'id-3', name: 'p3' });
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行2次push更新执行1次
expect(itemFn).toHaveBeenCalledTimes(3);
});
it('通过 unshift 在前面添加2行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
// 在前面添加2行
rObj.items.unshift({ id: 'id-3', name: 'p3' }, { id: 'id-4', name: 'p4' });
items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行2次unshift更新执行2次
expect(itemFn).toHaveBeenCalledTimes(4);
});
it('通过 set 在后面添加1行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
// 在后面添加一行
rObj.items.set([
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
{ id: 'id-3', name: 'p3' },
]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行2次push更新执行1次
expect(itemFn).toHaveBeenCalledTimes(3);
let li = container.querySelector('#id-3');
expect(li.innerHTML).toEqual('p3');
});
it('For标签使用使用push创建3000行表格数据', () => {
let reactiveObj;
const App = () => {
const sourceData = useReactive([]);
reactiveObj = sourceData;
return (
<div style={{ width: '100%', height: '100%', overflowY: 'auto' }}>
<table border='1' width='100%'>
<tr>
<th>序号</th>
<th>名称</th>
<th>年龄</th>
<th>性别</th>
<th>名族</th>
<th>其他</th>
</tr>
<For each={sourceData}>
{
eachItem => {
return (
<tr>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
</tr>
);
}
}
</For>
</table>
</div>
);
};
render(<App />, container);
// 不推荐循环push
for (let i = 0; i < 2; i++) {
reactiveObj.push({ value: i, color: null });
}
expect(reactiveObj.get().length).toEqual(2);
let items = container.querySelectorAll('tr');
expect(items.length).toEqual(3);
});
});

View File

@ -1,129 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, useReactive, For } from '../../../../../src/index';
import { beforeEach } from '@jest/globals';
const Row = ({ item }) => {
return <li id={item.id} key={item.id}>{item.name}</li>;
};
let rObj;
let ref;
let appFn;
let App;
let itemFn;
describe('测试 For 组件的删除', () => {
beforeEach(() => {
ref = createRef();
appFn = jest.fn();
itemFn = jest.fn();
App = () => {
const _rObj = useReactive({
items: [
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
{ id: 'id-3', name: 'p3' },
{ id: 'id-4', name: 'p4' },
{ id: 'id-5', name: 'p5' },
],
});
rObj = _rObj;
appFn();
return (
<div ref={ref}>
<For each={_rObj.items}>
{item => {
itemFn();
return <Row item={item} />;
}}
</For>
</div>
);
};
});
it('通过 pop 删除最后1行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(5);
// 删除最后一行
rObj.items.pop();
items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行5次pop无需更新
expect(itemFn).toHaveBeenCalledTimes(5);
});
it('通过 splice 删除中间2行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(5);
// 删除中间一行
rObj.items.splice(2, 2);
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行5次splice无需更新
expect(itemFn).toHaveBeenCalledTimes(5);
});
it('通过 splice 删除中间2行增加1行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(5);
// 删除中间2行增加1行
rObj.items.splice(2, 2, ...[{ id: 6, name: 'p6' }]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行5次splice新增1行会执行1次
expect(itemFn).toHaveBeenCalledTimes(6);
});
it('通过 set 删除中间2行', () => {
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(5);
// 删除中间2行
rObj.items.set([
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
{ id: 'id-5', name: 'p5' },
]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(appFn).toHaveBeenCalledTimes(1);
// 第一次渲染执行5次splice无需更新
expect(itemFn).toHaveBeenCalledTimes(5);
});
});

View File

@ -1,251 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, useReactive, reactive, memo, For } from '../../../../../src/index';
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
describe('测试 For 组件', () => {
it('使用For组件遍历reactive“数组”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
const App = () => {
const _rObj = useReactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
<For each={_rObj.items}>
{item => {
return <Item item={item} />;
}}
</For>
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
// 每次修改items都会触发整个组件刷新
rObj.items.set([{ name: 'p11', id: 1 }]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(1);
expect(fn).toHaveBeenCalledTimes(1);
// 每次修改items都会触发整个组件刷新
rObj.items.push({ name: 'p22', id: 2 });
items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive“数组”从[]变成有值', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
const App = () => {
const _rObj = useReactive({
items: [],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
<For each={_rObj.items}>
{item => {
return <Item item={item} />;
}}
</For>
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(0);
// 每次修改items都会触发整个组件刷新
rObj.items.set([{ name: 'p11', id: 1 }]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(1);
expect(fn).toHaveBeenCalledTimes(1);
// 每次修改items都会触发整个组件刷新
rObj.items.push({ name: 'p22', id: 2 });
items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(1);
});
it('数组3行变到4行', () => {
const state = reactive({
data: {
lines: [
{ id: 'id-1', label: '1' },
{ id: 'id-2', label: '2' },
{ id: 'id-3', label: '3' },
],
},
});
const Row = memo(({ item }) => {
return (
<tr>
<td>{item.id}</td>
<td>
<a id={item.id}>{item.label}</a>
</td>
</tr>
);
});
const RowList = () => {
return <For each={state.data.lines}>{item => <Row item={item} />}</For>;
};
const App = () => {
return (
<div>
<table>
<tbody>
<RowList />
</tbody>
</table>
</div>
);
};
render(<App />, container);
let a = container.querySelector('#id-1');
expect(a.innerHTML).toEqual('1');
expect(state.data.lines.length).toEqual(3);
state.data.set({
lines: [
{ id: 'id-4', label: '4' },
{ id: 'id-5', label: '5' },
{ id: 'id-6', label: '6' },
{ id: 'id-7', label: '7' },
],
});
expect(state.data.lines.length).toEqual(4);
a = container.querySelector('#id-4');
expect(a.innerHTML).toEqual('4');
const b = container.querySelector('#id-6');
expect(b.innerHTML).toEqual('6');
});
it('使用基本数据数组的loop方法', () => {
let rObj;
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
items: [1, 2, 3, 4],
});
rObj = _rObj;
fn();
return (
<div>
{_rObj.items.map(rItem => {
return <li>{rItem}</li>;
})}
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
// 每次修改items都会触发整个组件刷新
rObj.items.set([1, 2, 3]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(fn).toHaveBeenCalledTimes(2);
});
});
describe('数组reverse', () => {
it('调用数组的reverse方法', () => {
let rObj;
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
items: [
{ id: 1, name: 'p1' },
{ id: 2, name: 'p2' },
{ id: 3, name: 'p3' },
],
});
rObj = _rObj;
fn();
return (
<div>
<For each={_rObj.items}>
{item => {
return <Item item={item} />;
}}
</For>
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
// 反转
rObj.items.reverse();
items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,94 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive } from '../../../../src/index';
import { Block } from '../../../../src/reactive/components/Block';
describe('测试 Block 组件', () => {
it('使用 Block 控制更新范围', () => {
let rObj, rColor;
const ref = createRef();
const fn = jest.fn();
const fn1 = jest.fn();
const App = () => {
const _rObj = useReactive({ count: 0 });
const _rColor = useReactive('blue');
rObj = _rObj;
rColor = _rColor;
fn();
return (
<div ref={ref}>
111 222
<Block>
{() => {
fn1();
const count = _rObj.count.get();
return (
<>
<div>Count: {count}</div>
<div>{_rColor}</div>
</>
);
}}
</Block>
</div>
);
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 0</div><div>blue</div>');
// 会触发View刷新
rObj.count.set(1);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn1).toHaveBeenCalledTimes(2);
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 1</div><div>blue</div>');
// 不会触发View刷新
rColor.set('red');
expect(fn).toHaveBeenCalledTimes(1);
expect(fn1).toHaveBeenCalledTimes(2);
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 1</div><div>red</div>');
});
it('使用 Block 包裹一个Atom', () => {
let rObj;
const ref1 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
// div下面有多个元素_rObj就需要用RText包裹
<div ref={ref1}>
111 222
<Block>{_rObj}</Block>
</div>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('111 222blue');
rObj.set('red');
expect(fn).toHaveBeenCalledTimes(1);
expect(ref1.current.innerHTML).toEqual('111 222red');
});
});

View File

@ -1,105 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, useReactive, useComputed, For, Show, Switch } from '../../../../src/index';
describe('测试Switch、Show、For标签的组合使用时的组件渲染', () => {
it('Show、For标签的组合使用', () => {
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
let reactiveObj;
const ref = createRef();
const ref1 = createRef();
const fn = jest.fn();
const App = () => {
const dataList = useReactive([]);
reactiveObj = dataList;
const listLen = useComputed(() => {
return dataList.get().length;
});
fn();
return (
<>
<Show if={() => dataList.get().length > 0} else={() => <div />}>
<div ref={ref} style={{ display: 'flex' }}>
<For each={dataList}>{item => <Item item={item} />}</For>
</div>
</Show>
<div ref={ref1}>{listLen}</div>
</>
);
};
render(<App />, container);
let liItems = container.querySelectorAll('li');
expect(liItems.length).toEqual(0);
reactiveObj.push({ id: 1, name: '1' });
expect(reactiveObj.get().length).toEqual(1);
liItems = container.querySelectorAll('li');
expect(liItems.length).toEqual(1);
reactiveObj.push({ id: 2, name: '2' });
expect(reactiveObj.get().length).toEqual(2);
liItems = container.querySelectorAll('li');
expect(liItems.length).toEqual(2);
expect(ref1.current.innerHTML).toEqual('2');
expect(fn).toHaveBeenCalledTimes(1);
});
it('Switch、Show和For标签的组合使用', () => {
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
let reactiveObj;
const ref = createRef();
const App = () => {
const dataList = useReactive([]);
reactiveObj = dataList;
return (
<Switch>
<Show if={() => dataList.get().length === 0}>
<div />
</Show>
<Show if={() => dataList.get().length > 0}>
<div ref={ref} style={{ display: 'flex' }}>
<For each={dataList}>{item => <Item item={item} />}</For>
</div>
</Show>
</Switch>
);
};
render(<App />, container);
let liItems = container.querySelectorAll('li');
expect(liItems.length).toEqual(0);
reactiveObj.push({ id: 1, name: '1' });
expect(reactiveObj.get().length).toEqual(1);
liItems = container.querySelectorAll('li');
expect(liItems.length).toEqual(1);
});
});

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive, useCompute, reactive, RText } from '../../../../src/index';
describe('测试 RText 组件', () => {
it('使用RText精准更新', () => {
let rObj;
const ref1 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
// div下面有多个元素_rObj就需要用RText包裹
<div ref={ref1}>
111 222
<RText>{_rObj}</RText>
</div>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('111 222blue');
rObj.set('red');
expect(fn).toHaveBeenCalledTimes(1);
expect(ref1.current.innerHTML).toEqual('111 222red');
});
});

View File

@ -1,200 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive, useCompute, reactive, Show } from '../../../../src/index';
describe('测试 Show 组件', () => {
it('if为primitive值', () => {
let rObj;
const ref1 = createRef();
const ref2 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
// 如果else中的dom和children一个类型需要增加key否则会被框架当作同一个dom
<Show
if={_rObj}
else={
<div key="else" ref={ref2}>
Loading...
</div>
}
>
<div key="if" ref={ref1}>
{_rObj}
</div>
</Show>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('blue');
rObj.set('');
expect(ref2.current.innerHTML).toEqual('Loading...');
expect(fn).toHaveBeenCalledTimes(1);
});
it('if为primitive值没有else', () => {
let rObj;
const ref1 = createRef();
const ref2 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
// 如果else中的dom和children一个类型需要增加key否则会被框架当作同一个dom
<Show if={_rObj}>
<div ref={ref1}>{_rObj}</div>
</Show>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('blue');
rObj.set('');
expect(ref2.current).toEqual(null);
expect(fn).toHaveBeenCalledTimes(1);
});
it('if为reactive object值', () => {
let rObj;
const ref1 = createRef();
const ref2 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
color: 'blue',
});
rObj = _rObj;
fn();
return (
// 如果else中的dom和children一个类型需要增加key否则会被框架当作同一个dom
<Show
if={_rObj.color}
else={
<div key="else" ref={ref2}>
Loading...
</div>
}
>
<div key="if" ref={ref1}>
{_rObj.color}
</div>
</Show>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('blue');
rObj.color.set('');
expect(ref2.current.innerHTML).toEqual('Loading...');
expect(fn).toHaveBeenCalledTimes(1);
});
it('if为函数', () => {
let rObj;
const ref1 = createRef();
const ref2 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
color: 'blue',
});
rObj = _rObj;
fn();
return (
// 如果else中的dom和children一个类型需要增加key否则会被框架当作同一个dom
<Show
if={() => _rObj.color}
else={
<div key="else" ref={ref2}>
Loading...
</div>
}
>
<div key="if" ref={ref1}>
{_rObj.color}
</div>
</Show>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('blue');
rObj.color.set('');
expect(ref2.current.innerHTML).toEqual('Loading...');
expect(fn).toHaveBeenCalledTimes(1);
});
it('if的children、else是函数', () => {
const ref1 = createRef();
const ref2 = createRef();
const fn = jest.fn();
const _count = reactive(0);
const _rObj = reactive({
color: 'blue',
});
const App = () => {
fn();
return (
// 如果else中的dom和children一个类型需要增加key否则会被框架当作同一个dom
<Show
if={() => _rObj.color}
else={() => (
<div key="else" ref={ref2}>
Loading...
</div>
)}
>
{() => {
const text = useCompute(() => {
return _rObj.color.get() + _count.get();
});
return (
<div key="if" ref={ref1}>
{text}
</div>
);
}}
</Show>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('blue0');
// 修改children函数中使用到的响应式变量也会触发Show组件更新
_count.set(1);
expect(ref1.current.innerHTML).toEqual('blue1');
_rObj.color.set('');
expect(ref2.current.innerHTML).toEqual('Loading...');
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive, Show, Switch } from '../../../../src/index';
describe('测试 Switch 组件', () => {
it('Switch 配合 Show 使用', () => {
let rObj;
const refBlue = createRef();
const refRed = createRef();
const refYellow = createRef();
const refNothing = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
<Switch default={<div ref={refNothing}>nothing</div>}>
{/*if不能写成 _rObj === 'red' 或者 _rObj.get() === 'red' */}
<Show if={() => _rObj.get() === 'blue'}>
<div id="1" ref={refBlue}>
{_rObj}
</div>
</Show>
<Show if={() => _rObj.get() === 'red'}>
<div id="2" ref={refRed}>
{_rObj}
</div>
</Show>
<Show if={() => _rObj.get() === 'yellow'}>
<div id="3" ref={refYellow}>
{_rObj}
</div>
</Show>
</Switch>
);
};
render(<App />, container);
expect(refBlue.current.innerHTML).toEqual('blue');
// rObj被3个RContext依赖分别是Switch组件、Show组件、div[id=1]的Children
expect(rObj.usedRContexts.size).toEqual(3);
act(() => {
rObj.set('red');
});
expect(refRed.current.innerHTML).toEqual('red');
// rObj被3个Effect依赖分别是Switch组件、Show组件、div[id=2]的Children
expect(rObj.usedRContexts.size).toEqual(3);
act(() => {
rObj.set('black');
});
expect(refNothing.current.innerHTML).toEqual('nothing');
// rObj被1个RContext依赖分别是Switch组件
expect(rObj.usedRContexts.size).toEqual(1);
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,51 +0,0 @@
import Inula, { computed, createRef, reactive, render } from '../../../src/index';
describe('测试 computed', () => {
it('在class组件render中使用computed', () => {
let rObj;
let appInst;
const ref = createRef();
const fn = jest.fn();
class App extends Inula.Component {
constructor(props) {
super(props);
appInst = this;
this.state = {
name: 1,
};
this._rObj = reactive(1);
rObj = this._rObj;
}
render() {
const computedVal = computed(() => {
fn();
return this._rObj.get() + '!!!';
});
return <div ref={ref}>{computedVal}</div>;
}
}
render(<App />, container);
expect(ref.current.innerHTML).toEqual('1!!!'); // computed执行2次
expect(fn).toHaveBeenCalledTimes(1);
rObj.set('2');
expect(ref.current.innerHTML).toEqual('2!!!');
expect(fn).toHaveBeenCalledTimes(2); // computed执行2次
// 触发组件重新渲染
appInst.setState({ name: 2 });
expect(fn).toHaveBeenCalledTimes(3); // 生成新的一个computation再执行了1次computed总共执行3次
rObj.set('3');
expect(ref.current.innerHTML).toEqual('3!!!');
expect(fn).toHaveBeenCalledTimes(5); // 两个computation各执行了一次computed总共执行5次
});
});

View File

@ -1,558 +0,0 @@
import Inula, { computed, createRef, reactive, watchReactive } from '../../../../src/index';
import { template as _$template, insert as _$insert, setAttribute as _$setAttribute } from '../../../../src/no-vnode/dom';
import { createComponent as _$createComponent, render } from '../../../../src/no-vnode/core';
import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../../../../src/no-vnode/event';
import { Show } from '../../../../src/no-vnode/components/Show';
import { For } from '../../../../src/no-vnode/components/For';
describe('测试 no-vnode', () => {
it('简单的使用signal', () => {
/**
* 源码
* const CountingComponent = () => {
* const [count, setCount] = useSignal(0);
*
* return <div id="count">Count value is {count()}.</div>;
* };
*
* render(() => <CountingComponent />, container);
*/
let g_count;
// 编译后:
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`);
const CountingComponent = () => {
const count = reactive(0);
g_count = count;
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling,
_el$3 = _el$4.nextSibling;
_$insert(_el$, count, _el$4);
return _el$;
})();
};
render(() => _$createComponent(CountingComponent, {}), container);
_$delegateEvents(['click']);
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0<!---->.');
g_count.set(c => c + 1);
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
});
it('return数组click事件', () => {
/**
* 源码
* const CountingComponent = () => {
* const [count, setCount] = createSignal(0);
* const add = () => {
* setCount((c) => c + 1);
* }
* return <>
* <div id="count">Count value is {count()}.</div>
* <div><button onClick={add}>add</button></div>
* </>;
* };
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
_tmpl$2 = /*#__PURE__*/ _$template(`<div><button id="btn">add`);
const CountingComponent = () => {
const count = reactive(0);
const add = () => {
count.set(c => c + 1);
};
return [
(() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling,
_el$3 = _el$4.nextSibling;
_$insert(_el$, count, _el$4);
return _el$;
})(),
(() => {
const _el$5 = _tmpl$2(),
_el$6 = _el$5.firstChild;
_el$6.$$click = add;
return _el$5;
})(),
];
};
render(() => _$createComponent(CountingComponent, {}), container);
_$delegateEvents(['click']);
container.querySelector('#btn').click();
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
});
it('return 自定义组件', () => {
/**
* 源码
* const CountValue = (props) => {
* return <div>Count value is {props.count} .</div>;
* }
*
* const CountingComponent = () => {
* const [count, setCount] = createSignal(0);
* const add = () => {
* setCount((c) => c + 1);
* }
*
* return <div>
* <CountValue count={count} />
* <div><button onClick={add}>add</button></div>
* </div>;
* };
*
* render(() => <CountingComponent />, document.getElementById("app"));
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
_tmpl$2 = /*#__PURE__*/ _$template(`<div><div><button id="btn">add`);
const CountValue = props => {
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling,
_el$3 = _el$4.nextSibling;
_$insert(_el$, () => props.count, _el$4);
return _el$;
})();
};
const CountingComponent = () => {
const count = reactive(0);
const add = () => {
count.set(c => c + 1);
};
return (() => {
const _el$5 = _tmpl$2(),
_el$6 = _el$5.firstChild,
_el$7 = _el$6.firstChild;
_$insert(
_el$5,
_$createComponent(CountValue, {
count: count,
}),
_el$6
);
_el$7.$$click = add;
return _el$5;
})();
};
render(() => _$createComponent(CountingComponent, {}), container);
_$delegateEvents(['click']);
container.querySelector('#btn').click();
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
});
it('使用Show组件', () => {
/**
* 源码
* const CountValue = (props) => {
* return <div id="count">Count value is {props.count()}.</div>;
* }
*
* const CountingComponent = () => {
* const [count, setCount] = createSignal(0);
* const add = () => {
* setCount((c) => c + 1);
* }
*
* return <div>
* <Show when={count() > 0} fallback={<CountValue count={999} />}>
* <CountValue count={count} />
* </Show>
* <div><button id="btn" onClick={add}>add</button></div>
* </div>;
* };
*
* render(() => <CountingComponent />, document.getElementById("app"));
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
_tmpl$2 = /*#__PURE__*/ _$template(`<div><div><button id="btn">add`);
const CountValue = props => {
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling,
_el$3 = _el$4.nextSibling;
_$insert(_el$, () => props.count, _el$4);
return _el$;
})();
};
const CountingComponent = () => {
const count = reactive(0);
const add = () => {
count.set(c => c + 1);
};
return (() => {
const _el$5 = _tmpl$2(),
_el$6 = _el$5.firstChild,
_el$7 = _el$6.firstChild;
_$insert(
_el$5,
_$createComponent(Show, {
get if() {
return computed(() => count.get() > 0);
},
get else() {
return _$createComponent(CountValue, {
count: 999,
});
},
get children() {
return _$createComponent(CountValue, {
count: count,
});
},
}),
_el$6
);
_el$7.$$click = add;
return _el$5;
})();
};
render(() => _$createComponent(CountingComponent, {}), container);
_$delegateEvents(['click']);
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 999<!---->.');
container.querySelector('#btn').click();
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
});
it('使用For组件', () => {
/**
* 源码
* const Todo = (props) => {
* return <div>Count value is {props.todo.title}.</div>;
* }
*
* const CountingComponent = () => {
* const [state, setState] = createStore({
* counter: 2,
* todoList: [
* { id: 23, title: 'Birds' },
* { id: 27, title: 'Fish' }
* ]
* });
*
* const add = () => {
* setState('todoList', () => {
* return [
* { id: 23, title: 'Birds' },
* { id: 27, title: 'Fish' },
* { id: 27, title: 'Cat' }
* ];
* });
* }
*
* const push = () => {
* state.todoList.push({
* id: 27,
* title: 'Pig',
* },);
* };
*
* return <div>
* <div id="todos">
* <For each={state.todoList}>
* {todo => <><Todo todo={todo} /><Todo todo={todo} /></>}
* </For>
* </div>
* <div><button id="btn" onClick={add}>add</button></div>
* <div><button id="btn-push" onClick={push}>push</button></div>
* </div>;
* };
*
* render(() => <CountingComponent />, document.getElementById("app"));
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/_$template(`<div>Count value is <!>.`),
_tmpl$2 = /*#__PURE__*/_$template(`<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push`);
const Todo = props => {
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling,
_el$3 = _el$4.nextSibling;
_$insert(_el$, () => props.todo.title, _el$4);
return _el$;
})();
};
const CountingComponent = () => {
const state = reactive({
counter: 2,
todoList: [
{
id: 23,
title: 'Birds',
},
{
id: 27,
title: 'Fish',
},
],
});
const add = () => {
state.todoList.set(() => {
return [
{
id: 23,
title: 'Birds',
},
{
id: 27,
title: 'Fish',
},
{
id: 27,
title: 'Cat',
},
];
});
};
const push = () => {
state.todoList.push({
id: 27,
title: 'Pig',
},);
};
return (() => {
const _el$5 = _tmpl$2(),
_el$6 = _el$5.firstChild,
_el$7 = _el$6.nextSibling,
_el$8 = _el$7.firstChild,
_el$9 = _el$7.nextSibling,
_el$10 = _el$9.firstChild;
_$insert(
_el$6,
_$createComponent(For, {
get each() {
return state.todoList;
},
children: todo => [
_$createComponent(Todo, {
todo: todo,
}),
_$createComponent(Todo, {
todo: todo,
}),
],
})
);
_el$8.$$click = add;
_el$10.$$click = push;
return _el$5;
})();
};
render(() => _$createComponent(CountingComponent, {}), container);
_$delegateEvents(['click']);
expect(container.querySelector('#todos').innerHTML).toEqual(
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div>'
);
container.querySelector('#btn').click();
expect(container.querySelector('#todos').innerHTML).toEqual(
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Cat<!---->.</div>'
);
container.querySelector('#btn-push').click();
expect(container.querySelector('#todos').innerHTML).toEqual(
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Pig<!---->.</div><div>Count value is Pig<!---->.</div>'
);
});
it('使用effect, setAttribute, addEventListener', () => {
/**
* 源码
* const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean',
* 'elegant', 'easy', 'angry', 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important', 'inexpensive',
* 'cheap', 'expensive', 'fancy'];
*
* const random = (max: any) => Math.round(Math.random() * 1000) % max;
*
* let nextId = 1;
*
* function buildData(count: number) {
* let data = new Array(count);
*
* for (let i = 0; i < count; i++) {
* data[i] = {
* id: nextId++,
* label: `${A[random(A.length)]}`,
* }
* }
* return data;
* }
*
* const Row = (props) => {
* const selected = createMemo(() => {
* return props.item.selected ? 'danger' : '';
* });
*
* return (
* <tr class={selected()}>
* <td class="col-md-1">{props.item.label}</td>
* </tr>
* )
* };
*
* const RowList = (props) => {
* return <For each={props.list}>
* {(item) => <Row item={item}/>}
* </For>;
* };
*
* const Button = (props) => (
* <div class="col-sm-6">
* <button type="button" id={props.id} onClick={props.cb}>{props.title}</button>
* </div>
* );
*
* const Main = () => {
* const [state, setState] = createStore({data: [{id: 1, label: '111', selected: false}, {id: 2, label: '222', selected: false}], num: 2});
*
* function run() {
* setState('data', buildData(5));
* }
*
* return (
* <div>
* <div>
* <div>
* <div><h1>Horizon-reactive-novnode</h1></div>
* <div>
* <div>
* <Button id="run" title="Create 1,000 rows" cb={run}/>
* </div>
* </div>
* </div>
* </div>
* <table>
* <tbody id="tbody"><RowList list={state.data}/></tbody>
* </table>
* </div>
* );
* };
*
* render(() => <Main />, document.getElementById("app"));
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/_$template(`<tr><td class="col-md-1">`),
_tmpl$2 = /*#__PURE__*/_$template(`<div class="col-sm-6"><button type="button">`),
_tmpl$3 = /*#__PURE__*/_$template(`<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">`);
const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean', 'elegant', 'easy', 'angry', 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important', 'inexpensive', 'cheap', 'expensive', 'fancy'];
const random = max => Math.round(Math.random() * 1000) % max;
let nextId = 1;
function buildData(count) {
let data = new Array(count);
for (let i = 0; i < count; i++) {
data[i] = {
id: nextId++,
label: `${A[random(A.length)]}`
};
}
return data;
}
const Row = props => {
const selected = computed(() => {
return props.item.selected.get() ? "danger" : "";
});
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild;
_$insert(_el$2, () => props.item.label);
return _el$;
})();
};
const RowList = props => {
return _$createComponent(For, {
get each() {
return props.list;
},
children: item => _$createComponent(Row, {
item: item
})
});
};
const Button = props => (() => {
const _el$3 = _tmpl$2(),
_el$4 = _el$3.firstChild;
_$addEventListener(_el$4, "click", props.cb, true);
_$insert(_el$4, () => props.title);
watchReactive(() => _$setAttribute(_el$4, "id", props.id));
return _el$3;
})();
const Main = () => {
const state = reactive({
list: [{
id: 1,
label: '111'
}, {
id: 2,
label: '222'
}],
num: 2
});
function run() {
state.list.set(buildData(5));
}
return (() => {
const _el$5 = _tmpl$3(),
_el$6 = _el$5.firstChild,
_el$7 = _el$6.firstChild,
_el$8 = _el$7.firstChild,
_el$9 = _el$8.nextSibling,
_el$10 = _el$9.firstChild,
_el$11 = _el$6.nextSibling,
_el$12 = _el$11.firstChild;
_$insert(_el$10, _$createComponent(Button, {
id: "run",
title: "Create 1,000 rows",
cb: run
}));
_$insert(_el$12, _$createComponent(RowList, {
get list() {
return state.list;
}
}));
return _el$5;
})();
};
render(() => _$createComponent(Main, {}), container);
_$delegateEvents(["click"]);
expect(container.querySelector('#tbody').innerHTML).toEqual(
'<tr><td class="col-md-1">111</td></tr><tr><td class="col-md-1">222</td></tr>'
);
container.querySelector('#run').click();
expect(container.querySelector('#tbody').children.length).toEqual(5);
});
});

View File

@ -1,377 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, {
createRef,
For,
reactive,
render,
useCompute,
useReactive,
computed,
} from '../../../src/index';
describe('computed 基本使用', () => {
it('computed 返回的是一个响应式对象,用到的响应式对象是原始类型', () => {
const rObj = reactive('123');
const comp = computed(() => {
return rObj.get() + '!!!';
});
expect(comp.get()).toEqual('123!!!');
rObj.set('456');
expect(comp.get()).toEqual('456!!!');
});
it('computed 返回的是一个响应式对象,用到两个响应式对象', () => {
const rObj1 = reactive({ name: 'xiaoming' });
const rObj2 = reactive({ age: 18 });
const comp = computed(() => {
return rObj1.name.get() + ' is ' + rObj2.age.get();
});
expect(comp.get()).toEqual('xiaoming is 18');
rObj1.name.set('xiaowang');
rObj2.set(prev => ({ age: prev.age + 2 }));
expect(comp.get()).toEqual('xiaowang is 20');
});
it('computed 返回的是一个复杂响应式对象', () => {
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
const comp = computed(() => {
return { newArray: rObj.array.get().filter(x => x > 4) };
});
expect(comp.get()).toEqual({ newArray: [5, 6] });
expect(comp.newArray.get()).toEqual([5, 6]);
rObj.array.push(...[100]);
expect(comp.get()).toEqual({ newArray: [5, 6, 100] });
});
it('computed 返回的是一个响应式对象,用到的响应式对象是对象类型', () => {
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
const comp = computed(() => {
return rObj.array.get().filter(x => x > 4);
});
expect(comp.get()).toEqual([5, 6]);
rObj.array.set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
expect(comp.get()).toEqual([5, 6, 7, 8, 9]);
rObj.array.push(...[10, 11]);
expect(comp.get()).toEqual([5, 6, 7, 8, 9, 10, 11]);
rObj.set({ array: [100, 101, 102] });
expect(comp.get()).toEqual([100, 101, 102]);
});
it('computed 返回的是一个复杂响应式对象2', () => {
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
const comp = computed(() => {
return { newArray: rObj.array.get().filter(x => x > 4) };
});
expect(comp.newArray.get()).toEqual([5, 6]);
rObj.array.push(...[7, 8]);
expect(comp.newArray.get()).toEqual([5, 6, 7, 8]);
rObj.array.set([1, 100, 101, 102]);
expect(comp.newArray.get()).toEqual([100, 101, 102]);
expect(comp.get()).toEqual({ newArray: [100, 101, 102] });
});
});
describe('测试 useCompute', () => {
it('useComputed基本使用 使用get方法(组件式更新)', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('123');
rObj = _rObj;
const _cObj = useCompute(() => {
return _rObj.get() + '!!!';
});
fn();
return <div ref={ref}>{_cObj.get()}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('123!!!');
expect(fn).toHaveBeenCalledTimes(1);
rObj.set('456');
expect(ref.current.innerHTML).toEqual('456!!!');
expect(fn).toHaveBeenCalledTimes(2);
rObj.set('789');
expect(ref.current.innerHTML).toEqual('789!!!');
expect(fn).toHaveBeenCalledTimes(3);
});
it('useComputed基本使用 直接使用对象(Dom级更新)', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('123');
rObj = _rObj;
const _cObj = useCompute(() => {
return _rObj.get() + '!!!';
});
fn();
return <div ref={ref}>{_cObj}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('123!!!');
expect(fn).toHaveBeenCalledTimes(1);
rObj.set('456');
expect(ref.current.innerHTML).toEqual('456!!!');
rObj.set('789');
expect(ref.current.innerHTML).toEqual('789!!!');
expect(fn).toHaveBeenCalledTimes(1);
});
it('useComputed 基本使用2', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const compFn = jest.fn();
const App = () => {
const _rObj = useReactive({ array: [1, 2, 3, 4, 5, 6] });
rObj = _rObj;
const cObj = useCompute(() => {
compFn();
return { len: _rObj.array.get().filter(x => x >= 4).length };
});
fn();
return <div ref={ref}>{cObj.len}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('3');
rObj.array.push(...[7, 8]);
expect(ref.current.innerHTML).toEqual('5');
expect(fn).toHaveBeenCalledTimes(1);
rObj.array.unshift(...[0, 100]);
expect(ref.current.innerHTML).toEqual('6');
rObj.set({ array: [1, 100, 101, 102, 103] });
expect(ref.current.innerHTML).toEqual('4');
expect(compFn).toHaveBeenCalledTimes(4);
expect(fn).toHaveBeenCalledTimes(1);
});
it('连锁useComputed使用', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const compFn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
rObj = _rObj;
const double = useCompute(() => _rObj.get() * 2);
const dd = useCompute(() => {
compFn();
return double.get() * 2;
});
fn();
return <div ref={ref}>{dd}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('4');
expect(compFn).toHaveBeenCalledTimes(1);
rObj.set('2');
expect(ref.current.innerHTML).toEqual('8');
expect(compFn).toHaveBeenCalledTimes(2);
rObj.set('4');
expect(ref.current.innerHTML).toEqual('16');
expect(compFn).toHaveBeenCalledTimes(3);
expect(fn).toHaveBeenCalledTimes(1);
});
it('useComputed中使用到了两个响应式对象', () => {
let _rObj1;
let _rObj2;
const ref = createRef();
const fn = jest.fn();
const compFn = jest.fn();
const App = () => {
const rObj1 = useReactive({ name: 'xiaoming' });
const rObj2 = useReactive({ age: 18 });
_rObj1 = rObj1;
_rObj2 = rObj2;
const words = useCompute(() => {
compFn();
return `${rObj1.name.get()} is ${rObj2.age.get()}`;
});
fn();
return <div ref={ref}>{words}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('xiaoming is 18');
expect(compFn).toHaveBeenCalledTimes(1);
_rObj1.name.set('xiaowang');
expect(ref.current.innerHTML).toEqual('xiaowang is 18');
expect(compFn).toHaveBeenCalledTimes(2);
_rObj2.set({ age: 20 });
expect(ref.current.innerHTML).toEqual('xiaowang is 20');
expect(compFn).toHaveBeenCalledTimes(3);
_rObj1.name.set('laowang');
_rObj2.set({ age: 30 });
expect(ref.current.innerHTML).toEqual('laowang is 30');
expect(compFn).toHaveBeenCalledTimes(5);
expect(fn).toHaveBeenCalledTimes(1);
});
it('多个reactive的compute', () => {
let a;
const ref = createRef();
const compFn = jest.fn();
const computeFn = jest.fn();
const App = () => {
const _a = useReactive('a');
const b = useReactive('b');
const cond = useReactive(true);
a = _a;
const compute = useCompute(() => {
computeFn();
return cond.get() ? _a.get() : b.get();
});
compFn();
return (
<button
ref={ref}
className={compute}
onClick={() => {
cond.set(false);
}}
>
{compute}
</button>
);
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('a');
ref.current.click();
expect(ref.current.innerHTML).toEqual('b');
a.set('aa');
expect(computeFn).toHaveBeenCalledTimes(3);
expect(ref.current.innerHTML).toEqual('b');
});
it('useCompute返回一个数组对象', () => {
let rObj;
let cObj;
let ref = createRef();
let appFn = jest.fn();
let itemFn = jest.fn();
const App = () => {
const _rObj = useReactive([
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
{ id: 'id-3', name: 'p3' },
]);
rObj = _rObj;
const _cObj = useCompute(() => {
return _rObj.get().slice();
});
cObj = _cObj;
appFn();
return (
<div ref={ref}>
<For each={_cObj}>
{item => {
itemFn();
return (
<li id={item.id} key={item.id}>
{item.name}
</li>
);
}}
</For>
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
rObj.push({ id: 'id-4', name: 'p4' });
items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
// rObj[1].name.get();
rObj[1].set({ id: 'id-2', name: 'p222' });
let li = container.querySelector('#id-2');
expect(li.innerHTML).toEqual('p222');
// // 更新
// cObj.set([true]);
//
// items = container.querySelectorAll('li');
// expect(items.length).toEqual(1);
// expect(appFn).toHaveBeenCalledTimes(1);
//
// // 第一次渲染执行3次更新也触发了1次
// expect(itemFn).toHaveBeenCalledTimes(4);
});
xit('测试compute在checkbox中的使用', () => {
let a;
const ref = createRef();
const compFn = jest.fn();
const computeFn = jest.fn();
const App = () => {
const rObj = useReactive({ checked: true });
const checked = useCompute(() => {
return rObj.checked.get();
});
compFn();
return <Checkbox checked={checked} />;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('a');
ref.current.click();
expect(ref.current.innerHTML).toEqual('b');
a.set('aa');
expect(computeFn).toHaveBeenCalledTimes(3);
expect(ref.current.innerHTML).toEqual('b');
});
});

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive } from '../../../src/index';
import { Show } from '../../../src/reactive/components/Show';
import { Switch } from '../../../src/reactive/components/Switch';
describe('响应式数据usedRContexts', () => {
it('测试响应式数据的usedRContexts会随着VNode的删除而清除', () => {
let rObj;
const refBlue = createRef();
const refRed = createRef();
const refYellow = createRef();
const refNothing = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return (
<Switch default={<div ref={refNothing}>nothing</div>}>
{/*if不能写成 _rObj === 'red' 或者 _rObj.get() === 'red' */}
<Show if={() => _rObj.get() === 'blue'}>
<div id="1" ref={refBlue}>
{_rObj}
</div>
</Show>
<Show if={() => _rObj.get() === 'red'}>
<div id="2" ref={refRed}>
{_rObj}
</div>
</Show>
<Show if={() => _rObj.get() === 'yellow'}>
<div id="3" ref={refYellow}>
{_rObj}
</div>
</Show>
</Switch>
);
};
render(<App />, container);
expect(refBlue.current.innerHTML).toEqual('blue');
// rObj被3个RContext依赖分别是Switch组件、Show组件、div[id=1]的Children
expect(rObj.usedRContexts.size).toEqual(3);
act(() => {
rObj.set('red');
});
expect(refRed.current.innerHTML).toEqual('red');
// rObj被3个Effect依赖分别是Switch组件、Show组件、div[id=2]的Children
expect(rObj.usedRContexts.size).toEqual(3);
act(() => {
rObj.set('black');
});
expect(refNothing.current.innerHTML).toEqual('nothing');
// rObj被1个RContext依赖分别是Switch组件
expect(rObj.usedRContexts.size).toEqual(1);
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,189 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, {createRef, render, useReactive, useState, useEffect, useRef, Show} from '../../../src/index';
describe('传统API和响应式API混合使用', () => {
it('混合使用1', () => {
let rObj, isShow, update;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _isShow = useReactive(true);
isShow = _isShow;
const [_, setState] = useState({});
update = () => setState({});
return (
<Show if={isShow}>
<Child />
</Show>
);
};
const Child = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <div ref={ref} className={_rObj}></div>;
};
render(<App />, container);
expect(ref.current.className).toEqual('blue');
// 改变了DOM结构
isShow.set(false);
expect(ref.current).toEqual(null);
update();
expect(ref.current).toEqual(null);
});
it('混合使用2', () => {
// 全局变量来监听更新次数
let guid = 0;
function ReactiveComponent() {
const data = useReactive({ count: 0 });
const [count, setCount] = useState(0);
const [dir, setDir] = useState("asc"); // 用于排序方向
function add() {
data.count.set((c) => c + 1);
}
function addCount() {
setCount((c) => c + 1);
}
function toggleDir() {
setDir((d) => (d === "asc" ? "desc" : "asc"));
}
const vnode1 = <div id={"count"}>我是静态数据{count}</div>;
const vnode2 = <div id={"rCount"}>我是响应数据{data.count}</div>;
return (
<div>
<button id={"reorder"} onClick={toggleDir}>点击切换顺序</button>
<button id={"addRCount"} onClick={add}>点击++</button>
<button id={"addCount"} onClick={addCount}>点击 count++</button>
<div id={"content"}>
{dir === "asc" ? [vnode1, vnode2] : [vnode2, vnode1]}
</div>
</div>
);
}
render(<ReactiveComponent />, container);
// 点击按钮触发reactive count加1
container.querySelector('#addRCount').click();
expect(container.querySelector('#rCount').innerHTML).toEqual('我是响应数据1');
// // 点击按钮触发count加1
container.querySelector('#addCount').click();
container.querySelector('#addCount').click();
container.querySelector('#addCount').click();
expect(container.querySelector('#count').innerHTML).toEqual('我是静态数据3');
expect(container.querySelector('#rCount').innerHTML).toEqual('我是响应数据1');
expect(container.querySelector('#content').innerHTML).toEqual('<div id="count">我是静态数据3</div><div id="rCount">我是响应数据1</div>');
container.querySelector('#reorder').click();
expect(container.querySelector('#content').innerHTML).toEqual('<div id="rCount">我是响应数据1</div><div id="count">我是静态数据3</div>');
});
it('混合使用3', () => {
// 全局变量来监听更新次数
let guid = 0;
function ReactiveComponent() {
const renderCount = ++useRef(0).current;
const data = useReactive({ count: 0 });
const [state, setState] = useState(0);
const showData = useReactive({show:true});
// 切换显示
function handleClick() {
showData.show.set(s=> !s)
}
function add() {
data.count.set((c) => c + 1);
}
function addState() {
setState((c) => c + 1);
}
// 监听生命周期
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
console.log("组件Mounted", guid++);
mounted.current = true;
} else {
console.log("组件Updated", guid++);
}
});
return (
<div>
<div>响应式数据{data.count} </div>
<div>传统state: {state}</div>
<div>
组件渲染次数renderCount:{renderCount} 或guid={guid}
</div>
<hr />
<button onClick={handleClick}>点击切换显示</button>
<button onClick={add}>点击++</button>
<button onClick={addState}>点击state++</button>
<hr />
<div className="border">
<Show if={showData.show}>
<div>
<h1>我会被隐藏</h1>
<div> 我是响应式数据:{data.count}</div>
</div>
</Show>
{showData.show.get() && (
<div>
<h1>我会被隐藏</h1>
<div> 我是响应式数据:{data.count}</div>
</div>
)}
</div>
</div>
);
}
render(<ReactiveComponent />, container);
});
});

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, act, useReactive } from '../../../src/index';
describe('测试混合型的 children', () => {
it('children是 字符串+Atom 场景', () => {
let rObj;
const ref1 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(0);
rObj = _rObj;
fn();
return (
// div下面有多个元素
<div ref={ref1}>Count: {_rObj}</div>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('Count: 0');
rObj.set(1);
expect(fn).toHaveBeenCalledTimes(1);
expect(ref1.current.innerHTML).toEqual('Count: 1');
});
it('children是 字符串+Atom 场景2', () => {
let rObj;
const ref1 = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({ count: 0 });
rObj = _rObj;
fn();
return (
// div下面有多个元素
<div ref={ref1}>Count: {_rObj.count}</div>
);
};
render(<App />, container);
expect(ref1.current.innerHTML).toEqual('Count: 0');
rObj.count.set(1);
expect(fn).toHaveBeenCalledTimes(1);
expect(ref1.current.innerHTML).toEqual('Count: 1');
});
});

View File

@ -1,973 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, {
render,
createRef,
useReactive,
useCompute,
reactive,
computed,
watchReactive,
} from '../../../src/index';
import { GET_R_NODE } from '../../../src/reactive/proxy/RProxyHandler';
import { isAtom, isReactiveProxy, isRNode } from '../../../src/reactive/Utils';
describe('测试 useReactive(对象)', () => {
it('reactive基本使用', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
color: 'blue',
});
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.color}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('blue');
rObj.color.set('red');
expect(rObj.color.get()).toEqual('red');
expect(ref.current.innerHTML).toEqual('red');
rObj.color.set(prev => prev + '!!');
expect(rObj.color.get()).toEqual('red!!');
expect(ref.current.innerHTML).toEqual('red!!');
expect(fn).toHaveBeenCalledTimes(1);
});
it('响应式对象赋值修改为一个对象', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: { framework: 'Vue' },
});
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.data.framework}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('Vue');
rObj.data.set({ framework: 'React' });
expect(rObj.data.framework.get()).toEqual('React');
expect(ref.current.innerHTML).toEqual('React');
expect(fn).toHaveBeenCalledTimes(1);
});
it('赋值修改复杂响应式对象', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: { framework: { js: 'Vue' } },
});
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.data.framework.js}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('Vue');
rObj.data.set({ framework: { js: 'React' } });
expect(rObj.data.framework.get()).toEqual({ js: 'React' });
expect(ref.current.innerHTML).toEqual('React');
expect(fn).toHaveBeenCalledTimes(1);
});
it('赋值修改响应式对象中Atom的值', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
rdata: { framework: 'Vue' },
});
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.rdata.framework}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('Vue');
rObj.rdata.framework.set('React');
expect(rObj.rdata.get()).toEqual({ framework: 'React' });
expect(ref.current.innerHTML).toEqual('React');
expect(fn).toHaveBeenCalledTimes(1);
});
it('把响应式属性传递到子组件', () => {
let rObj;
const ref = createRef();
const fn1 = jest.fn();
const fn2 = jest.fn();
const App = () => {
const _rObj = useReactive({
data: {
color: 'blue',
},
});
rObj = _rObj;
fn1();
return <Child color={_rObj.data.color} />;
};
const Child = ({ color }) => {
fn2();
const cl = useCompute(() => {
return 'cl-' + color.get();
});
return <div ref={ref} className={cl}></div>;
};
render(<App />, container);
expect(ref.current.className).toEqual('cl-blue');
expect(fn1).toHaveBeenCalledTimes(1);
expect(fn2).toHaveBeenCalledTimes(1);
rObj.data.color.set('red');
expect(fn1).toHaveBeenCalledTimes(1);
expect(fn2).toHaveBeenCalledTimes(1);
expect(ref.current.className).toEqual('cl-red');
});
it('reactive对象中“原始数据”被赋值为“对象”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: 'blue',
});
rObj = _rObj;
_rObj.data.set({ color: 'red' });
fn();
return <div ref={ref}>{_rObj.data.color}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('red');
rObj.data.color.set('blue');
expect(rObj.data.color.get()).toEqual('blue');
expect(ref.current.innerHTML).toEqual('blue');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive对象中“对象”被赋值为“新对象”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: {
cl: 'blue',
},
});
rObj = _rObj;
_rObj.data.set({ color: 'red' });
fn();
return <div ref={ref}>{_rObj.data.color}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('red');
rObj.data.color.set('blue');
expect(rObj.data.color.get()).toEqual('blue');
expect(ref.current.innerHTML).toEqual('blue');
expect(fn).toHaveBeenCalledTimes(1);
});
});
describe('测试reactive数组', () => {
it('reactive“数组”length的使用', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: [
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
],
});
rObj = _rObj;
fn();
// 在DOM中使用length无法精细响应式
return <div ref={ref}>{_rObj.data.length}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('2');
rObj.data.set([{ name: 'p1', age: 1 }]);
expect(ref.current.innerHTML).toEqual('1');
expect(fn).toHaveBeenCalledTimes(2);
});
it('reactive“数组”的使用', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: [
{ name: 'p1', age: 1 },
{ name: 'p2', age: 2 },
],
});
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.data[0].name}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('p1');
// 这种修改无法响应!
// rObj.data.set([
// { name: 'p11', age: 1 },
// ]);
// 直接修改数组中被使用属性
rObj.data[0].name.set('p11');
expect(ref.current.innerHTML).toEqual('p11');
// 在DOM中使用length无法精细响应式
expect(fn).toHaveBeenCalledTimes(1);
});
it('jsx中通过items.get().map遍历reactive“数组”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
{_rObj.items.get().map(item => {
return <li key={item.id}>{item.name}</li>;
})}
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
// 每次修改items都会触发整个组件刷新
rObj.items.set([{ name: 'p11', age: 1 }]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(1);
expect(fn).toHaveBeenCalledTimes(2);
// 每次修改items都会触发整个组件刷新
rObj.items.push({ name: 'p22', id: 2 });
items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(3);
});
it('jsx中通过items.get().map遍历reactive“数组”孩子是Item', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
const App = () => {
const _rObj = useReactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
{/*items必须要调用get()才能map*/}
{_rObj.items.get().map(item => {
return <Item item={item} />;
})}
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(1);
// 每次修改items都会触发整个组件刷新
rObj.items.set([{ name: 'p11', age: 1 }]);
items = container.querySelectorAll('li');
expect(items.length).toEqual(1);
expect(fn).toHaveBeenCalledTimes(2);
// 每次修改items都会触发整个组件刷新
rObj.items.push({ name: 'p22', id: 2 });
items = container.querySelectorAll('li');
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(3);
});
it('jsx中通过items.map遍历reactive“数组”具有响应式', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const Item = ({ item }) => {
return <li key={item.id}>{item.name}</li>;
};
const App = () => {
const _rObj = useReactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
{ name: 'p3', id: 3 },
],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
{_rObj.items.map(item => {
return <Item item={item} />;
})}
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(fn).toHaveBeenCalledTimes(1);
rObj.items.set([
{ name: 'p11', age: 1 },
{ name: 'p22', age: 2 },
]);
items = container.querySelectorAll('li');
// 子元素不会响应式变化
expect(items.length).toEqual(2);
expect(fn).toHaveBeenCalledTimes(2);
});
it('jsx中通过items.map遍历reactive“数组”孩子是ItemItem对象具有响应式', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const Item = ({ item }) => {
const id = useCompute(() => {
return `id-${item.id.get()}`;
});
return (
<li key={item.id} id={id}>
{item.name}
</li>
);
};
const App = () => {
const _rObj = useReactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
],
});
rObj = _rObj;
fn();
return (
<div ref={ref}>
{_rObj.items.map(item => {
return <Item item={item} />;
})}
</div>
);
};
render(<App />, container);
let item = container.querySelector('#id-1');
expect(item.innerHTML).toEqual('p1');
expect(fn).toHaveBeenCalledTimes(1);
rObj.items[0].name.set('p111');
item = container.querySelector('#id-1');
// 子元素会响应式变化
expect(item.innerHTML).toEqual('p111');
expect(fn).toHaveBeenCalledTimes(1);
});
it('测试响应式数据', () => {
const obj = reactive({
data: [
{
id: '1',
value: 'val-1',
},
{
id: '2',
value: 'val-2',
},
],
});
// 使用让创建children
obj.data[1].value.read();
obj.set({
data: [
{
id: '11',
value: 'val-11',
},
],
});
obj.set({
data: [
{
id: '111',
value: 'val-111',
},
{
id: '222',
value: 'val-222',
},
],
});
expect(obj.data[1].value.get()).toEqual('val-222');
});
it('响应式对象为复杂对象时使用set重新设置', () => {
const rObj = reactive({ data: [1, 2, 3, 4, 5, 6] });
rObj.data.push(...[7, 8]);
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
rObj.data.set([100, 101]);
expect(rObj.get()).toEqual({ data: [100, 101] });
});
it('使用set直接修改响应式对象数组中某个元素的值', () => {
const rObj = reactive({ data: [1, 2, 3] });
rObj.data.push(...[4, 5, 6]);
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6]);
// 修改数组第4个元素
rObj.data[1].set({ val: 2 });
expect(rObj.get()).toEqual({ data: [1, { val: 2 }, 3, 4, 5, 6] });
});
it('使用set直接修改响应式对象数组中某个元素的值2', () => {
const rObj = reactive({ data: [1, 2, 3] });
rObj.data.push(...[4, 5, 6]);
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6]);
// 修改数组第4个元素
rObj.data[4].set({ val: 2 });
expect(rObj.get()).toEqual({ data: [1, 2, 3, 4, { val: 2 }, 6] });
});
it('在删除数组中一个数字再加一个对象类型是RNode', () => {
const rObj = reactive({ data: [1, 2, 3, 4] });
// 使用最后一个数据在children中创建出child
rObj.data[3].get();
// 删除最后一个数据
rObj.data.set([1, 2, 3]);
// 重新增加一个obj类型的数据
rObj.data.set([1, 2, 3, { val: 4 }]);
// rObj.data[3]是RNode
expect(isRNode(rObj.data[3][GET_R_NODE])).toBeTruthy();
expect(rObj.data[3].val.get()).toEqual(4);
});
xit('钻石问题', () => {
const fn = jest.fn();
const rObj = reactive(0);
const evenOrOdd = computed(() => (rObj.get() % 2 === 0 ? 'even' : 'odd'));
watchReactive(() => {
fn();
rObj.get();
evenOrOdd.get();
});
rObj.set(1);
// TODO
expect(fn).toHaveBeenCalledTimes(3);
});
it('数组中的数据由“对象”变成“字符串”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [
{ name: 'p1', id: 1 },
{ name: { n: 'p22' }, id: 2 },
],
});
watchReactive(rObj.items[1].name, () => {
fn();
});
watchReactive(rObj.items[1].name.n, () => {
fn1();
});
rObj.items.set([
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 }, // name 改为 基本数据类型
]);
expect(fn).toHaveBeenCalledTimes(1);
// 无法触发fn1
expect(fn1).toHaveBeenCalledTimes(0);
});
it('数组中的数据由“字符串”变成“对象”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [
{ name: 'p1', id: 1 },
{ name: 'p2', id: 2 },
],
});
watchReactive(rObj.items[1].name, () => {
fn();
});
// 允许使用或监听没有定义的属性
watchReactive(rObj.items[1].name.n, () => {
fn1();
});
rObj.items.set([
{ name: 'p1', id: 1 },
{ name: { n: 'p22' }, id: 2 },
]);
expect(fn).toHaveBeenCalledTimes(1);
// 可以触发fn1
expect(fn1).toHaveBeenCalledTimes(1);
});
it('访问一个不存在的属性,会抛出异常', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [{ name: 'p1' }, { name: 'p2' }],
});
watchReactive(() => {
rObj.items[1].get();
fn();
});
watchReactive(() => {
// 会抛异常
rObj.items[1].name.n.get();
fn1();
});
rObj.items.set([{ name: 'p1' }, { name: { n: 'p22' } }]);
expect(fn).toHaveBeenCalledTimes(2);
// 无法触发fn1
expect(fn1).toHaveBeenCalledTimes(2);
});
it('数组中的数据由“字符串”变成“对象”3', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [{ a: 1 }, 2, 3],
});
watchReactive(() => {
rObj.items[1].get();
fn();
});
rObj.items.set([2, 3, 4]);
expect(fn).toHaveBeenCalledTimes(2);
});
it('数组中的数据由“数字”变成“对象”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [1, 2, 3],
});
watchReactive(() => {
rObj.items[0].get();
fn();
});
watchReactive(() => {
rObj.get();
fn1();
});
rObj.items.set([{ a: 1 }, 3, 4]);
expect(fn).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn1).toHaveBeenCalledTimes(2);
});
it('数组中的数据由“对象”变成“数组”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [{ a: 1 }, 2, 3],
});
watchReactive(() => {
rObj.items[0].get();
fn();
});
watchReactive(() => {
rObj.get();
fn1();
});
rObj.items.set([[1], 3, 4]);
expect(fn).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn1).toHaveBeenCalledTimes(2);
});
it('数组中的数据由“空数组”变成“空数组”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [[], 2, 3],
});
watchReactive(() => {
rObj.items[0].get();
fn();
});
watchReactive(() => {
rObj.get();
fn1();
});
rObj.items.set([[], 3, 4]);
expect(fn).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn1).toHaveBeenCalledTimes(2);
});
it('数组中的2个数据由“对象”变成“对象”', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
});
watchReactive(() => {
rObj.items[0].a.get();
rObj.items[0].b.get();
fn();
});
watchReactive(() => {
rObj.get();
fn1();
});
// 第一个a 由{b: 1} -> {b: 2}能够精准更新
rObj.items.set([{ a: { b: 2 }, b: { c: 3 } }, { a: 3 }, 4]);
expect(fn).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn1).toHaveBeenCalledTimes(2);
});
it('数组中的2个数据由“对象”变成“对象”前一个属性能精准更新会触发后面那个', () => {
let fn = jest.fn();
let fn1 = jest.fn();
let fn2 = jest.fn();
let fn3 = jest.fn();
let fn4 = jest.fn();
const rObj = reactive({
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
});
watchReactive(() => {
rObj.items[0].a.get();
fn();
});
watchReactive(() => {
// b由 { c: 2 } -> { c: 3 } 可以触发
rObj.items[0].b.get();
fn1();
});
watchReactive(() => {
// b由 1 -> undefined 可以触发
rObj.items[0].a.b.get();
fn2();
});
watchReactive(() => {
// c由 2 -> 3 可以触发
rObj.items[0].b.c.get();
fn3();
});
watchReactive(() => {
rObj.get();
fn4();
});
// 第一个a 由{b: 1} -> {d: 2}能够精准更新
rObj.items.set([{ a: { d: 2 }, b: { c: 3 } }, { a: 3 }, 4]);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn1).toHaveBeenCalledTimes(2);
expect(fn2).toHaveBeenCalledTimes(2);
expect(fn3).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn4).toHaveBeenCalledTimes(2);
});
it('数组中的2个数据由“对象”变成“对象”前一个属性不能精准更新也不再触发后面那个', () => {
let fn = jest.fn();
let fn1 = jest.fn();
let fn2 = jest.fn();
let fn3 = jest.fn();
let fn4 = jest.fn();
const rObj = reactive({
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
});
watchReactive(() => {
rObj.items[0].a.get();
fn();
});
watchReactive(() => {
// b由 { c: 2 } -> { c: 3 } 可以触发
rObj.items[0].b.get();
fn1();
});
watchReactive(() => {
rObj.items[0].a.b.get();
fn2();
});
watchReactive(() => {
rObj.items[0].b.c.get();
fn3();
});
watchReactive(() => {
rObj.get();
fn4();
});
// 第一个 a 由{b: 1} -> 1 不能够精准更新
rObj.items.set([{ a: 1, b: { c: 3 } }, { a: 3 }, 4]);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn1).toHaveBeenCalledTimes(2);
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
expect(fn2).toHaveBeenCalledTimes(1);
// 前一个属性不能精准更新,也不触发后面那个的精准更新
expect(fn3).toHaveBeenCalledTimes(1);
// 父数据也会触发
expect(fn4).toHaveBeenCalledTimes(2);
});
it('数组中的2个数据由“对象”变成“[]”,前一个属性不能精准更新,也不再触发后面那个', () => {
let fn = jest.fn();
let fn1 = jest.fn();
let fn2 = jest.fn();
let fn3 = jest.fn();
let fn4 = jest.fn();
const rObj = reactive({
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
});
watchReactive(() => {
rObj.items[0].a.get();
fn();
});
watchReactive(() => {
// b由 { c: 2 } -> { c: 3 } 可以触发
rObj.items[0].b.get();
fn1();
});
watchReactive(() => {
rObj.items[0].a.b.get();
fn2();
});
watchReactive(() => {
rObj.items[0].b.c.get();
fn3();
});
watchReactive(() => {
rObj.get();
fn4();
});
// 第一个 a 由{b: 1} -> [] 不能够精准更新
rObj.items.set([{ a: [], b: { c: 3 } }, { a: 3 }, 4]);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn1).toHaveBeenCalledTimes(2);
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
expect(fn2).toHaveBeenCalledTimes(1);
// 前一个属性不能精准更新,也不触发后面那个的精准更新
expect(fn3).toHaveBeenCalledTimes(1);
// 父数据也会触发
expect(fn4).toHaveBeenCalledTimes(2);
});
it('数组中的2个数据由“对象”变成“null”前一个属性不能精准更新也不再触发后面那个', () => {
let fn = jest.fn();
let fn1 = jest.fn();
let fn2 = jest.fn();
let fn3 = jest.fn();
let fn4 = jest.fn();
const rObj = reactive({
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
});
watchReactive(() => {
rObj.items[0].a.get();
fn();
});
watchReactive(() => {
// b由 { c: 2 } -> { c: 3 } 可以触发
rObj.items[0].b.get();
fn1();
});
watchReactive(() => {
rObj.items[0].a.b.get();
fn2();
});
watchReactive(() => {
rObj.items[0].b.c.get();
fn3();
});
watchReactive(() => {
rObj.get();
fn4();
});
// 第一个 a 由{b: 1} -> [] 不能够精准更新
rObj.items.set([{ a: null, b: { c: 3 } }, { a: 3 }, 4]);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn1).toHaveBeenCalledTimes(2);
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
expect(fn2).toHaveBeenCalledTimes(1);
// 前一个属性不能精准更新,也不触发后面那个的精准更新
expect(fn3).toHaveBeenCalledTimes(1);
// 父数据也会触发
expect(fn4).toHaveBeenCalledTimes(2);
});
it('执行基本数据数组的loop方法', () => {
let fn = jest.fn();
let fn1 = jest.fn();
const rObj = reactive({
items: [1, 2, 3, 4],
});
rObj.items.forEach(rItem => {
expect(isReactiveProxy(rItem)).toBeTruthy();
});
watchReactive(() => {
rObj.items.get();
fn();
});
watchReactive(() => {
rObj.get();
fn1();
});
rObj.items.set([1, 2, 3]);
expect(fn).toHaveBeenCalledTimes(2);
// 父数据也会触发
expect(fn1).toHaveBeenCalledTimes(2);
});
});

View File

@ -1,266 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { render, createRef, useState, useReactive, useCompute } from '../../../src/index';
describe('测试 useReactive(原生数据)', () => {
it('reactive.get()作为children', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('1');
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj.get()}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('1');
rObj.set('2');
expect(ref.current.innerHTML).toEqual('2');
expect(fn).toHaveBeenCalledTimes(2);
});
it('reactive作为children', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('1');
rObj = _rObj;
fn();
return <div ref={ref}>{_rObj}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('1');
rObj.set('2');
expect(ref.current.innerHTML).toEqual('2');
rObj.set(prev => prev + '??');
expect(ref.current.innerHTML).toEqual('2??');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive.get()作为prop', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
rObj = _rObj;
fn();
return <div ref={ref} className={_rObj.get()}></div>;
};
render(<App />, container);
expect(ref.current.className).toEqual('1');
rObj.set(2);
expect(ref.current.className).toEqual('2');
expect(fn).toHaveBeenCalledTimes(2);
});
it('reactive作为prop', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
rObj = _rObj;
fn();
return <div ref={ref} className={_rObj}></div>;
};
render(<App />, container);
expect(ref.current.className).toEqual('1');
rObj.set(2);
expect(ref.current.className).toEqual('2');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive.get()传入style', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <div ref={ref} style={{ color: _rObj.get() }}></div>;
};
render(<App />, container);
const style = window.getComputedStyle(ref.current);
expect(style.color).toEqual('blue');
rObj.set('red');
expect(ref.current.style.color).toEqual('red');
expect(fn).toHaveBeenCalledTimes(2);
});
it('reactive传入style', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <div ref={ref} style={{ color: _rObj }}></div>;
};
render(<App />, container);
const style = window.getComputedStyle(ref.current);
expect(style.color).toEqual('blue');
rObj.set('red');
expect(ref.current.style.color).toEqual('red');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive传入Input value', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <input ref={ref} value={_rObj}></input>;
};
render(<App />, container);
expect(ref.current.value).toEqual('blue');
rObj.set('red');
expect(ref.current.value).toEqual('red');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive传入Textarea value', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <textarea ref={ref} value={_rObj}></textarea>;
};
render(<App />, container);
expect(ref.current.value).toEqual('blue');
rObj.set('red');
expect(ref.current.value).toEqual('red');
expect(fn).toHaveBeenCalledTimes(1);
});
it('reactive父组件刷新 effect不应该重新监听', () => {
let rObj, update;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const [_, setState] = useState({});
update = () => setState({});
return <Child />;
};
const Child = () => {
const _rObj = useReactive('blue');
rObj = _rObj;
fn();
return <div ref={ref} className={_rObj}></div>;
};
render(<App />, container);
expect(ref.current.className).toEqual('blue');
expect(fn).toHaveBeenCalledTimes(1);
update();
expect(fn).toHaveBeenCalledTimes(2);
rObj.set('red');
expect(fn).toHaveBeenCalledTimes(2);
expect(ref.current.className).toEqual('red');
});
it('不允许:从“原生数据”变成“对象”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive('1');
rObj = _rObj;
fn();
const cp = useCompute(() => {
return _rObj.get() === '1' ? '1' : _rObj.data.get();
});
return <div ref={ref}>{cp}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('1');
// 不允许:从“原生数据”变成“对象”
expect(() => rObj.set({ data: '2' })).toThrow(Error('Not allowed Change Primitive to Object'));
});
it('允许一个reactive属性从“原生数据”变成“对象”', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({
data: '1',
});
rObj = _rObj;
fn();
const cp = useCompute(() => {
return _rObj.data.get() === '1' ? '1' : _rObj.data.num.get();
});
return <div ref={ref}>{cp}</div>;
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('1');
rObj.data.set({ num: '2' });
expect(ref.current.innerHTML).toEqual('2');
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,96 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { createRef, render, useReactive } from '../../../src/index';
describe('测试在DOM的props中使用响应式数据', () => {
it('在class props中使用响应式数据', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({ class: 'c1', color: 'blue' });
rObj = _rObj;
fn();
return (
<div ref={ref} className={_rObj.class}>
{_rObj.color}
</div>
);
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('blue');
expect(ref.current.getAttribute('class')).toEqual('c1');
rObj.class.set('c2');
expect(ref.current.getAttribute('class')).toEqual('c2');
expect(fn).toHaveBeenCalledTimes(1);
});
it('在style中使用响应式数据', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({ class: 'c1', color: 'blue' });
rObj = _rObj;
fn();
return (
<div ref={ref} style={{ color: _rObj.color }}>
{_rObj.color}
</div>
);
};
render(<App />, container);
expect(ref.current.innerHTML).toEqual('blue');
expect(ref.current.getAttribute('style')).toEqual('color: blue;');
rObj.color.set('red');
expect(ref.current.getAttribute('style')).toEqual('color: red;');
expect(fn).toHaveBeenCalledTimes(1);
});
it('在input中使用响应式数据', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive({ class: 'c1', color: 'blue' });
rObj = _rObj;
fn();
return <input ref={ref} value={_rObj.color}></input>;
};
render(<App />, container);
expect(ref.current.value).toEqual('blue');
rObj.color.set('red');
expect(ref.current.value).toEqual('red');
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,122 +0,0 @@
import Inula, {render, createRef, useReactive, useWatch, useCompute, For} from '../../../src/index';
describe('测试 watch', () => {
it('watch 一个参数', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
useWatch(() => {
_rObj.get();
fn();
});
rObj = _rObj;
return <div ref={ref}>{_rObj}</div>;
};
render(<App />, container);
rObj.set('2');
expect(fn).toHaveBeenCalledTimes(2);
});
it('watch 2个参数', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
useWatch(_rObj, () => {
fn();
});
rObj = _rObj;
return <div ref={ref}>{_rObj}</div>;
};
render(<App />, container);
rObj.set('2');
expect(fn).toHaveBeenCalledTimes(1);
});
it('watch 2个参数第一个是函数', () => {
let rObj;
const ref = createRef();
const fn = jest.fn();
const App = () => {
const _rObj = useReactive(1);
useWatch(
() => {
_rObj.get();
},
() => {
fn();
}
);
rObj = _rObj;
return <div ref={ref}>{_rObj}</div>;
};
render(<App />, container);
rObj.set('2');
expect(fn).toHaveBeenCalledTimes(1);
});
it('响应式数据的孩子变更watch也应该被触发', () => {
let rObj;
let ref = createRef();
let fn = jest.fn();
let appFn = jest.fn();
let itemFn = jest.fn();
const App = () => {
const _rObj = useReactive([
{ id: 'id-1', name: 'p1' },
{ id: 'id-2', name: 'p2' },
{ id: 'id-3', name: 'p3' },
]);
rObj = _rObj;
useWatch(() => {
_rObj.get();
fn();
});
appFn();
return (
<div ref={ref}>
<For each={_rObj}>
{item => {
itemFn();
return (
<li id={item.id} key={item.id}>
{item.name}
</li>
);
}}
</For>
</div>
);
};
render(<App />, container);
let items = container.querySelectorAll('li');
expect(items.length).toEqual(3);
expect(fn).toHaveBeenCalledTimes(1);
rObj.push({ id: 'id-4', name: 'p4' });
items = container.querySelectorAll('li');
expect(items.length).toEqual(4);
expect(fn).toHaveBeenCalledTimes(2);
rObj[1].set({ id: 'id-2', name: 'p222' });
let li = container.querySelector('#id-2');
expect(li.innerHTML).toEqual('p222');
expect(fn).toHaveBeenCalledTimes(3);
});
});

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as Inula from '../../../src/index';
import { getLogUtils } from './testUtils';
export const App = props => {
const Parent = props.parent;
const Child = props.child;
return (
<div>
<Parent>
<Child />
</Parent>
</div>
);
};
export const Text = props => {
const LogUtils = getLogUtils();
LogUtils.log(props.text);
return <p id={props.id}>{props.text}</p>;
};
export function triggerClickEvent(container, id) {
const event = new MouseEvent('click', {
bubbles: true,
});
container.querySelector(`#${id}`).dispatchEvent(event);
}

View File

@ -1,23 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
global.MessageChannel = function MessageChannel() {
this.port1 = {};
this.port2 = {
// eslint-disable-next-line @typescript-eslint/no-empty-function
postMessage() {},
};
};
global.__VERSION__ = require('../../../package.json').version;

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { unmountComponentAtNode } from '../../../src/dom/DOMExternal';
import { getLogUtils } from './testUtils';
const LogUtils = getLogUtils();
global.isDev = process.env.NODE_ENV === 'development';
global.isTest = true;
global.container = null;
global.beforeEach(() => {
LogUtils.clear();
// 创建一个 DOM 元素作为渲染目标
global.container = document.createElement('div');
document.body.appendChild(global.container);
});
global.afterEach(() => {
unmountComponentAtNode(global.container);
global.container.remove();
global.container = null;
LogUtils.clear();
});
function runAssertion(fn) {
try {
fn();
} catch (error) {
return {
pass: false,
message: () => error.message,
};
}
return { pass: true };
}
function toMatchValue(LogUtils, expectedValues) {
return runAssertion(() => {
const actualValues = LogUtils.getAndClear();
expect(actualValues).toEqual(expectedValues);
});
}
// 使Jest感知自定义匹配器
expect.extend({
toMatchValue,
});

View File

@ -1,41 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
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

@ -1,66 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export const stopBubbleOrCapture = (e, value) => {
const LogUtils = getLogUtils();
LogUtils.log(value);
e.stopPropagation();
};
export function triggerClickEvent(container, id) {
const event = new MouseEvent('click', {
bubbles: true,
});
container.querySelector(`#${id}`).dispatchEvent(event);
}
class LogUtils {
constructor() {
this.dataArray = null;
}
log = value => {
if (this.dataArray === null) {
this.dataArray = [value];
} else {
this.dataArray.push(value);
}
};
getAndClear = () => {
if (this.dataArray === null) {
return [];
}
const values = this.dataArray;
this.dataArray = null;
return values;
};
getNotClear = () => {
return this.dataArray === null ? [] : this.dataArray;
};
clear = () => {
this.dataArray = this.dataArray ? null : this.dataArray;
};
}
let logger;
export function getLogUtils() {
if (!logger) {
logger = new LogUtils();
}
return logger;
}

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export default function dispatchChangeEvent(inputEle, value) {
const nativeInputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputSetter.call(inputEle, value);
inputEle.dispatchEvent(new Event('input', { bubbles: true }));
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
'use strict';
const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const inulaEcoPath = path.resolve(__dirname, '../../inula-ecosystem');
if (!fs.existsSync(inulaEcoPath)) {
throw Error('inula-ecosystem not found, put inula-core and inula-ecosystem in same folder plz!');
}
const cmd = process.argv[2];
childProcess.exec(
`npm run ${cmd}`,
{
cwd: inulaEcoPath,
},
function (error, stdout) {
if (error) {
console.log(`Error: ${error}`);
} else {
console.log(`STDOUT: ${stdout}`);
}
}
);

View File

@ -1,65 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import fs from 'fs';
import path from 'path';
import dts from 'rollup-plugin-dts';
function deleteFolder(filePath) {
if (fs.existsSync(filePath)) {
if (fs.lstatSync(filePath).isDirectory()) {
const files = fs.readdirSync(filePath);
files.forEach(file => {
const nextFilePath = path.join(filePath, file);
const states = fs.lstatSync(nextFilePath);
if (states.isDirectory()) {
deleteFolder(nextFilePath);
} else {
fs.unlinkSync(nextFilePath);
}
});
fs.rmdirSync(filePath);
} else if (fs.lstatSync(filePath).isFile()) {
fs.unlinkSync(filePath);
}
}
}
/**
* 删除非空文件夹
* @param folders {string[]}
* @returns {{buildEnd(): void, name: string}}
*/
export function cleanUp(folders) {
return {
name: 'clean-up',
buildEnd() {
folders.forEach(f => deleteFolder(f));
},
};
}
function buildTypeConfig() {
return {
input: ['./build/inula/@types/index.d.ts'],
output: {
file: './build/inula/@types/index.d.ts',
format: 'es',
},
plugins: [dts(), cleanUp(['./build/inula/@types/'])],
};
}
export default [buildTypeConfig()];

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import fs from 'fs';
export default function copyFiles(copyPairs) {
return {
name: 'copy-files',
generateBundle() {
copyPairs.forEach(({ from, to }) => {
console.log(`copy files: ${from}${to}`);
fs.copyFileSync(from, to);
});
},
};
}

View File

@ -1,130 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import nodeResolve from '@rollup/plugin-node-resolve';
import babel from '@rollup/plugin-babel';
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 inulaVersion } from '../../package.json';
const extensions = ['.js', '.ts', '.tsx'];
const libDir = path.join(__dirname, '../..');
const rootDir = path.join(__dirname, '../..');
const outDir = path.join(rootDir, 'build', 'inula');
if (!fs.existsSync(path.join(rootDir, 'build'))) {
fs.mkdirSync(path.join(rootDir, 'build'));
}
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir);
}
const outputResolve = (...p) => path.resolve(outDir, ...p);
const isDev = mode => {
return mode === 'development';
};
const getBasicPlugins = mode => {
return [
nodeResolve({
extensions,
modulesOnly: true,
}),
babel({
exclude: 'node_modules/**',
configFile: path.join(__dirname, '../../babel.config.js'),
babelHelpers: 'runtime',
extensions,
}),
replace({
values: {
'process.env.NODE_ENV': `"${mode}"`,
isDev: isDev(mode).toString(),
isTest: false,
__VERSION__: `"${inulaVersion}"`,
},
preventAssignment: true,
}),
];
};
function getOutputName(mode) {
return mode === 'production' ? `inula.${mode}.min.js` : `inula.${mode}.js`;
}
function genConfig(mode) {
const sourcemap = isDev(mode) ? 'inline' : false;
return {
input: path.resolve(libDir, 'src', 'index.ts'),
output: [
{
file: outputResolve('cjs', getOutputName(mode)),
sourcemap,
format: 'cjs',
},
{
file: outputResolve('umd', getOutputName(mode)),
sourcemap,
name: 'Inula',
format: 'umd',
},
],
plugins: [
...getBasicPlugins(mode),
execute('npm run build-types', true),
mode === 'production' && terser(),
copy([
{
from: path.join(libDir, '/npm/index.js'),
to: path.join(outDir, 'index.js'),
},
{
from: path.join(libDir, 'package.json'),
to: path.join(outDir, 'package.json'),
},
]),
],
};
}
function genJSXRuntimeConfig(mode) {
return {
input: path.resolve(libDir, 'src', 'jsx-runtime.ts'),
output: {
file: outputResolve('jsx-runtime.js'),
format: 'cjs',
},
plugins: [...getBasicPlugins(mode)],
};
}
function genJSXDEVRuntimeConfig(mode) {
return {
input: path.resolve(libDir, 'src', 'jsx-dev-runtime.ts'),
output: {
file: outputResolve('jsx-dev-runtime.js'),
format: 'cjs',
},
plugins: [...getBasicPlugins(mode)],
};
}
export default [genConfig('development'), genConfig('production'), genJSXRuntimeConfig(''), genJSXDEVRuntimeConfig('')];

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export type DomAnimationEvent = AnimationEvent;
export type DomClipboardEvent = ClipboardEvent;
export type DomCompositionEvent = CompositionEvent;
export type DomDragEvent = DragEvent;
export type DomFocusEvent = FocusEvent;
export type DomKeyboardEvent = KeyboardEvent;
export type DomMouseEvent = MouseEvent;
export type DomTouchEvent = TouchEvent;
export type DomPointerEvent = PointerEvent;
export type DomTransitionEvent = TransitionEvent;
export type DomUIEvent = UIEvent;
export type DomWheelEvent = WheelEvent;

Some files were not shown because too many files have changed in this diff Show More