From 01050d4f9a95da3369e0ecefa45f3ed4f6d3e425 Mon Sep 17 00:00:00 2001 From: cuijiaojiao1 Date: Mon, 18 Apr 2022 16:00:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=85=A5Hamcrest=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: cuijiaojiao1 --- .gitignore | 11 + CHANGELOG.md | 3 + LICENSE | 26 ++ README.OpenSource | 11 + README.en.md | 36 -- README.md | 87 ++-- build-profile.json5 | 31 ++ entry/.gitignore | 11 + entry/build-profile.json5 | 13 + entry/hvigorfile.js | 2 + entry/package-lock.json | 11 + entry/package.json | 15 + entry/src/main/config.json | 67 +++ entry/src/main/ets/MainAbility/app.ets | 23 + .../src/main/ets/MainAbility/pages/index.ets | 434 ++++++++++++++++++ .../main/resources/base/element/string.json | 12 + entry/src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes hamcrest/.gitignore | 3 + hamcrest/build-profile.json5 | 5 + hamcrest/hvigorfile.js | 3 + hamcrest/index.ets | 60 +++ hamcrest/package-lock.json | 5 + hamcrest/package.json | 20 + hamcrest/src/main/config.json | 23 + .../main/ets/components/BaseDescription.ets | 70 +++ .../src/main/ets/components/BaseMatcher.ets | 51 ++ .../src/main/ets/components/CustomMatcher.ets | 32 ++ .../src/main/ets/components/Description.ets | 85 ++++ .../main/ets/components/DiagnosingMatcher.ets | 36 ++ .../main/ets/components/FeatureMatcher.ets | 67 +++ hamcrest/src/main/ets/components/Matcher.ets | 48 ++ .../src/main/ets/components/MatcherAssert.ets | 46 ++ .../main/ets/components/NullDescription.ets | 43 ++ .../main/ets/components/SelfDescribing.ets | 32 ++ .../main/ets/components/StringDescription.ets | 63 +++ .../ets/components/assertion-error/index.d.ts | 11 + .../ets/components/assertion-error/index.js | 116 +++++ .../components/assertion-error/package.json | 62 +++ .../main/ets/components/beans/HasProperty.ets | 54 +++ .../components/collection/ArrayMatching.ets | 55 +++ .../components/collection/HasItemInArray.ets | 54 +++ .../ets/components/collection/IsArray.ets | 94 ++++ .../components/collection/IsArrayWithSize.ets | 62 +++ .../main/ets/components/collection/IsIn.ets | 44 ++ .../components/collection/IsMapContaining.ets | 119 +++++ .../components/collection/IsMapWithSize.ets | 82 ++++ .../src/main/ets/components/core/AllOf.ets | 75 +++ .../src/main/ets/components/core/AnyOf.ets | 67 +++ .../ets/components/core/CombinableMatcher.ets | 56 +++ .../src/main/ets/components/core/Every.ets | 46 ++ hamcrest/src/main/ets/components/core/Is.ets | 65 +++ .../main/ets/components/core/IsAnything.ets | 52 +++ .../src/main/ets/components/core/IsEqual.ets | 111 +++++ .../main/ets/components/core/IsInstanceOf.ets | 70 +++ .../components/core/IsIterableContaining.ets | 142 ++++++ .../src/main/ets/components/core/IsNot.ets | 53 +++ .../src/main/ets/components/core/IsNull.ets | 56 +++ .../src/main/ets/components/core/IsSame.ets | 67 +++ .../components/core/ShortcutCombination.ets | 44 ++ .../ets/components/core/StringContains.ets | 59 +++ .../ets/components/core/StringEndsWith.ets | 58 +++ .../core/StringRegularExpression.ets | 61 +++ .../ets/components/core/StringStartsWith.ets | 62 +++ .../ets/components/core/SubstringMatcher.ets | 56 +++ .../ets/components/internal/ArrayIterator.ets | 42 ++ .../ets/components/internal/NullSafety.ets | 27 ++ .../internal/SelfDescribingValue.ets | 31 ++ .../internal/SelfDescribingValueIterator.ets | 44 ++ .../main/ets/components/number/IsCloseTo.ets | 72 +++ .../src/main/ets/components/number/IsNaN.ets | 50 ++ .../components/number/OrderingComparison.ets | 92 ++++ .../components/text/CharSequenceLength.ets | 58 +++ .../ets/components/text/IsBlankString.ets | 65 +++ .../ets/components/text/IsEmptyString.ets | 90 ++++ .../text/IsEqualCompressingWhiteSpace.ets | 67 +++ .../components/text/IsEqualIgnoringCase.ets | 50 ++ .../ets/components/text/MatchesPattern.ets | 57 +++ .../main/resources/base/element/string.json | 8 + hvigorfile.js | 2 + package.json | 17 + screenshots/operation.png | Bin 0 -> 60289 bytes 81 files changed, 4147 insertions(+), 63 deletions(-) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.OpenSource delete mode 100644 README.en.md create mode 100644 build-profile.json5 create mode 100644 entry/.gitignore create mode 100644 entry/build-profile.json5 create mode 100644 entry/hvigorfile.js create mode 100644 entry/package-lock.json create mode 100644 entry/package.json create mode 100644 entry/src/main/config.json create mode 100644 entry/src/main/ets/MainAbility/app.ets create mode 100644 entry/src/main/ets/MainAbility/pages/index.ets create mode 100644 entry/src/main/resources/base/element/string.json create mode 100644 entry/src/main/resources/base/media/icon.png create mode 100644 hamcrest/.gitignore create mode 100644 hamcrest/build-profile.json5 create mode 100644 hamcrest/hvigorfile.js create mode 100644 hamcrest/index.ets create mode 100644 hamcrest/package-lock.json create mode 100644 hamcrest/package.json create mode 100644 hamcrest/src/main/config.json create mode 100644 hamcrest/src/main/ets/components/BaseDescription.ets create mode 100644 hamcrest/src/main/ets/components/BaseMatcher.ets create mode 100644 hamcrest/src/main/ets/components/CustomMatcher.ets create mode 100644 hamcrest/src/main/ets/components/Description.ets create mode 100644 hamcrest/src/main/ets/components/DiagnosingMatcher.ets create mode 100644 hamcrest/src/main/ets/components/FeatureMatcher.ets create mode 100644 hamcrest/src/main/ets/components/Matcher.ets create mode 100644 hamcrest/src/main/ets/components/MatcherAssert.ets create mode 100644 hamcrest/src/main/ets/components/NullDescription.ets create mode 100644 hamcrest/src/main/ets/components/SelfDescribing.ets create mode 100644 hamcrest/src/main/ets/components/StringDescription.ets create mode 100644 hamcrest/src/main/ets/components/assertion-error/index.d.ts create mode 100644 hamcrest/src/main/ets/components/assertion-error/index.js create mode 100644 hamcrest/src/main/ets/components/assertion-error/package.json create mode 100644 hamcrest/src/main/ets/components/beans/HasProperty.ets create mode 100644 hamcrest/src/main/ets/components/collection/ArrayMatching.ets create mode 100644 hamcrest/src/main/ets/components/collection/HasItemInArray.ets create mode 100644 hamcrest/src/main/ets/components/collection/IsArray.ets create mode 100644 hamcrest/src/main/ets/components/collection/IsArrayWithSize.ets create mode 100644 hamcrest/src/main/ets/components/collection/IsIn.ets create mode 100644 hamcrest/src/main/ets/components/collection/IsMapContaining.ets create mode 100644 hamcrest/src/main/ets/components/collection/IsMapWithSize.ets create mode 100644 hamcrest/src/main/ets/components/core/AllOf.ets create mode 100644 hamcrest/src/main/ets/components/core/AnyOf.ets create mode 100644 hamcrest/src/main/ets/components/core/CombinableMatcher.ets create mode 100644 hamcrest/src/main/ets/components/core/Every.ets create mode 100644 hamcrest/src/main/ets/components/core/Is.ets create mode 100644 hamcrest/src/main/ets/components/core/IsAnything.ets create mode 100644 hamcrest/src/main/ets/components/core/IsEqual.ets create mode 100644 hamcrest/src/main/ets/components/core/IsInstanceOf.ets create mode 100644 hamcrest/src/main/ets/components/core/IsIterableContaining.ets create mode 100644 hamcrest/src/main/ets/components/core/IsNot.ets create mode 100644 hamcrest/src/main/ets/components/core/IsNull.ets create mode 100644 hamcrest/src/main/ets/components/core/IsSame.ets create mode 100644 hamcrest/src/main/ets/components/core/ShortcutCombination.ets create mode 100644 hamcrest/src/main/ets/components/core/StringContains.ets create mode 100644 hamcrest/src/main/ets/components/core/StringEndsWith.ets create mode 100644 hamcrest/src/main/ets/components/core/StringRegularExpression.ets create mode 100644 hamcrest/src/main/ets/components/core/StringStartsWith.ets create mode 100644 hamcrest/src/main/ets/components/core/SubstringMatcher.ets create mode 100644 hamcrest/src/main/ets/components/internal/ArrayIterator.ets create mode 100644 hamcrest/src/main/ets/components/internal/NullSafety.ets create mode 100644 hamcrest/src/main/ets/components/internal/SelfDescribingValue.ets create mode 100644 hamcrest/src/main/ets/components/internal/SelfDescribingValueIterator.ets create mode 100644 hamcrest/src/main/ets/components/number/IsCloseTo.ets create mode 100644 hamcrest/src/main/ets/components/number/IsNaN.ets create mode 100644 hamcrest/src/main/ets/components/number/OrderingComparison.ets create mode 100644 hamcrest/src/main/ets/components/text/CharSequenceLength.ets create mode 100644 hamcrest/src/main/ets/components/text/IsBlankString.ets create mode 100644 hamcrest/src/main/ets/components/text/IsEmptyString.ets create mode 100644 hamcrest/src/main/ets/components/text/IsEqualCompressingWhiteSpace.ets create mode 100644 hamcrest/src/main/ets/components/text/IsEqualIgnoringCase.ets create mode 100644 hamcrest/src/main/ets/components/text/MatchesPattern.ets create mode 100644 hamcrest/src/main/resources/base/element/string.json create mode 100644 hvigorfile.js create mode 100644 package.json create mode 100644 screenshots/operation.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32a9ae7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +/entry/.preview +.cxx +/node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e43494e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 + + 1. 匹配器库功能实现,可以组合起来匹配 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..213a04d --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +BSD License + +Copyright (C) 2022 Huawei Device Co., Ltd. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 0000000..1c4f51e --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "JavaHamcrest", + "License": "BSD License", + "License File": " LICENSE ", + "Version Number": "2.2", + "Owner" : "Hamcrest" + "Upstream URL": "https://github.com/hamcrest/JavaHamcrest", + "Description": "Java (and original) version of Hamcrest" + } +] \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 1256845..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# Hamcrest - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 8e5e5e3..451c4e2 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,72 @@ -# Hamcrest +# hamcrest -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +## 简介 +> hamcrest是匹配器库,可以组合起来匹配。 -#### 软件架构 -软件架构说明 +![operation.png](screenshots/operation.png) +## 下载安装 +```shell +npm install @ohos/hamcrest --save +``` +OpenHarmony npm环境配置等更多内容,请参考 [如何安装OpenHarmony npm包](https://gitee.com/openharmony-tpc/docs/blob/master/OpenHarmony_npm_usage.md) 。 -#### 安装教程 +## 使用说明 +1. 引入文件及代码依赖 + ``` + import {AllOf} from '@ohos/hamcrest' + ``` -1. xxxx -2. xxxx -3. xxxx +2. 构建匹配器 + ``` + let matcher = AllOf.allOfMatches(StringContains.containsString('expected'), StringContains.containsString('value')) + ``` -#### 使用说明 +3. 调用匹配器匹配 + ``` + allOf() { + let matcher = AllOf.allOfMatches(StringContains.containsString('expected'), StringContains.containsString('value')) + console.info("allOf: " + matcher.matches('expected value')) + console.info("allOf: " + matcher.matches('value expected')) + console.info("allOf: " + matcher.matches('expected valu')) + } + ``` -1. xxxx -2. xxxx -3. xxxx +## 接口说明 -#### 参与贡献 +1. 匹配所有`AllOf.allOf()` +2. 匹配某一个`AnyOf.AnyOf()` +3. 匹配每一个`Every.everyItem()` +4. 匹配任意`IsAnything.anything()` +5. 匹配不是`IsNot.not()` +6. 匹配null`IsNull.nullValue()` +7. 匹配包含字符串`StringContains.containsString()` +8. 匹配以字符串结尾`StringEndsWith.endsWith()` +9. 匹配以字符串开头`StringStartsWith.startsWith()` +10. 匹配空字符串`IsEmptyString.emptyString()` -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +## 兼容性 +支持 OpenHarmony API version 8 及以上版本。 +## 目录结构 +```` +|---- hamcrest +| |---- entry # 示例代码文件夹 +| |---- hamcrest # hamcrest库文件夹 +| |---- index.ets # 对外接口 +| |---- src +| |---- main +| |---- components +| |---- core # 核心文件夹 +| |---- Every.ets # 匹配每一个 +| |---- AllOf.ets # 匹配所有 +| |---- IsAnything.ets # 匹配任意 +| |---- StringContains.ets # 匹配包含字符串 +| |---- README.md # 安装使用方法 +```` -#### 特技 +## 贡献代码 +使用过程中发现任何问题都可以提 [Issue](https://gitee.com/hihopeorg/Hamcrest/issues) 给我们,当然,我们也非常欢迎你给我们发 [PR](https://gitee.com/hihopeorg/Hamcrest/pulls) 。 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +## 开源协议 +本项目基于 [BSD License](https://gitee.com/hihopeorg/Hamcrest/blob/master/LICENSE) ,请自由地享受和参与开源。 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..7ea9d73 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "app": { + "signingConfigs": [], + "compileSdkVersion": 8, + "compatibleSdkVersion": 8, + "products": [ + { + "name": "default", + "signingConfig": "default", + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "hamcrest", + "srcPath": "./hamcrest" + } + ] +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000..88985e5 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +/entry/.preview +.cxx +/node_modules \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000..ae58d1d --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,13 @@ +{ + "apiType": 'faMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.js b/entry/hvigorfile.js new file mode 100644 index 0000000..bcec4c9 --- /dev/null +++ b/entry/hvigorfile.js @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').legacyHapTasks diff --git a/entry/package-lock.json b/entry/package-lock.json new file mode 100644 index 0000000..0c30734 --- /dev/null +++ b/entry/package-lock.json @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ohos/hamcrest": { + "version": "file:../hamcrest" + } + } +} diff --git a/entry/package.json b/entry/package.json new file mode 100644 index 0000000..92c8fcd --- /dev/null +++ b/entry/package.json @@ -0,0 +1,15 @@ +{ + "name": "entry", + "version": "1.0.0", + "ohos": { + "org": "huawei", + "buildTool": "hvigor", + "directoryLevel": "module" + }, + "description": "example description", + "repository": {}, + "license": "BSD License", + "dependencies": { + "@ohos/hamcrest": "file:../hamcrest" + } +} diff --git a/entry/src/main/config.json b/entry/src/main/config.json new file mode 100644 index 0000000..e58d701 --- /dev/null +++ b/entry/src/main/config.json @@ -0,0 +1,67 @@ +{ + "app": { + "bundleName": "com.example.hamcrest", + "vendor": "example", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.example.hamcrest", + "name": ".MyApplication", + "mainAbility": ".MainAbility", + "srcPath": "", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry", + "installationFree": false + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "visible": true, + "srcPath": "MainAbility", + "name": ".MainAbility", + "srcLanguage": "ets", + "icon": "$media:icon", + "description": "$string:description_mainability", + "formsEnabled": false, + "label": "$string:entry_MainAbility", + "type": "page", + "launchType": "standard" + } + ], + "js": [ + { + "mode": { + "syntax": "ets", + "type": "pageAbility" + }, + "pages": [ + "pages/index" + ], + "name": ".MainAbility", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/app.ets b/entry/src/main/ets/MainAbility/app.ets new file mode 100644 index 0000000..1325479 --- /dev/null +++ b/entry/src/main/ets/MainAbility/app.ets @@ -0,0 +1,23 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export default { + onCreate() { + console.info('Application onCreate') + }, + onDestroy() { + console.info('Application onDestroy') + }, +} \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/pages/index.ets b/entry/src/main/ets/MainAbility/pages/index.ets new file mode 100644 index 0000000..2fe995e --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/index.ets @@ -0,0 +1,434 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {HasProperty} from '@ohos/hamcrest' +import {StringContains} from '@ohos/hamcrest' +import {StringStartsWith} from '@ohos/hamcrest' +import {StringEndsWith} from '@ohos/hamcrest' +import {AllOf} from '@ohos/hamcrest' +import {AnyOf} from '@ohos/hamcrest' +import {IsNot} from '@ohos/hamcrest' +import {Is} from '@ohos/hamcrest' +import {IsEqual} from '@ohos/hamcrest' +import {IsNaN} from '@ohos/hamcrest' +import {IsEqualIgnoringCase} from '@ohos/hamcrest' +import {CharSequenceLength} from '@ohos/hamcrest' +import {IsBlankString} from '@ohos/hamcrest' +import {OrderingComparison} from '@ohos/hamcrest' +import {IsCloseTo} from '@ohos/hamcrest' +import {IsNull} from '@ohos/hamcrest' +import {IsArray} from '@ohos/hamcrest' +import {IsIn} from '@ohos/hamcrest' +import {MatchesPattern} from '@ohos/hamcrest' +import {Every} from '@ohos/hamcrest' + +@Entry +@Component +struct Index { + scroller: Scroller = new Scroller() + + hasProperty() { + let matcher = HasProperty.hasProperty('name') + console.info("hasProperty: " + matcher.matches({ name: 'Joe' })) + console.info("hasProperty: " + matcher.matches({ name: 'Joel' })) + console.info("hasProperty: " + matcher.matches({ age: 19 })) + } + + hasLength() { + let matcher = CharSequenceLength.hasLength(4); + console.info("hasLength: " + matcher.matches('aaaa')) + console.info("hasLength: " + matcher.matches('aaa')) + console.info("hasLength: " + matcher.matches('aa a')) + } + + anyOf() { + let matcherAnyOf = AnyOf.anyOfMatches(IsBlankString.blankString(), + IsEqualIgnoringCase.equalToIgnoringCase('aaa')) + console.log('anyOf: enter matcherAnyOf:' + matcherAnyOf.matches('AAA')) + console.log('anyOf: enter matcherAnyOf:' + matcherAnyOf.matches('bbb')) + console.log('anyOf: enter matcherAnyOf:' + matcherAnyOf.matches('')) + console.log('anyOf: enter matcherAnyOf:' + matcherAnyOf.matches('\n\r\t')) + } + + containsString() { + let matcher = StringContains.containsString('rat') + console.info("containsString: " + matcher.matches('jim the rat')) + console.info("containsString: " + matcher.matches('jim the rats')) + let matcher2 = StringContains.containsString('rats'); + console.info("containsString: " + matcher2.matches('jim the rat')) + } + + allOf() { + let matcher = AllOf.allOfMatches(StringContains.containsString('expected'), StringContains.containsString('value')) + console.info("allOf: " + matcher.matches('expected value')) + console.info("allOf: " + matcher.matches('value expected')) + console.info("allOf: " + matcher.matches('expected valu')) + } + + endsWith() { + let matcher = StringEndsWith.endsWith('test') + console.info("isArray: " + matcher.matches('aaabbbtest')) + console.info("isArray: " + matcher.matches('aaabbbtests')) + console.info("isArray: " + matcher.matches('testdjsdojw')) + } + + isNaN() { + let matcher = IsNaN.notANumber() + console.info("isNaN: " + matcher.matches(7)) + console.info("isNaN: " + matcher.matches(7.7)) + console.info("isNaN: " + matcher.matches(NaN)) + } + + isEqual() { + const value = [1, 2] + let matcher = IsEqual.equalTo(value) + console.info("isEqual: " + matcher.matches([1, 3])) + console.info("isEqual: " + matcher.matches([1, 2])) + let matcher2 = IsEqual.equalTo("string value") + console.info("isEqual: " + matcher2.matches("string value")) + console.info("isEqual: " + matcher2.matches("string values")) + } + + equalIgnoringCase() { + let matcher = IsEqualIgnoringCase.equalToIgnoringCase('Hello') + console.log('equalIgnoringCase:' + matcher.matches('heLLo')) + console.log('equalIgnoringCase:' + matcher.matches('hello')) + console.log('equalIgnoringCase:' + matcher.matches('HELLO')) + } + + orderingComparison() { + let matcher = OrderingComparison.greaterThan(5) + console.log('equalIgnoringCase:' + matcher.matches('6')) + console.log('equalIgnoringCase:' + matcher.matches('4')) + console.log('equalIgnoringCase:' + matcher.matches('5')) + + let matcher2 = OrderingComparison.greaterThanOrEqualTo(5) + console.log('equalIgnoringCase:' + matcher2.matches('6')) + console.log('equalIgnoringCase:' + matcher2.matches('4')) + console.log('equalIgnoringCase:' + matcher2.matches('5')) + + let matcher3 = OrderingComparison.lessThan(5) + console.log('equalIgnoringCase:' + matcher3.matches('6')) + console.log('equalIgnoringCase:' + matcher3.matches('4')) + console.log('equalIgnoringCase:' + matcher3.matches('5')) + + let matcher4 = OrderingComparison.lessThanOrEqualTo(5) + console.log('equalIgnoringCase:' + matcher4.matches('6')) + console.log('equalIgnoringCase:' + matcher4.matches('4')) + console.log('equalIgnoringCase:' + matcher4.matches('5')) + } + + isBlackString() { + let matcher = IsBlankString.blankString(); + console.log('isBlackString :' + matcher.matches('')) + console.log('isBlackString :' + matcher.matches('\n\r\t')) + + let matcher2 = IsBlankString.blankOrNullString(); + console.log('isBlackString: enter matcher2:' + matcher2.matches('\n\r\t')) + console.log('isBlackString: enter matcher2:' + matcher2.matches('null')) + console.log('isBlackString: enter matcher2:' + matcher2.matches(null)) + } + + isCloseTo() { + let matcher = IsCloseTo.closeTo(1.1, 0.0); + console.log('IsCloseTo :' + matcher.matches(1.10)) + + let matcher2 = IsCloseTo.closeTo(1.111111111, 0.0); + console.log('IsCloseTo :' + matcher.matches(1.1111111112)) + } + + isNull() { + let matcher = IsNull.nullValue() + console.log('isNull :' + matcher.matches(null)) + console.log('isNull :' + matcher.matches('null')) + console.log('isNull :' + matcher.matches('')) + } + + combinationMatch() { + let matcher = IsNot.not(StringContains.containsString('expected')); + console.info("combinationMatch: " + matcher.matches("expected value")) + console.info("combinationMatch: " + matcher.matches('another value')) + + let match = Is.is(StringStartsWith.startsWith('hamjest')) + console.info("combinationMatch: " + match.matches('hamjest is awesome')) + } + + isArray() { + let matcher = IsArray.array(IsEqual.equalTo('a'), IsEqual.equalTo('b'), IsEqual.equalTo('c')) + console.info("isArray: " + matcher.matches(['a', 'b', 'c'])) + console.info("isArray: " + matcher.matches(['a', 'd', 'b'])) + console.info("isArray: " + matcher.matches(['a', 'b'])) + } + + isIn() { + let matcher = IsIn.isIn(["bar", "foo"]) + console.info("isIn: " + matcher.matches("bar")) + console.info("isIn: " + matcher.matches(["bar", "foo"])) + + } + + every() { + let matcher = Every.everyItem(StringContains.containsString('a')) + console.info("every: " + matcher.matches(["AaA", "BaB", "CaC"])) + console.info("every: " + matcher.matches(["AAA", "BaB", "CbC"])) + } + + matchesPattern() { + let matcher = MatchesPattern.matchesPattern(new RegExp('bb')) + console.info("matchesPattern: " + matcher.matches('aabbcc')) + console.info("matchesPattern: " + matcher.matches('aabcc')) + console.info("matchesPattern: " + matcher.matches('abc')) + } + + build() { + + Stack({ alignContent: Alignment.TopStart }) { + Scroll(this.scroller) { + Column() { + Button() { + Text('hasProperty') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.hasProperty.bind(this)) + + Button() { + Text('hasLength') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(() => { + this.hasLength() + }) + + Button() { + Text('anyOf') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.anyOf.bind(this)) + + Button() { + Text('containsString') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.containsString.bind(this)) + + Button() { + Text('allOf') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.allOf.bind(this)) + + Button() { + Text('endsWith') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.endsWith.bind(this)) + + Button() { + Text('isNaN') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isNaN.bind(this)) + + Button() { + Text('isEqual') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isEqual.bind(this)) + + Button() { + Text('IsEqualIgnoringCase') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.equalIgnoringCase.bind(this)) + + Button() { + Text('orderingComparison') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.orderingComparison.bind(this)) + + Button() { + Text('isBlackString') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isBlackString.bind(this)) + + Button() { + Text('isCloseTo') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isCloseTo.bind(this)) + + Button() { + Text('isNull') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isNull.bind(this)) + + Button() { + Text('combinationMatch') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.combinationMatch.bind(this)) + + Button() { + Text('IsArray') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isArray.bind(this)) + + Button() { + Text('IsIn') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.isIn.bind(this)) + + Button() { + Text('every') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.every.bind(this)) + + Button() { + Text('matchesPattern') + .fontSize(25) + .fontWeight(FontWeight.Bold) + .padding({ left: 16, right: 16 }) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .onClick(this.matchesPattern.bind(this)) + + }.width('100%') + } + .scrollable(ScrollDirection.Vertical).scrollBar(BarState.On) + .scrollBarColor(Color.Gray).scrollBarWidth(1) + .onScroll((xOffset: number, yOffset: number) => { + console.info(xOffset + ' ' + yOffset) + }) + .onScrollEdge((side: Edge) => { + console.info('To the edge') + }) + .onScrollEnd(() => { + console.info('Scroll Stop') + }) + + }.width('100%').height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..57aecc4 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "entry_MainAbility", + "value": "Hamcrest" + }, + { + "name": "description_mainability", + "value": "eTS_Empty Ability" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 0) { + this.append(separator); + } + this.appendDescriptionOf(list[i]); + } + this.append(end); + return this; + } + + protected abstract append(str: String): void; +} diff --git a/hamcrest/src/main/ets/components/BaseMatcher.ets b/hamcrest/src/main/ets/components/BaseMatcher.ets new file mode 100644 index 0000000..1644cef --- /dev/null +++ b/hamcrest/src/main/ets/components/BaseMatcher.ets @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Matcher } from './Matcher' +import { Description } from './Description' +import { StringDescription } from './StringDescription' + +/** + * BaseClass for all Matcher implementations. + * + * @see Matcher + */ +export abstract class BaseMatcher implements Matcher { + public describeMismatch(item: Object, description: Description): void { + description.appendText("was ").appendValue(item); + } + + public toString(): String { + return StringDescription.convertToString(this); + } + + /** + * Useful null-check method. Writes a mismatch description if the actual object is null + * @param actual the object to check + * @param mismatch where to write the mismatch description, if any + * @return false iff the actual object is null + */ + protected static isNotNull(actual: Object, mismatch: Description): boolean { + if (actual == null) { + mismatch.appendText("was null"); + return false; + } + return true; + } + + public abstract matches(actual: Object): boolean; + + public abstract describeTo(description: Description): void; +} diff --git a/hamcrest/src/main/ets/components/CustomMatcher.ets b/hamcrest/src/main/ets/components/CustomMatcher.ets new file mode 100644 index 0000000..7edc677 --- /dev/null +++ b/hamcrest/src/main/ets/components/CustomMatcher.ets @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from './BaseMatcher' +import {Description} from './Description' + +export abstract class CustomMatcher extends BaseMatcher { + private fixedDescription: String; + + public CustomMatcher(description: String) { + if (description == null) { + throw new Error("Description should be non null!"); + } + this.fixedDescription = description; + } + + public describeTo(description: Description): void { + description.appendText(this.fixedDescription); + } +} diff --git a/hamcrest/src/main/ets/components/Description.ets b/hamcrest/src/main/ets/components/Description.ets new file mode 100644 index 0000000..6402406 --- /dev/null +++ b/hamcrest/src/main/ets/components/Description.ets @@ -0,0 +1,85 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { SelfDescribing } from './SelfDescribing' +import { NullDescription } from './NullDescription' + +/** + * A description of a Matcher. A Matcher will describe itself to a description + * which can later be used for reporting. + * + * @see Matcher#describeTo(Description) + */ +export interface Description { + + /** + * Appends some plain text to the description. + * + * @param text + * the text to append. + * @return the update description when displaying the matcher error. + */ + appendText(text: String): Description; + + /** + * Appends the description of a {@link SelfDescribing} value to this description. + * + * @param value + * the value to append. + * @return the update description when displaying the matcher error. + */ + appendDescriptionOf(value: SelfDescribing): Description; + + /** + * Appends an arbitrary value to the description. + * + * @param value + * the object to append. + * @return the update description when displaying the matcher error. + */ + appendValue(value: Object): Description; + + /** + * Appends a list of values to the description. + * + * @param + * the description type. + * @param start + * the prefix. + * @param separator + * the separator. + * @param end + * the suffix. + * @param values + * the values to append. + * @return the update description when displaying the matcher error. + */ + appendValueList(start: String, separator: String, end: String, ... list: SelfDescribing[]): Description; + + /** + * Appends a list of SelfDescribing objects + * to the description. + * @param start + * the prefix. + * @param separator + * the separator. + * @param end + * the suffix. + * @param values + * the values to append. + * @return the update description when displaying the matcher error. + */ + appendList(start: String, separator: String, end: String, list: SelfDescribing[]): Description; +} diff --git a/hamcrest/src/main/ets/components/DiagnosingMatcher.ets b/hamcrest/src/main/ets/components/DiagnosingMatcher.ets new file mode 100644 index 0000000..ee0dd99 --- /dev/null +++ b/hamcrest/src/main/ets/components/DiagnosingMatcher.ets @@ -0,0 +1,36 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from './BaseMatcher' +import { Description } from './Description' +import { BaseDescription } from './BaseDescription' + +/** + * TODO(ngd): Document. + * + * @param the type of matcher being diagnosed. + */ +export abstract class DiagnosingMatcher extends BaseMatcher { + + public matches(item: Object): boolean { + return this.matchesWithDiagnosingMatcher(item, BaseDescription.NONE); + } + + public describeMismatch(item: Object, mismatchDescription: Description): void { + this.matchesWithDiagnosingMatcher(item, mismatchDescription); + } + + protected abstract matchesWithDiagnosingMatcher(item: Object, mismatchDescription: Description): boolean; +} diff --git a/hamcrest/src/main/ets/components/FeatureMatcher.ets b/hamcrest/src/main/ets/components/FeatureMatcher.ets new file mode 100644 index 0000000..38d0375 --- /dev/null +++ b/hamcrest/src/main/ets/components/FeatureMatcher.ets @@ -0,0 +1,67 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {DiagnosingMatcher} from './DiagnosingMatcher' +import {Description} from './Description' +import {Matcher} from './Matcher' + +/** + * Supporting class for matching a feature of an object. Implement featureValueOf() + * in a subclass to pull out the feature to be matched against. + * + * @param The type of the object to be matched + * @param The type of the feature to be matched + */ +export abstract class FeatureMatcher extends DiagnosingMatcher { + + private subMatcher: Matcher ; + private featureDescription: String; + private featureName: String; + + /** + * Constructor + * @param subMatcher The matcher to apply to the feature + * @param featureDescription Descriptive text to use in describeTo + * @param featureName Identifying text for mismatch message + */ + public constructor(subMatcher: Matcher, featureDescription: String, featureName: String) { + super(); + this.subMatcher = subMatcher; + this.featureDescription = featureDescription; + this.featureName = featureName; + } + + /** + * Implement this to extract the interesting feature. + * @param actual the target object + * @return the feature to be matched + */ + protected abstract featureValueOf(actual: T): U; + + protected matchesWithDiagnosingMatcher(actual: T, mismatch: Description): boolean { + let featureValue: U = this.featureValueOf(actual); + if (!this.subMatcher.matches(featureValue)) { + mismatch.appendText(this.featureName).appendText(" "); + this.subMatcher.describeMismatch(featureValue, mismatch); + return false; + } + return true; + } + + public describeTo(description: Description): void { + description.appendText(this.featureDescription).appendText(" ") + .appendDescriptionOf(this.subMatcher); + } +} diff --git a/hamcrest/src/main/ets/components/Matcher.ets b/hamcrest/src/main/ets/components/Matcher.ets new file mode 100644 index 0000000..1e45d84 --- /dev/null +++ b/hamcrest/src/main/ets/components/Matcher.ets @@ -0,0 +1,48 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {SelfDescribing} from './SelfDescribing' +import {Description} from './Description' + +export interface Matcher extends SelfDescribing { + + /** + * Evaluates the matcher for argument item. + * + * This method matches against Object, instead of the generic type T. This is + * because the caller of the Matcher does not know at runtime what the type is + * (because of type erasure with Java generics). It is down to the implementations + * to check the correct type. + * + * @param actual the object against which the matcher is evaluated. + * @return true if item matches, otherwise false. + * + * @see BaseMatcher + */ + matches(actual: Object): boolean; + + /** + * Generate a description of why the matcher has not accepted the item. + * The description will be part of a larger description of why a matching + * failed, so it should be concise. + * This method assumes that matches(item) is false, but + * will not check this. + * + * @param actual The item that the Matcher has rejected. + * @param mismatchDescription + * The description to be built or appended to. + */ + describeMismatch(actual: Object, mismatchDescription: Description): void; +} diff --git a/hamcrest/src/main/ets/components/MatcherAssert.ets b/hamcrest/src/main/ets/components/MatcherAssert.ets new file mode 100644 index 0000000..5693bfe --- /dev/null +++ b/hamcrest/src/main/ets/components/MatcherAssert.ets @@ -0,0 +1,46 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from './Matcher' +import {Description} from './Description' +import {StringDescription} from './StringDescription' +import AssertionError from './assertion-error/index' + +export class MatcherAssert { + public static assertThat(actual: any, matcher: Matcher): void { + MatcherAssert.assertThatWithMatcher(actual, matcher, ""); + } + + public static assertThatWithMatcher(actual: any, matcher: Matcher, reason?: String): void { + if (!matcher.matches(actual)) { + let description: Description = new StringDescription(); + description.appendText(reason) + .appendText("\n") + .appendText("Expected: ") + .appendDescriptionOf(matcher) + .appendText("\n") + .appendText(" but: "); + matcher.describeMismatch(actual, description); + + throw new AssertionError(description.toString()); + } + } + + public static assertThatWithReason(reason: String, assertion: boolean): void { + if (!assertion) { + throw new AssertionError(reason.toString()); + } + } +} diff --git a/hamcrest/src/main/ets/components/NullDescription.ets b/hamcrest/src/main/ets/components/NullDescription.ets new file mode 100644 index 0000000..c4ccc45 --- /dev/null +++ b/hamcrest/src/main/ets/components/NullDescription.ets @@ -0,0 +1,43 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from './Description'; +import { SelfDescribing } from './SelfDescribing'; + +export class NullDescription implements Description { + public appendDescriptionOf(value: SelfDescribing): Description { + return this; + } + + public appendList(start: String, separator: String, end: String, values: SelfDescribing[]): Description { + return this; + } + + public appendText(text: String): Description { + return this; + } + + public appendValue(value: Object): Description{ + return this; + } + + public appendValueList(start: String, separator: String, end: String, ... values: SelfDescribing[]): Description{ + return this; + } + + public toString(): String { + return ""; + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/SelfDescribing.ets b/hamcrest/src/main/ets/components/SelfDescribing.ets new file mode 100644 index 0000000..40333fc --- /dev/null +++ b/hamcrest/src/main/ets/components/SelfDescribing.ets @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from './Description' + +/** + * The ability of an object to describe itself. + */ +export interface SelfDescribing { + + /** + * Generates a description of the object. The description may be part of a + * a description of a larger object of which this is just a component, so it + * should be worded appropriately. + * + * @param description + * The description to be built or appended to. + */ + describeTo(description: Description): void; +} diff --git a/hamcrest/src/main/ets/components/StringDescription.ets b/hamcrest/src/main/ets/components/StringDescription.ets new file mode 100644 index 0000000..1e93881 --- /dev/null +++ b/hamcrest/src/main/ets/components/StringDescription.ets @@ -0,0 +1,63 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseDescription } from './BaseDescription'; +import { SelfDescribing } from './SelfDescribing'; + +/** + * A {@link Description} that is stored as a string. + */ +export class StringDescription extends BaseDescription { + value = ''; + + constructor() { + super(); + } + + /** + * Return the description of a {@link SelfDescribing} object as a String. + * + * @param selfDescribing + * The object to be described. + * @return + * The description of the object. + */ + public static convertToString(selfDescribing: SelfDescribing): String { + return new StringDescription().appendDescriptionOf(selfDescribing).toString(); + } + + /** + * Alias for {@link #toString(SelfDescribing)}. + * + * @param selfDescribing + * The object to be described. + * @return + * The description of the object. + */ + public static asString(selfDescribing: SelfDescribing): String { + return this.convertToString(selfDescribing); + } + + protected append(str: String): void { + this.value += str; + } + + /** + * Returns the description as a string. + */ + public toString(): String { + return this.value; + } +} diff --git a/hamcrest/src/main/ets/components/assertion-error/index.d.ts b/hamcrest/src/main/ets/components/assertion-error/index.d.ts new file mode 100644 index 0000000..2b9becd --- /dev/null +++ b/hamcrest/src/main/ets/components/assertion-error/index.d.ts @@ -0,0 +1,11 @@ +type AssertionError = Error & T & { + showDiff: boolean; +}; + +interface AssertionErrorConstructor { + new(message: string, props?: T, ssf?: Function): AssertionError; +} + +declare const AssertionError: AssertionErrorConstructor; + +export = AssertionError; diff --git a/hamcrest/src/main/ets/components/assertion-error/index.js b/hamcrest/src/main/ets/components/assertion-error/index.js new file mode 100644 index 0000000..8466da8 --- /dev/null +++ b/hamcrest/src/main/ets/components/assertion-error/index.js @@ -0,0 +1,116 @@ +/*! + * assertion-error + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Return a function that will copy properties from + * one object to another excluding any originally + * listed. Returned function will create a new `{}`. + * + * @param {String} excluded properties ... + * @return {Function} + */ + +function exclude () { + var excludes = [].slice.call(arguments); + + function excludeProps (res, obj) { + Object.keys(obj).forEach(function (key) { + if (!~excludes.indexOf(key)) res[key] = obj[key]; + }); + } + + return function extendExclude () { + var args = [].slice.call(arguments) + , i = 0 + , res = {}; + + for (; i < args.length; i++) { + excludeProps(res, args[i]); + } + + return res; + }; +}; + +/*! + * Primary Exports + */ + +module.exports = AssertionError; + +/** + * ### AssertionError + * + * An extension of the JavaScript `Error` constructor for + * assertion and validation scenarios. + * + * @param {String} message + * @param {Object} properties to include (optional) + * @param {callee} start stack function (optional) + */ + +function AssertionError (message, _props, ssf) { + var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') + , props = extend(_props || {}); + + // default values + this.message = message || 'Unspecified AssertionError'; + this.showDiff = false; + + // copy from properties + for (var key in props) { + this[key] = props[key]; + } + + // capture stack trace + ssf = ssf || AssertionError; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ssf); + } else { + try { + throw new Error(); + } catch(e) { + this.stack = e.stack; + } + } +} + +/*! + * Inherit from Error.prototype + */ + +AssertionError.prototype = Object.create(Error.prototype); + +/*! + * Statically set name + */ + +AssertionError.prototype.name = 'AssertionError'; + +/*! + * Ensure correct constructor + */ + +AssertionError.prototype.constructor = AssertionError; + +/** + * Allow errors to be converted to JSON for static transfer. + * + * @param {Boolean} include stack (default: `true`) + * @return {Object} object that can be `JSON.stringify` + */ + +AssertionError.prototype.toJSON = function (stack) { + var extend = exclude('constructor', 'toJSON', 'stack') + , props = extend({ name: this.name }, this); + + // include stack if exists and not turned off + if (false !== stack && this.stack) { + props.stack = this.stack; + } + + return props; +}; diff --git a/hamcrest/src/main/ets/components/assertion-error/package.json b/hamcrest/src/main/ets/components/assertion-error/package.json new file mode 100644 index 0000000..6cc8b95 --- /dev/null +++ b/hamcrest/src/main/ets/components/assertion-error/package.json @@ -0,0 +1,62 @@ +{ + "_from": "assertion-error@^1.1.0", + "_id": "assertion-error@1.1.0", + "_inBundle": false, + "_integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "_location": "/assertion-error", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "assertion-error@^1.1.0", + "name": "assertion-error", + "escapedName": "assertion-error", + "rawSpec": "^1.1.0", + "saveSpec": null, + "fetchSpec": "^1.1.0" + }, + "_requiredBy": [ + "/hamjest" + ], + "_resolved": "https://repo.huaweicloud.com/repository/npm/assertion-error/-/assertion-error-1.1.0.tgz", + "_shasum": "e60b6b0e8f301bd97e5375215bda406c85118c0b", + "_spec": "assertion-error@^1.1.0", + "_where": "D:\\JsWorkSpace\\OHOS_APP_hamcrest\\entry\\src\\main\\ets\\MainAbility\\node_modules\\hamjest", + "author": { + "name": "Jake Luer", + "email": "jake@qualiancy.com", + "url": "http://qualiancy.com" + }, + "bugs": { + "url": "https://github.com/chaijs/assertion-error/issues" + }, + "bundleDependencies": false, + "dependencies": {}, + "deprecated": false, + "description": "Error constructor for test and validation frameworks that implements standardized AssertionError specification.", + "devDependencies": { + "component": "*", + "typescript": "^2.6.1" + }, + "engines": { + "node": "*" + }, + "homepage": "https://github.com/chaijs/assertion-error#readme", + "keywords": [ + "test", + "assertion", + "assertion-error" + ], + "license": "MIT", + "main": "./index", + "name": "assertion-error", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/chaijs/assertion-error.git" + }, + "scripts": { + "test": "make test" + }, + "types": "./index.d.ts", + "version": "1.1.0" +} diff --git a/hamcrest/src/main/ets/components/beans/HasProperty.ets b/hamcrest/src/main/ets/components/beans/HasProperty.ets new file mode 100644 index 0000000..cb67b6c --- /dev/null +++ b/hamcrest/src/main/ets/components/beans/HasProperty.ets @@ -0,0 +1,54 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { BaseMatcher } from '../BaseMatcher'; +import { Matcher } from '../Matcher'; + +export class HasProperty extends BaseMatcher { + private propertyName: string; + public constructor(propertyName: string) { + super(); + this.propertyName = propertyName; + } + + public matches(obj: T): boolean { + return obj == null ? false : obj.hasOwnProperty(this.propertyName) + } + + public describeMismatch(item: T, mismatchDescription: Description): void { + mismatchDescription.appendText("no ").appendValue(this.propertyName).appendText(" in ").appendValue(item); + } + + public describeTo(description: Description): void { + description.appendText("hasProperty(").appendValue(this.propertyName).appendText(")"); + } + + /** + * Creates a matcher that matches when the examined object has a JavaBean property + * with the specified name. + * For example: + *
assertThat(myBean, hasProperty("foo"))
+ * + * @param + * the matcher type. + * @param propertyName + * the name of the JavaBean property that examined beans should possess + * @return The matcher. + */ + public static hasProperty(propertyName: string): Matcher { + return new HasProperty(propertyName); + } +} diff --git a/hamcrest/src/main/ets/components/collection/ArrayMatching.ets b/hamcrest/src/main/ets/components/collection/ArrayMatching.ets new file mode 100644 index 0000000..c495362 --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/ArrayMatching.ets @@ -0,0 +1,55 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher' +import {HasItemInArray} from './HasItemInArray' +import {IsEqual} from '../core/IsEqual' + +export class ArrayMatching { + + /** + * Creates a matcher for arrays that matches when the examined array contains at least one item + * that is matched by the specified elementMatcher. Whilst matching, the traversal + * of the examined array will stop as soon as a matching element is found. + * For example: + *
assertThat(new String[] {"foo", "bar"}, hasItemInArray(startsWith("ba")))
+ * + * @param + * the matcher type. + * @param elementMatcher + * the matcher to apply to elements in examined arrays + * @return The matcher. + */ + public static hasItemInArrayMatcher(elementMatcher:Matcher ):Matcher { + return new HasItemInArray(elementMatcher); + } + + /** + * A shortcut to the frequently used hasItemInArray(equalTo(x)). + * For example: + *
assertThat(hasItemInArray(x))
+ * instead of: + *
assertThat(hasItemInArray(equalTo(x)))
+ * + * @param + * the matcher type. + * @param element + * the element that should be present in examined arrays + * @return The matcher. + */ + public static hasItemInArray( element:Array):Matcher> { + return this.hasItemInArrayMatcher(IsEqual.equalTo(element)); + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/HasItemInArray.ets b/hamcrest/src/main/ets/components/collection/HasItemInArray.ets new file mode 100644 index 0000000..c8802c2 --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/HasItemInArray.ets @@ -0,0 +1,54 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher' +import {BaseMatcher} from '../BaseMatcher' +import {Description} from '../Description' +import {IsIterableContaining} from '../core/IsIterableContaining' + +export class HasItemInArray extends BaseMatcher> { + private readonly elementMatcher: Matcher; + private readonly collectionMatcher: BaseMatcher ; + + public constructor(elementMatcher: Matcher) { + super() + this.elementMatcher = elementMatcher; + this.collectionMatcher = new IsIterableContaining(elementMatcher); + } + + public matches(item: Object): boolean { + var source = new Array(); + source.push(item) + return this.collectionMatcher.matches(source) + } + + public describeMismatch(item: Object, mismatchDescription: Description): void { + + } + + public matchesSafely(actual: Array): boolean { + return this.collectionMatcher.matches(actual); + } + + public describeMismatchSafely(actual: Array, mismatchDescription: Description): void { + this.collectionMatcher.describeMismatch(actual, mismatchDescription); + } + + public describeTo(description: Description): void { + description + .appendText("an array containing ") + .appendDescriptionOf(this.elementMatcher); + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/IsArray.ets b/hamcrest/src/main/ets/components/collection/IsArray.ets new file mode 100644 index 0000000..3f2846c --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/IsArray.ets @@ -0,0 +1,94 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from '../BaseMatcher'; +import {Description} from '../Description' +import {Matcher} from '../Matcher' + +/** + * Matcher for array whose elements satisfy a sequence of matchers. + * The array size must equal the number of element matchers. + */ +export class IsArray extends BaseMatcher { + elementMatchers: Matcher[] + + constructor(mArray: Matcher[]) { + super(); + this.elementMatchers = mArray; + } + + describeTo(description: Description) { + description.appendList(this.descriptionStart(), this.descriptionSeparator(), this.descriptionEnd(), this.elementMatchers) + } + + matches(actual: Object) { + /** + * 类型转换 + */ + var array = []; + Object.keys(actual).forEach(function(key:string){ + array.push(actual[key]) + }) + if (array.length != this.elementMatchers.length) return false; + + for (var i = 0; i < array.length; i++) { + if (!this.elementMatchers[i].matches(array[i])) return false; + } + + return true + }; + + describeMismatch(items: Object, description: Description) { + var actual = []; + for (var key in items) { + if (!item.hasOwnProperty(key)) { + continue; + } + var item = {}; + item[key] = items[key]; + actual.push(item); + } + + if (actual.length != this.elementMatchers.length) { + + description.appendText("array length was ").appendValue(actual.length); + return; + } + + for (var i = 0; i < actual.length; i++) { + if (!this.elementMatchers[i].matches(actual[i])) { + description.appendText("element ").appendValue(i).appendText(" "); + this.elementMatchers[i].describeMismatch(actual[i], description); + return; + } + } + } + + protected descriptionStart(): string { + return "["; + } + + protected descriptionSeparator(): string { + return ", "; + } + + protected descriptionEnd(): string { + return "]"; + } + + public static array(...elementMatchers: any[]):Matcher { + return new IsArray(elementMatchers) + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/IsArrayWithSize.ets b/hamcrest/src/main/ets/components/collection/IsArrayWithSize.ets new file mode 100644 index 0000000..6eb2680 --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/IsArrayWithSize.ets @@ -0,0 +1,62 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {FeatureMatcher} from '../FeatureMatcher'; +import {Matcher} from '../Matcher' +import {IsEqual} from '../core/IsEqual' + +export class IsArrayWithSize extends FeatureMatcher, Number>{ + + public constructor(sizeMatcher:Matcher< Number> ) { + super(sizeMatcher, "an array with size","array size"); + } + + + protected featureValueOf(actual:Array):Number { + return actual.length; + } + + /** + * Creates a matcher for arrays that matches when the length of the array + * satisfies the specified matcher. + * For example: + *
assertThat(new String[]{"foo", "bar"}, arrayWithSize(equalTo(2)))
+ * @param + * the matcher type. + * @param sizeMatcher + * a matcher for the length of an examined array + * @return The matcher. + */ + public static arrayWithSize(sizeMatcher:Matcher ):Matcher> { + return new IsArrayWithSize(sizeMatcher); + } + + /** + * Creates a matcher for arrays that matches when the length of the array + * equals the specified size. + * For example: + *
assertThat(new String[]{"foo", "bar"}, arrayWithSize(2))
+ * + * @param + * the matcher type. + * @param size + * the length that an examined array must have for a positive match + * @return The matcher. + */ + public static arrayWithSizeOf(size:number):Matcher> { + return IsArrayWithSize.arrayWithSize(IsEqual.equalTo(size)); + } + +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/IsIn.ets b/hamcrest/src/main/ets/components/collection/IsIn.ets new file mode 100644 index 0000000..3154de8 --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/IsIn.ets @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from '../BaseMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher' + +export class IsIn extends BaseMatcher { + collection: any[]; + + constructor(collection: any[]) { + super() + this.collection = collection + } + + matches(actual: Object) { + return this.collection.indexOf(actual) != -1 + } + + describeTo(buffer: Description) { + buffer.appendText("one of "); + buffer.appendList("{", ", ", "}", this.collection); + } + + public static isIn(collection: any[]): Matcher{ + return this.in(collection) + } + + public static in(collection: any[]): Matcher { + return new IsIn(collection); + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/IsMapContaining.ets b/hamcrest/src/main/ets/components/collection/IsMapContaining.ets new file mode 100644 index 0000000..aacfd9c --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/IsMapContaining.ets @@ -0,0 +1,119 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher' +import {BaseMatcher} from '../BaseMatcher' +import {Description} from '../Description' +import {IsAnything} from '../core/IsAnything' +import {StringDescription} from '../StringDescription' + +export class IsMapContaining extends BaseMatcher>{ + + private readonly keyMatcher:Matcher; + private readonly valueMatcher:Matcher; + + public constructor(keyMatcher:Matcher, valueMatcher:Matcher) { + super() + this.keyMatcher = keyMatcher; + this.valueMatcher = valueMatcher; + + } + + public matchesSafely( map:Map):boolean { + map.forEach((value,key,map)=>{ + if (this.keyMatcher.matches(key) && this.valueMatcher.matches(value)) { + return true; + } + }) + return false; + } + + public describeMismatchSafely( map:Map, mismatchDescription:Description) :void{ + console.info("IsMapContaining describeMismatchSafely:"+JSON.stringify(map)) + mismatchDescription.appendText("map was ").appendValueList("[", ", ", "]", null); + } + + public describeTo( description:Description):void { + description.appendText("map containing [") + .appendDescriptionOf(this.keyMatcher) + .appendText("->") + .appendDescriptionOf(this.valueMatcher) + .appendText("]"); + } + + /** + * Creates a matcher for {@link java.util.Map}s matching when the examined {@link java.util.Map} contains + * at least one entry whose key satisfies the specified keyMatcher and whose + * value satisfies the specified valueMatcher. + * For example: + *
assertThat(myMap, hasEntry(equalTo("bar"), equalTo("foo")))
+ * + * @param + * the map key type. + * @param + * the map value type. + * @param keyMatcher + * the key matcher that, in combination with the valueMatcher, must be satisfied by at least one entry + * @param valueMatcher + * the value matcher that, in combination with the keyMatcher, must be satisfied by at least one entry + * @return The matcher. + */ + public static hasEntry( keyMatcher:Matcher, valueMatcher:Matcher):Matcher> { + return new IsMapContaining(keyMatcher, valueMatcher); + } + + + /** + * Creates a matcher for {@link java.util.Map}s matching when the examined {@link java.util.Map} contains + * at least one key that satisfies the specified matcher. + * For example: + *
assertThat(myMap, hasKey(equalTo("bar")))
+ * + * @param + * the map key type. + * @param keyMatcher + * the matcher that must be satisfied by at least one key + * @return The matcher. + */ + public static hasKey( keyMatcher:Matcher):Matcher> { + return new IsMapContaining(keyMatcher, IsAnything.anything()); + } + + + /** + * Creates a matcher for {@link java.util.Map}s matching when the examined {@link java.util.Map} contains + * at least one value that satisfies the specified valueMatcher. + * For example: + *
assertThat(myMap, hasValue(equalTo("foo")))
+ * + * @param + * the value type. + * @param valueMatcher + * the matcher that must be satisfied by at least one value + * @return The matcher. + */ + public static hasValue(valueMatcher:Matcher):Matcher> { + return new IsMapContaining(IsAnything.anything(), valueMatcher); + } + public matches(map:Map): boolean { + map.forEach((value,key,map)=>{ + if (this.keyMatcher.matches(key) && this.valueMatcher.matches(value)) { + return true; + } + }) + return false; + } + +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/collection/IsMapWithSize.ets b/hamcrest/src/main/ets/components/collection/IsMapWithSize.ets new file mode 100644 index 0000000..3ecce16 --- /dev/null +++ b/hamcrest/src/main/ets/components/collection/IsMapWithSize.ets @@ -0,0 +1,82 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {FeatureMatcher} from '../FeatureMatcher'; +import {Matcher} from '../Matcher' +import {IsEqual} from '../core/IsEqual' + +export class IsMapWithSize extends FeatureMatcher, Number>{ + + public constructor( sizeMatcher:Matcher) { + super(sizeMatcher, "a map with size", "map size"); + } + + + protected featureValueOf(actual:Map):Number { + return actual.size; + } + + /** + * Creates a matcher for {@link java.util.Map}s that matches when the size() method returns + * a value that satisfies the specified matcher. + * For example: + *
assertThat(myMap, is(aMapWithSize(equalTo(2))))
+ * + * @param + * the map key type. + * @param + * the map value type. + * @param sizeMatcher + * a matcher for the size of an examined {@link java.util.Map} + * @return The matcher. + */ + public static aMapWithSizeOfMatcher(sizeMatcher:Matcher ):Matcher> { + return new IsMapWithSize(sizeMatcher); + } + + /** + * Creates a matcher for {@link java.util.Map}s that matches when the size() method returns + * a value equal to the specified size. + * For example: + *
assertThat(myMap, is(aMapWithSize(2)))
+ * + * @param + * the map key type. + * @param + * the map value type. + * @param size + * the expected size of an examined {@link java.util.Map} + * @return The matcher. + */ + public static aMapWithSize(size:number):Matcher> { + return IsMapWithSize.aMapWithSizeOfMatcher(IsEqual.equalTo(size)); + } + + /** + * Creates a matcher for {@link java.util.Map}s that matches when the size() method returns + * zero. + * For example: + *
assertThat(myMap, is(anEmptyMap()))
+ * + * @param + * the map key type. + * @param + * the map value type. + * @return The matcher. + */ + public static anEmptyMap():Matcher> { + return IsMapWithSize.aMapWithSizeOfMatcher(IsEqual.equalTo(0)); + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/core/AllOf.ets b/hamcrest/src/main/ets/components/core/AllOf.ets new file mode 100644 index 0000000..cd6e6ea --- /dev/null +++ b/hamcrest/src/main/ets/components/core/AllOf.ets @@ -0,0 +1,75 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { DiagnosingMatcher } from '../DiagnosingMatcher'; +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; + +/** + * Calculates the logical conjunction of multiple matchers. Evaluation is shortcut, so + * subsequent matchers are not called if an earlier matcher returns false. + */ +export class AllOf extends DiagnosingMatcher { + private matchers: Array>; + constructor(matchers: Array>) { + super() + this.matchers = matchers; + } + + public matchesWithDiagnosingMatcher(o: Object, mismatch: Description): boolean { + for (let matcher of this.matchers) { + if (!matcher.matches(o)) { + mismatch.appendDescriptionOf(matcher).appendText(" "); + matcher.describeMismatch(o, mismatch); + return false; + } + } + return true; + } + + public describeTo(description: Description): void { + description.appendList("(", " " + "and" + " ", ")", this.matchers); + } + + /** + * Creates a matcher that matches if the examined object matches ALL of the specified matchers. + * For example: + *
assertThat("myValue", allOf(startsWith("my"), containsString("Val")))
+ * + * @param + * the matcher type. + * @param matchers + * all the matchers must pass. + * @return The matcher. + */ + public static allOf(matchers: Array>): Matcher { + return new AllOf(matchers); + } + + /** + * Creates a matcher that matches if the examined object matches ALL of the specified matchers. + * For example: + *
assertThat("myValue", allOf(startsWith("my"), containsString("Val")))
+ * + * @param + * the matcher type. + * @param matchers + * all the matchers must pass. + * @return The matcher. + */ + public static allOfMatches(...matchers: Matcher[]): Matcher { + return AllOf.allOf(matchers); + } +} diff --git a/hamcrest/src/main/ets/components/core/AnyOf.ets b/hamcrest/src/main/ets/components/core/AnyOf.ets new file mode 100644 index 0000000..5005178 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/AnyOf.ets @@ -0,0 +1,67 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { ShortcutCombination } from './ShortcutCombination'; +import { Matcher } from '../Matcher'; + + +/** + * Calculates the logical disjunction of multiple matchers. Evaluation is shortcut, so + * subsequent matchers are not called if an earlier matcher returns true. + */ +export class AnyOf extends ShortcutCombination { + public constructor(matchers: Array>) { + super(matchers); + } + + public matches(o: Object): boolean { + return super.matchesWithShortcut(o, true); + } + + public describeTo(description: Description): void { + super.describeToWithShortcut(description, "or"); + } + + /** + * Creates a matcher that matches if the examined object matches ANY of the specified matchers. + * For example: + *
assertThat("myValue", anyOf(startsWith("foo"), containsString("Val")))
+ * + * @param + * the matcher type. + * @param matchers + * any the matchers must pass. + * @return The matcher. + */ + public static anyOf(matchers: Array>): AnyOf { + return new AnyOf(matchers); + } + + /** + * Creates a matcher that matches if the examined object matches ANY of the specified matchers. + * For example: + *
assertThat("myValue", anyOf(startsWith("foo"), containsString("Val")))
+ * + * @param + * the matcher type. + * @param matchers + * any the matchers must pass. + * @return The matcher. + */ + public static anyOfMatches(... matchers: Matcher[]): AnyOf { + return AnyOf.anyOf(matchers); + } +} diff --git a/hamcrest/src/main/ets/components/core/CombinableMatcher.ets b/hamcrest/src/main/ets/components/core/CombinableMatcher.ets new file mode 100644 index 0000000..be37e09 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/CombinableMatcher.ets @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {DiagnosingMatcher} from '../DiagnosingMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {AllOf} from './AllOf'; +import {AnyOf} from './AnyOf'; + +export class CombinableMatcher extends DiagnosingMatcher { + private matcher: Matcher; + + public constructor(matcher: Matcher) { + super() + this.matcher = matcher; + } + + protected matchesWithDiagnosingMatcher(item: T, mismatch: Description): boolean { + if (!this.matcher.matches(item)) { + this.matcher.describeMismatch(item, mismatch); + return false; + } + return true; + } + + public describeTo(description: Description): void{ + description.appendDescriptionOf(this.matcher); + } + + public and(other: Matcher): CombinableMatcher{ + return new CombinableMatcher(new AllOf(this.templatedListWith(other))); + } + + public or(other: Matcher): CombinableMatcher { + return new CombinableMatcher(new AnyOf(this.templatedListWith(other))); + } + + private templatedListWith(other: Matcher): Array>{ + let matchers: Array> = []; + matchers.push(this.matcher); + matchers.push(other); + return matchers; + } +} diff --git a/hamcrest/src/main/ets/components/core/Every.ets b/hamcrest/src/main/ets/components/core/Every.ets new file mode 100644 index 0000000..0515feb --- /dev/null +++ b/hamcrest/src/main/ets/components/core/Every.ets @@ -0,0 +1,46 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {DiagnosingMatcher} from '../DiagnosingMatcher'; + +export class Every extends DiagnosingMatcher> { + private matcher: Matcher; + + public constructor(matcher: Matcher) { + super(); + this.matcher = matcher; + } + + public matchesWithDiagnosingMatcher(collection: T[], mismatchDescription: Description): boolean { + for (let t of collection) { + if (!this.matcher.matches(t)) { + mismatchDescription.appendText("an item "); + this.matcher.describeMismatch(t, mismatchDescription); + return false; + } + } + return true; + } + + public describeTo(description: Description): void { + description.appendText("every item is ").appendDescriptionOf(this.matcher); + } + + public static everyItem(itemMatcher: Matcher): Matcher> { + return new Every (itemMatcher); + } +} diff --git a/hamcrest/src/main/ets/components/core/Is.ets b/hamcrest/src/main/ets/components/core/Is.ets new file mode 100644 index 0000000..cb5a9e2 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/Is.ets @@ -0,0 +1,65 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from '../BaseMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; + +/** + * Decorates another Matcher, retaining the behaviour but allowing tests + * to be slightly more expressive. + * + * For example: assertThat(cheese, equalTo(smelly)) + * vs. assertThat(cheese, is(equalTo(smelly))) + */ +export class Is extends BaseMatcher { + + private matcher: Matcher; + + public constructor( matcher: Matcher) { + super(); + this.matcher = matcher; + } + + public matches( arg:Object):boolean { + return this.matcher.matches(arg); + } + + public describeTo( description: Description): void { + description.appendText("is ").appendDescriptionOf(this.matcher); + } + + public describeMismatch( item: Object, mismatchDescription: Description): void { + this.matcher.describeMismatch(item, mismatchDescription); + } + + /** + * Decorates another Matcher, retaining its behaviour, but allowing tests + * to be slightly more expressive. + * For example: + *
assertThat(cheese, is(equalTo(smelly)))
+ * instead of: + *
assertThat(cheese, equalTo(smelly))
+ * + * @param + * the matcher type. + * @param matcher + * the matcher to wrap. + * @return The matcher. + */ + public static is(matcher: Matcher): Matcher { + return new Is(matcher); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsAnything.ets b/hamcrest/src/main/ets/components/core/IsAnything.ets new file mode 100644 index 0000000..4ba2b04 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsAnything.ets @@ -0,0 +1,52 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; +import { BaseMatcher } from '../BaseMatcher'; + +/** + * A matcher that always returns true. + */ +export class IsAnything extends BaseMatcher { + private message: string; + public constructor(message?: string) { + super(); + if (message) { + this.message = message; + } + this.message = 'ANYTHING'; + } + + public matches(o: Object): boolean { + return true; + } + + public describeTo(description: Description): void{ + description.appendText(this.message); + } + + /** + * Creates a matcher that always matches, regardless of the examined object, but describes + * itself with the specified {@link String}. + * + * @param description + * a meaningful {@link String} used when describing itself + * @return The matcher. + */ + public static anything(description?: string): Matcher { + return new IsAnything(description); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsEqual.ets b/hamcrest/src/main/ets/components/core/IsEqual.ets new file mode 100644 index 0000000..dfc790e --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsEqual.ets @@ -0,0 +1,111 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from '../BaseMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; + +/** + * Is the value equal to another value, as tested by the + * {@link java.lang.Object#equals} invokedMethod? + */ +export class IsEqual extends BaseMatcher { + private expectedValue: Object; + + public constructor(equalArg: T) { + super(); + this.expectedValue = equalArg; + } + + public matches(actualValue: Object): boolean{ + return IsEqual.areEqual(actualValue, this.expectedValue); + } + + public describeTo(description: Description): void { + description.appendValue(this.expectedValue); + } + + private static areEqual(actual: Object, expected: Object): boolean { + if (actual == null) { + return expected == null; + } + + if (expected != null && Array.isArray(actual)) { + return Array.isArray(expected) && IsEqual.areArraysEqual(actual, expected); + } + + return actual == expected; + } + + private static areArraysEqual(actualArray: Object, expectedArray: Object): boolean { + return IsEqual.areArrayLengthsEqual(actualArray, expectedArray) && IsEqual.areArrayElementsEqual(actualArray, expectedArray); + } + + private static areArrayLengthsEqual(actualArray: Object, expectedArray: Object): boolean { + return (> actualArray).length == (> expectedArray).length; + } + + private static areArrayElementsEqual(actualArray: Object, expectedArray: Object): boolean { + for (let i = 0; i < (> actualArray).length; i++) { + if (!IsEqual.areEqual(actualArray[i], expectedArray[i])) { + return false; + } + } + return true; + } + + /** + * Creates a matcher that matches when the examined object is logically equal to the specified + * operand, as determined by calling the {@link java.lang.Object#equals} method on + * the examined object. + * + *

If the specified operand is null then the created matcher will only match if + * the examined object's equals method returns true when passed a + * null (which would be a violation of the equals contract), unless the + * examined object itself is null, in which case the matcher will return a positive + * match.

+ * + *

The created matcher provides a special behaviour when examining Arrays, whereby + * it will match if both the operand and the examined object are arrays of the same length and + * contain items that are equal to each other (according to the above rules) in the same + * indexes.

+ * For example: + *
+     * assertThat("foo", equalTo("foo"));
+     * assertThat(new String[] {"foo", "bar"}, equalTo(new String[] {"foo", "bar"}));
+     * 
+ * + * @param + * the matcher type. + * @param operand + * the value to check. + * @return The matcher. + */ + public static equalTo(operand: Object): Matcher { + return new IsEqual (operand); + } + + /** + * Creates an {@link org.hamcrest.core.IsEqual} matcher that does not enforce the values being + * compared to be of the same static type. + * + * @param operand + * the value to check. + * @return The matcher. + */ + public static equalToObject(operand: Object): Matcher { + return new IsEqual (operand); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsInstanceOf.ets b/hamcrest/src/main/ets/components/core/IsInstanceOf.ets new file mode 100644 index 0000000..81fc4d4 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsInstanceOf.ets @@ -0,0 +1,70 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {DiagnosingMatcher} from '../DiagnosingMatcher'; +import {Matcher} from '../Matcher'; + +/** + * Tests whether the value is an instance of a class. + * Classes of basic types will be converted to the relevant "Object" classes + */ +export class IsInstanceOf extends DiagnosingMatcher { + + private expectedClass; + + /** + * Creates a new instance of IsInstanceOf + * + * @param expectedClass The predicate evaluates to true for instances of this class + * or one of its subclasses. + */ + public constructor(expectedClass) { + super(); + this.expectedClass = expectedClass; + } + + protected matchesWithDiagnosingMatcher(item: Object, mismatch: Description) : boolean{ + if (null == item) { + mismatch.appendText("null"); + return false; + } + + return item instanceof this.expectedClass; + } + + public describeTo( description: Description): void { + description.appendText("an instance of ").appendText(this.expectedClass.constructor.name); + } + + /** + * Creates a matcher that matches when the examined object is an instance of the specified type, + * as determined by calling the {@link java.lang.Class#isInstance(Object)} method on that type, passing the + * the examined object. + * + *

The created matcher assumes no relationship between specified type and the examined object.

+ * For example: + *
assertThat(new Canoe(), instanceOf(Paddlable.class));
+ * + * @param + * the matcher type. + * @param type + * the type to check. + * @return The matcher. + */ + public static instanceOf(typeValue: Object): Matcher { + return new IsInstanceOf(typeValue); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsIterableContaining.ets b/hamcrest/src/main/ets/components/core/IsIterableContaining.ets new file mode 100644 index 0000000..bd0160a --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsIterableContaining.ets @@ -0,0 +1,142 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {DiagnosingMatcher} from '../DiagnosingMatcher'; +import {IsEqual} from './IsEqual'; +import {AllOf} from './AllOf'; + +export class IsIterableContaining extends DiagnosingMatcher { + private elementMatcher: Matcher; + + public constructor(elementMatcher: Matcher) { + super(); + this.elementMatcher = elementMatcher; + } + + protected matchesWithDiagnosingMatcher(collection: Array, mismatchDescription: Description): boolean { + if (this.isEmpty(collection)) { + mismatchDescription.appendText("was empty"); + return false; + } + + for (let item of collection) { + if (this.elementMatcher.matches(item)) { + return true; + } + } + + mismatchDescription.appendText("mismatches were: ["); + let isPastFirst: boolean = false; + for (let item of collection) { + if (isPastFirst) { + mismatchDescription.appendText(", "); + } + this.elementMatcher.describeMismatch(item, mismatchDescription); + isPastFirst = true; + } + mismatchDescription.appendText("]"); + return false; + } + + private isEmpty(iterable: Array): boolean { + return iterable == null ? true : iterable.length == 0; + } + + public describeTo(description: Description): void { + description + .appendText("a collection containing ") + .appendDescriptionOf(this.elementMatcher); + } + + /** + * Creates a matcher for {@link Iterable}s that only matches when a single pass over the + * examined {@link Iterable} yields at least one item that is matched by the specified + * itemMatcher. Whilst matching, the traversal of the examined {@link Iterable} + * will stop as soon as a matching item is found. + * For example: + *
assertThat(Arrays.asList("foo", "bar"), hasItem(startsWith("ba")))
+ * + * @param + * the matcher type. + * @param itemMatcher + * the matcher to apply to items provided by the examined {@link Iterable} + * @return The matcher. + */ + public static hasItem(itemMatcher: Matcher): Matcher { + return new IsIterableContaining(itemMatcher); + } + + /** + * Creates a matcher for {@link Iterable}s that only matches when a single pass over the + * examined {@link Iterable} yields at least one item that is equal to the specified + * item. Whilst matching, the traversal of the examined {@link Iterable} + * will stop as soon as a matching item is found. + * For example: + *
assertThat(Arrays.asList("foo", "bar"), hasItem("bar"))
+ * + * @param + * the matcher type. + * @param item + * the item to compare against the items provided by the examined {@link Iterable} + * @return The matcher. + */ + public static hasTargetItem(item: Object): Matcher { + // Doesn't forward to hasItem() method so compiler can sort out generics. + return new IsIterableContaining(IsEqual.equalTo(item)); + } + + /** + * Creates a matcher for {@link Iterable}s that matches when consecutive passes over the + * examined {@link Iterable} yield at least one item that is matched by the corresponding + * matcher from the specified itemMatchers. Whilst matching, each traversal of + * the examined {@link Iterable} will stop as soon as a matching item is found. + * For example: + *
assertThat(Arrays.asList("foo", "bar", "baz"), hasItems(endsWith("z"), endsWith("o")))
+ * + * @param + * the matcher type. + * @param itemMatchers + * the matchers to apply to items provided by the examined {@link Iterable} + * @return The matcher. + */ + public static hasItems(...itemMatchers: Matcher[]): Matcher { + return AllOf.allOf(itemMatchers); + } + + /** + * Creates a matcher for {@link Iterable}s that matches when consecutive passes over the + * examined {@link Iterable} yield at least one item that is equal to the corresponding + * item from the specified items. Whilst matching, each traversal of the + * examined {@link Iterable} will stop as soon as a matching item is found. + * For example: + *
assertThat(Arrays.asList("foo", "bar", "baz"), hasItems("baz", "foo"))
+ * + * @param + * the matcher type. + * @param items + * the items to compare against the items provided by the examined {@link Iterable} + * @return The matcher. + */ + public static hasTargetItems(...items: Object[]): Matcher { + let all: Matcher[] = [] + for (let item of items) { + all.push(IsIterableContaining.hasTargetItem(item)) + } + + return AllOf.allOf(all); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsNot.ets b/hamcrest/src/main/ets/components/core/IsNot.ets new file mode 100644 index 0000000..6517ede --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsNot.ets @@ -0,0 +1,53 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from '../BaseMatcher'; +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; + +/** + * Calculates the logical negation of a matcher. + */ +export class IsNot extends BaseMatcher { + private matcher: Matcher; + public constructor(matcher: Matcher) { + super(); + this.matcher = matcher; + } + + public matches(arg: Object): boolean { + return!this.matcher.matches(arg); + } + + public describeTo(description: Description): void { + description.appendText("not ").appendDescriptionOf(this.matcher); + } + + /** + * Creates a matcher that wraps an existing matcher, but inverts the logic by which + * it will match. + * For example: + *
assertThat(cheese, is(not(equalTo(smelly))))
+ * + * @param + * the matcher type. + * @param matcher + * the matcher whose sense should be inverted + * @return The matcher. + */ + public static not(matcher: Matcher): Matcher{ + return new IsNot(matcher); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsNull.ets b/hamcrest/src/main/ets/components/core/IsNull.ets new file mode 100644 index 0000000..798863c --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsNull.ets @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from '../BaseMatcher'; +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; +import { IsNot } from './IsNot'; + +/** + * Is the value null? + */ +export class IsNull extends BaseMatcher { + public matches(o: Object): boolean { + return o == null; + } + + public describeTo(description: Description): void{ + description.appendText("null"); + } + + /** + * Creates a matcher that matches if examined object is null. + * For example: + *
assertThat(cheese, is(nullValue())
+ * + * @return The matcher. + */ + public static nullValue(): Matcher{ + return new IsNull(); + } + + /** + * A shortcut to the frequently used not(nullValue()). + * For example: + *
assertThat(cheese, is(notNullValue()))
+ * instead of: + *
assertThat(cheese, is(not(nullValue())))
+ * + * @return The matcher. + */ + public static notNullValue(): Matcher { + return IsNot.not(IsNull.nullValue()); + } +} diff --git a/hamcrest/src/main/ets/components/core/IsSame.ets b/hamcrest/src/main/ets/components/core/IsSame.ets new file mode 100644 index 0000000..e8aa695 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/IsSame.ets @@ -0,0 +1,67 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from '../BaseMatcher'; +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; + +/** + * Is the value the same object as another value? + */ +export class IsSame extends BaseMatcher { + private object: T; + public constructor(object: T) { + super(); + this.object = object; + } + + public matches(arg: Object): boolean { + return arg === this.object; + } + + public describeTo(description: Description): void { + description.appendText("sameInstance(") + .appendValue(this.object) + .appendText(")"); + } + + /** + * Creates a matcher that matches only when the examined object is the same instance as + * the specified target object. + * + * @param + * the matcher type. + * @param target + * the target instance against which others should be assessed + * @return The matcher. + */ + public static sameInstance(target: Object): Matcher { + return new IsSame (target); + } + + /** + * Creates a matcher that matches only when the examined object is the same instance as + * the specified target object. + * + * @param + * the matcher type. + * @param target + * the target instance against which others should be assessed + * @return The matcher. + */ + public static theInstance(target: Object): Matcher{ + return new IsSame (target); + } +} diff --git a/hamcrest/src/main/ets/components/core/ShortcutCombination.ets b/hamcrest/src/main/ets/components/core/ShortcutCombination.ets new file mode 100644 index 0000000..40bae19 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/ShortcutCombination.ets @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from '../BaseMatcher'; +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; + +export abstract class ShortcutCombination extends BaseMatcher { + private matchers: Array>; + + public constructor(matchers: Array>) { + super(); + this.matchers = matchers; + } + + public abstract matches(o: Object): boolean; + + public abstract describeTo(description: Description): void; + + protected matchesWithShortcut(o: Object, shortcut: boolean): boolean { + for (let matcher of this.matchers) { + if (matcher.matches(o) == shortcut) { + return shortcut; + } + } + return!shortcut; + } + + public describeToWithShortcut(description: Description, operator: String): void { + description.appendList("(", " " + operator + " ", ")", this.matchers); + } +} diff --git a/hamcrest/src/main/ets/components/core/StringContains.ets b/hamcrest/src/main/ets/components/core/StringContains.ets new file mode 100644 index 0000000..4694024 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/StringContains.ets @@ -0,0 +1,59 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher'; +import {SubstringMatcher} from './SubstringMatcher'; + +/** + * Tests if the argument is a string that contains a specific substring. + */ +export class StringContains extends SubstringMatcher { + public constructor(ignoringCase: boolean, substring: String) { + super("containing", ignoringCase, substring); + } + + protected evalSubstringOf(s: String): boolean { + return super.converted(s).indexOf(super.converted(this.substring).toString()) >= 0; + return false; + } + + /** + * Creates a matcher that matches if the examined {@link String} contains the specified + * {@link String} anywhere. + * For example: + *
assertThat("myStringOfNote", containsString("ring"))
+ * + * @param substring + * the substring that the returned matcher will expect to find within any examined string + * @return The matcher. + */ + public static containsString(substring: String): Matcher { + return new StringContains(false, substring); + } + + /** + * Creates a matcher that matches if the examined {@link String} contains the specified + * {@link String} anywhere, ignoring case. + * For example: + *
assertThat("myStringOfNote", containsStringIgnoringCase("Ring"))
+ * + * @param substring + * the substring that the returned matcher will expect to find within any examined string + * @return The matcher. + */ + public static containsStringIgnoringCase(substring: String): Matcher { + return new StringContains(true, substring); + } +} diff --git a/hamcrest/src/main/ets/components/core/StringEndsWith.ets b/hamcrest/src/main/ets/components/core/StringEndsWith.ets new file mode 100644 index 0000000..66f3254 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/StringEndsWith.ets @@ -0,0 +1,58 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher'; +import {SubstringMatcher} from './SubstringMatcher'; + +/** + * Tests if the argument is a string that ends with a specific substring. + */ +export class StringEndsWith extends SubstringMatcher { + public constructor(ignoringCase: boolean, substring: String) { + super("ending with", ignoringCase, substring); + } + + protected evalSubstringOf(s: String): boolean { + return super.converted(s).endsWith(super.converted(this.substring).toString()); + } + + /** + * Creates a matcher that matches if the examined {@link String} ends with the specified + * {@link String}. + * For example: + *
assertThat("myStringOfNote", endsWith("Note"))
+ * + * @param suffix + * the substring that the returned matcher will expect at the end of any examined string + * @return The matcher. + */ + public static endsWith(suffix: String): Matcher { + return new StringEndsWith(false, suffix); + } + + /** + * Creates a matcher that matches if the examined {@link String} ends with the specified + * {@link String}, ignoring case. + * For example: + *
assertThat("myStringOfNote", endsWithIgnoringCase("note"))
+ * + * @param suffix + * the substring that the returned matcher will expect at the end of any examined string + * @return The matcher. + */ + public static endsWithIgnoringCase(suffix: String): Matcher{ + return new StringEndsWith(true, suffix); + } +} diff --git a/hamcrest/src/main/ets/components/core/StringRegularExpression.ets b/hamcrest/src/main/ets/components/core/StringRegularExpression.ets new file mode 100644 index 0000000..769f5b5 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/StringRegularExpression.ets @@ -0,0 +1,61 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {DiagnosingMatcher} from '../DiagnosingMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; + +export class StringRegularExpression extends DiagnosingMatcher { + private pattern: RegExp; + + protected constructor(pattern: RegExp) { + super(); + this.pattern = pattern; + } + + public describeTo(description: Description): void { + description.appendText("a string matching the pattern ").appendValue(this.pattern); + } + + protected matchesWithDiagnosingMatcher(actual: string, mismatchDescription: Description): boolean{ + if (!this.pattern.test(actual.toString())) { + mismatchDescription.appendText("the string was ").appendValue(actual); + return false; + } + return true; + } + + /** + * Creates a matcher that checks if the examined string matches a specified {@link java.util.regex.Pattern}. + * + * @param pattern + * the pattern to be used. + * @return The matcher. + */ + public static matchesRegex(pattern: RegExp): Matcher { + return new StringRegularExpression(pattern); + } + + /** + * Creates a matcher that checks if the examined string matches a specified regex. + * + * @param regex + * The regex to be used for the validation. + * @return The matcher. + */ + public static matchesStringRegex(regex: string): Matcher { + return StringRegularExpression.matchesRegex(new RegExp(regex)); + } +} diff --git a/hamcrest/src/main/ets/components/core/StringStartsWith.ets b/hamcrest/src/main/ets/components/core/StringStartsWith.ets new file mode 100644 index 0000000..1fb73b2 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/StringStartsWith.ets @@ -0,0 +1,62 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Matcher } from '../Matcher'; +import { SubstringMatcher } from './SubstringMatcher'; + +/** + * Tests if the argument is a string that starts with a specific substring. + */ +export class StringStartsWith extends SubstringMatcher { + public constructor(ignoringCase: boolean, substring: String) { + super("starting with", ignoringCase, substring) + } + + protected evalSubstringOf(s: String): boolean { + return super.converted(s).startsWith(super.converted(this.substring).toString()); + } + + /** + *

+ * Creates a matcher that matches if the examined {@link String} starts with the specified + * {@link String}. + *

+ * For example: + *
assertThat("myStringOfNote", startsWith("my"))
+ * + * @param prefix + * the substring that the returned matcher will expect at the start of any examined string + * @return The matcher. + */ + public static startsWith(prefix: String): Matcher { + return new StringStartsWith(false, prefix); + } + + /** + *

+ * Creates a matcher that matches if the examined {@link String} starts with the specified + * {@link String}, ignoring case + *

+ * For example: + *
assertThat("myStringOfNote", startsWithIgnoringCase("My"))
+ * + * @param prefix + * the substring that the returned matcher will expect at the start of any examined string + * @return The matcher. + */ + public static startsWithIgnoringCase(prefix: String): Matcher { + return new StringStartsWith(true, prefix); + } +} diff --git a/hamcrest/src/main/ets/components/core/SubstringMatcher.ets b/hamcrest/src/main/ets/components/core/SubstringMatcher.ets new file mode 100644 index 0000000..8ce2316 --- /dev/null +++ b/hamcrest/src/main/ets/components/core/SubstringMatcher.ets @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { BaseMatcher } from '../BaseMatcher'; + +export abstract class SubstringMatcher extends BaseMatcher { + private relationship: String; + private ignoringCase: boolean; + protected substring: String; + protected constructor(relationship: String, ignoringCase: boolean, substring: String) { + super(); + this.relationship = relationship; + this.ignoringCase = ignoringCase; + this.substring = substring; + if (null == substring) { + throw new Error("missing substring"); + } + } + + public matches(item: String): boolean { + return this.evalSubstringOf(this.ignoringCase ? item.toLowerCase() : item); + } + + public describeMismatch(item: String, mismatchDescription: Description): void { + mismatchDescription.appendText("was \"").appendText(item).appendText("\""); + } + + public describeTo(description: Description): void { + description.appendText("a string ") + .appendText(this.relationship) + .appendText(" ") + .appendValue(this.substring); + if (this.ignoringCase) { + description.appendText(" ignoring case"); + } + } + + protected converted(arg: String): String { + return this.ignoringCase ? arg.toLowerCase() : arg; + } + + protected abstract evalSubstringOf(str: String): boolean; +} diff --git a/hamcrest/src/main/ets/components/internal/ArrayIterator.ets b/hamcrest/src/main/ets/components/internal/ArrayIterator.ets new file mode 100644 index 0000000..76b8392 --- /dev/null +++ b/hamcrest/src/main/ets/components/internal/ArrayIterator.ets @@ -0,0 +1,42 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export class ArrayIterator{ + private readonly array:Array; + private currentIndex:number = 0; + + public constructor( array:Array) { + if (!Array.isArray(array)) { + throw new Error("not an array"); + } + this.array = array; + } + + + public hasNext():boolean { + return this.currentIndex < this.array.length; + } + + + public next():Object { + this.currentIndex++; + return this.array.pop() + } + + public remove() :void{ + throw new Error("cannot remove items from an array"); + } + +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/internal/NullSafety.ets b/hamcrest/src/main/ets/components/internal/NullSafety.ets new file mode 100644 index 0000000..fa8156d --- /dev/null +++ b/hamcrest/src/main/ets/components/internal/NullSafety.ets @@ -0,0 +1,27 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {IsNull} from '../core/IsNull' +import {Matcher} from '../Matcher' + +export class NullSafety{ + public static nullSafe(itemMatchers:Matcher[]){ + var matchers:Array> = new Array>(itemMatchers.length); + itemMatchers.forEach((value:Matcher,index,array)=>{ + matchers.push(value == null ? IsNull.nullValue() : value); + }) + return matchers; +} +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/internal/SelfDescribingValue.ets b/hamcrest/src/main/ets/components/internal/SelfDescribingValue.ets new file mode 100644 index 0000000..97d501c --- /dev/null +++ b/hamcrest/src/main/ets/components/internal/SelfDescribingValue.ets @@ -0,0 +1,31 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {SelfDescribing} from '../SelfDescribing' +import {Description} from '../Description' + +export class SelfDescribingValue implements SelfDescribing{ + + private value:object; + + public constructor( value:object) { + this.value = value; + } + + + public describeTo(description:Description):void { + description.appendValue(this.value); + } +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/internal/SelfDescribingValueIterator.ets b/hamcrest/src/main/ets/components/internal/SelfDescribingValueIterator.ets new file mode 100644 index 0000000..1131496 --- /dev/null +++ b/hamcrest/src/main/ets/components/internal/SelfDescribingValueIterator.ets @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export class SelfDescribingValueIterator{ + + + private readonly values:Array; + private currentIndex:number = 0; + + public constructor( array:Array) { + if (!Array.isArray(array)) { + throw new Error("not an array"); + } + this.values = array; + } + + + public hasNext():boolean { + return this.currentIndex < this.values.length; + } + + + public next():Object { + this.currentIndex++; + return this.values.pop() + } + + public remove() :void{ + throw new Error("cannot remove items from an array"); + } + +} \ No newline at end of file diff --git a/hamcrest/src/main/ets/components/number/IsCloseTo.ets b/hamcrest/src/main/ets/components/number/IsCloseTo.ets new file mode 100644 index 0000000..8ff2557 --- /dev/null +++ b/hamcrest/src/main/ets/components/number/IsCloseTo.ets @@ -0,0 +1,72 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {BaseMatcher} from '../BaseMatcher'; + +/** + * Is the value a number equal to a value within some range of + * acceptable error? + */ +export class IsCloseTo extends BaseMatcher { + private delta: number; + private value: number; + + public constructor(value: number, error: number) { + super(); + this.delta = error; + this.value = value; + } + + public matches(item: number): boolean { + return this.actualDelta(item) <= 0.0; + } + + public describeMismatch(item: number, mismatchDescription: Description): void { + mismatchDescription.appendValue(item) + .appendText(" differed by ") + .appendValue(this.actualDelta(item)) + .appendText(" more than delta ") + .appendValue(this.delta); + } + + public describeTo(description: Description): void { + description.appendText("a numeric value within ") + .appendValue(this.delta) + .appendText(" of ") + .appendValue(this.value); + } + + private actualDelta(item: number): number { + return Math.abs(item - this.value) - this.delta; + } + + /** + * Creates a matcher of {@link Double}s that matches when an examined double is equal + * to the specified operand, within a range of +/- error. + * For example: + *
assertThat(1.03, is(closeTo(1.0, 0.03)))
+ * + * @param operand + * the expected value of matching doubles + * @param error + * the delta (+/-) within which matches will be allowed + * @return The matcher. + */ + public static closeTo(operand: number, error: number): Matcher { + return new IsCloseTo(operand, error); + } +} diff --git a/hamcrest/src/main/ets/components/number/IsNaN.ets b/hamcrest/src/main/ets/components/number/IsNaN.ets new file mode 100644 index 0000000..f317790 --- /dev/null +++ b/hamcrest/src/main/ets/components/number/IsNaN.ets @@ -0,0 +1,50 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {BaseMatcher} from '../BaseMatcher'; + +/** + * Is the value a number actually not a number (NaN)? + */ +export class IsNaN extends BaseMatcher { + private constructor() { + super(); + } + + public matches(item: number): boolean { + return Number.isNaN(item); + } + + public describeMismatch(item: number, mismatchDescription: Description): void { + mismatchDescription.appendText("was ").appendValue(item); + } + + public describeTo(description: Description): void{ + description.appendText("a double value of NaN"); + } + + /** + * Creates a matcher of {@link Double}s that matches when an examined double is not a number. + * For example: + *
assertThat(Double.NaN, is(notANumber()))
+ * + * @return The matcher. + */ + public static notANumber(): Matcher { + return new IsNaN(); + } +} diff --git a/hamcrest/src/main/ets/components/number/OrderingComparison.ets b/hamcrest/src/main/ets/components/number/OrderingComparison.ets new file mode 100644 index 0000000..e1cd71e --- /dev/null +++ b/hamcrest/src/main/ets/components/number/OrderingComparison.ets @@ -0,0 +1,92 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Matcher} from '../Matcher'; +import {Description} from '../Description'; + +export class OrderingComparison { + private constructor() { + } + + public static comparesEqualTo(value: number): Matcher { + return { + matches(actual: number): boolean { + return value == actual; + }, + describeMismatch(actual: number, mismatchDescription: Description): void{ + super.describeMismatch(actual, mismatchDescription); + }, + describeTo(description: Description): void{ + description.appendText('equal to ').appendValue(value); + } + } + } + + public static greaterThan(value: number): Matcher{ + return { + matches(actual: number): boolean { + return actual > value; + }, + describeMismatch(actual: number, mismatchDescription: Description): void{ + super.describeMismatch(actual, mismatchDescription); + }, + describeTo(description: Description): void{ + description.appendText('greater than ').appendValue(value); + } + } + } + + public static greaterThanOrEqualTo(value: number): Matcher { + return { + matches(actual: number): boolean { + return actual >= value; + }, + describeMismatch(actual: number, mismatchDescription: Description): void{ + super.describeMismatch(actual, mismatchDescription); + }, + describeTo(description: Description): void{ + description.appendText('greater than or equal to ').appendValue(value); + } + } + } + + public static lessThan(value: number): Matcher { + return { + matches(actual: number): boolean { + return actual < value; + }, + describeMismatch(actual: number, mismatchDescription: Description): void{ + super.describeMismatch(actual, mismatchDescription); + }, + describeTo(description: Description): void{ + description.appendText('less than ').appendValue(value); + } + } + } + + public static lessThanOrEqualTo(value: number): Matcher { + return { + matches(actual: number): boolean { + return actual <= value; + }, + describeMismatch(actual: number, mismatchDescription: Description): void{ + super.describeMismatch(actual, mismatchDescription); + }, + describeTo(description: Description): void{ + description.appendText('less than or equal to ').appendValue(value); + } + } + } +} diff --git a/hamcrest/src/main/ets/components/text/CharSequenceLength.ets b/hamcrest/src/main/ets/components/text/CharSequenceLength.ets new file mode 100644 index 0000000..39f4792 --- /dev/null +++ b/hamcrest/src/main/ets/components/text/CharSequenceLength.ets @@ -0,0 +1,58 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {FeatureMatcher} from '../FeatureMatcher'; +import {Matcher} from '../Matcher'; +import {IsEqual} from '../core/IsEqual'; + +export class CharSequenceLength extends FeatureMatcher { + public constructor(lengthMatcher: Matcher) { + super(lengthMatcher, "a CharSequence with length", "length"); + } + + protected featureValueOf(actual: String): Number { + return actual.length; + } + + /** + * Creates a matcher of {@link CharSequence} that matches when a char sequence has the given length + * For example: + * + *
+     * assertThat("text", hasLength(4))
+     * 
+ * + * @param length the expected length of the string + * @return The matcher. + */ + public static hasLength(length: number): Matcher { + return new CharSequenceLength(IsEqual.equalTo(length)); + } + + /** + * Creates a matcher of {@link CharSequence} that matches when a char sequence has the given length + * For example: + * + *
+      * assertThat("text", hasLength(lessThan(4)))
+      * 
+ * + * @param lengthMatcher the expected length of the string + * @return The matcher. + */ + public static hasLengthMatcher(lengthMatcher: Matcher): Matcher { + return new CharSequenceLength(lengthMatcher); + } +} diff --git a/hamcrest/src/main/ets/components/text/IsBlankString.ets b/hamcrest/src/main/ets/components/text/IsBlankString.ets new file mode 100644 index 0000000..0cb5dab --- /dev/null +++ b/hamcrest/src/main/ets/components/text/IsBlankString.ets @@ -0,0 +1,65 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; +import { BaseMatcher } from '../BaseMatcher' +import { AnyOf } from '../core/AnyOf' +import { IsNull } from '../core/IsNull' + + +/** + * Matches blank Strings (and null). + */ +export class IsBlankString extends BaseMatcher { + private static BLANK_INSTANCE: IsBlankString = new IsBlankString(); + private static NULL_OR_BLANK_INSTANCE: Matcher = AnyOf.anyOfMatches(IsNull.nullValue(), IsBlankString.BLANK_INSTANCE); + private static REGEX_WHITESPACE: RegExp = new RegExp("\\s"); + private constructor() { + super(); + } + + public matches(item: String): boolean{ + return IsBlankString.REGEX_WHITESPACE.test(item.toString()); + } + + public describeTo(description: Description): void { + description.appendText("a blank string"); + } + + /** + * Creates a matcher of {@link String} that matches when the examined string contains + * zero or more whitespace characters and nothing else. + * For example: + *
assertThat("  ", is(blankString()))
+ * + * @return The matcher. + */ + public static blankString(): Matcher { + return IsBlankString.BLANK_INSTANCE; + } + + /** + * Creates a matcher of {@link String} that matches when the examined string is null, or + * contains zero or more whitespace characters and nothing else. + * For example: + *
assertThat(((String)null), is(blankOrNullString()))
+ * + * @return The matcher. + */ + public static blankOrNullString(): Matcher{ + return IsBlankString.NULL_OR_BLANK_INSTANCE; + } +} diff --git a/hamcrest/src/main/ets/components/text/IsEmptyString.ets b/hamcrest/src/main/ets/components/text/IsEmptyString.ets new file mode 100644 index 0000000..c0bb98c --- /dev/null +++ b/hamcrest/src/main/ets/components/text/IsEmptyString.ets @@ -0,0 +1,90 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Description } from '../Description'; +import { Matcher } from '../Matcher'; +import { BaseMatcher } from '../BaseMatcher' +import { AnyOf } from '../core/AnyOf' +import { IsNull } from '../core/IsNull' + +/** + * Matches empty Strings (and null). + */ +export class IsEmptyString extends BaseMatcher { + private static INSTANCE: IsEmptyString = new IsEmptyString(); + private static NULL_OR_EMPTY_INSTANCE: Matcher = AnyOf.anyOfMatches(IsNull.nullValue(), IsEmptyString.INSTANCE); + private constructor() { + super() + } + + public matches(item: String): boolean { + if (item == null) { + return false + } + return item.length == 0 + } + + public describeTo(description: Description): void { + description.appendText("an empty string"); + } + + /** + * Creates a matcher of {@link String} that matches when the examined string has zero length. + * For example: + *
assertThat("", isEmptyString())
+ * + * @deprecated use is(emptyString()) instead + * @return The matcher. + */ + public static isEmptyString(): Matcher { + return IsEmptyString.emptyString(); + } + + /** + * Creates a matcher of {@link String} that matches when the examined string has zero length. + * For example: + *
assertThat("", is(emptyString()))
+ * + * @return The matcher. + */ + public static emptyString(): Matcher { + return IsEmptyString.INSTANCE; + } + + /** + * Creates a matcher of {@link String} that matches when the examined string is null, or + * has zero length. + * For example: + *
assertThat(((String)null), isEmptyOrNullString())
+ * + * @deprecated use is(emptyOrNullString()) instead + * @return The matcher. + */ + public static isEmptyOrNullString(): Matcher { + return IsEmptyString.emptyOrNullString(); + } + + /** + * Creates a matcher of {@link String} that matches when the examined string is null, or + * has zero length. + * For example: + *
assertThat(((String)null), is(emptyOrNullString()))
+ * + * @return The matcher. + */ + public static emptyOrNullString(): Matcher { + return IsEmptyString.NULL_OR_EMPTY_INSTANCE; + } +} diff --git a/hamcrest/src/main/ets/components/text/IsEqualCompressingWhiteSpace.ets b/hamcrest/src/main/ets/components/text/IsEqualCompressingWhiteSpace.ets new file mode 100644 index 0000000..f033160 --- /dev/null +++ b/hamcrest/src/main/ets/components/text/IsEqualCompressingWhiteSpace.ets @@ -0,0 +1,67 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; +import {BaseMatcher} from '../BaseMatcher'; + +/** + * Tests if a string is equal to another string, compressing any changes in whitespace. + */ +export class IsEqualCompressingWhiteSpace extends BaseMatcher { + + private str: String; + + public constructor(str: String) { + super(); + if (str == null) { + throw new Error("Non-null value required"); + } + this.str = str; + } + + protected getString(): String { + return this.str; + } + + public matches(item: String): boolean { + console.info("matches: 1 " + ' this.str is ' + this.str + ', item is ' + item) + console.info("matches: 2 " + this.stripSpaces(this.str)) + console.info("matches: 3 " + this.stripSpaces(item)) + return this.stripSpaces(this.str) == (this.stripSpaces(item)); + } + + public describeMismatch(item: String, mismatchDescription: Description): void { + mismatchDescription.appendText("was ").appendValue(item); + } + + public describeTo(description: Description): void { + description.appendText("a string equal to ") + .appendValue(this.str) + .appendText(" compressing white space"); + } + + public stripSpaces(toBeStripped: String): String { + return toBeStripped.replace(/\s/g, "").trim(); + } + + public static equalToIgnoringWhiteSpace(expectedString: String): Matcher { + return new IsEqualCompressingWhiteSpace(expectedString); + } + + public static equalToCompressingWhiteSpace(expectedString: String): Matcher { + return new IsEqualCompressingWhiteSpace(expectedString); + } +} diff --git a/hamcrest/src/main/ets/components/text/IsEqualIgnoringCase.ets b/hamcrest/src/main/ets/components/text/IsEqualIgnoringCase.ets new file mode 100644 index 0000000..e0176d5 --- /dev/null +++ b/hamcrest/src/main/ets/components/text/IsEqualIgnoringCase.ets @@ -0,0 +1,50 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { BaseMatcher } from '../BaseMatcher' +import { Description } from '../Description' +import { Matcher } from '../Matcher' + +/** + * Tests if a string is equal to another string, regardless of the case. + */ +export class IsEqualIgnoringCase extends BaseMatcher { + private value: String; + constructor(value: String) { + super() + if (value == null) { + throw new Error("Non-null value required"); + } + this.value = value; + } + + public matches(item: String): boolean { + return this.value.toLowerCase() == item.toLowerCase(); + } + + public describeMismatch(item: String, mismatchDescription: Description): void { + super.describeMismatch(item, mismatchDescription); + } + + public describeTo(description: Description): void { + description.appendText("a string equal to ") + .appendValue(this.value) + .appendText(" ignoring case"); + } + + public static equalToIgnoringCase(expectedString: String): Matcher { + return new IsEqualIgnoringCase(expectedString); + } +} diff --git a/hamcrest/src/main/ets/components/text/MatchesPattern.ets b/hamcrest/src/main/ets/components/text/MatchesPattern.ets new file mode 100644 index 0000000..c5d2542 --- /dev/null +++ b/hamcrest/src/main/ets/components/text/MatchesPattern.ets @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2022 Huawei Device Co., Ltd. +* Licensed under the BSD License, (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import {BaseMatcher} from '../BaseMatcher'; +import {Description} from '../Description'; +import {Matcher} from '../Matcher'; + +export class MatchesPattern extends BaseMatcher { + private pattern: RegExp; + + public constructor(pattern: RegExp) { + super() + this.pattern = pattern; + } + + public matches(item: string): boolean { + return this.pattern.test(item); + } + + public describeTo(description: Description): void { + description.appendText("a string matching the pattern '" + this.pattern + "'"); + } + + /** + * Creates a matcher of {@link java.lang.String} that matches when the examined string + * + * @param pattern + * the text pattern to match. + * @return The matcher. + */ + public static matchesPattern(pattern: RegExp): Matcher { + return new MatchesPattern(pattern); + } + + /** + * Creates a matcher of {@link java.lang.String} that matches when the examined string + * + * @param regex + * the regex to match. + * @return The matcher. + */ + public static matchesStringPattern(regex: string): Matcher { + return new MatchesPattern(new RegExp(regex)); + } +} diff --git a/hamcrest/src/main/resources/base/element/string.json b/hamcrest/src/main/resources/base/element/string.json new file mode 100644 index 0000000..1e76de0 --- /dev/null +++ b/hamcrest/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from npm package" + } + ] +} diff --git a/hvigorfile.js b/hvigorfile.js new file mode 100644 index 0000000..cff9f0d --- /dev/null +++ b/hvigorfile.js @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').legacyAppTasks \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2853d6f --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "ohos_app_hamcrest", + "version": "1.0.0", + "ohos": { + "org": "huawei", + "buildTool": "hvigor", + "directoryLevel": "project" + }, + "description": "example description", + "repository": {}, + "license": "BSD License", + "dependencies": { + "hypium": "^1.0.0", + "@ohos/hvigor": "1.0.6", + "@ohos/hvigor-ohos-plugin": "1.0.6" + } +} diff --git a/screenshots/operation.png b/screenshots/operation.png new file mode 100644 index 0000000000000000000000000000000000000000..ee774330337c5dc6ccb15228c900062b9d8a0746 GIT binary patch literal 60289 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

_t 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

*Y^Y)&&R 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

(@%*?^I4j^OoQGv@OJE`#5Wu9HinHO6nJ|^ZGs0 z-M}LixzlQ9+QV{q_h*fXzhUY_bR_WXs6Y@92*jF@5=u!)346tayuQ^LeBzT9!4h@@ Oc$DPR9MA^!%`g%KeD literal 0 HcmV?d00001 -- Gitee