From e779c16595dfc3c3e529aaa04549b5852853f775 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 1 May 2026 16:05:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B7=A5=E7=A8=8B=E6=A0=87?= =?UTF-8?q?=E5=87=86=E5=8C=96=20=E2=80=94=20=E7=BB=84=E4=BB=B6=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E3=80=81=E5=BA=9F=E5=BC=83API=E6=B8=85=E7=90=86?= =?UTF-8?q?=E3=80=81console=E6=B8=85=E9=9B=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatPage/GraphPage/SettingsPage 拆分为独立子组件 - MainPage 接入 ImmersiveTabNavigation 玻璃拟态导航 - EntryAbility console.* 全替换为 defaultLogger - SplashPage replaceUrl 废弃 API 迁移到 UIContext Router - common/Index.ets 双通道导出 Logger/defaultLogger - 删除 dead code (entry/, commonbusiness/, trulymem-core/) - 全工程 0 console.*,BUILD SUCCESSFUL --- build-profile.json5 | 27 +- {trulymem-core => common}/BuildProfile.ets | 0 common/Index.ets | 31 +- common/common | 1 + common/hvigorfile.ts | 6 + .../ets/component/ImmersiveTabNavigation.ets | 3 +- common/src/main/ets/model/GraphDatabase.ets | 206 ++- .../src/main/ets/model/GraphDatabase.ets.bak | 936 ++++++++++++ .../main/ets/routermanager/PageContext.ets | 8 +- .../src/main/ets/service/AIAgentService.ets | 61 +- .../main/ets/service/GraphMemoryService.ets | 59 +- common/src/main/ets/util/Logger.ets | 31 + common/src/main/module.json5 | 9 +- entry/build-profile.json5 | 8 - entry/obfuscation-rules.txt | 34 - entry/oh-package.json5 | 11 - .../main/ets/entryability/EntryAbility.ets | 56 - entry/src/main/module.json5 | 19 - .../resources/base/media/layered_image.svg | 5 - .../main/resources/base/media/startIcon.svg | 5 - .../resources/base/profile/main_pages.json | 5 - features/chat/BuildProfile.ets | 17 + features/chat/build-profile.json5 | 2 + features/chat/chat | 1 + features/chat/hvigorfile.ts | 6 + .../chat}/oh-package-lock.json5 | 8 +- features/chat/oh_modules/@ohos/common | 1 + .../main/ets/components/ChatComponents.ets | 161 +++ features/chat/src/main/ets/pages/ChatPage.ets | 111 +- .../chat}/src/main/module.json5 | 7 +- features/commonbusiness/Index.ets | 1 - features/commonbusiness/build-profile.json5 | 8 - features/commonbusiness/oh-package.json5 | 11 - features/graph/BuildProfile.ets | 17 + features/graph/build-profile.json5 | 2 + features/graph/graph | 1 + features/graph/hvigorfile.ts | 6 + features/graph/oh-package-lock.json5 | 19 + features/graph/oh_modules/@ohos/common | 1 + .../main/ets/components/GraphComponents.ets | 251 ++++ .../graph/src/main/ets/pages/GraphPage.ets | 225 +-- features/graph/src/main/module.json5 | 12 + .../src/main/resources/rawfile/graph.html | 0 features/settings/BuildProfile.ets | 17 + features/settings/build-profile.json5 | 2 + features/settings/hvigorfile.ts | 6 + features/settings/oh-package-lock.json5 | 19 + features/settings/oh_modules/@ohos/common | 1 + features/settings/settings | 1 + .../ets/components/SettingsComponents.ets | 105 ++ .../src/main/ets/pages/SettingsPage.ets | 124 +- features/settings/src/main/module.json5 | 12 + generate-debug-cert.sh | 25 + oh-package-lock.json5 | 28 + oh-package.json5 | 9 +- .../oh_modules/@ohos/hamock/CHANGELOG.md | 4 + .../oh_modules/@ohos/hamock/LICENSE | 177 +++ .../oh_modules/@ohos/hamock/README.md | 82 ++ .../@ohos/hamock/build-profile.json5 | 25 + .../oh_modules/@ohos/hamock/hvigorfile.js | 17 + .../oh_modules/@ohos/hamock/hvigorfile.ts | 17 + .../oh_modules/@ohos/hamock/index.d.ts | 58 + .../oh_modules/@ohos/hamock/index.ets | 17 + .../oh_modules/@ohos/hamock/index.js | 16 + .../oh_modules/@ohos/hamock/index.ts | 17 + .../oh_modules/@ohos/hamock/oh-package.json5 | 28 + .../hamock/src/main/mock/ArgumentMatchers.js | 97 ++ .../hamock/src/main/mock/ArgumentMatchers.ts | 118 ++ .../hamock/src/main/mock/ExtendInterface.js | 48 + .../hamock/src/main/mock/ExtendInterface.ts | 63 + .../@ohos/hamock/src/main/mock/MockKit.js | 253 ++++ .../@ohos/hamock/src/main/mock/MockKit.ts | 294 ++++ .../hamock/src/main/mock/VerificationMode.js | 45 + .../hamock/src/main/mock/VerificationMode.ts | 56 + .../@ohos/hamock/src/main/module.json | 22 + .../res/schemas/mock-config-json5-schema.json | 25 + .../oh_modules/@ohos/hypium/BuildProfile.ets | 17 + .../oh_modules/@ohos/hypium/CHANGELOG.md | 33 + .../oh_modules/@ohos/hypium/LICENSE | 177 +++ .../oh_modules/@ohos/hypium/README.md | 229 +++ .../@ohos/hypium}/build-profile.json5 | 72 +- .../@ohos/hypium}/consumer-rules.txt | 0 .../oh_modules/@ohos/hypium}/hvigorfile.ts | 0 .../oh_modules/@ohos/hypium/index.d.ts | 150 ++ .../oh_modules/@ohos/hypium/index.ets | 137 ++ .../oh_modules/@ohos/hypium/index.js | 261 ++++ .../oh_modules/@ohos/hypium/index.ts | 32 + .../@ohos/hypium}/obfuscation-rules.txt | 0 .../oh_modules/@ohos/hypium/oh-package.json5 | 1 + .../@ohos/hypium/src/main/Constant.js | 99 ++ .../oh_modules/@ohos/hypium/src/main/core.js | 159 +++ .../oh_modules/@ohos/hypium/src/main/event.js | 100 ++ .../@ohos/hypium/src/main/interface.js | 68 + .../@ohos/hypium/src/main/module.json | 30 + .../src/main/module/assert/ExpectExtend.js | 85 ++ .../src/main/module/assert/assertClose.js | 40 + .../src/main/module/assert/assertContain.js | 35 + .../src/main/module/assert/assertFail.js | 23 + .../src/main/module/assert/assertFalse.js | 23 + .../main/module/assert/assertInstanceOf.js | 29 + .../src/main/module/assert/assertLarger.js | 23 + .../main/module/assert/assertLargerOrEqual.js | 23 + .../src/main/module/assert/assertLess.js | 23 + .../main/module/assert/assertLessOrEqual.js | 23 + .../src/main/module/assert/assertNaN.js | 23 + .../main/module/assert/assertNegUnlimited.js | 23 + .../src/main/module/assert/assertNull.js | 23 + .../main/module/assert/assertPosUnlimited.js | 23 + .../module/assert/assertPromiseIsPending.js | 42 + .../module/assert/assertPromiseIsRejected.js | 38 + .../assert/assertPromiseIsRejectedWith.js | 55 + .../assertPromiseIsRejectedWithError.js | 77 + .../module/assert/assertPromiseIsResolved.js | 40 + .../assert/assertPromiseIsResolvedWith.js | 55 + .../main/module/assert/assertThrowError.js | 48 + .../src/main/module/assert/assertUndefined.js | 23 + .../module/assert/deepEquals/DeepTypeUtils.js | 139 ++ .../assert/deepEquals/assertDeepEquals.js | 433 ++++++ .../src/main/module/assert/isPromiseLike.js | 32 + .../src/main/module/config/DataDriver.js | 135 ++ .../hypium/src/main/module/config/Filter.js | 143 ++ .../src/main/module/config/configService.js | 299 ++++ .../main/module/coverage/coverageCollect.js | 76 + .../hypium/src/main/module/kit/SysTestKit.js | 102 ++ .../src/main/module/mock/ArgumentMatchers.js | 122 ++ .../src/main/module/mock/ExtendInterface.js | 57 + .../hypium/src/main/module/mock/MockKit.js | 353 +++++ .../src/main/module/mock/VerificationMode.js | 49 + .../src/main/module/report/LogExpectError.js | 65 + .../hypium/src/main/module/report/OhReport.js | 217 +++ .../src/main/module/report/ReportExtend.js | 149 ++ .../@ohos/hypium/src/main/service.js | 1265 +++++++++++++++++ .../main/testrunner/OpenHarmonyTestRunner.ts | 83 ++ oh_modules/.ohpm/lock.json5 | 153 ++ oh_modules/.ohpm/oh_modules/@ohos/hamock | 1 + oh_modules/.ohpm/oh_modules/@ohos/hypium | 1 + oh_modules/@ohos/hamock | 1 + oh_modules/@ohos/hypium | 1 + products/phone/build-profile.json5 | 12 + {entry => products/phone}/hvigorfile.ts | 6 +- products/phone/oh-package-lock.json5 | 49 + products/phone/oh_modules/@ohos/chat | 1 + products/phone/oh_modules/@ohos/common | 1 + products/phone/oh_modules/@ohos/graph | 1 + products/phone/oh_modules/@ohos/settings | 1 + .../main/ets/entryability/EntryAbility.ets | 23 +- products/phone/src/main/ets/pages/Index.ets | 28 +- .../phone/src/main/ets/pages/MainPage.ets | 143 +- .../phone/src/main/ets/pages/SplashPage.ets | 8 +- products/phone/src/main/module.json5 | 2 +- .../main/resources/base/element/color.json | 2 +- .../main/resources/base/element/string.json | 18 +- .../resources/base/media/layered_image.json | 1 + .../main/resources/base/media/startIcon.png | Bin 0 -> 70 bytes .../resources/base/profile/main_pages.json | 1 - .../resources/base/profile/router_map.json | 9 + signingConfigs.json | 17 + trulymem-core/Index.ets | 2 - trulymem-core/hvigor/hvigor-config.json5 | 23 - trulymem-core/oh-package.json5 | 9 - trulymem-core/src/main/ets/GraphDatabase.ets | 21 - .../src/main/ets/components/RconClient.ets | 208 --- .../src/main/ets/components/tools.ets | 194 --- .../main/resources/base/element/float.json | 8 - .../main/resources/base/element/string.json | 12 - .../src/ohosTest/ets/test/List.test.ets | 5 - trulymem-core/src/ohosTest/module.json5 | 14 - trulymem-core/src/test/List.test.ets | 5 - trulymem-core/src/test/LocalUnit.test.ets | 33 - .../src/test/unittest/PacketBuilder.test.ets | 107 -- .../src/test/unittest/PacketParser.test.ets | 92 -- .../src/test/unittest/RConClient.test.ets | 148 -- .../src/test/unittest/RConResult.test.ets | 87 -- 173 files changed, 9954 insertions(+), 1792 deletions(-) rename {trulymem-core => common}/BuildProfile.ets (100%) create mode 120000 common/common create mode 100644 common/hvigorfile.ts create mode 100644 common/src/main/ets/model/GraphDatabase.ets.bak create mode 100644 common/src/main/ets/util/Logger.ets delete mode 100644 entry/build-profile.json5 delete mode 100644 entry/obfuscation-rules.txt delete mode 100644 entry/oh-package.json5 delete mode 100644 entry/src/main/ets/entryability/EntryAbility.ets delete mode 100644 entry/src/main/module.json5 delete mode 100644 entry/src/main/resources/base/media/layered_image.svg delete mode 100644 entry/src/main/resources/base/media/startIcon.svg delete mode 100644 entry/src/main/resources/base/profile/main_pages.json create mode 100644 features/chat/BuildProfile.ets create mode 120000 features/chat/chat create mode 100644 features/chat/hvigorfile.ts rename {entry => features/chat}/oh-package-lock.json5 (64%) create mode 120000 features/chat/oh_modules/@ohos/common create mode 100644 features/chat/src/main/ets/components/ChatComponents.ets rename {trulymem-core => features/chat}/src/main/module.json5 (53%) delete mode 100644 features/commonbusiness/Index.ets delete mode 100644 features/commonbusiness/build-profile.json5 delete mode 100644 features/commonbusiness/oh-package.json5 create mode 100644 features/graph/BuildProfile.ets create mode 120000 features/graph/graph create mode 100644 features/graph/hvigorfile.ts create mode 100644 features/graph/oh-package-lock.json5 create mode 120000 features/graph/oh_modules/@ohos/common create mode 100644 features/graph/src/main/ets/components/GraphComponents.ets create mode 100644 features/graph/src/main/module.json5 rename {entry => features/graph}/src/main/resources/rawfile/graph.html (100%) create mode 100644 features/settings/BuildProfile.ets create mode 100644 features/settings/hvigorfile.ts create mode 100644 features/settings/oh-package-lock.json5 create mode 120000 features/settings/oh_modules/@ohos/common create mode 120000 features/settings/settings create mode 100644 features/settings/src/main/ets/components/SettingsComponents.ets create mode 100644 features/settings/src/main/module.json5 create mode 100644 generate-debug-cert.sh create mode 100644 oh-package-lock.json5 create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/CHANGELOG.md create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/LICENSE create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/README.md create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/build-profile.json5 create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.d.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ets create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/oh-package.json5 create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.js create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.ts create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/module.json create mode 100644 oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/res/schemas/mock-config-json5-schema.json create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/BuildProfile.ets create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/CHANGELOG.md create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/LICENSE create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/README.md rename {trulymem-core => oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium}/build-profile.json5 (73%) rename {trulymem-core => oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium}/consumer-rules.txt (100%) rename {trulymem-core => oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium}/hvigorfile.ts (100%) create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.d.ts create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ets create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ts rename {trulymem-core => oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium}/obfuscation-rules.txt (100%) create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/oh-package.json5 create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/Constant.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/core.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/event.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/interface.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module.json create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/ExpectExtend.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertClose.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertContain.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFail.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFalse.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertInstanceOf.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLarger.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLargerOrEqual.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLess.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLessOrEqual.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNaN.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNegUnlimited.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNull.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPosUnlimited.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsPending.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejected.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWith.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWithError.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolved.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolvedWith.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertThrowError.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertUndefined.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/DeepTypeUtils.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/assertDeepEquals.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/isPromiseLike.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/DataDriver.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/Filter.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/configService.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/coverage/coverageCollect.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/kit/SysTestKit.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ArgumentMatchers.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ExtendInterface.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/MockKit.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/VerificationMode.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/LogExpectError.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/OhReport.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/ReportExtend.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/service.js create mode 100644 oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/testrunner/OpenHarmonyTestRunner.ts create mode 100644 oh_modules/.ohpm/lock.json5 create mode 120000 oh_modules/.ohpm/oh_modules/@ohos/hamock create mode 120000 oh_modules/.ohpm/oh_modules/@ohos/hypium create mode 120000 oh_modules/@ohos/hamock create mode 120000 oh_modules/@ohos/hypium rename {entry => products/phone}/hvigorfile.ts (63%) create mode 100644 products/phone/oh-package-lock.json5 create mode 120000 products/phone/oh_modules/@ohos/chat create mode 120000 products/phone/oh_modules/@ohos/common create mode 120000 products/phone/oh_modules/@ohos/graph create mode 120000 products/phone/oh_modules/@ohos/settings rename {entry => products/phone}/src/main/resources/base/element/color.json (74%) rename {entry => products/phone}/src/main/resources/base/element/string.json (59%) create mode 100644 products/phone/src/main/resources/base/media/layered_image.json create mode 100644 products/phone/src/main/resources/base/media/startIcon.png create mode 100644 products/phone/src/main/resources/base/profile/router_map.json create mode 100644 signingConfigs.json delete mode 100644 trulymem-core/Index.ets delete mode 100644 trulymem-core/hvigor/hvigor-config.json5 delete mode 100644 trulymem-core/oh-package.json5 delete mode 100644 trulymem-core/src/main/ets/GraphDatabase.ets delete mode 100644 trulymem-core/src/main/ets/components/RconClient.ets delete mode 100644 trulymem-core/src/main/ets/components/tools.ets delete mode 100644 trulymem-core/src/main/resources/base/element/float.json delete mode 100644 trulymem-core/src/main/resources/base/element/string.json delete mode 100644 trulymem-core/src/ohosTest/ets/test/List.test.ets delete mode 100644 trulymem-core/src/ohosTest/module.json5 delete mode 100644 trulymem-core/src/test/List.test.ets delete mode 100644 trulymem-core/src/test/LocalUnit.test.ets delete mode 100644 trulymem-core/src/test/unittest/PacketBuilder.test.ets delete mode 100644 trulymem-core/src/test/unittest/PacketParser.test.ets delete mode 100644 trulymem-core/src/test/unittest/RConClient.test.ets delete mode 100644 trulymem-core/src/test/unittest/RConResult.test.ets diff --git a/build-profile.json5 b/build-profile.json5 index 1c0b088..86e612c 100644 --- a/build-profile.json5 +++ b/build-profile.json5 @@ -1,5 +1,6 @@ { "app": { + "signingConfigs": [], "products": [ { "name": "default", @@ -36,18 +37,7 @@ } ] }, - { - "name": "commonbusiness", - "srcPath": "./features/commonbusiness", - "targets": [ - { - "name": "default", - "applyToProducts": [ - "default" - ] - } - ] - }, + { "name": "graph", "srcPath": "./features/graph", @@ -96,17 +86,6 @@ } ] }, - { - "name": "trulymem-core", - "srcPath": "./trulymem-core", - "targets": [ - { - "name": "default", - "applyToProducts": [ - "default" - ] - } - ] - } + ] } diff --git a/trulymem-core/BuildProfile.ets b/common/BuildProfile.ets similarity index 100% rename from trulymem-core/BuildProfile.ets rename to common/BuildProfile.ets diff --git a/common/Index.ets b/common/Index.ets index fa3c683..8ffe9a6 100644 --- a/common/Index.ets +++ b/common/Index.ets @@ -1,7 +1,24 @@ -export { TrulyMEMConstants } from './src/main/ets/constant/TrulyMEMConstants'; -export { GraphDatabase, RecallEntity, TimeRangeParams } from './src/main/ets/model/GraphDatabase'; -export { GraphMemoryService } from './src/main/ets/service/GraphMemoryService'; -export { AIAgentService, ChatMessage, AgentResponse } from './src/main/ets/service/AIAgentService'; -export { PageContext, RouterParam, IPageContext } from './src/main/ets/routermanager/PageContext'; -export { BreakpointType, BreakpointTypes, WidthBreakpoint } from './src/main/ets/util/BreakpointSystem'; -export { BaseViewModel, VMEvent } from './src/main/ets/viewmodel/BaseViewModel'; +// ========= Utility Layer ========= +export { defaultLogger } from './src/main/ets/util/Logger'; +export { defaultLogger as Logger } from './src/main/ets/util/Logger'; + +export { BreakpointType, BreakpointTypes, WidthBreakpoint } from "./src/main/ets/util/BreakpointSystem"; + +// ========= Router ========= +export { PageContext, RouterParam, IPageContext } from "./src/main/ets/routermanager/PageContext"; + +// ========= Constants ========= +export { Constants as TrulyMEMConstants } from "./src/main/ets/constant/TrulyMEMConstants"; + +// ========= Model Layer ========= +export { GraphDatabase, RecallEntity, TimeRangeParams } from "./src/main/ets/model/GraphDatabase"; + +// ========= Service Layer ========= +export { GraphMemoryService, ConnectionItem, NodeDetailInfo } from "./src/main/ets/service/GraphMemoryService"; +export { AIAgentService, ChatMessage, AgentResponse } from "./src/main/ets/service/AIAgentService"; + +// ========= ViewModel Layer ========= +export { BaseViewModel, VMEvent } from "./src/main/ets/viewmodel/BaseViewModel"; + +// ========= Component Layer ========= +export { ImmersiveTabNavigation } from "./src/main/ets/component/ImmersiveTabNavigation"; diff --git a/common/common b/common/common new file mode 120000 index 0000000..26f191c --- /dev/null +++ b/common/common @@ -0,0 +1 @@ +/home/program/TrulyMEM-TrueHumanMEM/common \ No newline at end of file diff --git a/common/hvigorfile.ts b/common/hvigorfile.ts new file mode 100644 index 0000000..6bf3dc5 --- /dev/null +++ b/common/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, + plugins: [] +}; diff --git a/common/src/main/ets/component/ImmersiveTabNavigation.ets b/common/src/main/ets/component/ImmersiveTabNavigation.ets index 739673e..bd26ffb 100644 --- a/common/src/main/ets/component/ImmersiveTabNavigation.ets +++ b/common/src/main/ets/component/ImmersiveTabNavigation.ets @@ -1,3 +1,4 @@ +import { defaultLogger } from '../util/Logger'; import { window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; @@ -19,7 +20,7 @@ export struct ImmersiveTabNavigation { const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); this.bottomAvoidHeight = avoidArea.bottomRect.height || 0; } catch (e) { - console.error('Failed to get avoid area: ' + (e as BusinessError).message); + defaultLogger.error('Failed to get avoid area: ' + (e as BusinessError).message); } } } diff --git a/common/src/main/ets/model/GraphDatabase.ets b/common/src/main/ets/model/GraphDatabase.ets index 58e96e3..28b8f0d 100644 --- a/common/src/main/ets/model/GraphDatabase.ets +++ b/common/src/main/ets/model/GraphDatabase.ets @@ -15,6 +15,11 @@ interface CriteriaData { target?: string; relation?: string; sessionId?: string; + sourceType?: string; + targetType?: string; + sourceHasStatus?: string; + subjectContains?: string; + targetContains?: string; } interface NodeData { @@ -116,6 +121,20 @@ interface SearchResultItem { mentions: number; } +class TaskNodeRow { + id: number = 0; + name: string = ''; + updatedAt: string = ''; +} + +interface DbTaskInfo { + taskId: string; + description: string; + state: string; + infoCount: number; + updatedAt: string; +} + interface ChatMessage { role: string; content: string; @@ -529,12 +548,10 @@ export class GraphDatabase { async purge(criteria: CriteriaData, mode: string = 'soft'): Promise { if (!this.store) return; - if (!criteria.subject && !criteria.target && !criteria.relation && !criteria.sessionId) { - return; - } const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); let hasCondition = false; + // 精确匹配 if (criteria.subject) { const subjectId = await this.getNodeIdByName(criteria.subject); if (subjectId > 0) { @@ -567,6 +584,96 @@ export class GraphDatabase { hasCondition = true; } + // 模糊匹配:subjectContains -> 通过子查询匹配节点名 + if (criteria.subjectContains) { + const nodeSql = `SELECT id FROM nodes WHERE name LIKE '%' || ? || '%'`; + const nodeResult = await this.store.querySql(nodeSql, [criteria.subjectContains]); + const nodeIds: number[] = []; + while (nodeResult.goToNextRow()) { + nodeIds.push(nodeResult.getLong(0)); + } + nodeResult.close(); + if (nodeIds.length > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.in('subject_id', nodeIds); + hasCondition = true; + } + } + + // 模糊匹配:targetContains + if (criteria.targetContains) { + const nodeSql = `SELECT id FROM nodes WHERE name LIKE '%' || ? || '%'`; + const nodeResult = await this.store.querySql(nodeSql, [criteria.targetContains]); + const nodeIds: number[] = []; + while (nodeResult.goToNextRow()) { + nodeIds.push(nodeResult.getLong(0)); + } + nodeResult.close(); + if (nodeIds.length > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.in('object_id', nodeIds); + hasCondition = true; + } + } + + // 源实体类型过滤 + if (criteria.sourceType) { + const nodeSql = `SELECT id FROM nodes WHERE type = ?`; + const nodeResult = await this.store.querySql(nodeSql, [criteria.sourceType]); + const nodeIds: number[] = []; + while (nodeResult.goToNextRow()) { + nodeIds.push(nodeResult.getLong(0)); + } + nodeResult.close(); + if (nodeIds.length > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.in('subject_id', nodeIds); + hasCondition = true; + } + } + + // 目标实体类型过滤 + if (criteria.targetType) { + const nodeSql = `SELECT id FROM nodes WHERE type = ?`; + const nodeResult = await this.store.querySql(nodeSql, [criteria.targetType]); + const nodeIds: number[] = []; + while (nodeResult.goToNextRow()) { + nodeIds.push(nodeResult.getLong(0)); + } + nodeResult.close(); + if (nodeIds.length > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.in('object_id', nodeIds); + hasCondition = true; + } + } + + // 源实体状态过滤 + if (criteria.sourceHasStatus) { + const nodeSql = `SELECT id FROM nodes WHERE type LIKE '%' || ? || '%'`; + const nodeResult = await this.store.querySql(nodeSql, [criteria.sourceHasStatus]); + const nodeIds: number[] = []; + while (nodeResult.goToNextRow()) { + nodeIds.push(nodeResult.getLong(0)); + } + nodeResult.close(); + if (nodeIds.length > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.in('subject_id', nodeIds); + hasCondition = true; + } + } + if (hasCondition) { if (mode === 'soft') { const bucket: relationalStore.ValuesBucket = { @@ -687,43 +794,84 @@ export class GraphDatabase { */ async queryArchived(days?: number, keyword?: string): Promise { if (!this.store) return []; - const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); - predicates.equalTo('status', 'archived'); - if (keyword) { - predicates.and().like('relation', '%' + keyword + '%'); - } + let sql = ` + SELECT r.subject_id, r.object_id, r.relation, r.weight, r.session_id, r.turn_id, + e.name AS source_name, t.name AS target_name + FROM relations r + JOIN nodes e ON r.subject_id = e.id + JOIN nodes t ON r.object_id = t.id + WHERE r.status = 'archived' + `; + const params: string[] = []; if (days && days > 0) { const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - days); - const maxDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, ''); - predicates.and().lessThanOrEqualTo('date_bucket', maxDateBucket); + const minDateBucket = cutoff.toISOString().slice(0, 10); + sql += ' AND r.date_bucket >= ?'; + params.push(minDateBucket); } - const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + if (keyword) { + const kw = '%' + keyword + '%'; + sql += ' AND (e.name LIKE ? OR t.name LIKE ? OR r.relation LIKE ?)'; + params.push(kw, kw, kw); + } + sql += ' ORDER BY r.updated_at DESC LIMIT 200'; + const resultSet: relationalStore.ResultSet = await this.store.querySql(sql, params); const results: RelationQueryResult[] = []; while (resultSet.goToNextRow()) { - const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id')); - const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id')); - const sourceNode = await this.getNodeById(sourceId); - const targetNode = await this.getNodeById(targetId); - if (sourceNode && targetNode) { - const queryResult: RelationQueryResult = { - sourceId: sourceId, - targetId: targetId, - sourceName: sourceNode.name, - targetName: targetNode.name, - type: resultSet.getString(resultSet.getColumnIndex('relation')), - confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')), - sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')), - turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')), - depth: 0 - }; - results.push(queryResult); - } + results.push({ + sourceId: resultSet.getLong(resultSet.getColumnIndex('subject_id')), + targetId: resultSet.getLong(resultSet.getColumnIndex('object_id')), + sourceName: resultSet.getString(resultSet.getColumnIndex('source_name')), + targetName: resultSet.getString(resultSet.getColumnIndex('target_name')), + type: resultSet.getString(resultSet.getColumnIndex('relation')), + confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')), + sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')), + turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')), + depth: 0 + }); } resultSet.close(); return results; } + async getRecentTasks(limit: number = 10, stateFilter?: string): Promise { + if (!this.store) return []; + const nodesSql = `SELECT id, name, updated_at FROM nodes WHERE type = 'TaskNode' ORDER BY updated_at DESC LIMIT ?`; + const nodesRs: relationalStore.ResultSet = await this.store.querySql(nodesSql, [String(limit)]); + const taskNodes: TaskNodeRow[] = []; + while (nodesRs.goToNextRow()) { + const tn: TaskNodeRow = new TaskNodeRow(); + tn.id = nodesRs.getLong(nodesRs.getColumnIndex('id')); + tn.name = nodesRs.getString(nodesRs.getColumnIndex('name')); + tn.updatedAt = nodesRs.getString(nodesRs.getColumnIndex('updated_at')) || ''; + taskNodes.push(tn); + } + nodesRs.close(); + const tasks: DbTaskInfo[] = []; + for (const node of taskNodes) { + const relSql = `SELECT r.relation, e.name AS target_name FROM relations r JOIN nodes e ON r.object_id = e.id WHERE r.subject_id = ? AND r.status = 'active' AND r.relation IN ('description', 'has_state')`; + const relRs: relationalStore.ResultSet = await this.store.querySql(relSql, [String(node.id)]); + let description = ''; + let state = '进行中'; + while (relRs.goToNextRow()) { + const rt: string = relRs.getString(relRs.getColumnIndex('relation')); + const targetName: string = relRs.getString(relRs.getColumnIndex('target_name')); + if (rt === 'description') description = targetName; + if (rt === 'has_state') state = targetName; + } + relRs.close(); + if (stateFilter && state !== stateFilter) continue; + const cntSql = `SELECT COUNT(*) AS cnt FROM relations WHERE subject_id = ? AND relation = 'CONTAINS_INFO' AND status = 'active'`; + const cntRs: relationalStore.ResultSet = await this.store.querySql(cntSql, [String(node.id)]); + let infoCount = 0; + if (cntRs.goToNextRow()) infoCount = cntRs.getLong(cntRs.getColumnIndex('cnt')); + cntRs.close(); + tasks.push({ taskId: node.name, description, state, infoCount, updatedAt: node.updatedAt }); + } + return tasks; + } + private async removeOrphanNodes(): Promise { if (!this.store) return 0; let deleted = 0; diff --git a/common/src/main/ets/model/GraphDatabase.ets.bak b/common/src/main/ets/model/GraphDatabase.ets.bak new file mode 100644 index 0000000..58e96e3 --- /dev/null +++ b/common/src/main/ets/model/GraphDatabase.ets.bak @@ -0,0 +1,936 @@ +import relationalStore from '@ohos.data.relationalStore'; +import { Context } from '@ohos.abilityAccessCtrl'; + +interface NodeNameCacheItem { name: string; type: string; mentions: number; } +export interface TimeRangeParams { days: number; } + +interface TripletData { + subject: string; + relation: string; + object: string; +} + +interface CriteriaData { + subject?: string; + target?: string; + relation?: string; + sessionId?: string; +} + +interface NodeData { + id: number; + label: string; + type: string; + mentions: number; + depth?: number; +} + +interface EdgeData { + from: number; + to: number; + label: string; + weight: number; + depth?: number; + sessionId?: string; + turnId?: number; +} + +interface GraphData { + nodes: NodeData[]; + edges: EdgeData[]; +} + +export interface RecallEntity { + name: string; + type: string; + mention_count: number; + depth?: number; +} + +interface RecallRelation { + source: string; + target: string; + type: string; + confidence: number; + session_id?: string; + turn_id?: number; + depth?: number; +} + +interface RecallResult { + entities: RecallEntity[]; + relations: RecallRelation[]; + message: string; +} + +interface BfsEntity { + id: number; + name: string; + type: string; + mentions: number; + depth: number; +} + +export interface RelationQueryResult { + sourceId: number; + targetId: number; + sourceName: string; + targetName: string; + type: string; + confidence: number; + sessionId?: string; + turnId?: number; + depth: number; +} + +interface NodeQueryResult { + id: number; + name: string; + type: string; + mentions: number; + depth: number; +} + +interface CleanupResult { + cleaned: number; + deleted_relations?: number; + deleted_orphans?: number; + dry_run?: boolean; + message?: string; +} + +interface IntrospectResult { + entity_count: number; + relation_count: number; + message: string; +} + +interface ArchiveResult { + archived: number; + message: string; +} + +interface SearchResultItem { + name: string; + type: string; + mentions: number; +} + +interface ChatMessage { + role: string; + content: string; + session_id?: string; +} + +interface SnapshotData { + entities: RecallEntity[]; + relations: RecallRelation[]; +} + +const STORE_CONFIG: relationalStore.StoreConfig = { + name: 'trulymem.db', + securityLevel: relationalStore.SecurityLevel.S1 +}; + +export class GraphDatabase { + private store?: relationalStore.RdbStore; + private context?: Context; + + async init(context: Context): Promise { + this.context = context; + this.store = await relationalStore.getRdbStore(context, STORE_CONFIG); + await this.createTables(); + } + + private async createTables(): Promise { + if (!this.store) return; + await this.store.executeSql(` + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + type TEXT DEFAULT 'concept', + mentions INTEGER DEFAULT 1, + created_at TEXT, + updated_at TEXT + ) + `); + await this.store.executeSql(` + CREATE TABLE IF NOT EXISTS relations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + subject_id INTEGER NOT NULL, + relation TEXT NOT NULL, + object_id INTEGER NOT NULL, + weight REAL DEFAULT 1.0, + session_id TEXT, + turn_id INTEGER, + created_at TEXT, + updated_at TEXT, + status TEXT DEFAULT 'active', + date_bucket TEXT + ) + `); + await this.store.executeSql(` + CREATE TABLE IF NOT EXISTS chat_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT, + role TEXT NOT NULL, + content TEXT NOT NULL, + tools TEXT, + created_at TEXT + ) + `); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_node_name ON nodes(name)`); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_node_type ON nodes(type)`); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_rel_source ON relations(subject_id)`); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_rel_target ON relations(object_id)`); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_rel_type ON relations(relation)`); + await this.store.executeSql(`CREATE INDEX IF NOT EXISTS idx_rel_status ON relations(status)`); + } + + async commit(triplets: TripletData[], entityTypes?: Record, sessionId?: string, turnId?: number): Promise { + if (!this.store) return; + for (const triplet of triplets) { + const subjectId: number = await this.upsertNode(triplet.subject, entityTypes?.[triplet.subject]); + const objectId: number = await this.upsertNode(triplet.object, entityTypes?.[triplet.object]); + const existingId: number = await this.checkDuplicateRelation(subjectId, triplet.relation, objectId); + if (existingId > 0) { + continue; + } + const now = new Date().toISOString(); + const dateBucket = now.split('T')[0]; + const bucket: relationalStore.ValuesBucket = { + 'subject_id': subjectId, + 'relation': triplet.relation, + 'object_id': objectId, + 'session_id': sessionId || null, + 'turn_id': turnId || null, + 'created_at': now, + 'updated_at': now, + 'status': 'active', + 'date_bucket': dateBucket + }; + await this.store.insert('relations', bucket); + } + } + + private async checkDuplicateRelation(subjectId: number, relation: string, objectId: number): Promise { + if (!this.store) return -1; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('subject_id', subjectId).and().equalTo('relation', relation).and().equalTo('object_id', objectId).and().equalTo('status', 'active'); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id']); + if (resultSet.goToFirstRow()) { + const id: number = resultSet.getLong(resultSet.getColumnIndex('id')); + resultSet.close(); + return id; + } + resultSet.close(); + return -1; + } + + private async upsertNode(name: string, entityType?: string): Promise { + if (!this.store) return -1; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.equalTo('name', name); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'mentions']); + const now = new Date().toISOString(); + if (resultSet.goToNextRow()) { + const id: number = resultSet.getLong(resultSet.getColumnIndex('id')); + const mentions: number = resultSet.getLong(resultSet.getColumnIndex('mentions')); + resultSet.close(); + const updatePredicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + updatePredicates.equalTo('id', id); + const bucket: relationalStore.ValuesBucket = { + 'mentions': mentions + 1, + 'updated_at': now + }; + await this.store.update(bucket, updatePredicates); + return id; + } + resultSet.close(); + const bucket: relationalStore.ValuesBucket = { + 'name': name, + 'type': entityType || 'concept', + 'mentions': 1, + 'created_at': now, + 'updated_at': now + }; + return await this.store.insert('nodes', bucket); + } + + async recall(queryIntent: string, seedEntities?: string[], depth: number = 2, timeRange?: TimeRangeParams, sessionFilter?: string): Promise { + if (!this.store) { + return { entities: [], relations: [], message: 'Database not initialized' }; + } + // 计算时间范围过滤 + let minDateBucket: string | undefined; + if (timeRange && timeRange.days && timeRange.days > 0) { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - timeRange.days); + minDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, ''); + } + const keywords = queryIntent.toLowerCase().replace(/,/g, ' ').split(/\s+/).filter(w => w.trim()); + const allEntities: BfsEntity[] = []; + const entityIds = new Set(); + let seedEntityIds = new Set(); + + if (keywords.length === 0 && (!seedEntities || seedEntities.length === 0)) { + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.orderByDesc('mentions'); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']); + while (resultSet.goToNextRow() && allEntities.length < 50) { + const id = resultSet.getLong(resultSet.getColumnIndex('id')); + const name = resultSet.getString(resultSet.getColumnIndex('name')); + const type = resultSet.getString(resultSet.getColumnIndex('type')); + const mentions = resultSet.getLong(resultSet.getColumnIndex('mentions')); + entityIds.add(id); + allEntities.push({ id, name, type, mentions, depth: 0 }); + } + resultSet.close(); + } else { + if (seedEntities && seedEntities.length > 0) { + for (const seedName of seedEntities) { + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.equalTo('name', seedName); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']); + while (resultSet.goToNextRow()) { + const id = resultSet.getLong(resultSet.getColumnIndex('id')); + if (!entityIds.has(id)) { + entityIds.add(id); + seedEntityIds.add(id); + allEntities.push({ + id, + name: resultSet.getString(resultSet.getColumnIndex('name')), + type: resultSet.getString(resultSet.getColumnIndex('type')), + mentions: resultSet.getLong(resultSet.getColumnIndex('mentions')), + depth: 0 + }); + } + } + resultSet.close(); + } + } + for (const keyword of keywords) { + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.like('name', `%${keyword}%`); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']); + while (resultSet.goToNextRow()) { + const id = resultSet.getLong(resultSet.getColumnIndex('id')); + if (!entityIds.has(id)) { + entityIds.add(id); + allEntities.push({ + id, + name: resultSet.getString(resultSet.getColumnIndex('name')), + type: resultSet.getString(resultSet.getColumnIndex('type')), + mentions: resultSet.getLong(resultSet.getColumnIndex('mentions')), + depth: 0 + }); + } + } + resultSet.close(); + } + } + + const allRelations: RelationQueryResult[] = []; + let currentLayerIds = new Set(entityIds); + const visitedEntityIds = new Set(entityIds); + // 批量预加载所有相关节点名称,减少 N+1 查询 + const nodeNameCache = new Map(); + + for (let layer = 0; layer < depth && currentLayerIds.size > 0; layer++) { + const currentIds = Array.from(currentLayerIds); + const relations = await this.getRelationsForNodes(currentIds, sessionFilter, minDateBucket); + const nextLayerIds = new Set(); + + for (const rel of relations) { + allRelations.push(rel); + if (!visitedEntityIds.has(rel.targetId)) { + nextLayerIds.add(rel.targetId); + } + if (rel.targetId !== rel.sourceId && !visitedEntityIds.has(rel.sourceId)) { + nextLayerIds.add(rel.sourceId); + } + } + + for (const newId of nextLayerIds) { + if (!visitedEntityIds.has(newId)) { + visitedEntityIds.add(newId); + // 优先从缓存获取,避免 N+1 查询 + const cached = nodeNameCache.get(newId); + if (cached) { + const addedEntity: BfsEntity = { + id: newId, + name: cached.name, + type: cached.type, + mentions: cached.mentions, + depth: layer + 1 + }; + allEntities.push(addedEntity); + } else { + const nodeData = await this.getNodeById(newId); + if (nodeData) { + const cacheItem: NodeNameCacheItem = { name: nodeData.name, type: nodeData.type, mentions: nodeData.mentions }; + nodeNameCache.set(newId, cacheItem); + const addedEntity: BfsEntity = { + id: nodeData.id, + name: nodeData.name, + type: nodeData.type, + mentions: nodeData.mentions, + depth: layer + 1 + }; + allEntities.push(addedEntity); + } + } + } + } + currentLayerIds = nextLayerIds; + } + + const entities: RecallEntity[] = allEntities.map(e => { + const entity: RecallEntity = { + name: e.name, + type: e.type, + mention_count: e.mentions, + depth: e.depth + }; + return entity; + }); + const relations: RecallRelation[] = allRelations.map(r => { + const rel: RecallRelation = { + source: r.sourceName, + target: r.targetName, + type: r.type, + confidence: r.confidence, + session_id: r.sessionId, + turn_id: r.turnId, + depth: r.depth + }; + return rel; + }); + + return { + entities, + relations, + message: `找到 ${entities.length} 个实体, ${relations.length} 条关系` + }; + } + + private async getNodeById(id: number): Promise { + if (!this.store) return null; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.equalTo('id', id); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']); + if (resultSet.goToNextRow()) { + const node: NodeQueryResult = { + id: resultSet.getLong(resultSet.getColumnIndex('id')), + name: resultSet.getString(resultSet.getColumnIndex('name')), + type: resultSet.getString(resultSet.getColumnIndex('type')), + mentions: resultSet.getLong(resultSet.getColumnIndex('mentions')), + depth: 0 + }; + resultSet.close(); + return node; + } + resultSet.close(); + return null; + } + + private async getRelationsForNodes(nodeIds: number[], sessionFilter?: string, minDateBucket?: string): Promise { + if (!this.store || nodeIds.length === 0) return []; + const relations: RelationQueryResult[] = []; + // 批量预加载所有节点名称到缓存,避免 N+1 查询 + const nodeNameCache = new Map(); + for (const id of nodeIds) { + const node = await this.getNodeById(id); + if (node) { + const cacheItem: NodeNameCacheItem = { name: node.name, type: node.type, mentions: node.mentions }; + nodeNameCache.set(id, cacheItem); + } + } + for (const nodeId of nodeIds) { + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'active').and().equalTo('subject_id', nodeId); + if (sessionFilter) { + predicates.and().equalTo('session_id', sessionFilter); + } + if (minDateBucket) { + predicates.and().greaterThanOrEqualTo('date_bucket', minDateBucket); + } + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + while (resultSet.goToNextRow()) { + const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id')); + const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id')); + const sourceNode = nodeNameCache.get(sourceId) || await this.getNodeById(sourceId); + const targetNode = nodeNameCache.get(targetId) || await this.getNodeById(targetId); + if (sourceNode && targetNode) { + relations.push({ + sourceId, + targetId, + sourceName: sourceNode.name, + targetName: targetNode.name, + type: resultSet.getString(resultSet.getColumnIndex('relation')), + confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')), + sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')), + turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')), + depth: 1 + }); + } + } + resultSet.close(); + + const predicates2: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates2.equalTo('status', 'active').and().equalTo('object_id', nodeId); + if (sessionFilter) { + predicates2.and().equalTo('session_id', sessionFilter); + } + if (minDateBucket) { + predicates2.and().greaterThanOrEqualTo('date_bucket', minDateBucket); + } + const resultSet2: relationalStore.ResultSet = await this.store.query(predicates2, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + while (resultSet2.goToNextRow()) { + const sourceId = resultSet2.getLong(resultSet2.getColumnIndex('subject_id')); + const targetId = resultSet2.getLong(resultSet2.getColumnIndex('object_id')); + const sourceNode = nodeNameCache.get(sourceId) || await this.getNodeById(sourceId); + const targetNode = nodeNameCache.get(targetId) || await this.getNodeById(targetId); + if (sourceNode && targetNode) { + relations.push({ + sourceId, + targetId, + sourceName: sourceNode.name, + targetName: targetNode.name, + type: resultSet2.getString(resultSet2.getColumnIndex('relation')), + confidence: resultSet2.getDouble(resultSet2.getColumnIndex('weight')), + sessionId: resultSet2.getString(resultSet2.getColumnIndex('session_id')), + turnId: resultSet2.getLong(resultSet2.getColumnIndex('turn_id')), + depth: 1 + }); + } + } + resultSet2.close(); + } + return relations; + } + + async search(keyword: string): Promise { + if (!this.store) return []; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.like('name', `%${keyword}%`); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['name', 'type', 'mentions']); + const results: SearchResultItem[] = []; + while (resultSet.goToNextRow()) { + results.push({ + name: resultSet.getString(resultSet.getColumnIndex('name')), + type: resultSet.getString(resultSet.getColumnIndex('type')), + mentions: resultSet.getLong(resultSet.getColumnIndex('mentions')) + }); + } + resultSet.close(); + return results; + } + + async purge(criteria: CriteriaData, mode: string = 'soft'): Promise { + if (!this.store) return; + if (!criteria.subject && !criteria.target && !criteria.relation && !criteria.sessionId) { + return; + } + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + let hasCondition = false; + + if (criteria.subject) { + const subjectId = await this.getNodeIdByName(criteria.subject); + if (subjectId > 0) { + predicates.equalTo('subject_id', subjectId); + hasCondition = true; + } + } + if (criteria.target) { + const targetId = await this.getNodeIdByName(criteria.target); + if (targetId > 0) { + if (hasCondition) { + predicates.and(); + } + predicates.equalTo('object_id', targetId); + hasCondition = true; + } + } + if (criteria.relation) { + if (hasCondition) { + predicates.and(); + } + predicates.equalTo('relation', criteria.relation); + hasCondition = true; + } + if (criteria.sessionId) { + if (hasCondition) { + predicates.and(); + } + predicates.equalTo('session_id', criteria.sessionId); + hasCondition = true; + } + + if (hasCondition) { + if (mode === 'soft') { + const bucket: relationalStore.ValuesBucket = { + 'status': 'deleted', + 'updated_at': new Date().toISOString() + }; + await this.store.update(bucket, predicates); + } else { + await this.store.delete(predicates); + } + } + await this.removeOrphanNodes(); + } + + /** + * 记忆图谱 — 在指定时间范围内查询关系和节点 + * 对应 tools.memory_graph + */ + async graph(timeRange: TimeRangeParams, sessionFilter?: string): Promise { + if (!this.store) return { nodes: [], edges: [] }; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'active'); + if (sessionFilter) { + predicates.and().equalTo('session_id', sessionFilter); + } + if (timeRange && timeRange.days && timeRange.days > 0) { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - timeRange.days); + const minDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, ''); + predicates.and().greaterThanOrEqualTo('date_bucket', minDateBucket); + } + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + const nodeIds = new Set(); + const edges: EdgeData[] = []; + while (resultSet.goToNextRow()) { + const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id')); + const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id')); + nodeIds.add(sourceId); + nodeIds.add(targetId); + const edge: EdgeData = { + from: sourceId, + to: targetId, + label: resultSet.getString(resultSet.getColumnIndex('relation')), + weight: resultSet.getDouble(resultSet.getColumnIndex('weight')), + sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')), + turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')) + }; + edges.push(edge); + } + resultSet.close(); + const nodes: NodeData[] = []; + for (const id of nodeIds) { + const node = await this.getNodeById(id); + if (node) { + const nodeData: NodeData = { id: node.id, label: node.name, type: node.type, mentions: node.mentions }; + nodes.push(nodeData); + } + } + const result: GraphData = { nodes, edges }; + return result; + } + + /** + * 记忆快照 — 在指定时间范围内查询实体和关系 + * 对应 tools.memory_snapshot + */ + async snapshot(timeRange: TimeRangeParams, sessionFilter?: string): Promise { + if (!this.store) return { entities: [], relations: [] }; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'active'); + if (sessionFilter) { + predicates.and().equalTo('session_id', sessionFilter); + } + if (timeRange && timeRange.days && timeRange.days > 0) { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - timeRange.days); + const minDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, ''); + predicates.and().greaterThanOrEqualTo('date_bucket', minDateBucket); + } + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + const nodeIds = new Set(); + const relations: RecallRelation[] = []; + while (resultSet.goToNextRow()) { + const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id')); + const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id')); + nodeIds.add(sourceId); + nodeIds.add(targetId); + const sourceNode = await this.getNodeById(sourceId); + const targetNode = await this.getNodeById(targetId); + if (sourceNode && targetNode) { + const rel: RecallRelation = { + source: sourceNode.name, + target: targetNode.name, + type: resultSet.getString(resultSet.getColumnIndex('relation')), + confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')), + session_id: resultSet.getString(resultSet.getColumnIndex('session_id')), + turn_id: resultSet.getLong(resultSet.getColumnIndex('turn_id')) + }; + relations.push(rel); + } + } + resultSet.close(); + const entities: RecallEntity[] = []; + for (const id of nodeIds) { + const node = await this.getNodeById(id); + if (node) { + const recallEntity: RecallEntity = { name: node.name, type: node.type, mention_count: node.mentions }; + entities.push(recallEntity); + } + } + const snapResult: SnapshotData = { entities, relations }; + return snapResult; + } + + /** + * 查询已归档的记忆 + * 对应 tools.memory_query_archived + */ + async queryArchived(days?: number, keyword?: string): Promise { + if (!this.store) return []; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'archived'); + if (keyword) { + predicates.and().like('relation', '%' + keyword + '%'); + } + if (days && days > 0) { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - days); + const maxDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, ''); + predicates.and().lessThanOrEqualTo('date_bucket', maxDateBucket); + } + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']); + const results: RelationQueryResult[] = []; + while (resultSet.goToNextRow()) { + const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id')); + const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id')); + const sourceNode = await this.getNodeById(sourceId); + const targetNode = await this.getNodeById(targetId); + if (sourceNode && targetNode) { + const queryResult: RelationQueryResult = { + sourceId: sourceId, + targetId: targetId, + sourceName: sourceNode.name, + targetName: targetNode.name, + type: resultSet.getString(resultSet.getColumnIndex('relation')), + confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')), + sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')), + turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')), + depth: 0 + }; + results.push(queryResult); + } + } + resultSet.close(); + return results; + } + + private async removeOrphanNodes(): Promise { + if (!this.store) return 0; + let deleted = 0; + // 优化:批量查询所有有关系的节点 ID,避免 O(N²) 逐节点检查 + const activeRelPred: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + activeRelPred.equalTo('status', 'active'); + const relResultSet: relationalStore.ResultSet = await this.store.query(activeRelPred, ['subject_id', 'object_id']); + const relatedIds = new Set(); + while (relResultSet.goToNextRow()) { + relatedIds.add(relResultSet.getLong(relResultSet.getColumnIndex('subject_id'))); + relatedIds.add(relResultSet.getLong(relResultSet.getColumnIndex('object_id'))); + } + relResultSet.close(); + + // 查询所有节点,筛选出不在关系中的孤儿节点 + const nodePred: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + const nodeResultSet: relationalStore.ResultSet = await this.store.query(nodePred, ['id']); + const orphanIds: number[] = []; + while (nodeResultSet.goToNextRow()) { + const nodeId = nodeResultSet.getLong(nodeResultSet.getColumnIndex('id')); + if (!relatedIds.has(nodeId)) { + orphanIds.push(nodeId); + } + } + nodeResultSet.close(); + + // 批量删除孤儿节点 + for (const orphanId of orphanIds) { + const deletePred: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + deletePred.equalTo('id', orphanId); + await this.store.delete(deletePred); + deleted++; + } + return deleted; + } + + private async getNodeIdByName(name: string): Promise { + if (!this.store) return -1; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + predicates.equalTo('name', name); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id']); + if (resultSet.goToNextRow()) { + const id = resultSet.getLong(resultSet.getColumnIndex('id')); + resultSet.close(); + return id; + } + resultSet.close(); + return -1; + } + + async introspect(): Promise { + if (!this.store) return { entity_count: 0, relation_count: 0, message: 'Database not initialized' }; + const nodePredicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + const nodeResultSet = await this.store.query(nodePredicates, ['id']); + let entityCount = 0; + while (nodeResultSet.goToNextRow()) { + entityCount++; + } + nodeResultSet.close(); + + const relPredicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + relPredicates.equalTo('status', 'active'); + const relResultSet = await this.store.query(relPredicates, ['id']); + let relationCount = 0; + while (relResultSet.goToNextRow()) { + relationCount++; + } + relResultSet.close(); + + return { + entity_count: entityCount, + relation_count: relationCount, + message: `数据库包含 ${entityCount} 个实体, ${relationCount} 条关系` + }; + } + + async archive(days: number): Promise { + if (!this.store) return { archived: 0, message: 'Database not initialized' }; + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - days); + const cutoffStr = cutoffDate.toISOString(); + + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'active').and().lessThan('created_at', cutoffStr); + const bucket: relationalStore.ValuesBucket = { + 'status': 'archived', + 'updated_at': new Date().toISOString() + }; + const count = await this.store.update(bucket, predicates); + + return { + archived: count, + message: `归档了 ${count} 条关系` + }; + } + + async cleanup(dryRun: boolean): Promise { + if (!this.store) return { cleaned: 0, message: 'Database not initialized' }; + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - 90); + const cutoffStr = cutoffDate.toISOString(); + + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + predicates.equalTo('status', 'deleted').and().lessThan('updated_at', cutoffStr); + + let deleted = 0; + if (dryRun) { + const resultSet = await this.store.query(predicates, ['id']); + while (resultSet.goToNextRow()) { + deleted++; + } + resultSet.close(); + } else { + deleted = await this.store.delete(predicates); + const orphanCount = await this.removeOrphanNodes(); + return { + cleaned: deleted + orphanCount, + deleted_relations: deleted, + deleted_orphans: orphanCount, + dry_run: false, + message: `删除了 ${deleted} 条关系, ${orphanCount} 个孤立实体` + } as CleanupResult; + } + + return { + cleaned: deleted, + deleted_relations: deleted, + dry_run: true, + message: `将删除 ${deleted} 条关系` + } as CleanupResult; + } + + async saveChatMessage(role: string, content: string, tools?: string, sessionId?: string): Promise { + if (!this.store) return; + const bucket: relationalStore.ValuesBucket = { + 'session_id': sessionId || null, + 'role': role, + 'content': content, + 'tools': tools || null, + 'created_at': new Date().toISOString() + }; + await this.store.insert('chat_records', bucket); + } + + async getChatHistory(limit?: number, sessionId?: string): Promise { + if (!this.store) return []; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records'); + if (sessionId) { + predicates.equalTo('session_id', sessionId); + } + predicates.orderByDesc('created_at'); + if (limit) { + predicates.limitAs(limit); + } + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['role', 'content', 'session_id']); + const messages: ChatMessage[] = []; + while (resultSet.goToNextRow()) { + const msg: ChatMessage = { + role: resultSet.getString(resultSet.getColumnIndex('role')), + content: resultSet.getString(resultSet.getColumnIndex('content')), + session_id: resultSet.getString(resultSet.getColumnIndex('session_id')) + }; + messages.push(msg); + } + resultSet.close(); + return messages.reverse(); + } + + async clearChatHistory(): Promise { + if (!this.store) return; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('chat_records'); + await this.store.delete(predicates); + } + + private async getAllNodes(): Promise { + if (!this.store) return []; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('nodes'); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['id', 'name', 'type', 'mentions']); + const nodes: NodeData[] = []; + while (resultSet.goToNextRow()) { + const node: NodeData = { + id: resultSet.getLong(resultSet.getColumnIndex('id')), + label: resultSet.getString(resultSet.getColumnIndex('name')), + type: resultSet.getString(resultSet.getColumnIndex('type')), + mentions: resultSet.getLong(resultSet.getColumnIndex('mentions')) + }; + nodes.push(node); + } + resultSet.close(); + return nodes; + } + + private async getAllEdges(): Promise { + if (!this.store) return []; + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations'); + const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'relation', 'object_id', 'weight']); + const edges: EdgeData[] = []; + while (resultSet.goToNextRow()) { + const edge: EdgeData = { + from: resultSet.getLong(resultSet.getColumnIndex('subject_id')), + to: resultSet.getLong(resultSet.getColumnIndex('object_id')), + label: resultSet.getString(resultSet.getColumnIndex('relation')), + weight: resultSet.getDouble(resultSet.getColumnIndex('weight')) + }; + edges.push(edge); + } + resultSet.close(); + return edges; + } +} \ No newline at end of file diff --git a/common/src/main/ets/routermanager/PageContext.ets b/common/src/main/ets/routermanager/PageContext.ets index 4b4ba34..911e0f9 100644 --- a/common/src/main/ets/routermanager/PageContext.ets +++ b/common/src/main/ets/routermanager/PageContext.ets @@ -1,3 +1,5 @@ +import { defaultLogger } from '../util/Logger'; + export interface RouterParam { routerName: string; param?: object; @@ -24,7 +26,7 @@ export class PageContext implements IPageContext { try { this.pathStack.replacePath({ name: data.routerName, param: data.param }, animated); } catch (err) { - console.error('Open Page ' + data.routerName + ' failed. ' + err.code + ' ' + err.message); + defaultLogger.error('replacePage: ' + data.routerName + ' failed. ' + err.code + ' ' + err.message); } } @@ -32,7 +34,7 @@ export class PageContext implements IPageContext { try { this.pathStack.pushPath({ name: data.routerName, param: data.param }, animated); } catch (err) { - console.error('Open Page ' + data.routerName + ' failed. ' + err.code + ' ' + err.message); + defaultLogger.error('openPage: ' + data.routerName + ' failed. ' + err.code + ' ' + err.message); } } @@ -40,7 +42,7 @@ export class PageContext implements IPageContext { try { this.pathStack.pop(animated); } catch (err) { - console.error('Pop Page failed. ' + err.code + ' ' + err.message); + defaultLogger.error('popPage failed. ' + err.code + ' ' + err.message); } } diff --git a/common/src/main/ets/service/AIAgentService.ets b/common/src/main/ets/service/AIAgentService.ets index 030a9a5..25a55cc 100644 --- a/common/src/main/ets/service/AIAgentService.ets +++ b/common/src/main/ets/service/AIAgentService.ets @@ -68,6 +68,9 @@ interface ToolPropertiesDefinition { dryRun?: ToolParamProperty; keyword?: ToolParamProperty; attribute?: ToolParamProperty; + sourceType?: ToolParamProperty; + targetType?: ToolParamProperty; + sourceHasStatus?: ToolParamProperty; } interface ToolParamProperty { @@ -218,19 +221,23 @@ function buildSystemPrompt(personaContext: string): string { ## 核心身份 ${personaContext || '你是一个帮助用户记录和回忆信息的助手。你的核心能力是基于图数据库的记忆系统。'} -## 记忆系统 -你的记忆存储在图数据库中,每个记忆都是「实体 - 关系 - 实体」的三元组形式。 -每轮对话开始,你都会按以下顺序操作: +## ⚠️ 内部执行顺序(不得向用户输出) -### 步骤1:查询人设 -使用 memory_recall 查询当前人设,确保回复风格一致。 +以下步骤是内部流程,绝对不要在你的回复中提及或输出。应当仅通过工具调用悄悄完成,回复时直接给出自然的对话内容。 -### 步骤2:查询工作记忆链 -使用 memory_recall 查询任务链,了解会话上下文和进展中的任务。 +1. memory_recall → 查询人设图 +2. task_query → 查询工作记忆链/最近任务 +3. 处理对话内容 + 思考回复 +4. memory_commit → 写入本轮关键信息到图数据库 +5. task_archive → 归档已完成的旧任务 +6. 条件: 本轮调用 ≥5 次查询类工具 → context_rewrite 压缩工具 JSON -### 步骤3:处理用户请求 +## 三元组规范 +使用 memory_commit 时,subject/relation/object 每个字段必须是一个短关键字(1~5个字),不能是完整句子。 -### 步骤4:更新工作记忆链 +## 任务信息节点规范 +- info_nodes 只能包含该任务专属的具体信息节点,严禁关联"用户"、"AI"、"系统"等全局通用实体 +- 全局实体的信息直接用独立关系记录,不需要通过 Task 中转 ## 可用工具 - memory_recall(queryIntent, seedEntities?, depth?, timeRange?, sessionFilter?): 检索记忆 @@ -252,9 +259,10 @@ ${personaContext || '你是一个帮助用户记录和回忆信息的助手。 - task_query(limit?, stateFilter?): 查询任务列表 ## 工具调用规则 -1. 每轮对话必须按顺序执行:步骤1查询人设 → 步骤2查询工作记忆链 → 步骤3处理请求 -2. context_rewrite 必须单独调用,不能和其他工具在同一轮一起调! -3. 工具调用 ≥5 次后应使用 context_rewrite 压缩上下文 +1. ⚠️ 在完成所有工具调用之前,绝对不要输出任何文字。先默默调用工具,等所有结果返回后再输出一次完整的回复。 +2. 每轮对话必须按顺序执行:步骤1查询人设 → 步骤2查询工作记忆链 → 步骤3处理请求 +3. context_rewrite 必须单独调用,不能和其他工具在同一轮一起调! +4. 工具调用 ≥5 次后应使用 context_rewrite 压缩上下文 ## 写入规则 用户明确表达以下信息时必须写入记忆: @@ -278,10 +286,13 @@ const tripletPropsDict: ToolPropertiesDefinition = { confidence: makeNumberProp('置信度') }; const purgeCriteriaDict: ToolPropertiesDefinition = { - subjectContains: makeStringProp(''), - relationType: makeStringProp(''), - targetContains: makeStringProp(''), - sessionId: makeStringProp('') + subjectContains: makeStringProp('源实体名包含(模糊匹配)'), + relationType: makeStringProp('关系类型'), + targetContains: makeStringProp('目标实体名包含(模糊匹配)'), + sessionId: makeStringProp('会话ID过滤'), + sourceType: makeStringProp('源实体类型过滤(如 TaskNode)'), + targetType: makeStringProp('目标实体类型过滤'), + sourceHasStatus: makeStringProp('源实体状态过滤(如 archived)') }; const newRelDict: ToolPropertiesDefinition = { relation: makeStringProp(''), @@ -360,23 +371,23 @@ const personaRemoveProps: ToolPropertiesDefinition = { }; const TOOLS_DEFINITION: ToolFunctionDef[] = [ - makeToolDef('memory_recall', '检索记忆。支持关键词、种子实体、深度扩展。返回相关实体和关系。', recallProps, ['queryIntent']), - makeToolDef('memory_commit', '写入记忆。将三元组写入图数据库。', commitProps, ['triplets']), - makeToolDef('memory_purge', '删除或修正记忆。支持条件删除和纠错替代。', purgeProps, ['criteria', 'mode']), + makeToolDef('memory_recall', '检索记忆。支持关键词、时间范围、会话过滤。返回相关实体和关系。\n\n【⚠️ 强制执行顺序 - 每轮必须严格遵守】\n1. 步骤1(必须首先执行): 查询人设图\n2. 步骤2(必须第二步执行): 查询工作记忆链\n【重要】跳过步骤1或步骤2将导致系统错误!', recallProps, ['queryIntent']), + makeToolDef('memory_commit', '写入记忆。将三元组写入图数据库,支持批量写入。\n\n【重要】写入原则:\n- 用户明确表达的信息 → 必须写入\n- AI推理得到的信息 → 可以写入,但需标注[推测]\n- 避免写入冗余或无意义的信息', commitProps, ['triplets']), + makeToolDef('memory_purge', '删除或修正记忆。支持条件删除和纠错替代。\n\n【使用场景】\n- 纠错替代修正错误信息\n- 删除特定类型的节点关系\n- 删除残留在已归档任务上的状态关系\n\n【重要】\n- 优先使用 supersede 模式修正错误\n- 软删除不会物理删除数据', purgeProps, ['criteria', 'mode']), makeToolDef('memory_introspect', '查看记忆状态。返回实体数量、关系数量、热点实体。', introspectProps), makeToolDef('memory_archive', '归档旧记忆。将N天前的非活跃关系标记为归档状态。', archiveProps2, ['days']), makeToolDef('memory_cleanup', '清理无效数据。物理删除已删除状态超过90天的关系和孤立节点。', cleanupProps), - makeToolDef('memory_query_archived', '查询已归档的记忆。返回所有 status=archived 的关系记录。', queryArchivedProps), - makeToolDef('context_rewrite', '压缩本轮对话的工具调用上下文。将冗长的JSON工具结果提炼为简洁摘要。', contextRewriteProps, ['summary']), + makeToolDef('memory_query_archived', '查询已归档的记忆。\n\n【使用场景】\n- 想了解之前归档过哪些记忆\n- 按关键词搜索归档内容\n- 按时间范围查看最近归档的历史\n\n【注意】\n- 只返回 status=archived 的原始关系记录\n- days 和 keyword 可以单独使用或组合使用', queryArchivedProps), + makeToolDef('context_rewrite', '压缩本轮对话的工具调用上下文。将冗长的JSON工具结果提炼为简洁摘要。\n\n【使用场景】\n- 本轮已执行 ≥5 次查询类工具调用\n- 【⚠️ 强制要求】context_rewrite 必须单独调用,不能和其他工具在同一轮一起调!', contextRewriteProps, ['summary']), makeToolDef('persona_update', '更新AI人设属性(语气、风格、性格等)。', personaProps), makeToolDef('persona_remove', '删除单条人设属性。保留其他人设不变。', personaRemoveProps, ['attribute']), makeToolDef('persona_clear', '清除所有人设信息。', EMPTY_PROPS), - makeToolDef('task_create', '创建新的工作记忆任务节点。', createProps, ['taskId', 'description']), + makeToolDef('task_create', '创建新的工作记忆任务节点。\n\n【重要】info_nodes 只能包含该任务专属的具体信息节点(如\"成语接龙_当前成语\"),**严禁关联\"用户\"、\"AI\"、\"系统\"等全局通用实体**——这些实体不应通过任务中转。', createProps, ['taskId', 'description']), makeToolDef('task_set_state', '设置任务状态。', setStateProps, ['taskId', 'state']), makeToolDef('task_delete', '删除任务节点。', deleteProps, ['taskId']), - makeToolDef('task_link_info', '关联信息节点到任务。', linkInfoProps, ['taskId', 'infoNodeNames']), - makeToolDef('task_archive', '归档任务。将任务设为已暂停,写入归档摘要。', archiveProps, ['taskId']), - makeToolDef('task_query', '查询最近的任务列表。', queryProps) + makeToolDef('task_link_info', '关联信息节点到任务。\n\n【重要】info_node_names只能放任务专属的具体信息节点(如\"成语接龙_当前成语\"),**严禁放\"用户\"、\"AI\"、\"系统\"等全局通用实体**——这些实体不应通过任务中转。', linkInfoProps, ['taskId', 'infoNodeNames']), + makeToolDef('task_archive', '归档已完成/过期的任务。将任务状态设为 archived,同时写入完成摘要到图数据库。\n\n【使用场景】\n1. 话题转变时归档旧任务\n2. 已完成的任务及时归档\n3. 长时间无更新的任务归档\n\n【注意】优先使用 task_archive 替代 task_set_state(state=archived),因为它会自动写入完成摘要。', archiveProps, ['taskId']), + makeToolDef('task_query', '查询最近的任务列表。按更新时间倒序排列。新对话开始时优先使用此工具获取所有进展中的任务,避免重复创建。', queryProps) ]; // ========= 工具名称映射 ========= diff --git a/common/src/main/ets/service/GraphMemoryService.ets b/common/src/main/ets/service/GraphMemoryService.ets index a74323d..1eb3ac0 100644 --- a/common/src/main/ets/service/GraphMemoryService.ets +++ b/common/src/main/ets/service/GraphMemoryService.ets @@ -4,6 +4,7 @@ * 参考:main 分支 core/tools/memory_tools.py */ import { GraphDatabase, RelationQueryResult, TimeRangeParams } from '../model/GraphDatabase'; +import { defaultLogger } from '../util/Logger'; // ========= 接口定义 ========= @@ -129,6 +130,9 @@ export interface PurgeCriteriaParams { relationType?: string; targetContains?: string; sessionId?: string; + sourceType?: string; + targetType?: string; + sourceHasStatus?: string; } export interface NewRelationParams { @@ -412,7 +416,12 @@ export class GraphMemoryService { subject: subjContains, relation: relType, target: tgtContains, - sessionId: sessId + sessionId: sessId, + subjectContains: params.criteria.subjectContains, + targetContains: params.criteria.targetContains, + sourceType: params.criteria.sourceType, + targetType: params.criteria.targetType, + sourceHasStatus: params.criteria.sourceHasStatus }, params.mode === 'hard' ? 'hard' : 'soft'); const purgeResult: MemoryPurgeResult = { deletedCount: subjContains || tgtContains || relType || sessId ? 1 : 0, @@ -619,7 +628,7 @@ export class GraphMemoryService { const result = await this.db.queryArchived(days, keyword); return result; } catch (e) { - console.error('queryArchived error: ' + JSON.stringify(e)); + defaultLogger.error('queryArchived error: ' + JSON.stringify(e)); return []; } } @@ -895,49 +904,9 @@ export class GraphMemoryService { async taskQuery(params?: TaskQueryParams): Promise { const limit: number = params?.limit ?? 10; const stateFilter: string | undefined = params?.stateFilter; - - // 查询所有 TaskNode 类型的节点 - const allNodes = await this.db.search(''); - const taskNodes = allNodes.filter(n => n.type === 'TaskNode'); - - const tasks: TaskInfo[] = []; - for (const node of taskNodes.slice(0, limit)) { - // 查 description 和 has_state - const result = await this.db.recall(node.name, [node.name], 1); - let description = ''; - let state = '进行中'; - let infoCount = 0; - for (const rel of result.relations) { - if (rel.type === 'description' && rel.source === node.name) { - description = rel.target; - } - if (rel.type === 'has_state' && rel.source === node.name) { - state = rel.target; - } - if (rel.type === 'CONTAINS_INFO' && rel.source === node.name) { - infoCount++; - } - } - - // 状态过滤 - if (stateFilter && state !== stateFilter) { - continue; - } - - const taskInfo: TaskInfo = { - taskId: node.name, - description, - state, - infoCount, - updatedAt: '' - }; - tasks.push(taskInfo); - } - - // 按 updatedAt 排序(目前没有 updatedAt,用现有顺序) const queryResult: TaskQueryResult = { - tasks, - message: `找到 ${tasks.length} 个任务` + tasks: await this.db.getRecentTasks(limit, stateFilter) as TaskInfo[], + message: `找到 ${limit} 个任务` }; return queryResult; } @@ -1027,7 +996,7 @@ export class GraphMemoryService { }; return detail; } catch (err) { - console.error('getNodeDetail error: ' + JSON.stringify(err)); + defaultLogger.error('getNodeDetail error: ' + JSON.stringify(err)); return null; } } diff --git a/common/src/main/ets/util/Logger.ets b/common/src/main/ets/util/Logger.ets new file mode 100644 index 0000000..7efb2ec --- /dev/null +++ b/common/src/main/ets/util/Logger.ets @@ -0,0 +1,31 @@ +import { hilog } from "@kit.PerformanceAnalysisKit"; + +class Logger { + private domain: number; + private prefix: string; + private format: string = "%{public}s, %{public}s"; + + public constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xFF00; + } + + public debug(...args: Object[]): void { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + public info(...args: Object[]): void { + hilog.info(this.domain, this.prefix, this.format, args); + } + + public warn(...args: Object[]): void { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + public error(...args: Object[]): void { + hilog.error(this.domain, this.prefix, this.format, args); + } +} + +export const defaultLogger = new Logger("[TrulyMEM]"); +export default defaultLogger; diff --git a/common/src/main/module.json5 b/common/src/main/module.json5 index 08343c6..210518c 100644 --- a/common/src/main/module.json5 +++ b/common/src/main/module.json5 @@ -1,13 +1,12 @@ { "module": { "name": "common", - "type": "shared", - "description": "TrulyMEM common shared module", + "type": "har", + "description": "TrulyMEM common module", "deviceTypes": [ "phone", "tablet", "2in1" - ], - "deliveryWithInstall": true + ] } -} +} \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 deleted file mode 100644 index 14d5b83..0000000 --- a/entry/build-profile.json5 +++ /dev/null @@ -1,8 +0,0 @@ -{ - "apiType": "stageMode", - "targets": [ - { - "name": "default" - } - ] -} \ No newline at end of file diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt deleted file mode 100644 index b15857f..0000000 --- a/entry/obfuscation-rules.txt +++ /dev/null @@ -1,34 +0,0 @@ -# Define project specific obfuscation rules here. -# You can include the obfuscation configuration files in the current module's build-profile.json5. -# -# For more details, see -# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 - -# Obfuscation options: -# -disable-obfuscation: disable all obfuscations -# -enable-property-obfuscation: obfuscate the property names -# -enable-toplevel-obfuscation: obfuscate the names in the global scope -# -compact: remove unnecessary blank spaces and all line feeds -# -remove-log: remove all console.* statements -# -print-namecache: print the name cache that contains the mapping from the old names to new names -# -apply-namecache: reuse the given cache file - -# Keep options: -# -keep-property-name: specifies property names that you want to keep -# -keep-global-name: specifies names that you want to keep in the global scope - --enable-property-obfuscation --enable-toplevel-obfuscation --enable-filename-obfuscation --enable-export-obfuscation - -# 保留JSON解析相关的属性名,防止混淆导致JSON解析失败 --keep-property-name -id -name -nameEn -icon -isCustom -builtin -custom -pixelMap \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 deleted file mode 100644 index b75b87b..0000000 --- a/entry/oh-package.json5 +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "entry", - "version": "1.0.0", - "description": "TrulyMEM - A personal memory and knowledge graph application.", - "main": "", - "author": "", - "license": "", - "dependencies": { - "trulymem-core": "file:../trulymem-core" - } -} diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets deleted file mode 100644 index 25ec1fb..0000000 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ /dev/null @@ -1,56 +0,0 @@ -import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit'; -import { window } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; - -export default class EntryAbility extends UIAbility { - onCreate(want: Want, param: AbilityConstant.LaunchParam): void { - console.info('EntryAbility onCreate'); - } - - onDestroy(): void { - console.info('EntryAbility onDestroy'); - } - - onWindowStageCreate(windowStage: window.WindowStage): void { - const windowClass: window.Window = windowStage.getMainWindowSync(); - - try { - windowClass.setWindowBackgroundColor('#00000000'); - } catch (e) { - console.error('Failed to set background color: ' + (e as BusinessError).message); - } - - try { - windowClass.setWindowSystemBarProperties({ - statusBarColor: '#00000000', - navigationBarColor: '#00000000', - statusBarContentColor: '#FFFFFF', - navigationBarContentColor: '#FFFFFF' - }); - } catch (e) { - console.error('Failed to set system bar: ' + (e as BusinessError).message); - } - - AppStorage.setOrCreate('main_window', windowClass); - - windowStage.loadContent('pages/Index', (err: BusinessError) => { - if (err.code) { - console.error('Failed to load content: ' + err.message); - return; - } - console.info('Succeeded in loading content'); - }); - } - - onWindowStageDestroy(): void { - console.info('EntryAbility onWindowStageDestroy'); - } - - onForeground(): void { - console.info('EntryAbility onForeground'); - } - - onBackground(): void { - console.info('EntryAbility onBackground'); - } -} diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 deleted file mode 100644 index 8e109a5..0000000 --- a/entry/src/main/module.json5 +++ /dev/null @@ -1,19 +0,0 @@ -{ - "module": { - "name": "entry", - "type": "har", - "description": "$string:module_desc", - "deviceTypes": ["phone", "tablet", "2in1"], - "deliveryWithInstall": true, - "installationFree": false, - "pages": "$profile:main_pages", - "requestPermissions": [ - { - "name": "ohos.permission.INTERNET" - }, - { - "name": "ohos.permission.GET_NETWORK_INFO" - } - ] - } -} diff --git a/entry/src/main/resources/base/media/layered_image.svg b/entry/src/main/resources/base/media/layered_image.svg deleted file mode 100644 index 2e9f58a..0000000 --- a/entry/src/main/resources/base/media/layered_image.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - T - diff --git a/entry/src/main/resources/base/media/startIcon.svg b/entry/src/main/resources/base/media/startIcon.svg deleted file mode 100644 index 669d96d..0000000 --- a/entry/src/main/resources/base/media/startIcon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - TM - diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json deleted file mode 100644 index 1898d94..0000000 --- a/entry/src/main/resources/base/profile/main_pages.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "src": [ - "pages/Index" - ] -} diff --git a/features/chat/BuildProfile.ets b/features/chat/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/features/chat/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/features/chat/build-profile.json5 b/features/chat/build-profile.json5 index b823c6e..79961f9 100644 --- a/features/chat/build-profile.json5 +++ b/features/chat/build-profile.json5 @@ -1,5 +1,7 @@ { "apiType": "stageMode", + "buildOption": { + }, "targets": [ { "name": "default" diff --git a/features/chat/chat b/features/chat/chat new file mode 120000 index 0000000..91b7861 --- /dev/null +++ b/features/chat/chat @@ -0,0 +1 @@ +/home/program/TrulyMEM-TrueHumanMEM/features/chat \ No newline at end of file diff --git a/features/chat/hvigorfile.ts b/features/chat/hvigorfile.ts new file mode 100644 index 0000000..6bf3dc5 --- /dev/null +++ b/features/chat/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, + plugins: [] +}; diff --git a/entry/oh-package-lock.json5 b/features/chat/oh-package-lock.json5 similarity index 64% rename from entry/oh-package-lock.json5 rename to features/chat/oh-package-lock.json5 index d8334d9..6941f2c 100644 --- a/entry/oh-package-lock.json5 +++ b/features/chat/oh-package-lock.json5 @@ -6,13 +6,13 @@ "lockfileVersion": 3, "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "specifiers": { - "librcon@../librcon": "librcon@../librcon" + "@ohos/common@../../common": "@ohos/common@../../common" }, "packages": { - "librcon@../librcon": { - "name": "librcon", + "@ohos/common@../../common": { + "name": "@ohos/common", "version": "1.0.0", - "resolved": "../librcon", + "resolved": "../../common", "registryType": "local" } } diff --git a/features/chat/oh_modules/@ohos/common b/features/chat/oh_modules/@ohos/common new file mode 120000 index 0000000..3da9bd8 --- /dev/null +++ b/features/chat/oh_modules/@ohos/common @@ -0,0 +1 @@ +../../../../common \ No newline at end of file diff --git a/features/chat/src/main/ets/components/ChatComponents.ets b/features/chat/src/main/ets/components/ChatComponents.ets new file mode 100644 index 0000000..d67a04e --- /dev/null +++ b/features/chat/src/main/ets/components/ChatComponents.ets @@ -0,0 +1,161 @@ +import { ChatMessage } from '@ohos/common'; + +/** + * ChatMessageBubble — 单条聊天消息气泡 + * 封装消息的角色标识、内容样式、玻璃拟态背景 + */ +@Component +export struct ChatMessageBubble { + @ObjectLink msg: ChatMessage; + + build() { + Column() { + // 角色标识 + Text(this.msg.role === 'user' ? '🧑 你' : '🤖 AI') + .fontSize(11) + .fontColor(this.msg.role === 'user' ? '#7C4DFF' : '#999') + .width('100%') + + // 消息内容 + Text(this.msg.content) + .fontSize(15) + .width('100%') + .margin({ top: 4 }) + .fontColor('#FFFFFF') + } + .padding(12) + .backgroundColor(this.msg.role === 'user' ? 'rgba(124,77,255,0.15)' : 'rgba(245,245,245,0.1)') + .borderRadius(12) + .border({ + width: 1, + color: this.msg.role === 'user' ? 'rgba(124,77,255,0.3)' : 'rgba(255,255,255,0.1)' + }) + .backgroundBlurStyle(BlurStyle.Thin) + .margin({ left: 8, right: 8, bottom: 8 }) + .width('100%') + .alignItems(HorizontalAlign.Start) + } +} + +/** + * ThinkingIndicator — AI 思考中指示器 + * 玻璃拟态加载动画 + 文字提示 + */ +@Component +export struct ThinkingIndicator { + build() { + Row() { + LoadingProgress() + .width(20) + .height(20) + .margin({ right: 8 }) + .color('#7C4DFF') + Text('AI 思考中...') + .fontSize(13) + .fontColor('#7C4DFF') + } + .padding(12) + .backgroundColor('rgba(124,77,255,0.1)') + .borderRadius(12) + .border({ width: 1, color: 'rgba(124,77,255,0.2)' }) + .backgroundBlurStyle(BlurStyle.Thin) + .margin({ left: 8, right: 8, bottom: 8 }) + } +} + +/** + * ToolCallLogPanel — 工具调用日志面板 + * 橙色风格,显示 Agent 调用的工具链 + */ +@Component +export struct ToolCallLogPanel { + @Prop logText: string; + + build() { + Text(this.logText) + .fontSize(10) + .fontColor('#FF9800') + .backgroundColor('rgba(255,152,0,0.1)') + .padding(8) + .borderRadius(8) + .border({ width: 1, color: 'rgba(255,152,0,0.2)' }) + .backgroundBlurStyle(BlurStyle.Thin) + .margin({ left: 8, right: 8, bottom: 4 }) + .lineHeight(16) + } +} + +/** + * ChatInputBar — 底部输入栏 + * TextArea + 发送按钮,主题色边框 + */ +@Component +export struct ChatInputBar { + @Link inputText: string; + @Prop isThinking: boolean; + onSend?: () => void; + + build() { + Row() { + TextArea({ text: this.inputText, placeholder: '输入消息...' }) + .layoutWeight(1) + .onChange((v: string) => { this.inputText = v; }) + .height(40) + .backgroundColor('rgba(255,255,255,0.1)') + .borderRadius(8) + .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) + + Button('发送') + .enabled(!this.isThinking) + .onClick(() => { this.onSend?.(); }) + .backgroundColor('#7C4DFF') + .borderRadius(8) + } + .width('100%') + .padding(8) + .backgroundColor('rgba(255,255,255,0.05)') + .backgroundBlurStyle(BlurStyle.Regular) + .border({ + width: 1, + color: 'rgba(124,77,255,0.2)', + style: BorderStyle.Solid + }) + } +} + +/** + * ChatMessageList — 聊天消息列表容器 + * 整合消息气泡、思考指示器、工具日志 + */ +@Component +export struct ChatMessageList { + @Prop messages: ChatMessage[]; + @Prop isThinking: boolean; + @Prop toolCallLog: string; + private scrollController: Scroller = new Scroller(); + + build() { + List() { + ForEach(this.messages, (msg: ChatMessage) => { + ListItem() { + ChatMessageBubble({ msg: msg }) + } + }) + + if (this.isThinking) { + ListItem() { + ThinkingIndicator() + } + } + + if (this.toolCallLog && !this.isThinking) { + ListItem() { + ToolCallLogPanel({ logText: this.toolCallLog }) + } + } + } + .width('100%') + .layoutWeight(1) + .backgroundColor('rgba(0,0,0,0.1)') + } +} diff --git a/features/chat/src/main/ets/pages/ChatPage.ets b/features/chat/src/main/ets/pages/ChatPage.ets index d2fb153..d2ab1c4 100644 --- a/features/chat/src/main/ets/pages/ChatPage.ets +++ b/features/chat/src/main/ets/pages/ChatPage.ets @@ -1,6 +1,5 @@ -import { GraphDatabase } from '@ohos/common'; -import { GraphMemoryService } from '@ohos/common'; -import { AIAgentService, ChatMessage, AgentResponse } from '@ohos/common'; +import { GraphDatabase, GraphMemoryService, AIAgentService, ChatMessage, AgentResponse, Logger } from '@ohos/common'; +import { ChatMessageList, ChatInputBar } from '../components/ChatComponents'; @Component export struct ChatPage { @@ -9,7 +8,6 @@ export struct ChatPage { @Prop db: GraphDatabase; @State toolCallLog: string = ''; @State isThinking: boolean = false; - private scrollController: Scroller = new Scroller(); private agentService?: AIAgentService; async aboutToAppear() { @@ -62,7 +60,7 @@ export struct ChatPage { await this.db.saveChatMessage('assistant', agentResponse.content, this.toolCallLog, this.agentService.getSessionId()); this.messages = [...this.messages, { role: 'assistant', content: agentResponse.content }]; } catch (err) { - console.error('Agent request failed: ' + JSON.stringify(err)); + Logger.error('Agent request failed: ' + JSON.stringify(err)); this.messages = [...this.messages, { role: 'assistant', content: `⚠️ 请求失败: ${err.message || JSON.stringify(err)}` }]; } finally { this.isThinking = false; @@ -71,101 +69,20 @@ export struct ChatPage { build() { Column() { - // 聊天列表 - List() { - ForEach(this.messages, (msg: ChatMessage) => { - ListItem() { - Column() { - // 角色标识 - Text(msg.role === 'user' ? '🧑 你' : '🤖 AI') - .fontSize(11) - .fontColor(msg.role === 'user' ? '#7C4DFF' : '#999') - .width('100%') + ChatMessageList({ + messages: this.messages, + isThinking: this.isThinking, + toolCallLog: this.toolCallLog + }) - // 消息内容 - Text(msg.content) - .fontSize(15) - .width('100%') - .margin({ top: 4 }) - .fontColor('#FFFFFF') - } - .padding(12) - .backgroundColor(msg.role === 'user' ? 'rgba(124,77,255,0.15)' : 'rgba(245,245,245,0.1)') - .borderRadius(12) - .border({ width: 1, color: msg.role === 'user' ? 'rgba(124,77,255,0.3)' : 'rgba(255,255,255,0.1)' }) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ left: 8, right: 8, bottom: 8 }) - .width('100%') - .alignItems(HorizontalAlign.Start) - } - }) - - // loading 指示 - if (this.isThinking) { - ListItem() { - Row() { - LoadingProgress() - .width(20) - .height(20) - .margin({ right: 8 }) - .color('#7C4DFF') - Text('AI 思考中...') - .fontSize(13) - .fontColor('#7C4DFF') - } - .padding(12) - .backgroundColor('rgba(124,77,255,0.1)') - .borderRadius(12) - .border({ width: 1, color: 'rgba(124,77,255,0.2)' }) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ left: 8, right: 8, bottom: 8 }) - } - } - - // 工具调用日志 - if (this.toolCallLog && !this.isThinking) { - ListItem() { - Text(this.toolCallLog) - .fontSize(10) - .fontColor('#FF9800') - .backgroundColor('rgba(255,152,0,0.1)') - .padding(8) - .borderRadius(8) - .border({ width: 1, color: 'rgba(255,152,0,0.2)' }) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ left: 8, right: 8, bottom: 4 }) - .lineHeight(16) - } - } - } - .width('100%') - .layoutWeight(1) - .backgroundColor('rgba(0,0,0,0.1)') - - // 输入区 - Row() { - TextArea({ text: this.inputText, placeholder: '输入消息...' }) - .layoutWeight(1) - .onChange((v: string) => { this.inputText = v; }) - .height(40) - .backgroundColor('rgba(255,255,255,0.1)') - .borderRadius(8) - .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) - - Button('发送') - .enabled(!this.isThinking) - .onClick(() => this.sendMessage()) - .backgroundColor('#7C4DFF') - .borderRadius(8) - } - .width('100%') - .padding(8) - .backgroundColor('rgba(255,255,255,0.05)') - .backgroundBlurStyle(BlurStyle.Regular) - .border({ width: 1, color: 'rgba(124,77,255,0.2)', style: BorderStyle.Solid }) + ChatInputBar({ + inputText: this.inputText, + isThinking: this.isThinking, + onSend: (): void => { this.sendMessage(); } + }) } .width('100%') .height('100%') .backgroundColor('rgba(26,27,46,0.95)') } -} +} \ No newline at end of file diff --git a/trulymem-core/src/main/module.json5 b/features/chat/src/main/module.json5 similarity index 53% rename from trulymem-core/src/main/module.json5 rename to features/chat/src/main/module.json5 index 6b6e18b..ec75452 100644 --- a/trulymem-core/src/main/module.json5 +++ b/features/chat/src/main/module.json5 @@ -1,11 +1,10 @@ { "module": { - "name": "librcon", + "name": "chat", "type": "har", - "requestPermissions": [ - ], + "description": "TrulyMEM chat feature module", "deviceTypes": [ - "default", + "phone", "tablet", "2in1" ] diff --git a/features/commonbusiness/Index.ets b/features/commonbusiness/Index.ets deleted file mode 100644 index 90d8999..0000000 --- a/features/commonbusiness/Index.ets +++ /dev/null @@ -1 +0,0 @@ -// Common business module - placeholder for shared business logic diff --git a/features/commonbusiness/build-profile.json5 b/features/commonbusiness/build-profile.json5 deleted file mode 100644 index b823c6e..0000000 --- a/features/commonbusiness/build-profile.json5 +++ /dev/null @@ -1,8 +0,0 @@ -{ - "apiType": "stageMode", - "targets": [ - { - "name": "default" - } - ] -} diff --git a/features/commonbusiness/oh-package.json5 b/features/commonbusiness/oh-package.json5 deleted file mode 100644 index d1d957d..0000000 --- a/features/commonbusiness/oh-package.json5 +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@ohos/commonbusiness", - "version": "1.0.0", - "description": "TrulyMEM common business module", - "main": "Index.ets", - "author": "", - "license": "", - "dependencies": { - "@ohos/common": "file:../../common" - } -} diff --git a/features/graph/BuildProfile.ets b/features/graph/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/features/graph/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/features/graph/build-profile.json5 b/features/graph/build-profile.json5 index b823c6e..79961f9 100644 --- a/features/graph/build-profile.json5 +++ b/features/graph/build-profile.json5 @@ -1,5 +1,7 @@ { "apiType": "stageMode", + "buildOption": { + }, "targets": [ { "name": "default" diff --git a/features/graph/graph b/features/graph/graph new file mode 120000 index 0000000..f659735 --- /dev/null +++ b/features/graph/graph @@ -0,0 +1 @@ +/home/program/TrulyMEM-TrueHumanMEM/features/graph \ No newline at end of file diff --git a/features/graph/hvigorfile.ts b/features/graph/hvigorfile.ts new file mode 100644 index 0000000..6bf3dc5 --- /dev/null +++ b/features/graph/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, + plugins: [] +}; diff --git a/features/graph/oh-package-lock.json5 b/features/graph/oh-package-lock.json5 new file mode 100644 index 0000000..6941f2c --- /dev/null +++ b/features/graph/oh-package-lock.json5 @@ -0,0 +1,19 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/common@../../common": "@ohos/common@../../common" + }, + "packages": { + "@ohos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/features/graph/oh_modules/@ohos/common b/features/graph/oh_modules/@ohos/common new file mode 120000 index 0000000..3da9bd8 --- /dev/null +++ b/features/graph/oh_modules/@ohos/common @@ -0,0 +1 @@ +../../../../common \ No newline at end of file diff --git a/features/graph/src/main/ets/components/GraphComponents.ets b/features/graph/src/main/ets/components/GraphComponents.ets new file mode 100644 index 0000000..de39192 --- /dev/null +++ b/features/graph/src/main/ets/components/GraphComponents.ets @@ -0,0 +1,251 @@ +import web_webview from '@ohos.web.webview'; +import { GraphDatabase, RecallEntity, GraphMemoryService, ConnectionItem, NodeDetailInfo, Logger } from '@ohos/common'; + +/** + * GraphNodeSearchBar — 图节点搜索栏 + * 悬浮在 WebView 上方的搜索输入框 + */ +@Component +export struct GraphNodeSearchBar { + @Link searchText: string; + onSearchInput?: (value: string) => void; + + build() { + Column() { + TextInput({ placeholder: '搜索节点...', text: this.searchText }) + .width('80%') + .height(40) + .backgroundColor('rgba(10, 10, 26, 0.8)') + .fontColor('#ffffff') + .placeholderColor('#666688') + .borderRadius(8) + .border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' }) + .margin({ top: 20 }) + .onChange((value: string) => { + this.onSearchInput?.(value); + }) + } + .width('100%') + .position({ x: 0, y: 0 }) + .zIndex(10) + } +} + +/** + * NodeDetailPanel — 节点详情浮层 + * 显示选中节点的名称、类型、提及次数、连接关系 + */ +@Component +export struct NodeDetailPanel { + @Prop detail: NodeDetailInfo; + onClose?: () => void; + + build() { + Column() { + Column() { + Text(this.detail.name) + .fontSize(18) + .fontColor('#44ff88') + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('类型: ' + this.detail.type) + .fontSize(14) + .fontColor('#aaaacc') + + Text('提及次数: ' + this.detail.mention_count) + .fontSize(14) + .fontColor('#aaaacc') + + Text('连接数: ' + this.detail.connection_count) + .fontSize(14) + .fontColor('#aaaacc') + + if (this.detail.connections && this.detail.connections.length > 0) { + Text('连接关系:') + .fontSize(14) + .fontColor('#8888aa') + .margin({ top: 10, bottom: 5 }) + List() { + ForEach(this.detail.connections, (conn: ConnectionItem) => { + ListItem() { + Text(conn.type + ': ' + conn.target_name) + .fontSize(12) + .fontColor('#aaaacc') + } + }) + } + .height(100) + } + + Button('关闭') + .width(80) + .height(30) + .margin({ top: 15 }) + .backgroundColor('rgba(100, 100, 255, 0.3)') + .fontColor('#ffffff') + .onClick(() => { + this.onClose?.(); + }) + } + .padding(20) + .backgroundColor('rgba(10, 10, 26, 0.95)') + .borderRadius(12) + .border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' }) + .width(300) + } + .width('100%') + .height('100%') + .backgroundColor('rgba(0, 0, 0, 0.5)') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .zIndex(20) + } +} + +/** + * GraphWebView — 图可视化 WebView 封装 + * 包含 WebView 配置、JS Bridge 注册、数据加载回调 + */ +@Component +export struct GraphWebView { + private controller: web_webview.WebviewController = new web_webview.WebviewController(); + private bridge?: NativeBridge; + + onPageEnd?: () => void; + + getController(): web_webview.WebviewController { + return this.controller; + } + + setBridge(bridge: NativeBridge): void { + this.bridge = bridge; + } + + build() { + Web({ src: $rawfile('graph.html'), controller: this.controller }) + .javaScriptAccess(true) + .width('100%') + .height('100%') + .zoomAccess(true) + .onPageEnd(() => { + this.onPageEnd?.(); + }) + .javaScriptProxy({ + object: this.bridge, + name: 'nativeBridge', + methodList: ['onNodeClick', 'onSearch'], + asyncMethodList: ['requestGraphData'], + controller: this.controller + }) + } +} + +/** + * NativeBridge — WebView 原生桥接类(移动自 GraphPage) + * 负责 ArkTS ↔ WebView JavaScript 双向通信 + */ +export class NativeBridge { + private controller: web_webview.WebviewController; + private onRequestGraphData: () => void; + private onNodeClickCallback: (nodeId: number, nodeName: string) => void; + private onSearchCallback: (query: string) => void; + + constructor( + controller: web_webview.WebviewController, + onRequestGraphData: () => void, + onNodeClickCallback: (nodeId: number, nodeName: string) => void, + onSearchCallback: (query: string) => void + ) { + this.controller = controller; + this.onRequestGraphData = onRequestGraphData; + this.onNodeClickCallback = onNodeClickCallback; + this.onSearchCallback = onSearchCallback; + } + + onNodeClick(nodeId: number, nodeName: string): void { + Logger.info('Node clicked: id=' + nodeId + ', name=' + nodeName); + if (this.onNodeClickCallback) { + this.onNodeClickCallback(nodeId, nodeName); + } + } + + onSearch(query: string): void { + Logger.info('Search from WebView: ' + query); + if (this.onSearchCallback) { + this.onSearchCallback(query); + } + } + + requestGraphData(): void { + Logger.info('requestGraphData called from WebView'); + if (this.onRequestGraphData) { + this.onRequestGraphData(); + } + } +} + +/** + * GraphDataService — 图数据查询服务 + * 封装从 GraphDatabase 读取节点和边的逻辑 + */ +export class GraphDataService { + private db: GraphDatabase; + + constructor(db: GraphDatabase) { + this.db = db; + } + + async getAllNodes(): Promise { + const result = await this.db.search(''); + return result.map((r, idx): GraphNodeItem => { + return { + id: idx + 1, + label: r.name, + type: r.type, + mentions: r.mentions + }; + }); + } + + async getAllEdges(): Promise { + const recallResult = await this.db.recall('', [], 3); + const nameToId: Record = {}; + recallResult.entities.forEach((e: RecallEntity, idx: number): void => { + nameToId[e.name as string] = idx + 1; + }); + const edgeItems: GraphEdgeItem[] = []; + for (let i = 0; i < recallResult.relations.length; i++) { + const r = recallResult.relations[i]; + const sourceId = nameToId[r.source]; + const targetId = nameToId[r.target]; + if (sourceId !== undefined && targetId !== undefined) { + edgeItems.push({ + id: i + 1, + source: sourceId, + target: targetId, + label: r.type, + relation: r.type + }); + } + } + return edgeItems; + } +} + +// ========= 内部类型定义 ========= + +interface GraphNodeItem { + id: number; + label: string; + type: string; + mentions: number; +} + +interface GraphEdgeItem { + id: number; + source: number; + target: number; + label: string; + relation: string; +} \ No newline at end of file diff --git a/features/graph/src/main/ets/pages/GraphPage.ets b/features/graph/src/main/ets/pages/GraphPage.ets index fd3b3b9..8df9206 100644 --- a/features/graph/src/main/ets/pages/GraphPage.ets +++ b/features/graph/src/main/ets/pages/GraphPage.ets @@ -1,13 +1,25 @@ /** - * GraphPage — 记忆星图页面 + * GraphPage — 记忆星图页面(重构后) * 使用 WebView 显示 Three.js 3D 图可视化 - * 通过 javaScriptProxy 与 WebView 双向通信 + * 子组件:GraphWebView、GraphNodeSearchBar、NodeDetailPanel、GraphDataService、NativeBridge */ import web_webview from '@ohos.web.webview'; -import { GraphDatabase, RecallEntity } from '@ohos/common'; -import { GraphMemoryService, ConnectionItem, NodeDetailInfo } from '@ohos/common'; +import { + GraphDatabase, + NodeDetailInfo, + Logger, + RecallEntity, + GraphMemoryService +} from '@ohos/common'; +import { + GraphWebView, + GraphNodeSearchBar, + NodeDetailPanel, + GraphDataService, + NativeBridge +} from '../components/GraphComponents'; -// ========= 图数据结构定义 ========= +// ========= GraphPage 组件 ========= interface GraphNodeItem { id: number; @@ -24,52 +36,6 @@ interface GraphEdgeItem { relation: string; } -// NodeDetailInfo 已从 GraphMemoryService 导入 - -// ========= WebView 原生桥接 ========= - -class NativeBridge { - private controller: web_webview.WebviewController; - private onRequestGraphData: () => void; - private onNodeClickCallback: (nodeId: number, nodeName: string) => void; - private onSearchCallback: (query: string) => void; - - constructor( - controller: web_webview.WebviewController, - onRequestGraphData: () => void, - onNodeClickCallback: (nodeId: number, nodeName: string) => void, - onSearchCallback: (query: string) => void - ) { - this.controller = controller; - this.onRequestGraphData = onRequestGraphData; - this.onNodeClickCallback = onNodeClickCallback; - this.onSearchCallback = onSearchCallback; - } - - onNodeClick(nodeId: number, nodeName: string): void { - console.info('Node clicked: id=' + nodeId + ', name=' + nodeName); - if (this.onNodeClickCallback) { - this.onNodeClickCallback(nodeId, nodeName); - } - } - - onSearch(query: string): void { - console.info('Search from WebView: ' + query); - if (this.onSearchCallback) { - this.onSearchCallback(query); - } - } - - requestGraphData(): void { - console.info('requestGraphData called from WebView'); - if (this.onRequestGraphData) { - this.onRequestGraphData(); - } - } -} - -// ========= GraphPage 组件 ========= - @Component export struct GraphPage { private controller: web_webview.WebviewController = new web_webview.WebviewController(); @@ -101,14 +67,13 @@ export struct GraphPage { */ private async handleNodeClick(nodeId: number, nodeName: string): Promise { try { - // 从数据库查询节点详细信息 - const detail = await this.graphService.getNodeDetail(nodeName); + const detail: NodeDetailInfo | null = await this.graphService.getNodeDetail(nodeName); if (detail) { this.selectedNodeDetail = detail; this.showNodeDetail = true; } } catch (err) { - console.error('handleNodeClick error: ' + JSON.stringify(err)); + Logger.error('handleNodeClick error: ' + JSON.stringify(err)); } } @@ -137,49 +102,20 @@ export struct GraphPage { } /** - * 从数据库读取全量图数据,通过 runJavaScript 推送给 WebView - */ - private async pushGraphDataToWebView(): Promise { - try { - // 获取所有节点 - const allNodes: GraphNodeItem[] = await this.getAllNodesData(); - // 获取所有活跃关系 - const allEdges: GraphEdgeItem[] = await this.getAllEdgesData(); - - // 通过 JavaScript Bridge 推送数据 - if (this.controller) { - const jsCode: string = - `window.loadGraphData(${JSON.stringify({ nodes: allNodes, edges: allEdges })});`; - this.controller.runJavaScript(jsCode); - } - - this.nodeCount = allNodes.length; - this.edgeCount = allEdges.length; - } catch (err) { - console.error('pushGraphDataToWebView error: ' + JSON.stringify(err)); - } - } - - /** - * 从数据库查询所有节点 + * 从数据库读取全量图数据,推送给 WebView */ private async getAllNodesData(): Promise { const result = await this.db.search(''); - const items: GraphNodeItem[] = result.map((r, idx): GraphNodeItem => { - const item: GraphNodeItem = { + return result.map((r, idx): GraphNodeItem => { + return { id: idx + 1, label: r.name, type: r.type, mentions: r.mentions }; - return item; }); - return items; } - /** - * 从数据库查询所有活跃关系 - */ private async getAllEdgesData(): Promise { const recallResult = await this.db.recall('', [], 3); const nameToId: Record = {}; @@ -192,19 +128,39 @@ export struct GraphPage { const sourceId: number | undefined = nameToId[r.source]; const targetId: number | undefined = nameToId[r.target]; if (sourceId !== undefined && targetId !== undefined) { - const edgeItem: GraphEdgeItem = { + edgeItems.push({ id: i + 1, source: sourceId, target: targetId, label: r.type, relation: r.type - }; - edgeItems.push(edgeItem); + }); } } return edgeItems; } + /** + * 从数据库读取全量图数据,推送给 WebView + */ + private async pushGraphDataToWebView(): Promise { + try { + const allNodes: GraphNodeItem[] = await this.getAllNodesData(); + const allEdges: GraphEdgeItem[] = await this.getAllEdgesData(); + + if (this.controller) { + const jsCode: string = + `window.loadGraphData(${JSON.stringify({ nodes: allNodes, edges: allEdges })});`; + this.controller.runJavaScript(jsCode); + } + + this.nodeCount = allNodes.length; + this.edgeCount = allEdges.length; + } catch (err) { + Logger.error('pushGraphDataToWebView error: ' + JSON.stringify(err)); + } + } + build() { Stack() { // WebView 显示 3D 星图 @@ -212,11 +168,10 @@ export struct GraphPage { .javaScriptAccess(true) .width('100%') .height('100%') - .zoomAccess(true) // 启用缩放(自带双指捏合) + .zoomAccess(true) .onPageEnd(() => { this.pushGraphDataToWebView(); }) - // 注册原生桥接对象,供 WebView JavaScript 调用 .javaScriptProxy({ object: this.bridge, name: 'nativeBridge', @@ -225,89 +180,21 @@ export struct GraphPage { controller: this.controller }) - // 搜索框组件 - 在 WebView 上方 - Column() { - TextInput({ placeholder: '搜索节点...', text: this.searchText }) - .width('80%') - .height(40) - .backgroundColor('rgba(10, 10, 26, 0.8)') - .fontColor('#ffffff') - .placeholderColor('#666688') - .borderRadius(8) - .border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' }) - .margin({ top: 20 }) - .onChange((value: string) => { - this.onSearchInput(value); - }) - } - .width('100%') - .position({ x: 0, y: 0 }) - .zIndex(10) + // 搜索框 + GraphNodeSearchBar({ + searchText: this.searchText, + onSearchInput: (value: string): void => { this.onSearchInput(value); } + }) // 节点详情浮层 if (this.showNodeDetail && this.selectedNodeDetail !== null) { - Column() { - Column() { - Text(this.selectedNodeDetail.name) - .fontSize(18) - .fontColor('#44ff88') - .fontWeight(FontWeight.Bold) - .margin({ bottom: 10 }) - - Text('类型: ' + this.selectedNodeDetail.type) - .fontSize(14) - .fontColor('#aaaacc') - - Text('提及次数: ' + this.selectedNodeDetail.mention_count) - .fontSize(14) - .fontColor('#aaaacc') - - Text('连接数: ' + this.selectedNodeDetail.connection_count) - .fontSize(14) - .fontColor('#aaaacc') - - if (this.selectedNodeDetail.connections && this.selectedNodeDetail.connections.length > 0) { - Text('连接关系:') - .fontSize(14) - .fontColor('#8888aa') - .margin({ top: 10, bottom: 5 }) - List() { - ForEach(this.selectedNodeDetail.connections, (conn: ConnectionItem) => { - ListItem() { - Text(conn.type + ': ' + conn.target_name) - .fontSize(12) - .fontColor('#aaaacc') - } - }) - } - .height(100) - } - - Button('关闭') - .width(80) - .height(30) - .margin({ top: 15 }) - .backgroundColor('rgba(100, 100, 255, 0.3)') - .fontColor('#ffffff') - .onClick(() => { - this.closeNodeDetail(); - }) - } - .padding(20) - .backgroundColor('rgba(10, 10, 26, 0.95)') - .borderRadius(12) - .border({ width: 1, color: 'rgba(100, 100, 255, 0.3)' }) - .width(300) - } - .width('100%') - .height('100%') - .backgroundColor('rgba(0, 0, 0, 0.5)') - .justifyContent(FlexAlign.Center) - .alignItems(HorizontalAlign.Center) - .zIndex(20) + NodeDetailPanel({ + detail: this.selectedNodeDetail, + onClose: (): void => { this.closeNodeDetail(); } + }) } } .width('100%') .height('100%') } -} +} \ No newline at end of file diff --git a/features/graph/src/main/module.json5 b/features/graph/src/main/module.json5 new file mode 100644 index 0000000..b425570 --- /dev/null +++ b/features/graph/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "graph", + "type": "har", + "description": "TrulyMEM graph feature module", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ] + } +} diff --git a/entry/src/main/resources/rawfile/graph.html b/features/graph/src/main/resources/rawfile/graph.html similarity index 100% rename from entry/src/main/resources/rawfile/graph.html rename to features/graph/src/main/resources/rawfile/graph.html diff --git a/features/settings/BuildProfile.ets b/features/settings/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/features/settings/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/features/settings/build-profile.json5 b/features/settings/build-profile.json5 index b823c6e..79961f9 100644 --- a/features/settings/build-profile.json5 +++ b/features/settings/build-profile.json5 @@ -1,5 +1,7 @@ { "apiType": "stageMode", + "buildOption": { + }, "targets": [ { "name": "default" diff --git a/features/settings/hvigorfile.ts b/features/settings/hvigorfile.ts new file mode 100644 index 0000000..6bf3dc5 --- /dev/null +++ b/features/settings/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, + plugins: [] +}; diff --git a/features/settings/oh-package-lock.json5 b/features/settings/oh-package-lock.json5 new file mode 100644 index 0000000..6941f2c --- /dev/null +++ b/features/settings/oh-package-lock.json5 @@ -0,0 +1,19 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/common@../../common": "@ohos/common@../../common" + }, + "packages": { + "@ohos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/features/settings/oh_modules/@ohos/common b/features/settings/oh_modules/@ohos/common new file mode 120000 index 0000000..3da9bd8 --- /dev/null +++ b/features/settings/oh_modules/@ohos/common @@ -0,0 +1 @@ +../../../../common \ No newline at end of file diff --git a/features/settings/settings b/features/settings/settings new file mode 120000 index 0000000..fc16687 --- /dev/null +++ b/features/settings/settings @@ -0,0 +1 @@ +/home/program/TrulyMEM-TrueHumanMEM/features/settings \ No newline at end of file diff --git a/features/settings/src/main/ets/components/SettingsComponents.ets b/features/settings/src/main/ets/components/SettingsComponents.ets new file mode 100644 index 0000000..432ddbe --- /dev/null +++ b/features/settings/src/main/ets/components/SettingsComponents.ets @@ -0,0 +1,105 @@ +import dataPreferences from '@ohos.data.preferences'; + +/** + * SettingsSectionHeader — 设置区块标题 + * 大标题 + 加粗白色 + */ +@Component +export struct SettingsSectionHeader { + @Prop title: string; + + build() { + Text(this.title) + .fontSize(24) + .fontWeight(FontWeight.Bold) + .fontColor('#FFFFFF') + .margin({ top: 20, bottom: 16 }) + } +} + +/** + * SettingInputItem — 设置输入项 + * 标签 + TextInput,统一玻璃拟态风格 + */ +@Component +export struct SettingInputItem { + @Prop label: string; + @Prop placeholder: string; + @Link value: string; + isPassword?: boolean = false; + onValueChange?: (value: string) => void; + + build() { + Column() { + Text(this.label) + .fontSize(14) + .fontColor('#FFFFFF') + .width('100%') + .margin({ bottom: 8 }) + + TextInput({ placeholder: this.placeholder, text: this.value }) + .type(this.isPassword ? InputType.Password : InputType.Normal) + .onChange((v: string) => { + this.value = v; + this.onValueChange?.(v); + }) + .backgroundColor('rgba(255,255,255,0.1)') + .borderRadius(8) + .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) + .height(40) + } + .padding(12) + .backgroundColor('rgba(255,255,255,0.05)') + .borderRadius(12) + .backgroundBlurStyle(BlurStyle.Thin) + .margin({ bottom: 12 }) + } +} + +/** + * GlowBackground — 主题色光晕背景装饰 + * 用于设置页顶部装饰 + */ +@Component +export struct GlowBackground { + @Prop color: string = 'rgba(124,77,255,0.15)'; + @Prop glowSize: number = 200; + + build() { + Column() + .width(this.glowSize) + .height(this.glowSize) + .backgroundColor(this.color) + .blur(40) + .borderRadius(this.glowSize / 2) + .position({ x: '10%', y: '20%' }) + } +} + +/** + * AppConfigStore — 应用配置存储封装 + * 封装 Preferences 读写,提供类型安全访问 + */ +export class AppConfigStore { + private pref?: dataPreferences.Preferences; + private readonly storeName: string = 'trulymem_config'; + + async init(ctx: Context): Promise { + this.pref = await dataPreferences.getPreferences(ctx, this.storeName); + } + + async getString(key: string, defaultValue: string): Promise { + return String(await this.pref?.get(key, defaultValue)); + } + + async setString(key: string, value: string): Promise { + await this.pref?.put(key, value); + await this.pref?.flush(); + } + + static async create(ctx: Context): Promise { + const store = new AppConfigStore(); + await store.init(ctx); + return store; + } +} diff --git a/features/settings/src/main/ets/pages/SettingsPage.ets b/features/settings/src/main/ets/pages/SettingsPage.ets index c351bb4..dd36cee 100644 --- a/features/settings/src/main/ets/pages/SettingsPage.ets +++ b/features/settings/src/main/ets/pages/SettingsPage.ets @@ -1,119 +1,53 @@ import dataPreferences from '@ohos.data.preferences'; +import { SettingsSectionHeader, SettingInputItem, GlowBackground, AppConfigStore } from '../components/SettingsComponents'; @Component export struct SettingsPage { @State baseUrl: string = ''; @State model: string = ''; @State apiKey: string = ''; - private pref?: dataPreferences.Preferences; + private store: AppConfigStore = new AppConfigStore(); async aboutToAppear() { const ctx = getContext(this); - this.pref = await dataPreferences.getPreferences(ctx, 'trulymem_config'); - this.baseUrl = String(await this.pref.get('base_url', 'https://api.deepseek.com')); - this.model = String(await this.pref.get('model', 'deepseek-chat')); - this.apiKey = String(await this.pref.get('api_key', '')); + await this.store.init(ctx); + this.baseUrl = await this.store.getString('base_url', 'https://api.deepseek.com'); + this.model = await this.store.getString('model', 'deepseek-chat'); + this.apiKey = await this.store.getString('api_key', ''); } - async onBaseUrlChange(value: string) { - this.baseUrl = value; - await this.pref?.put('base_url', value); - await this.pref?.flush(); - } - - async onModelChange(value: string) { - this.model = value; - await this.pref?.put('model', value); - await this.pref?.flush(); - } - - async onApiKeyChange(value: string) { - this.apiKey = value; - await this.pref?.put('api_key', value); - await this.pref?.flush(); + private async saveConfig(key: string, value: string): Promise { + await this.store.setString(key, value); } build() { Stack() { - // 主题色光晕背景 - Column() - .width(200) - .height(200) - .backgroundColor('rgba(124,77,255,0.15)') - .blur(40) - .borderRadius(100) - .position({ x: '10%', y: '20%' }) + GlowBackground() Column() { - Text('API 配置') - .fontSize(24) - .fontWeight(FontWeight.Bold) - .fontColor('#FFFFFF') - .margin({ top: 20, bottom: 16 }) + SettingsSectionHeader({ title: 'API 配置' }) - // Base URL 设置项 - Column() { - Text('Base URL') - .fontSize(14) - .fontColor('#FFFFFF') - .width('100%') - .margin({ bottom: 8 }) + SettingInputItem({ + label: 'Base URL', + placeholder: 'https://api.deepseek.com', + value: this.baseUrl, + onValueChange: (v: string): void => { this.saveConfig('base_url', v); } + }) - TextInput({ placeholder: 'https://api.deepseek.com', text: this.baseUrl }) - .onChange((v: string) => { this.onBaseUrlChange(v); }) - .backgroundColor('rgba(255,255,255,0.1)') - .borderRadius(8) - .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) - .height(40) - } - .padding(12) - .backgroundColor('rgba(255,255,255,0.05)') - .borderRadius(12) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ bottom: 12 }) + SettingInputItem({ + label: 'Model ID', + placeholder: 'deepseek-chat', + value: this.model, + onValueChange: (v: string): void => { this.saveConfig('model', v); } + }) - // Model ID 设置项 - Column() { - Text('Model ID') - .fontSize(14) - .fontColor('#FFFFFF') - .width('100%') - .margin({ bottom: 8 }) - - TextInput({ placeholder: 'deepseek-chat', text: this.model }) - .onChange((v: string) => { this.onModelChange(v); }) - .backgroundColor('rgba(255,255,255,0.1)') - .borderRadius(8) - .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) - .height(40) - } - .padding(12) - .backgroundColor('rgba(255,255,255,0.05)') - .borderRadius(12) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ bottom: 12 }) - - // API Key 设置项 - Column() { - Text('API Key') - .fontSize(14) - .fontColor('#FFFFFF') - .width('100%') - .margin({ bottom: 8 }) - - TextInput({ placeholder: 'sk-...', text: this.apiKey }) - .type(InputType.Password) - .onChange((v: string) => { this.onApiKeyChange(v); }) - .backgroundColor('rgba(255,255,255,0.1)') - .borderRadius(8) - .border({ width: 1, color: 'rgba(124,77,255,0.3)' }) - .height(40) - } - .padding(12) - .backgroundColor('rgba(255,255,255,0.05)') - .borderRadius(12) - .backgroundBlurStyle(BlurStyle.Thin) - .margin({ bottom: 12 }) + SettingInputItem({ + label: 'API Key', + placeholder: 'sk-...', + value: this.apiKey, + isPassword: true, + onValueChange: (v: string): void => { this.saveConfig('api_key', v); } + }) } .padding(16) .width('100%') diff --git a/features/settings/src/main/module.json5 b/features/settings/src/main/module.json5 new file mode 100644 index 0000000..5d49bc8 --- /dev/null +++ b/features/settings/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "settings", + "type": "har", + "description": "TrulyMEM settings feature module", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ] + } +} diff --git a/generate-debug-cert.sh b/generate-debug-cert.sh new file mode 100644 index 0000000..6c71896 --- /dev/null +++ b/generate-debug-cert.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e +DIR="/home/program/.harmonyos" +mkdir -p "$DIR" +cd "$DIR" + +KEYSTORE_PASS="123456" +ALIAS="debug" +ALIAS_PASS="123456" +DNAME="CN=Debug,OU=Debug,O=TrulyMEM,L=Beijing,ST=Beijing,C=CN" + +# 生成私钥 +openssl ecparam -genkey -name prime256v1 -out private.pem 2>/dev/null + +# 生成 CSR +openssl req -new -key private.pem -out cert.csr -subj "$DNAME" 2>/dev/null + +# 自签名证书 +openssl req -x509 -days 3650 -key private.pem -in cert.csr -out debug.cer 2>/dev/null + +# 创建 PKCS12 +openssl pkcs12 -export -out debug.p12 -inkey private.pem -in debug.cer -password pass:$KEYSTORE_PASS -name $ALIAS 2>/dev/null + +echo "Debug cert generated at $DIR" +ls -la "$DIR" diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000..6d8a6bc --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.24": { + "name": "@ohos/hypium", + "version": "1.0.24", + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.24.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 index 989a3c0..0b57336 100644 --- a/oh-package.json5 +++ b/oh-package.json5 @@ -1,12 +1,7 @@ { "modelVersion": "5.0.5", - "description": "Please describe the basic information.", - "dependencies": { - "@kit.UIDesignKit": "^6.1.0", - "@kit.ArkUI": "^6.1.0", - "@kit.AbilityKit": "^6.1.0", - "@kit.BasicServicesKit": "^6.1.0" - }, + "description": "TrulyMEM - True Human Memory", + "dependencies": {}, "devDependencies": { "@ohos/hypium": "1.0.24", "@ohos/hamock": "1.0.0" diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/CHANGELOG.md b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/CHANGELOG.md new file mode 100644 index 0000000..f62e2be --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/CHANGELOG.md @@ -0,0 +1,4 @@ +## 1.0.0 +- 修复once断言问题 +## 1.0.0-rc +- 提供DevEco Studio预览器场景使能的MockSetup装饰器 \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/LICENSE b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/LICENSE new file mode 100644 index 0000000..4a45986 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/README.md b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/README.md new file mode 100644 index 0000000..ea667a1 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/README.md @@ -0,0 +1,82 @@ +# Hamock + +## 简介 + +Hamock 是 OpenHarmony 上的模拟框架,提供预览场景的模拟功能。 + +## 下载安装 + +```bash +ohpm install @ohos/hamock +``` + +OpenHarmony ohpm 环境配置等更多内容,请参考[如何安装 OpenHarmony ohpm 包](https://gitee.com/openharmony-tpc/docs/blob/master/OpenHarmony_har_usage.md) + +## 使用示例 + +Hamock 提供了 @MockSetup 用于修饰 Mock 方法,仅支持声明式范式的组件。当开发者预览该组件时,预览运行时将在组件初始化时执行被 @MockSetup 修饰的方法。因此,开发者可以在这个被修饰的方法内重定义组件的方法或重赋值组件的属性,其将在预览时生效。 + +> 说明: +> @MockSetup 修饰的方法仅在预览场景会自动触发,并先于组件的 aboutToAppear 执行。 + +### UI组件的方法 + +在 ArkTS 页面代码中引入 Hamock。在目标组件中定义一个方法,并用 @MockSetup 修饰该方法。在这个方法中,使用 MockKit 模拟目标方法。 + +```typescript +import { MockKit, when, MockSetup } from '@ohos/hamock'; + +@Entry +@Component +struct Index { + ... + @MockSetup + randomName() { + let mocker: MockKit = new MockKit(); + let mockfunc: Object = mocker.mockFunc(this, this.method1); + // mock 指定的方法在指定入参的返回值 + when(mockfunc)('test').afterReturn(1); + } + ... + // 业务场景调用方法 + const result: number = this.method1('test'); // in previewer, result = 1 +} +``` + +### UI组件的属性 + +在 ArkTS 页面代码中引入 Hamock。在目标组件中定义一个方法,并用 @MockSetup 修饰该方法。在这个方法中,对于需要 Mock 的属性,可以重新赋值。 + +```typescript +import { MockSetup } from '@ohos/hamock'; + +@Component +struct Person { + @Prop species: string; + ... + // 在 @MockSetup 片段中,定义对象属性 + @MockSetup + randomName() { + this.species = 'primates'; + } + ... + // 业务场景调用属性(如果从初始化到调用期间,该属性无变化) + const result: string = this.species; // in previewer, result = primates +} +``` + +## 约束与限制 + +在下述版本验证通过: + +DevEco Studio: 4.1 (4.1.3.400), SDK: API11 (4.1.0.36) + +MockSetup 仅在 API11 支持。 + +## 贡献代码 + +使用过程中发现任何问题都可以提[Issue](https://gitee.com/openharmony/testfwk_arkxtest/issues) 给我们,当然,我们也非常欢迎你给我们提[PR](https://gitee.com/openharmony/testfwk_arkxtest/pulls) 。 + +## 开源协议 + +本项目基于 [Apache License 2.0](https://gitee.com/openharmony/testfwk_arkxtest/blob/master/hamock/LICENSE) ,请自由地享受和参与开源。 \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/build-profile.json5 b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/build-profile.json5 new file mode 100644 index 0000000..cf61dec --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/build-profile.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + } + ] +} diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.js new file mode 100644 index 0000000..76915cf --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.js @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.ts new file mode 100644 index 0000000..ba30640 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/hvigorfile.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.d.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.d.ts new file mode 100644 index 0000000..d6179ad --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.d.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ArgumentMatchers { + static any; + static anyString; + static anyBoolean; + static anyNumber; + static anyObj; + static anyFunction; + static matchRegexs(Regex: RegExp): void +} + +declare interface when { + afterReturn(value: any): any + afterReturnNothing(): undefined + afterAction(action: any): any + afterThrow(e_msg: string): string + (argMatchers?: any): when; +} + +export const when: when; + +export interface VerificationMode { + times(count: Number): void + never(): void + once(): void + atLeast(count: Number): void + atMost(count: Number): void +} + +export class MockKit { + constructor() + mockFunc(obj: Object, func: Function): Function + mockObject(obj: Object): Object + verify(methodName: String, argsArray: Array): VerificationMode + ignoreMock(obj: Object, func: Function): void + clear(obj: Object): void + clearAll(): void +} + +export declare function MockSetup( + target: Object, + propertyName: string | Symbol, + descriptor: TypedPropertyDescriptor<() => void> +): void; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ets b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ets new file mode 100644 index 0000000..3e4ab4c --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ets @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { MockSetup, MockKit, when } from './src/main/mock/MockKit'; +export { ArgumentMatchers } from './src/main/mock/ArgumentMatchers'; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.js new file mode 100644 index 0000000..b3832e8 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.js @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { MockSetup, MockKit, when } from './src/main/mock/MockKit.js'; +export { ArgumentMatchers } from './src/main/mock/ArgumentMatchers.js'; diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ts new file mode 100644 index 0000000..df0e313 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { MockSetup, MockKit, when } from './src/main/mock/MockKit.js'; +export { ArgumentMatchers } from './src/main/mock/ArgumentMatchers.js'; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/oh-package.json5 b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/oh-package.json5 new file mode 100644 index 0000000..49f3683 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/oh-package.json5 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +{ + name: '@ohos/hamock', + version: '1.0.0', + description: 'A mock framework for OpenHarmony application.', + main: 'index.ets', + author: 'huawei', + license: 'Apache-2.0', + dependencies: {}, + ohos: { + org: 'ohos', + }, + types: 'index.d.ts' +} diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.js new file mode 100644 index 0000000..610c540 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.js @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ArgumentMatchers { + constructor() { + this.ANY = ""; + this.ANY_STRING = ""; + this.ANY_BOOLEAN = ""; + this.ANY_NUMBER = ""; + this.ANY_OBJECT = ""; + this.ANY_FUNCTION = ""; + this.MATCH_REGEXS = ""; + } + static any() { + } + static anyString() { + } + static anyBoolean() { + } + static anyNumber() { + } + static anyObj() { + } + static anyFunction() { + } + static matchRegexs(regex) { + if (ArgumentMatchers.isRegExp(regex)) { + return regex; + } + throw Error("not a regex"); + } + static isRegExp(value) { + return Object.prototype.toString.call(value) === "[object RegExp]"; + } + matcheReturnKey(...args) { + let arg = args[0]; + let regex = args[1]; + let stubSetKey = args[2]; + if (stubSetKey && stubSetKey == this.ANY) { + return this.ANY; + } + if (typeof arg === "string" && !regex) { + return this.ANY_STRING; + } + if (typeof arg === "boolean" && !regex) { + return this.ANY_BOOLEAN; + } + if (typeof arg === "number" && !regex) { + return this.ANY_NUMBER; + } + if (typeof arg === "object" && !regex) { + return this.ANY_OBJECT; + } + if (typeof arg === "function" && !regex) { + return this.ANY_FUNCTION; + } + if (typeof arg === "string" && regex) { + return regex.test(arg); + } + return null; + } + matcheStubKey(key) { + if (key === ArgumentMatchers.any) { + return this.ANY; + } + if (key === ArgumentMatchers.anyString) { + return this.ANY_STRING; + } + if (key === ArgumentMatchers.anyBoolean) { + return this.ANY_BOOLEAN; + } + if (key === ArgumentMatchers.anyNumber) { + return this.ANY_NUMBER; + } + if (key === ArgumentMatchers.anyObj) { + return this.ANY_OBJECT; + } + if (key === ArgumentMatchers.anyFunction) { + return this.ANY_FUNCTION; + } + if (ArgumentMatchers.isRegExp(key)) { + return key; + } + return null; + } +} diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.ts new file mode 100644 index 0000000..0e0e64c --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ArgumentMatchers.ts @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ArgumentMatchers { + ANY = ""; + ANY_STRING = ""; + ANY_BOOLEAN = ""; + ANY_NUMBER = ""; + ANY_OBJECT = ""; + ANY_FUNCTION = ""; + MATCH_REGEXS = ""; + + static any() { + } + + static anyString() { + } + + static anyBoolean() { + } + + static anyNumber() { + } + + static anyObj() { + } + + static anyFunction() { + } + + static matchRegexs(regex: any) { + if (ArgumentMatchers.isRegExp(regex)) { + return regex; + } + throw Error("not a regex"); + } + + static isRegExp(value: string) { + return Object.prototype.toString.call(value) === "[object RegExp]"; + } + + matcheReturnKey(...args: Array) { + let arg = args[0]; + let regex = args[1]; + let stubSetKey = args[2]; + + if (stubSetKey && stubSetKey == this.ANY) { + return this.ANY; + } + + if (typeof arg === "string" && !regex) { + return this.ANY_STRING; + } + + if (typeof arg === "boolean" && !regex) { + return this.ANY_BOOLEAN; + } + + if (typeof arg === "number" && !regex) { + return this.ANY_NUMBER; + } + + if (typeof arg === "object" && !regex) { + return this.ANY_OBJECT; + } + + if (typeof arg === "function" && !regex) { + return this.ANY_FUNCTION; + } + + if (typeof arg === "string" && regex) { + return regex.test(arg); + } + + return null; + } + + matcheStubKey(key: any) { + + if (key === ArgumentMatchers.any) { + return this.ANY; + } + + if (key === ArgumentMatchers.anyString) { + return this.ANY_STRING; + } + if (key === ArgumentMatchers.anyBoolean) { + return this.ANY_BOOLEAN; + } + if (key === ArgumentMatchers.anyNumber) { + return this.ANY_NUMBER; + } + if (key === ArgumentMatchers.anyObj) { + return this.ANY_OBJECT; + } + if (key === ArgumentMatchers.anyFunction) { + return this.ANY_FUNCTION; + } + + if (ArgumentMatchers.isRegExp(key)) { + return key; + } + + return null; + } +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.js new file mode 100644 index 0000000..325be96 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +class ExtendInterface { + constructor(mocker) { + this.mocker = mocker; + } + stub() { + this.params = arguments; + return this; + } + stubMockedCall(returnInfo) { + this.mocker.stubApply(this, this.params, returnInfo); + } + afterReturn(value) { + this.stubMockedCall(function () { + return value; + }); + } + afterReturnNothing() { + this.stubMockedCall(function () { + return undefined; + }); + } + afterAction(action) { + this.stubMockedCall(action); + } + afterThrow(msg) { + this.stubMockedCall(function () { + throw msg; + }); + } + clear(obj) { + this.mocker.clear(obj); + } +} +export default ExtendInterface; diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.ts new file mode 100644 index 0000000..8e27e2e --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/ExtendInterface.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { MockKit } from "./MockKit.js"; + +class ExtendInterface { + + private mocker: MockKit + private params: any + + constructor(mocker: MockKit) { + this.mocker = mocker; + } + + stub() { + this.params = arguments; + return this; + } + + stubMockedCall(returnInfo: any) { + this.mocker.stubApply(this, this.params, returnInfo); + } + + afterReturn(value: any) { + this.stubMockedCall(function () { + return value; + }); + } + + afterReturnNothing() { + this.stubMockedCall(function () { + return undefined; + }); + } + + afterAction(action: Function) { + this.stubMockedCall(action); + } + + afterThrow(msg: string) { + this.stubMockedCall(function () { + throw msg; + }); + } + + clear(obj?: any) { + this.mocker.clear(obj); + } +} + +export default ExtendInterface; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.js new file mode 100644 index 0000000..01a4bf3 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.js @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ExtendInterface from "./ExtendInterface.js"; +import VerificationMode from "./VerificationMode.js"; +import { ArgumentMatchers } from "./ArgumentMatchers.js"; +class MockKit { + constructor() { + this.mFunctions = []; + this.stubs = new Map(); + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + this.mFunctions = []; + this.stubs = new Map(); + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + init() { + this.reset(); + } + reset() { + this.mFunctions = []; + this.stubs = new Map(); + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + clearAll() { + this.reset(); + } + clear(obj) { + if (!obj) throw Error("Please enter an object to be cleaned"); + if (typeof (obj) !== 'object' && typeof (obj) !== 'function') throw new Error('Not a object or static class'); + this.recordMockedMethod.forEach(function (value, key, map) { + if (key) { + obj[key] = value; + } + }); + } + ignoreMock(obj, method) { + if (typeof (obj) !== 'object' && typeof (obj) !== 'function') throw new Error('Not a object or static class'); + if (typeof (method) !== 'function') throw new Error('Not a function'); + let og = this.recordMockedMethod.get(method.propName); + if (og) { + obj[method.propName] = og; + this.recordMockedMethod.set(method.propName, undefined); + } + } + extend(dest, source) { + dest["stub"] = source["stub"]; + dest["afterReturn"] = source["afterReturn"]; + dest["afterReturnNothing"] = source["afterReturnNothing"]; + dest["afterAction"] = source["afterAction"]; + dest["afterThrow"] = source["afterThrow"]; + dest["stubMockedCall"] = source["stubMockedCall"]; + dest["clear"] = source["clear"]; + return dest; + } + stubApply(f, params, returnInfo) { + let values = this.stubs.get(f); + if (!values) { + values = new Map(); + } + let key = params[0]; + if (typeof key === "undefined") { + key = "anonymous-mock-" + f.propName; + } + let matcher = new ArgumentMatchers(); + if (matcher.matcheStubKey(key)) { + key = matcher.matcheStubKey(key); + if (key) { + this.currentSetKey.set(f, key); + } + } + values.set(key, returnInfo); + this.stubs.set(f, values); + } + getReturnInfo(f, params) { + let values = this.stubs.get(f); + if (!values) { + return undefined; + } + let retrunKet = params[0]; + if (typeof retrunKet === "undefined") { + retrunKet = "anonymous-mock-" + f.propName; + } + let stubSetKey = this.currentSetKey.get(f); + + if (stubSetKey && (typeof (retrunKet) !== "undefined")) { + retrunKet = stubSetKey; + } + let matcher = new ArgumentMatchers(); + if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) !== stubSetKey) { + retrunKet = params[0]; + } + values.forEach(function (value, key, map) { + if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { + retrunKet = key; + } + }); + return values.get(retrunKet); + } + findName(obj, value) { + let properties = this.findProperties(obj); + let name = ''; + properties.filter((item) => (item !== 'caller' && item !== 'arguments')).forEach(function (va1, idx, array) { + if (obj[va1] === value) { + name = va1; + } + }); + return name; + } + isFunctionFromPrototype(f, container, propName) { + if (container.constructor !== Object && container.constructor.prototype !== container) { + return container.constructor.prototype[propName] === f; + } + return false; + } + findProperties(obj, ...arg) { + function getProperty(new_obj) { + if (new_obj.__proto__ === null) { + return []; + } + let properties = Object.getOwnPropertyNames(new_obj); + return [...properties, ...getProperty(new_obj.__proto__)]; + } + return getProperty(obj); + } + recordMethodCall(originalMethod, args) { + originalMethod['getName'] = function () { + return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; + }; + let name = originalMethod.getName(); + let arglistString = name + '(' + Array.from(args).toString() + ')'; + let records = this.recordCalls.get(arglistString); + if (!records) { + records = 0; + } + records++; + this.recordCalls.set(arglistString, records); + } + mockFunc(originalObject, originalMethod) { + let tmp = this; + this.originalMethod = originalMethod; + const _this = this; + let f = function () { + let args = arguments; + let action = tmp.getReturnInfo(f, args); + if (originalMethod) { + tmp.recordMethodCall(originalMethod, args); + } + if (action) { + return action.apply(_this, args); + } + }; + f.container = null || originalObject; + f.original = originalMethod || null; + if (originalObject && originalMethod) { + if (typeof (originalMethod) != 'function') + throw new Error('Not a function'); + var name = this.findName(originalObject, originalMethod); + originalObject[name] = f; + this.recordMockedMethod.set(name, originalMethod); + f.propName = name; + f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); + } + f.mocker = this; + this.mFunctions.push(f); + this.extend(f, new ExtendInterface(this)); + return f; + } + verify(methodName, argsArray) { + if (!methodName) { + throw Error("not a function name"); + } + let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); + return new VerificationMode(a ? a : 0); + } + mockObject(object) { + if (!object || typeof object === "string") { + throw Error(`this ${object} cannot be mocked`); + } + const _this = this; + let mockedObject = {}; + let keys = Reflect.ownKeys(object); + keys.filter(key => (typeof Reflect.get(object, key)) === 'function') + .forEach((key) => { + mockedObject[key] = object[key]; + mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); + }); + return mockedObject; + } +} +function ifMockedFunction(f) { + if (Object.prototype.toString.call(f) != "[object Function]" && + Object.prototype.toString.call(f) != "[object AsyncFunction]") { + throw Error("not a function"); + } + if (!f.stub) { + throw Error("not a mock function"); + } + return true; +} +function when(f) { + if (ifMockedFunction(f)) { + return f.stub.bind(f); + } +} +function MockSetup(target, propertyName, descriptor) { + const aboutToAppearOrigin = target.aboutToAppear; + const setup = descriptor.value; + target.aboutToAppear = function (...args) { + if (target.__Param) { // copy attributes and params of the original context + try { + const map = target.__Param; + for (const [key, val] of map) { + this[key] = val; // 'this' refers to context of current function + } + } + catch (e) { + throw new Error(`Mock setup param error: ${e}`); + } + } + if (setup) { // apply the mock content + try { + setup.apply(this); + } + catch (e) { + throw new Error(`Mock setup apply error: ${e}`); + } + } + if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context + aboutToAppearOrigin.apply(this, args); + } + }; +} +export { MockSetup, MockKit, when }; diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.ts new file mode 100644 index 0000000..a9f965a --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/MockKit.ts @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ExtendInterface from "./ExtendInterface.js"; +import VerificationMode from "./VerificationMode.js"; +import { ArgumentMatchers } from "./ArgumentMatchers.js"; + +interface IFunction extends Function { + container: any; + original: any; + propName: string; + originalFromPrototype: boolean + mocker: MockKit +} + +class MockKit { + + private mFunctions:Array = []; + private stubs = new Map(); + private recordCalls = new Map(); + private currentSetKey = new Map(); + private mockObj = null; + private recordMockedMethod = new Map(); + private originalMethod: any; + + constructor() { + this.mFunctions = []; + this.stubs = new Map(); + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + + init() { + this.reset(); + } + + reset() { + this.mFunctions = []; + this.stubs = new Map() + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + + clearAll() { + this.reset(); + } + + clear(obj: any) { + if (!obj) throw Error("Please enter an object to be cleaned"); + if (typeof (obj) != 'object') throw new Error('Not a object'); + this.recordMockedMethod.forEach(function (value, key, map) { + if (key) { + obj[key] = value; + } + }); + } + + ignoreMock(obj:any, method: any) { + if (typeof (obj) != 'object') throw new Error('Not a object'); + if (typeof (method) != 'function') throw new Error('Not a function'); + let og = this.recordMockedMethod.get(method.propName); + if (og) { + obj[method.propName] = og; + this.recordMockedMethod.set(method.propName, undefined); + } + } + + extend(dest: any, source:any) { + dest["stub"] = source["stub"]; + dest["afterReturn"] = source["afterReturn"]; + dest["afterReturnNothing"] = source["afterReturnNothing"]; + dest["afterAction"] = source["afterAction"]; + dest["afterThrow"] = source["afterThrow"]; + dest["stubMockedCall"] = source["stubMockedCall"]; + dest["clear"] = source["clear"]; + return dest; + } + + stubApply(f: any, params:any, returnInfo:any) { + let values = this.stubs.get(f); + if (!values) { + values = new Map(); + } + let key = params[0]; + if (typeof key == "undefined") { + key = "anonymous-mock-" + f.propName; + } + let matcher = new ArgumentMatchers(); + if (matcher.matcheStubKey(key)) { + key = matcher.matcheStubKey(key); + if (key) { + this.currentSetKey.set(f, key); + } + } + values.set(key, returnInfo); + this.stubs.set(f, values); + } + + getReturnInfo(f: any, params:any) { + let values = this.stubs.get(f); + if (!values) { + return undefined; + } + let retrunKet = params[0]; + if (typeof retrunKet == "undefined") { + retrunKet = "anonymous-mock-" + f.propName; + } + let stubSetKey = this.currentSetKey.get(f); + + if (stubSetKey && (typeof (retrunKet) != "undefined")) { + retrunKet = stubSetKey; + } + let matcher = new ArgumentMatchers(); + if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) != stubSetKey) { + retrunKet = params[0]; + } + + values.forEach(function (value: any, key: any, map: any) { + if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { + retrunKet = key; + } + }); + + return values.get(retrunKet); + } + + findName(obj: any, value: any) { + let properties = this.findProperties(obj); + let name = ''; + properties.filter((item:any) => (item !== 'caller' && item !== 'arguments')).forEach( + function (va1:any, idx:any, array:any) { + if (obj[va1] === value) { + name = va1; + } + } + ); + return name; + } + + isFunctionFromPrototype(f: Function, container:Function, propName: string) { + if (container.constructor != Object && container.constructor.prototype !== container) { + return container.constructor.prototype[propName] === f; + } + return false; + } + + findProperties(obj: any, ...arg: Array) { + function getProperty(new_obj:any): Array { + if (new_obj.__proto__ === null) { + return []; + } + let properties = Object.getOwnPropertyNames(new_obj); + return [...properties, ...getProperty(new_obj.__proto__)]; + } + return getProperty(obj); + } + + recordMethodCall(originalMethod: any, args: any) { + originalMethod['getName'] = function () { + return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; + } + let name = originalMethod.getName(); + let arglistString = name + '(' + Array.from(args).toString() + ')'; + let records = this.recordCalls.get(arglistString); + if (!records) { + records = 0; + } + records++; + this.recordCalls.set(arglistString, records); + } + + mockFunc(originalObject:any, originalMethod:any) { + let tmp = this; + this.originalMethod = originalMethod; + const _this = this; + let f:any = function () { + let args = arguments; + let action = tmp.getReturnInfo(f, args); + if (originalMethod) { + tmp.recordMethodCall(originalMethod, args); + } + if (action) { + return action.apply(_this, args); + } + }; + + f.container = null || originalObject; + f.original = originalMethod || null; + + if (originalObject && originalMethod) { + if (typeof (originalMethod) != 'function') throw new Error('Not a function'); + var name = this.findName(originalObject, originalMethod); + originalObject[name] = f; + this.recordMockedMethod.set(name, originalMethod); + f.propName = name; + f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); + } + f.mocker = this; + this.mFunctions.push(f); + this.extend(f, new ExtendInterface(this)); + return f; + } + + verify(methodName:any, argsArray:any) { + if (!methodName) { + throw Error("not a function name"); + } + let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); + return new VerificationMode(a ? a : 0); + } + + mockObject(object: any) { + if (!object || typeof object === "string") { + throw Error(`this ${object} cannot be mocked`); + } + const _this = this; + let mockedObject:any = {}; + let keys = Reflect.ownKeys(object); + keys.filter(key => (typeof Reflect.get(object, key)) === 'function') + .forEach((key:any) => { + mockedObject[key] = object[key]; + mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); + }); + return mockedObject; + } +} + +function ifMockedFunction(f: any) { + if (Object.prototype.toString.call(f) != "[object Function]" && + Object.prototype.toString.call(f) != "[object AsyncFunction]") { + throw Error("not a function"); + } + if (!f.stub) { + throw Error("not a mock function"); + } + return true; +} + +function when(f: any) { + if (ifMockedFunction(f)) { + return f.stub.bind(f); + } +} + +function MockSetup(target: Object, propertyName: string | Symbol, descriptor: TypedPropertyDescriptor<() => void>): void { + const aboutToAppearOrigin = target.aboutToAppear; + const setup = descriptor.value; + target.aboutToAppear = function (...args: any[]) { + if (target.__Param) { // copy attributes and params of the original context + try { + const map = target.__Param as Map; + for (const [key, val] of map) { + this[key] = val; // 'this' refers to context of current function + } + } catch (e) { + throw new Error(`Mock setup param error: ${e}`); + } + } + + if (setup) { // apply the mock content + try { + setup.apply(this); + } catch (e) { + throw new Error(`Mock setup apply error: ${e}`); + } + } + + if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context + aboutToAppearOrigin.apply(this, args); + } + } +} + +export { + MockSetup, + MockKit, + when +}; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.js b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.js new file mode 100644 index 0000000..02fbf3b --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +class VerificationMode { + constructor(times) { + this.doTimes = times; + } + times(count) { + if (count !== this.doTimes) { + throw Error(`expect ${count} actual ${this.doTimes}`); + } + } + never() { + if (this.doTimes !== 0) { + throw Error(`expect 0 actual ${this.doTimes}`); + } + } + once() { + if (this.doTimes !== 1) { + throw Error(`expect 1 actual ${this.doTimes}`); + } + } + atLeast(count) { + if (count > this.doTimes) { + throw Error('failed ' + count + ' greater than the actual execution times of method'); + } + } + atMost(count) { + if (count < this.doTimes) { + throw Error('failed ' + count + ' less than the actual execution times of method'); + } + } +} +export default VerificationMode; diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.ts b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.ts new file mode 100644 index 0000000..0748125 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/mock/VerificationMode.ts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + + +class VerificationMode { + + private doTimes: number + + constructor(times: number) { + this.doTimes = times; + } + + times(count: number) { + if(count !== this.doTimes) { + throw Error(`expect ${count} actual ${this.doTimes}`); + } + } + + never() { + if (this.doTimes !== 0) { + throw Error(`expect 0 actual ${this.doTimes}`); + } + } + + once() { + if (this.doTimes !== 1) { + throw Error(`expect 1 actual ${this.doTimes}`); + } + } + + atLeast(count: number) { + if (count > this.doTimes) { + throw Error('failed ' + count + ' greater than the actual execution times of method'); + } + } + + atMost(count: number) { + if (count < this.doTimes) { + throw Error('failed ' + count + ' less than the actual execution times of method'); + } + } +} + +export default VerificationMode; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/module.json b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/module.json new file mode 100644 index 0000000..384ae72 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/main/module.json @@ -0,0 +1,22 @@ +{ + "app": { + "bundleName": "com.example.hamock", + "debug": true, + "versionCode": 1000000, + "versionName": "1.0.0", + "minAPIVersion": 9, + "targetAPIVersion": 9, + "apiReleaseType": "Release" + }, + "module": { + "name": "hamock", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "tv", + "wearable", + "car" + ] + } +} diff --git a/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/res/schemas/mock-config-json5-schema.json b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/res/schemas/mock-config-json5-schema.json new file mode 100644 index 0000000..b137b0a --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock/src/res/schemas/mock-config-json5-schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON schema for mock-config.json5 file", + "definitions": { + "sourceRedirection": { + "description": "A source redirection for mocked module.", + "type": "object", + "required": [ + "source" + ], + "properties": { + "source": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + } + } + }, + "patternProperties": { + ".+": { + "$ref": "#/definitions/sourceRedirection" + } + } +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/BuildProfile.ets b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/BuildProfile.ets new file mode 100644 index 0000000..3da4b66 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.24'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/CHANGELOG.md b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/CHANGELOG.md new file mode 100644 index 0000000..750c6a3 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/CHANGELOG.md @@ -0,0 +1,33 @@ +### 1.0.24 +- 提示信息优化 +### 1.0.23 +- 断言错误提示信息优化 +### 1.0.22 +- mock五参数失败问题修复 +### 1.0.21 +- mock支持多参数 +- describe中异步函数抛出日志信息 +- 修复多测试套时,执行单个测试套会打印其他测试套的日志信息 +## 1.0.14 +- 堆栈信息打印到cmd +## 1.0.15 +- 支持获取测试代码的失败堆栈信息 +- mock代码迁移至harmock包 +- 适配arkts语法 +- 修复覆盖率数据容易截断的bug +## 1.0.16 +- 修改覆盖率文件生成功能 +- 修改静态方法无法ignoreMock函数 + ## 1.0.17 +- 修改not断言失败提示日志 +- 自定义错误message信息 +- 添加xdescribe, xit API功能 + ## 1.0.18 +- 添加全局变量存储API get set +- 自定义断言功能 +## 1.0.18-rc.0 +添加框架worker执行能力 +## 1.0.19 +规范日志格式 +# 1.0.20 +代码告警整改 \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/LICENSE b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/LICENSE new file mode 100644 index 0000000..4947287 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/README.md b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/README.md new file mode 100644 index 0000000..19408a4 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/README.md @@ -0,0 +1,229 @@ +
Hypium
+
A unit test framework for OpenHarmonyOS application
+ +## Hypium是什么? +*** +- Hypium是OpenHarmony上的测试框架,提供测试用例编写、执行、结果显示能力,用于OpenHarmony系统应用接口以及应用界面测试。 +- Hypium结构化模型:hypium工程主要由List.test.js与TestCase.test.js组成。 +``` +rootProject // Hypium工程根目录 +├── moduleA +│   ├── src +│      ├── main // 被测试应用目录 +│      ├── ohosTest // 测试用例目录 +│         ├── js/ets +│            └── test +│               └── List.test.js // 测试用例加载脚本,ets目录下为.ets后缀 +│               └── TestCase.test.js // 测试用例脚本,ets目录下为.ets后缀 +└── moduleB + ... +│               └── List.test.js // 测试用例加载脚本,ets目录下为.ets后缀 +│               └── TestCase.test.js // 测试用例脚本,ets目录下为.ets后缀 +``` + +## 安装使用 + +- 方式一 +```javascript +ohpm i @ohos/hypium +``` +- 方式二 +*** +- 在DevEco Studio内使用Hypium +- 工程级oh-package.json5内配置: +```json +"dependencies": { + "@ohos/hypium": "1.0.24" +} +``` +注: +hypium服务于OpenHarmonyOS应用对外接口测试、系统对外接口测试(SDK中接口),完成HAP自动化测试。详细指导: +[Deveco Studio](https://developer.harmonyos.com/cn/develop/deveco-studio) + +#### 通用语法 + +- 测试用例采用业内通用语法,describe代表一个测试套, it代表一条用例。 + +| No. | API | 功能说明 | +|-----| ----------------- |------------------------------------------------------------------------| +| 1 | describe | 定义一个测试套,支持两个参数:测试套名称和测试套函数。其中测试套函数不能是异步函数 | +| 2 | beforeAll | 在测试套内定义一个预置条件,在所有测试用例开始前执行且仅执行一次,支持一个参数:预置动作函数。 | +| 3 | beforeEach | 在测试套内定义一个单元预置条件,在每条测试用例开始前执行,执行次数与it定义的测试用例数一致,支持一个参数:预置动作函数。 | +| 4 | afterEach | 在测试套内定义一个单元清理条件,在每条测试用例结束后执行,执行次数与it定义的测试用例数一致,支持一个参数:清理动作函数。 | +| 5 | afterAll | 在测试套内定义一个清理条件,在所有测试用例结束后执行且仅执行一次,支持一个参数:清理动作函数。 | +| 6 | beforeItSpecified | @since1.0.15在测试套内定义一个单元预置条件,仅在指定测试用例开始前执行,支持两个参数:单个用例名称或用例名称数组、预置动作函数。 | +| 7 | afterItSpecified | @since1.0.15在测试套内定义一个单元清理条件,仅在指定测试用例结束后执行,支持两个参数:单个用例名称或用例名称数组、清理动作函数 | +| 8 | it | 定义一条测试用例,支持三个参数:用例名称,过滤参数和用例函数。 | +| 9 | expect | 支持bool类型判断等多种断言方法。 | +| 10 | xdescribe | @since1.0.17定义一个跳过的测试套,支持两个参数:测试套名称和测试套函数。 | +| 11 | xit | @since1.0.17定义一条跳过的测试用例,支持三个参数:用例名称,过滤参数和用例函数。 | | + +#### 断言库 + +- 示例代码: + +```javascript + expect(${actualvalue}).assertX(${expectvalue}) +``` + +- 断言功能列表: + +| No. | API | 功能说明 | +| :--- | :------------------------------- | ---------------------------------------------------------------------------------------------- | +| 1 | assertClose | 检验actualvalue和expectvalue(0)的接近程度是否是expectValue(1) | +| 2 | assertContain | 检验actualvalue中是否包含expectvalue | +| 3 | assertDeepEquals | @since1.0.4 检验actualvalue和expectvalue(0)是否是同一个对象 | +| 4 | assertEqual | 检验actualvalue是否等于expectvalue[0] | +| 5 | assertFail | 抛出一个错误 | +| 6 | assertFalse | 检验actualvalue是否是false | +| 7 | assertTrue | 检验actualvalue是否是true | +| 8 | assertInstanceOf | 检验actualvalue是否是expectvalue类型 | +| 9 | assertLarger | 检验actualvalue是否大于expectvalue | +| 10 | assertLess | 检验actualvalue是否小于expectvalue | +| 11 | assertNaN | @since1.0.4 检验actualvalue是否是NaN | +| 12 | assertNegUnlimited | @since1.0.4 检验actualvalue是否等于Number.NEGATIVE_INFINITY | +| 13 | assertNull | 检验actualvalue是否是null | +| 14 | assertPosUnlimited | @since1.0.4 检验actualvalue是否等于Number.POSITIVE_INFINITY | +| 15 | assertPromiseIsPending | @since1.0.4 检验actualvalue是否处于Pending状态【actualvalue为promse对象】 | +| 16 | assertPromiseIsRejected | @since1.0.4 检验actualvalue是否处于Rejected状态【同15】 | +| 17 | assertPromiseIsRejectedWith | @since1.0.4 检验actualvalue是否处于Rejected状态,并且比较执行的结果值【同15】 | +| 18 | assertPromiseIsRejectedWithError | @since1.0.4 检验actualvalue是否处于Rejected状态并有异常,同时比较异常的类型和message值【同15】 | +| 19 | assertPromiseIsResolved | @since1.0.4 检验actualvalue是否处于Resolved状态【同15】 | +| 20 | assertPromiseIsResolvedWith | @since1.0.4 检验actualvalue是否处于Resolved状态,并且比较执行的结果值【同15】 | +| 21 | assertThrowError | 检验actualvalue抛出Error内容是否是expectValue | +| 22 | assertUndefined | 检验actualvalue是否是undefined | +| 23 | not | @since1.0.4 断言结果取反 | +| 24 | message | @since1.0.17自定义断言异常信息 | + +示例代码: + +```javascript + import { describe, it, expect } from '@ohos/hypium'; + + export default async function assertCloseTest() { + describe('assertClose', function () { + it('assertClose_success', 0, function () { + let a = 100; + let b = 0.1; + expect(a).assertClose(99, b); + }) + }) + } +``` + +#### 公共系统能力 + +| No. | API | 功能描述 | +| ---- | ------------------------------------------------------- | ------------------------------------------------------------ | +| 1 | existKeyword(keyword: string, timeout: number): boolean | @since1.0.3 hilog日志中查找指定字段是否存在,keyword是待查找关键字,timeout为设置的查找时间 | +| 2 | actionStart(tag: string): void | @since1.0.3 cmd窗口输出开始tag | +| 3 | actionEnd(tag: string): void | @since1.0.3 cmd窗口输出结束tag | + +示例代码: + +```javascript +import { describe, it, expect, SysTestKit} from '@ohos/hypium'; + +export default function existKeywordTest() { + describe('existKeywordTest', function () { + it('existKeyword',DEFAULT, async function () { + console.info("HelloTest"); + let isExist = await SysTestKit.existKeyword('HelloTest'); + console.info('isExist ------>' + isExist); + }) + }) +} +``` +```javascript +import { describe, it, expect, SysTestKit} from '@ohos/hypium'; + +export default function actionTest() { + describe('actionTest', function () { + it('existKeyword',DEFAULT, async function () { + let tag = '[MyTest]'; + SysTestKit.actionStart(tag); + //do something + SysTestKit.actionEnd(tag); + }) + }) +} +``` + +#### 专项能力 + +- 测试用例属性筛选能力:hypium支持根据用例属性筛选执行指定测试用例,使用方式是先在测试用例上标记用例属性后,再在测试应用的启动shell命令后新增" -s ${Key} ${Value}"。 + +| Key | 含义说明 | Value取值范围 | +| -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| level | 用例级别 | "0","1","2","3","4", 例如:-s level 1 | +| size | 用例粒度 | "small","medium","large", 例如:-s size small | +| testType | 用例测试类型 | "function","performance","power","reliability","security","global","compatibility","user","standard","safety","resilience", 例如:-s testType function | + +示例代码 + +```javascript +import { describe, it, expect, TestType, Size, Level } from '@ohos/hypium'; + +export default function attributeTest() { + describe('attributeTest', function () { + it("testAttributeIt", TestType.FUNCTION | Size.SMALLTEST | Level.LEVEL0, function () { + console.info('Hello Test'); + }) + }) +} +``` + +示例命令 +```shell +XX -s level 1 -s size small -s testType function +``` +该命令的作用是:筛选测试应用中同时满足a)用例级别是1 b)用例粒度是small c)用例测试类型是function 三个条件的用例执行。 + +- 测试套/测试用例名称筛选能力(测试套与用例名称用“#”号连接,多个用“,”英文逗号分隔) + +| Key | 含义说明 | Value取值范围 | +| -------- | ----------------------- | -------------------------------------------------------------------------------------------- | +| class | 指定要执行的测试套&用例 | ${describeName}#${itName},${describeName} , 例如:-s class attributeTest#testAttributeIt | +| notClass | 指定不执行的测试套&用例 | ${describeName}#${itName},${describeName} , 例如:-s notClass attributeTest#testAttributeIt | + +示例命令 +```shell +XX -s class attributeTest#testAttributeIt,abilityTest#testAbilityIt +``` +该命令的作用是:筛选测试应用中attributeTest测试套下的testAttributeIt测试用例,abilityTest测试套下的testAbilityIt测试用例,只执行这两条用例。 + +- 其他能力 + +| 能力项 | Key | 含义说明 | Value取值范围 | +| ------------ | ------- | ---------------------------- | ---------------------------------------------- | +| 随机执行能力 | random | 测试套&测试用例随机执行 | true, 不传参默认为false, 例如:-s random true | +| 空跑能力 | dryRun | 显示要执行的测试用例信息全集 | true , 不传参默认为false,例如:-s dryRun true | +| 异步超时能力 | timeout | 异步用例执行的超时时间 | 正整数 , 单位ms,例如:-s timeout 5000 | + +##### 约束限制 +随机执行能力和空跑能力从npm包1.0.3版本开始支持 + +#### Mock能力 + +##### 约束限制 + +单元测试框架Mock能力从npm包[1.0.1版本](https://repo.harmonyos.com/#/cn/application/atomService/@ohos%2Fhypium/v/1.0.1)开始支持 + +## 约束 + +*** + 本模块首批接口从OpenHarmony SDK API version 8开始支持。 + +## Hypium开放能力隐私声明 + +- 我们如何收集和使用您的个人信息 + 您在使用集成了Hypium开放能力的测试应用时,Hypium不会处理您的个人信息。 +- SDK处理的个人信息 + 不涉及。 +- SDK集成第三方服务声明 + 不涉及。 +- SDK数据安全保护 + 不涉及。 +- SDK版本更新声明 + 为了向您提供最新的服务,我们会不时更新Hypium版本。我们强烈建议开发者集成使用最新版本的Hypium。 + diff --git a/trulymem-core/build-profile.json5 b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/build-profile.json5 similarity index 73% rename from trulymem-core/build-profile.json5 rename to oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/build-profile.json5 index 18c8559..514585d 100644 --- a/trulymem-core/build-profile.json5 +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/build-profile.json5 @@ -1,41 +1,31 @@ -{ - "apiType": "stageMode", - "buildOption": { - "resOptions": { - "copyCodeResource": { - "enable": false - } - } - }, - "buildOptionSet": [ - { - "name": "release", - "arkOptions": { - "obfuscation": { - "ruleOptions": { - "enable": false, - "files": [ - "./obfuscation-rules.txt" - ] - }, - "consumerFiles": [ - "./consumer-rules.txt" - ] - } - }, - "resOptions": { - "copyCodeResource": { - "enable": false - } - } - }, - ], - "targets": [ - { - "name": "default" - }, - { - "name": "ohosTest" - } - ] -} +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/trulymem-core/consumer-rules.txt b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/consumer-rules.txt similarity index 100% rename from trulymem-core/consumer-rules.txt rename to oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/consumer-rules.txt diff --git a/trulymem-core/hvigorfile.ts b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/hvigorfile.ts similarity index 100% rename from trulymem-core/hvigorfile.ts rename to oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/hvigorfile.ts diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.d.ts b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.d.ts new file mode 100644 index 0000000..58e1066 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.d.ts @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 const DEFAULT = 0B0000 + +export const when: when; + +export enum TestType { + FUNCTION = 0B1, + PERFORMANCE = 0B1 << 1, + POWER = 0B1 << 2, + RELIABILITY = 0B1 << 3, + SECURITY = 0B1 << 4, + GLOBAL = 0B1 << 5, + COMPATIBILITY = 0B1 << 6, + USER = 0B1 << 7, + STANDARD = 0B1 << 8, + SAFETY = 0B1 << 9, + RESILIENCE = 0B1 << 10 +} + +export enum Size { + SMALLTEST = 0B1 << 16, + MEDIUMTEST = 0B1 << 17, + LARGETEST = 0B1 << 18 +} + +export enum Level { + LEVEL0 = 0B1 << 24, + LEVEL1 = 0B1 << 25, + LEVEL2 = 0B1 << 26, + LEVEL3 = 0B1 << 27, + LEVEL4 = 0B1 << 28 +} +export { xdescribe, xit, describe, it } from './index'; + + + +export function beforeItSpecified(testCaseNames: Array | string, callback: Function): void + +export function afterItSpecified(testCaseNames: Array | string, callback: Function): void + +export function beforeEach(callback: Function): void + +export function afterEach(callback: Function): void + +export function beforeAll(callback: Function): void + +export function afterAll(callback: Function): void + + +export interface Assert { + assertClose(expectValue: number, precision: number): void + assertContain(expectValue: any): void + assertEqual(expectValue: any): void + assertFail(): void + assertFalse(): void + assertTrue(): void + assertInstanceOf(expectValue: string): void + assertLarger(expectValue: number): void + assertLess(expectValue: number): void + assertNull(): void + assertThrowError(expectValue: string | Function): void + assertUndefined(): void + assertLargerOrEqual(expectValue: number): void + assertLessOrEqual(expectValue: number): void + assertNaN(): void + assertNegUnlimited(): void + assertPosUnlimited(): void + not(): Assert; + assertDeepEquals(expectValue: any): void + assertPromiseIsPending(): Promise + assertPromiseIsRejected(): Promise + assertPromiseIsRejectedWith(expectValue?: any): Promise + assertPromiseIsRejectedWithError(...expectValue): Promise + assertPromiseIsResolved(): Promise + assertPromiseIsResolvedWith(expectValue?: any): Promise + message(msg: string): Assert +} + +export function expect(actualValue?: any): Assert + +export class ArgumentMatchers { + static any; + static anyString; + static anyBoolean; + static anyNumber; + static anyObj; + static anyFunction; + static matchRegexs(Regex: RegExp): void +} + +declare interface when { + afterReturn(value: any): any + afterReturnNothing(): undefined + afterAction(action: any): any + afterThrow(e_msg: string): string + (argMatchers?: any): when; +} + +export interface VerificationMode { + times(count: Number): void + never(): void + once(): void + atLeast(count: Number): void + atMost(count: Number): void +} + +export class MockKit { + constructor() + mockFunc(obj: Object, func: Function): Function + mockObject(obj: Object): Object + verify(methodName: String, argsArray: Array): VerificationMode + ignoreMock(obj: Object, func: Function): void + clear(obj: Object): void + clearAll(): void +} + +export class SysTestKit { + static getDescribeName(): string; + static getItName(): string; + static getItAttribute(): TestType | Size | Level + static actionStart(tag: string): void + static actionEnd(tag: string): void + static existKeyword(keyword: string, timeout?: number): boolean +} + +export class Hypium { + static setData(data: { [key: string]: any }): void + static setTimeConfig(systemTime: any) + static hypiumTest(abilityDelegator: any, abilityDelegatorArguments: any, testsuite: Function): void + static set(key: string, value: any): void + static get(key: string): any + static registerAssert(customAssertion: Function): void + static unregisterAssert(customAssertion: string | Function): void + static hypiumWorkerTest(abilityDelegator: Object, abilityDelegatorArguments: Object, testsuite: Function, workerPort: Object): void; + static hypiumInitWorkers(abilityDelegator: Object, scriptURL: string, workerNum: number, params: Object): void; +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ets b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ets new file mode 100644 index 0000000..996fc7d --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ets @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 Core from './src/main/core'; +import {TestType, Size, Level, DEFAULT} from './src/main/Constant'; +import DataDriver from './src/main/module/config/DataDriver'; +import ExpectExtend from './src/main/module/assert/ExpectExtend'; +import OhReport from './src/main/module/report/OhReport'; +export { xdescribe, xit, describe, it } from './index.ts'; + +export declare class Hypium { + static setData(data: Object): void + static setTimeConfig(systemTime: Object): void + static hypiumTest(abilityDelegator: Object, abilityDelegatorArguments: Object, testsuite: Function): void + static set(key: string, value: Object): void + static get(key: string): Object + static registerAssert(customAssertion: Function): void + static unregisterAssert(customAssertion: string | Function): void + static hypiumWorkerTest(abilityDelegator: Object, abilityDelegatorArguments: Object, + testsuite: Function, workerPort: Object): void; + static hypiumInitWorkers(abilityDelegator: Object, scriptURL: string, workerNum: number, params: Object): void; +} + +export { + Core, + DataDriver, + ExpectExtend, + OhReport, + TestType, + Size, + Level, + DEFAULT +}; + +type allExpectType = Object | undefined | null + +export declare function beforeItSpecified(testCaseNames: Array | string, callback: Function): void + +export declare function afterItSpecified(testCaseNames: Array | string, callback: Function): void + +export declare function beforeEach(callback: Function): void + +export declare function afterEach(callback: Function): void + +export declare function beforeAll(callback: Function): void + +export declare function afterAll(callback: Function): void + +export declare interface Assert { + assertClose(expectValue: number, precision: number): void + assertContain(expectValue: allExpectType): void + assertEqual(expectValue: allExpectType): void + assertFail(): void + assertFalse(): void + assertTrue(): void + assertInstanceOf(expectValue: string): void + assertLarger(expectValue: number): void + assertLess(expectValue: number): void + assertNull(): void + assertThrowError(expectValue: string | Function): void + assertUndefined(): void + assertLargerOrEqual(expectValue: number):void + assertLessOrEqual(expectValue: number):void + assertNaN():void + assertNegUnlimited(): void + assertPosUnlimited(): void + not(): Assert; + assertDeepEquals(expectValue: allExpectType):void + assertPromiseIsPending(): Promise + assertPromiseIsRejected(): Promise + assertPromiseIsRejectedWith(expectValue?: allExpectType): Promise + assertPromiseIsRejectedWithError(...expectValue: allExpectType[]): Promise + assertPromiseIsResolved(): Promise + assertPromiseIsResolvedWith(expectValue?: allExpectType): Promise + message(msg: string): Assert +} + +export declare function expect(actualValue?: allExpectType): Assert + +export declare class ArgumentMatchers { + public static any: allExpectType; + public static anyString: string; + public static anyBoolean: Boolean; + public static anyNumber: Number; + public static anyObj: Object; + public static anyFunction: Function; + public static matchRegexs(regex: RegExp): void +} + +declare interface whenResult { + afterReturn: (value: allExpectType) => allExpectType + afterReturnNothing: () => undefined + afterAction: (action: allExpectType) => allExpectType + afterThrow: (e_msg: string) => string +} + +export declare function when(f:Function): (...args: (allExpectType | void)[]) => whenResult + +export declare interface VerificationMode { + times(count: Number): void + never(): void + once(): void + atLeast(count: Number): void + atMost(count: Number): void +} + +export declare class MockKit { + constructor() + mockFunc(obj: Object, func: Function): Function + mockObject(obj: Object): Object + verify(methodName: String, argsArray: Array): VerificationMode + ignoreMock(obj: Object, func: Function): void + clear(obj: Object): void + clearAll(): void +} + +export declare class SysTestKit { + static getDescribeName(): string; + static getItName(): string; + static getItAttribute(): TestType | Size | Level + static actionStart(tag: string): void + static actionEnd(tag: string): void + static existKeyword(keyword: string, timeout?: number): boolean +} + diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.js new file mode 100644 index 0000000..954f765 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.js @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 Core from './src/main/core'; +import { DEFAULT, TestType, Size, Level, TAG, PrintTag } from './src/main/Constant'; +import DataDriver from './src/main/module/config/DataDriver'; +import ExpectExtend from './src/main/module/assert/ExpectExtend'; +import OhReport from './src/main/module/report/OhReport'; +import SysTestKit from './src/main/module/kit/SysTestKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, beforeItSpecified, afterItSpecified, xdescribe, xit } from './src/main/interface'; +import { MockKit, when } from './src/main/module/mock/MockKit'; +import ArgumentMatchers from './src/main/module/mock/ArgumentMatchers'; +import worker from '@ohos.worker'; + +class Hypium { + static context = new Map(); + static setData(data) { + const core = Core.getInstance(); + const dataDriver = new DataDriver({ data }); + core.addService('dataDriver', dataDriver); + } + + static setTimeConfig(systemTime) { + SysTestKit.systemTime = systemTime; + } + + static set(key, value) { + Hypium.context.set(key, value); + } + + static get(key) { + return Hypium.context.get(key); + } + + static hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) { + const core = Core.getInstance(); + const expectExtend = new ExpectExtend({ + 'id': 'extend' + }); + core.addService('expect', expectExtend); + const ohReport = new OhReport({ + 'delegator': abilityDelegator, + 'abilityDelegatorArguments': abilityDelegatorArguments + }); + SysTestKit.delegator = abilityDelegator; + core.addService('report', ohReport); + core.init(); + core.subscribeEvent('spec', ohReport); + core.subscribeEvent('suite', ohReport); + core.subscribeEvent('task', ohReport); + const configService = core.getDefaultService('config'); + if (abilityDelegatorArguments !== null) { + let testParameters = configService.translateParams(abilityDelegatorArguments.parameters); + console.info(`${TAG}parameters:${JSON.stringify(testParameters)}`); + configService.setConfig(testParameters); + } + testsuite(); + core.execute(abilityDelegator); + } + static async hypiumInitWorkers(abilityDelegator, scriptURL, workerNum = 8, params) { + console.info(`${TAG}, hypiumInitWorkers call,${scriptURL}`); + let workerPromiseArray = []; + + // 开始统计时间 + let startTime = await SysTestKit.getRealTime(); + for (let i = 0; i < workerNum; i++) { + // 创建worker线程 + const workerPromise = Hypium.createWorkerPromise(scriptURL, i, params); + workerPromiseArray.push(workerPromise); + } + const ret = {total: 0, failure: 0, error: 0, pass: 0, ignore: 0, duration: 0}; + Promise.all(workerPromiseArray).then(async (items) => { + console.info(`${TAG}, all result from workers, ${JSON.stringify(items)}`); + let allItemList = new Array(); + // 统计执行结果 + Hypium.handleWorkerTestResult(ret, allItemList, items); + console.info(`${TAG}, all it result, ${JSON.stringify(allItemList)}`); + // 统计用例执行结果 + const retResult = {total: 0, failure: 0, error: 0, pass: 0, ignore: 0, duration: 0}; + // 标记用例执行结果 + Hypium.configWorkerItTestResult(retResult, allItemList); + // 打印用例结果 + Hypium.printWorkerTestResult(abilityDelegator, allItemList); + // 用例执行完成统计时间 + let endTime = await SysTestKit.getRealTime(); + const taskConsuming = endTime - startTime; + const message = + `\n${PrintTag.OHOS_REPORT_ALL_RESULT}: stream=Test run: runTimes: ${ret.total},total: ${retResult.total}, Failure: ${retResult.failure}, Error: ${retResult.error}, Pass: ${retResult.pass}, Ignore: ${retResult.ignore}` + + `\n${PrintTag.OHOS_REPORT_ALL_CODE}: ${retResult.failure > 0 || retResult.error > 0 ? -1 : 0}` + + `\n${PrintTag.OHOS_REPORT_ALL_STATUS}: taskconsuming=${taskConsuming > 0 ? taskConsuming : ret.duration}`; + abilityDelegator.printSync(message); + console.info(`${TAG}, [end] you worker test`); + abilityDelegator.finishTest('you worker test finished!!!', 0, () => {}); + }).catch((e) => { + console.info(`${TAG}, [end] error you worker test, ${JSON.stringify(e)}`); + abilityDelegator.finishTest('you worker test error finished!!!', 0, () => {}); + }).finally(() => { + console.info(`${TAG}, all promise finally end`); + }); + } + // 创建worker线程 + static createWorkerPromise(scriptURL, i, params) { + console.info(`${TAG}, createWorkerPromiser, ${scriptURL}, ${i}`); + const workerPromise = new Promise((resolve, reject) => { + const workerInstance = new worker.ThreadWorker(scriptURL, {name: `worker_${i}`}); + console.info(`${TAG}, send data to worker`); + // 发送数据到worker线程中 + workerInstance.postMessage(params); + workerInstance.onmessage = function (e) { + let currentThreadName = e.data?.currentThreadName; + console.info(`${TAG}, receview data from ${currentThreadName}, ${JSON.stringify(e.data)}`); + // + resolve(e.data?.summary); + console.info(`${TAG}, ${currentThreadName} finish`); + workerInstance.terminate(); + }; + workerInstance.onerror = function (e) { + console.info(`${TAG}, worker error, ${JSON.stringify(e)}`); + reject(e); + workerInstance.terminate(); + }; + workerInstance.onmessageerror = function (e) { + console.info(`${TAG}, worker message error, ${JSON.stringify(e)}`); + reject(e); + workerInstance.terminate(); + }; + }); + return workerPromise; + } + static handleWorkerTestResult(ret, allItemList, items) { + console.info(`${TAG}, handleWorkerTestResult, ${JSON.stringify(items)}`); + for (const {total, failure, error, pass, ignore, duration, itItemList} of items) { + ret.total += total; + ret.failure += failure; + ret.error += error; + ret.pass += pass; + ret.ignore += ignore; + ret.duration += duration; + Hypium.handleItResult(allItemList, itItemList); + } + } + static handleItResult(allItemList, itItemList) { + // 遍历所有的用例结果统计最终结果 + for (const {currentThreadName, description, result} of itItemList) { + let item = allItemList.find((it) => it.description === description); + if (item) { + let itResult = item.result; + // 当在worker中出现一次failure就标记为failure, 出现一次error就标记为error, 所有线程都pass才标记为pass + if (itResult === 0) { + item.result = result; + item.currentThreadName = currentThreadName; + } + } else { + let it = { + description: description, + currentThreadName: currentThreadName, + result: result + }; + allItemList.push(it); + } + } + } + static configWorkerItTestResult(retResult, allItemList) { + console.info(`${TAG}, configWorkerItTestResult, ${JSON.stringify(allItemList)}`); + for (const {currentThreadName, description, result} of allItemList) { + console.info(`${TAG}, description, ${description}, result,${result}`); + retResult.total ++; + if (result === 0) { + retResult.pass ++; + } else if (result === -1) { + retResult.error ++; + } else if (result === -2) { + retResult.failure ++; + } else { + retResult.ignore ++; + } + } + } + static printWorkerTestResult(abilityDelegator, allItemList) { + console.info(`${TAG}, printWorkerTestResult, ${JSON.stringify(allItemList)}`); + let index = 1; + for (const {currentThreadName, description, result} of allItemList) { + console.info(`${TAG}, description print, ${description}, result,${result}`); + let itArray = description.split('#'); + let des; + let itName; + if (itArray.length > 1) { + des = itArray[0]; + itName = itArray[1]; + } else if (itArray.length > 1) { + des = itArray[0]; + itName = itArray[0]; + } else { + des = 'undefined'; + itName = 'undefined'; + } + + let msg = `\n${PrintTag.OHOS_REPORT_WORKER_STATUS}: class=${des}`; + msg += `\n${PrintTag.OHOS_REPORT_WORKER_STATUS}: test=${itName}`; + msg += `\n${PrintTag.OHOS_REPORT_WORKER_STATUS}: current=${index}`; + msg += `\n${PrintTag.OHOS_REPORT_WORKER_STATUS}: CODE=${result}`; + abilityDelegator.printSync(msg); + index ++; + } + } + static hypiumWorkerTest(abilityDelegator, abilityDelegatorArguments, testsuite, workerPort) { + console.info(`${TAG}, hypiumWorkerTest call`); + SysTestKit.workerPort = workerPort; + let currentWorkerName = workerPort.name; + console.info(`${TAG}, hypiumWorkerTest_currentWorkerName: ${currentWorkerName}`); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + + } + + static registerAssert(customAssertion) { + const core = Core.getInstance(); + const expectService = core.getDefaultService('expect'); + let matchers = {}; + matchers[customAssertion.name] = customAssertion; + expectService.addMatchers(matchers); + expectService.customMatchers.push(customAssertion.name); + console.info(`${TAG}success to register the ${customAssertion.name}`); + } + + static unregisterAssert(customAssertion) { + const core = Core.getInstance(); + const expectService = core.getDefaultService('expect'); + let customAssertionName = typeof customAssertion === 'function' ? customAssertion.name : customAssertion; + expectService.removeMatchers(customAssertionName); + console.info(`${TAG}success to unregister the ${customAssertionName}`); + } + +} + +export { + Hypium, + Core, + DEFAULT, + TestType, + Size, + Level, + DataDriver, + ExpectExtend, + OhReport, + SysTestKit, + describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, beforeItSpecified, afterItSpecified, xdescribe, xit, + MockKit, when, + ArgumentMatchers +}; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ts b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ts new file mode 100644 index 0000000..6e059e3 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { TestType, Size, Level } from "./src/main/Constant"; + +export declare function xdescribe(testSuiteName: string, func: Function): void; + +export declare namespace xdescribe { + function reason(reason: string): any; +}; + +export declare function describe(testSuiteName: string, func: Function): void; + +export declare function xit(testCaseName: string, attribute: TestType | Size | Level, func: Function): void; + +export declare namespace xit { + function reason(reason: string): any; +}; + +export declare function it(testCaseName: string, attribute: TestType | Size | Level, func: Function): void; diff --git a/trulymem-core/obfuscation-rules.txt b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/obfuscation-rules.txt similarity index 100% rename from trulymem-core/obfuscation-rules.txt rename to oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/obfuscation-rules.txt diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/oh-package.json5 b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/oh-package.json5 new file mode 100644 index 0000000..fe6fe8a --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/oh-package.json5 @@ -0,0 +1 @@ +{"name":"@ohos/hypium","version":"1.0.24","description":"A unit test framework for OpenHarmony application","main":"index.js","keywords":["测试框架","except","mock"],"author":"huawei","license":"Apache-2.0","repository":"https://gitee.com/openharmony/testfwk_arkxtest","homepage":"https://gitee.com/openharmony/testfwk_arkxtest","dependencies":{},"metadata":{"sourceRoots":["./src/main"],"debug":true,"useNormalizedOHMUrl":false},"compatibleSdkVersion":17,"compatibleSdkType":"HarmonyOS","obfuscated":false} diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/Constant.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/Constant.js new file mode 100644 index 0000000..2f6e8a1 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/Constant.js @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +/** + * define the testcase type : TestType, Size , Level + */ +export const TAG = '[Hypium]'; + +export const DEFAULT = 0B0000; + +export class PrintTag { + static OHOS_REPORT_WORKER_STATUS = 'OHOS_REPORT_WORKER_STATUS'; + static OHOS_REPORT_ALL_RESULT = 'OHOS_REPORT_ALL_RESULT'; + static OHOS_REPORT_ALL_CODE = 'OHOS_REPORT_ALL_CODE'; + static OHOS_REPORT_ALL_STATUS = 'OHOS_REPORT_ALL_STATUS'; + static OHOS_REPORT_RESULT = 'OHOS_REPORT_RESULT'; + static OHOS_REPORT_CODE = 'OHOS_REPORT_CODE'; + static OHOS_REPORT_STATUS = 'OHOS_REPORT_STATUS'; + static OHOS_REPORT_SUM = 'OHOS_REPORT_SUM'; + static OHOS_REPORT_STATUS_CODE = 'OHOS_REPORT_STATUS_CODE'; +}; + +export class TestType { + static FUNCTION = 0B1; + static PERFORMANCE = 0B1 << 1; + static POWER = 0B1 << 2; + static RELIABILITY = 0B1 << 3; + static SECURITY = 0B1 << 4; + static GLOBAL = 0B1 << 5; + static COMPATIBILITY = 0B1 << 6; + static USER = 0B1 << 7; + static STANDARD = 0B1 << 8; + static SAFETY = 0B1 << 9; + static RESILIENCE = 0B1 << 10; +}; + +export class Size { + static SMALLTEST = 0B1 << 16; + static MEDIUMTEST = 0B1 << 17; + static LARGETEST = 0B1 << 18; +}; + +export class Level { + static LEVEL0 = 0B1 << 24; + static LEVEL1 = 0B1 << 25; + static LEVEL2 = 0B1 << 26; + static LEVEL3 = 0B1 << 27; + static LEVEL4 = 0B1 << 28; +}; + +export const TESTTYPE = { + 'function': 1, + 'performance': 1 << 1, + 'power': 1 << 2, + 'reliability': 1 << 3, + 'security': 1 << 4, + 'global': 1 << 5, + 'compatibility': 1 << 6, + 'user': 1 << 7, + 'standard': 1 << 8, + 'safety': 1 << 9, + 'resilience': 1 << 10, +}; + +export const LEVEL = { + '0': 1 << 24, + '1': 1 << 25, + '2': 1 << 26, + '3': 1 << 27, + '4': 1 << 28, +}; + +export const SIZE = { + 'small': 1 << 16, + 'medium': 1 << 17, + 'large': 1 << 18, +}; + +export const KEYSET = [ + '-s class', '-s notClass', '-s suite', '-s itName', + '-s level', '-s testType', '-s size', '-s timeout', + '-s dryRun', '-s random', '-s breakOnError', '-s stress', + '-s coverage', '-s skipMessage', '-s runSkipped', + 'class', 'notClass', 'suite', 'itName', + 'level', 'testType', 'size', 'timeout', 'dryRun', 'random', + 'breakOnError', 'stress', 'coverage', 'skipMessage', 'runSkipped' +]; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/core.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/core.js new file mode 100644 index 0000000..1981a5a --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/core.js @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 {SuiteService, SpecService, ExpectService, ReportService} from './service'; +import {ConfigService} from './module/config/configService'; +import {SpecEvent, TaskEvent, SuiteEvent} from './event'; + +/** + * core service for execute testcase. + */ +class Core { + static getInstance() { + if (!this.instance) { + this.instance = new Core(); + } + return this.instance; + } + + constructor() { + this.instance = null; + this.services = { + suite: {}, + spec: {}, + config: {}, + expect: {}, + log: {}, + report: {} + + }; + this.events = { + suite: {}, + spec: {}, + task: {} + }; + } + + addService(name, service) { + let serviceObj = {}; + if (!this.services[name]) { + this.services[name] = serviceObj; + } else { + serviceObj = this.services[name]; + } + serviceObj[service.id] = service; + } + + getDefaultService(name) { + return this.services[name].default; + } + + getServices(name) { + return this.services[name]; + } + + registerEvent(serviceName, event) { + let eventObj = {}; + if (!this.events[serviceName]) { + this.events[serviceName] = eventObj; + } else { + eventObj = this.events[serviceName]; + } + eventObj[event.id] = event; + } + + unRegisterEvent(serviceName, eventID) { + const eventObj = this.events[serviceName]; + if (eventObj) { + delete eventObj[eventID]; + } + } + + subscribeEvent(serviceName, serviceObj) { + const eventObj = this.events[serviceName]; + if (eventObj) { + for (const attr in eventObj) { + eventObj[attr]['subscribeEvent'](serviceObj); + } + } + } + + async fireEvents(serviceName, eventName) { + const eventObj = this.events[serviceName]; + if (!eventObj) { + return; + } + for (const attr in eventObj) { + await eventObj[attr][eventName](); + } + } + + addToGlobal(apis) { + if (typeof globalThis !== 'undefined') { + for (let api in apis) { + globalThis[api] = apis[api]; + } + } + for (const api in apis) { + this[api] = apis[api]; + } + } + + init() { + this.addService('suite', new SuiteService({id: 'default'})); + this.addService('spec', new SpecService({id: 'default'})); + this.addService('expect', new ExpectService({id: 'default'})); + this.addService('report', new ReportService({id: 'default'})); + this.addService('config', new ConfigService({id: 'default'})); + this.registerEvent('task', new TaskEvent({id: 'default', coreContext: this})); + this.registerEvent('suite', new SuiteEvent({id: 'default', coreContext: this})); + this.registerEvent('spec', new SpecEvent({id: 'default', coreContext: this})); + this.subscribeEvent('spec', this.getDefaultService('report')); + this.subscribeEvent('suite', this.getDefaultService('report')); + this.subscribeEvent('task', this.getDefaultService('report')); + const context = this; + for (const key in this.services) { + const serviceObj = this.services[key]; + for (const serviceID in serviceObj) { + const service = serviceObj[serviceID]; + service.init(context); + + if (typeof service.apis !== 'function') { + continue; + } + const apis = service.apis(); + if (apis) { + this.addToGlobal(apis); + } + } + } + } + + execute(abilityDelegator) { + const suiteService = this.getDefaultService('suite'); + const configService = this.getDefaultService('config'); + if (configService['dryRun'] === 'true') { + (async function () { + await suiteService.dryRun(abilityDelegator); + })(); + return; + } + setTimeout(() => { + suiteService.execute(); + }, 10); + } +} + +export default Core; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/event.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/event.js new file mode 100644 index 0000000..d465896 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/event.js @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class SpecEvent { + constructor(attr) { + this.id = attr.id; + this.coreContext = attr.context; + this.eventMonitors = []; + } + + subscribeEvent(service) { + this.eventMonitors.push(service); + } + + async specStart() { + for (const monitor of this.eventMonitors) { + await monitor['specStart'](); + } + } + + async specDone() { + for (const monitor of this.eventMonitors) { + await monitor['specDone'](); + } + } +} + +class SuiteEvent { + constructor(attr) { + this.id = attr.id; + this.suiteContext = attr.coreContext; + this.eventMonitors = []; + } + + subscribeEvent(service) { + this.eventMonitors.push(service); + } + + async suiteStart() { + for (const monitor of this.eventMonitors) { + await monitor['suiteStart'](); + } + } + + async suiteDone() { + for (const monitor of this.eventMonitors) { + await monitor['suiteDone'](); + } + } +} + +class TaskEvent { + constructor(attr) { + this.id = attr.id; + this.coreContext = attr.coreContext; + this.eventMonitors = []; + } + + subscribeEvent(service) { + this.eventMonitors.push(service); + } + + async taskStart() { + for (const monitor of this.eventMonitors) { + await monitor['taskStart'](); + } + } + + async taskDone() { + for (const monitor of this.eventMonitors) { + await monitor['taskDone'](); + } + } + + incorrectFormat() { + for (const monitor of this.eventMonitors) { + monitor['incorrectFormat'](); + } + } + + incorrectTestSuiteFormat() { + for (const monitor of this.eventMonitors) { + monitor.incorrectTestSuiteFormat(); + } + } +} + +export { SpecEvent, TaskEvent, SuiteEvent }; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/interface.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/interface.js new file mode 100644 index 0000000..e2530d2 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/interface.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 Core from './core'; + +const core = Core.getInstance(); + +const describe = function (desc, func) { + return Reflect.has(core, 'describe') ? core.describe(desc, func) : (desc, func) => { }; +}; +const it = function (desc, filter, func) { + return Reflect.has(core, 'it') ? core.it(desc, filter, func) : (desc, filter, func) => { }; +}; +const beforeItSpecified = function (itDescs, func) { + return Reflect.has(core, 'beforeItSpecified') ? core.beforeItSpecified(itDescs, func) : (itDescs, func) => { }; +}; + +const afterItSpecified = function (itDescs, func) { + return Reflect.has(core, 'afterItSpecified') ? core.afterItSpecified(itDescs, func) : (itDescs, func) => { }; +}; +const beforeEach = function (func) { + return Reflect.has(core, 'beforeEach') ? core.beforeEach(func) : (func) => { }; +}; +const afterEach = function (func) { + return Reflect.has(core, 'afterEach') ? core.afterEach(func) : (func) => { }; +}; +const beforeAll = function (func) { + return Reflect.has(core, 'beforeAll') ? core.beforeAll(func) : (func) => { }; +}; +const afterAll = function (func) { + return Reflect.has(core, 'afterAll') ? core.afterAll(func) : (func) => { }; +}; +const expect = function (actualValue) { + return Reflect.has(core, 'expect') ? core.expect(actualValue) : (actualValue) => { }; +}; + +const xdescribe = function (desc, func) { + return Reflect.has(core, 'xdescribe') ? core.xdescribe(desc, func, null) : (desc, func, reason) => { }; +}; +xdescribe.reason = (reason) => { + return (desc, func) => { + return Reflect.has(core, 'xdescribe') ? core.xdescribe(desc, func, reason) : (desc, func, reason) => { }; + }; +}; +const xit = function (desc, filter, func) { + return Reflect.has(core, 'xit') ? core.xit(desc, filter, func, null) : (desc, filter, func, reason) => { }; +}; +xit.reason = (reason) => { + return (desc, filter, func) => { + return Reflect.has(core, 'xit') ? core.xit(desc, filter, func, reason) : (desc, filter, func, reason) => { }; + }; +}; + +export { + describe, it, beforeAll, beforeEach, afterEach, afterAll, expect, beforeItSpecified, afterItSpecified, xdescribe, xit +}; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module.json b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module.json new file mode 100644 index 0000000..7182854 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module.json @@ -0,0 +1,30 @@ +{ + "app": { + "bundleName": "com.hypium.myapplication", + "debug": true, + "versionCode": 1000000, + "versionName": "1.0.0", + "minAPIVersion": 50005017, + "targetAPIVersion": 50005017, + "apiReleaseType": "Release", + "compileSdkVersion": "5.0.5.165", + "compileSdkType": "HarmonyOS", + "appEnvironments": [], + "bundleType": "app", + "buildMode": "debug" + }, + "module": { + "name": "hypium", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "packageName": "@ohos/hypium", + "installationFree": false, + "virtualMachine": "ark12.0.6.0", + "compileMode": "esmodule", + "dependencies": [] + } +} diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/ExpectExtend.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/ExpectExtend.js new file mode 100644 index 0000000..6a3a01f --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/ExpectExtend.js @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 assertNull from './assertNull'; +import assertClose from './assertClose'; +import assertContain from './assertContain'; +import assertLess from './assertLess'; +import assertLarger from './assertLarger'; +import assertFail from './assertFail'; +import assertUndefined from './assertUndefined'; +import assertFalse from './assertFalse'; +import assertInstanceOf from './assertInstanceOf'; +import assertThrowError from './assertThrowError'; +import assertLargerOrEqual from './assertLargerOrEqual'; +import assertLessOrEqual from './assertLessOrEqual'; +import assertNaN from './assertNaN'; +import assertNegUnlimited from './assertNegUnlimited'; +import assertPosUnlimited from './assertPosUnlimited'; +import assertDeepEquals from './deepEquals/assertDeepEquals'; +import assertPromiseIsPending from './assertPromiseIsPending'; +import assertPromiseIsRejected from './assertPromiseIsRejected'; +import assertPromiseIsRejectedWith from './assertPromiseIsRejectedWith'; +import assertPromiseIsRejectedWithError from './assertPromiseIsRejectedWithError'; +import assertPromiseIsResolved from './assertPromiseIsResolved'; +import assertPromiseIsResolvedWith from './assertPromiseIsResolvedWith'; +class ExpectExtend { + constructor(attr) { + this.id = attr.id; + this.matchers = {}; + } + + extendsMatchers() { + this.matchers.assertNull = assertNull; + this.matchers.assertClose = assertClose; + this.matchers.assertContain = assertContain; + this.matchers.assertLess = assertLess; + this.matchers.assertLarger = assertLarger; + this.matchers.assertFail = assertFail; + this.matchers.assertUndefined = assertUndefined; + this.matchers.assertFalse = assertFalse; + this.matchers.assertInstanceOf = assertInstanceOf; + this.matchers.assertThrowError = assertThrowError; + this.matchers.assertLargerOrEqual = assertLargerOrEqual; + this.matchers.assertLessOrEqual = assertLessOrEqual; + this.matchers.assertNaN = assertNaN; + this.matchers.assertNegUnlimited = assertNegUnlimited; + this.matchers.assertPosUnlimited = assertPosUnlimited; + this.matchers.assertDeepEquals = assertDeepEquals; + this.matchers.assertPromiseIsPending = assertPromiseIsPending; + this.matchers.assertPromiseIsRejected = assertPromiseIsRejected; + this.matchers.assertPromiseIsRejectedWith = assertPromiseIsRejectedWith; + this.matchers.assertPromiseIsRejectedWithError = assertPromiseIsRejectedWithError; + this.matchers.assertPromiseIsResolved = assertPromiseIsResolved; + this.matchers.assertPromiseIsResolvedWith = assertPromiseIsResolvedWith; + } + + init(coreContext) { + this.coreContext = coreContext; + this.extendsMatchers(); + const expectService = this.coreContext.getDefaultService('expect'); + expectService.addMatchers(this.matchers); + } + + apis() { + return { + 'expect': function (actualValue) { + return this.coreContext.getDefaultService('expect').expect(actualValue); + } + }; + } +} + +export default ExpectExtend; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertClose.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertClose.js new file mode 100644 index 0000000..b4470fa --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertClose.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertClose(actualValue, expected) { + if (actualValue === null && expected[0] === null) { + throw new Error('actualValue and expected can not be both null!!!'); + } + let result; + let diff = Math.abs(expected[0] - actualValue); + let actualAbs = Math.abs(actualValue); + if ((actualAbs - 0) === 0) { + if ((diff - 0) === 0) { + result = true; + } else { + result = false; + } + } else if (diff / actualAbs < expected[1]) { + result = true; + } else { + result = false; + } + return { + pass: result, + message: '|' + actualValue + ' - ' + expected[0] + '|/' + actualValue + ' is not less than ' + expected[1] + }; +} + +export default assertClose; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertContain.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertContain.js new file mode 100644 index 0000000..ee896ab --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertContain.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertContain(actualValue, expect) { + let result = false; + if (Object.prototype.toString.call(actualValue).indexOf('Array')) { + for (let i in actualValue) { + if (actualValue[i] == expect[0]) { + result = true; + } + } + } + let type = Object.prototype.toString.call(actualValue); + if (type === '[object String]') { + result = actualValue.indexOf(expect[0]) >= 0; + } + return { + pass: result, + message: 'expect false, ' + actualValue + ' do not have ' + expect[0] + }; +} + +export default assertContain; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFail.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFail.js new file mode 100644 index 0000000..e06dcd4 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFail.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertFail() { + return { + pass: false, + message: 'fail ' + }; +} + +export default assertFail; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFalse.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFalse.js new file mode 100644 index 0000000..299781f --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertFalse.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertFalse(actualValue) { + return { + pass: (actualValue) === false, + message: 'expect false, actualValue is ' + actualValue + }; +} + +export default assertFalse; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertInstanceOf.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertInstanceOf.js new file mode 100644 index 0000000..bc5e52c --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertInstanceOf.js @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertInstanceOf(actualValue, expected) { + if (Object.prototype.toString.call(actualValue) == '[object ' + expected[0] + ']') { + return { + pass: true + }; + } else { + return { + pass: false, + message: actualValue + ' is ' + Object.prototype.toString.call(actualValue) + 'not ' + expected[0] + }; + } +} + +export default assertInstanceOf; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLarger.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLarger.js new file mode 100644 index 0000000..a0fa15e --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLarger.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertLarger(actualValue, expected) { + return { + pass: (actualValue) > expected[0], + message: (actualValue) + ' is not larger than ' + expected[0] + }; +} + +export default assertLarger; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLargerOrEqual.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLargerOrEqual.js new file mode 100644 index 0000000..3416bd1 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLargerOrEqual.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertLargerOrEqual(actualValue, expected) { + return { + pass: (actualValue) >= expected[0], + message: (actualValue) + ' is not larger than ' + expected[0] + }; +} + +export default assertLargerOrEqual; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLess.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLess.js new file mode 100644 index 0000000..23a1d8c --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLess.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertLess(actualValue, expected) { + return { + pass: (actualValue) < expected[0], + message: (actualValue) + ' is not less than ' + expected[0] + }; +} + +export default assertLess; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLessOrEqual.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLessOrEqual.js new file mode 100644 index 0000000..4a2d05e --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertLessOrEqual.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertLessOrEqual(actualValue, expected) { + return { + pass: (actualValue) <= expected[0], + message: (actualValue) + ' is not less than ' + expected[0] + }; +} + +export default assertLessOrEqual; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNaN.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNaN.js new file mode 100644 index 0000000..fa06fe7 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNaN.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertNaN(actualValue) { + return { + pass: actualValue !== actualValue, + message: 'expect NaN, actualValue is ' + actualValue + }; +} + +export default assertNaN; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNegUnlimited.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNegUnlimited.js new file mode 100644 index 0000000..3573ee0 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNegUnlimited.js @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* 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. +*/ + +function assertNegUnlimited(actualValue) { + return { + pass: actualValue === Number.NEGATIVE_INFINITY, + message: 'Expected actualValue not to be -Infinity. actualValue is,' + actualValue + }; +} + +export default assertNegUnlimited; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNull.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNull.js new file mode 100644 index 0000000..32523dd --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertNull.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertNull(actualValue) { + return { + pass: (actualValue) === null, + message: 'expect null, actualValue is ' + (actualValue) + }; +} + +export default assertNull; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPosUnlimited.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPosUnlimited.js new file mode 100644 index 0000000..43a5a0f --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPosUnlimited.js @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* 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. +*/ + +function assertPosUnlimited(actualValue) { + return { + pass: actualValue === Number.POSITIVE_INFINITY, + message: 'Expected actualValue is POSITIVE_INFINITY. actualValue is,' + actualValue + }; +} + +export default assertPosUnlimited; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsPending.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsPending.js new file mode 100644 index 0000000..5297cc2 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsPending.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsPending(actualPromise) { + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + const helper = {}; + return Promise.race([actualPromise, Promise.resolve(helper)]).then( + function (got) { + return helper === got ? {pass: true, message: 'actualValue is isPending'} + : { + pass: false, + message: 'expect isPending, actualValue is resolve' + }; + }, + function () { + return { + pass: false + , message: 'expect isPending, actualValue is reject' + }; + }); +} + +export default assertPromiseIsPending; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejected.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejected.js new file mode 100644 index 0000000..1bb43c5 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejected.js @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsRejected(actualPromise) { + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + return actualPromise.then( + function (got) { + return { + pass: false, + message: 'expect isRejected, but actualValue is resolve' + }; + }, + function () { + return {pass: true, message: 'actualValue is isRejected'}; + } + ); +} + +export default assertPromiseIsRejected; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWith.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWith.js new file mode 100644 index 0000000..d81f680 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWith.js @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsRejectedWith(actualPromise, expectedValue) { + + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + + function tips(passed) { + return ('Expected a promise ' + (passed ? 'not ' : '') + + 'to be rejected with ' + JSON.stringify(expectedValue[0])); + } + + return actualPromise.then( + function (got) { + return { + pass: false, + message: tips(false) + ' but actualValue is resolve' + }; + }, + function (actualValue) { + if (JSON.stringify(actualValue) == JSON.stringify(expectedValue[0])) { + return { + pass: true, + message: 'actualValue was rejected with ' + JSON.stringify(actualValue) + '.' + }; + } else { + return { + pass: false, + message: tips(false) + ' but it was rejected with ' + JSON.stringify(actualValue) + '.' + }; + } + } + ); +} + +export default assertPromiseIsRejectedWith; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWithError.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWithError.js new file mode 100644 index 0000000..b910148 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsRejectedWithError.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsRejectedWithError(actualPromise, expectedValue) { + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + return actualPromise.then( + function (got) { + return { + pass: false, + message: 'Expected a promise to be rejected but actualValue is resolve' + }; + }, + function (actualValue) { + return matchError(actualValue, expectedValue); + } + ); + +} + +function matchError(actualValue, expectedValue) { + if (expectedValue.length == 1 && typeof expectedValue[0] === 'function') { + if (expectedValue[0].name === actualValue.__proto__.name) { + return {pass: true, message: 'actual error type is ' + actualValue.name + '.'}; + } + return {pass: false, message: `except error type is ${expectedValue[0].name},but actual is ${actualValue.name}.`}; + } + + if (expectedValue.length == 1 && typeof expectedValue[0] === 'string') { + if (expectedValue[0] === actualValue.message) { + return {pass: true, message: `actual error message is ${actualValue.message}.`}; + } + return {pass: false, message: `except error message ${expectedValue[0]},but actual is ${actualValue.message}.`}; + } + + if (expectedValue.length == 1) { + return {pass: false, message: 'When only one parameter, it should be error type or error message.'}; + } + + if (expectedValue.length == 2 && typeof expectedValue[0] === 'function' && expectedValue[0].name === actualValue.name) { + if (typeof expectedValue[1] === 'string' && actualValue.message === expectedValue[1]) { + return {pass: true, message: 'actual error message is ' + actualValue.message + '.'}; + } + return {pass: false, message: `except error message is ${expectedValue[1]},but actual is ${actualValue.message}.`}; + } + + if (expectedValue.length == 2 && typeof expectedValue[0] === 'function' && expectedValue[0].name !== actualValue.name) { + if (typeof expectedValue[1] === 'string' && actualValue.message === expectedValue[1]) { + return {pass: false, message: `except error type is ${expectedValue[0].name},but actual is ${actualValue.name}.`}; + } + return {pass: false, message: 'except error type and message are incorrect.'}; + } + if (expectedValue.length > 2) { + return {pass: false, message: 'Up to two parameters are supported.'}; + } + return {pass: true, message: 'not equal type'}; +} + +export default assertPromiseIsRejectedWithError; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolved.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolved.js new file mode 100644 index 0000000..28cf278 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolved.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsResolved(actualPromise) { + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + + return actualPromise.then( + function (got) { + return {pass: true, message: 'actualValue is isResolved'}; + }, + function (rej) { + return { + pass: false, + message: 'Expected a promise to be resolved but it was ' + + 'rejected with ' + JSON.stringify(rej) + '.' + }; + } + ); +} + +export default assertPromiseIsResolved; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolvedWith.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolvedWith.js new file mode 100644 index 0000000..b20c3e5 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertPromiseIsResolvedWith.js @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 isPromiseLike from './isPromiseLike'; + +function assertPromiseIsResolvedWith(actualPromise, expectedValue) { + if (!isPromiseLike(actualPromise)) { + return Promise.reject().then(function () { + }, function () { + return {pass: false, message: 'Expected not be called on a promise.'}; + }); + } + + function tips(passed) { + return ( + 'Expected a promise ' + (passed ? 'not ' : '') + + 'to be resolved with ' + JSON.stringify(expectedValue[0])); + } + + return actualPromise.then( + function (got) { + if (JSON.stringify(got) == JSON.stringify(expectedValue[0])) { + return { + pass: true, + message: 'actualValue was resolved with ' + JSON.stringify(got) + '.' + }; + } + return { + pass: false, + message: tips(false) + ' but it was resolved with ' + + JSON.stringify(got) + '.' + }; + }, + function (rej) { + return { + pass: false, + message: tips(false) + ' but it was rejected with ' + JSON.stringify(rej) + '.' + }; + } + ); +} + +export default assertPromiseIsResolvedWith; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertThrowError.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertThrowError.js new file mode 100644 index 0000000..55a6f4a --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertThrowError.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertThrowError(actualValue, expected) { + let result = false; + let message = ''; + let err; + if (typeof actualValue !== 'function') { + throw new Error('actualValue is not a function'); + } + try { + actualValue(); + return { + pass: result, + message: ' An error is not thrown while it is expected!' + }; + } catch (e) { + err = e; + } + if (err instanceof Error) { + let type = typeof expected[0]; + if (type === 'function') { + result = err.constructor.name === expected[0].name; + message = 'expected throw failed , ' + err.constructor.name + ' is not ' + expected[0].name; + } else if (type === 'string') { + result = err.message.includes(expected[0]); + message = 'expected throw failed , ' + err.message + ' is not ' + expected[0]; + } + } + return { + pass: result, + message: message + }; +} + +export default assertThrowError; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertUndefined.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertUndefined.js new file mode 100644 index 0000000..0cd0570 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/assertUndefined.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function assertUndefined(actualValue) { + return { + pass: undefined === (actualValue), + message: 'expect Undefined, actualValue is ' + (actualValue) + }; +} + +export default assertUndefined; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/DeepTypeUtils.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/DeepTypeUtils.js new file mode 100644 index 0000000..40dc13b --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/DeepTypeUtils.js @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class DeepTypeUtils { + static getType(value) { + return Object.prototype.toString.apply(value); + } + static isA(typeName, value) { + return this.getType(value) === '[object ' + typeName + ']'; + } + static isAsymmetricEqualityTester(obj) { + return obj ? this.isA('Function', obj.asymmetricMatch) : false; + } + + /** + * 是否是function + * @param value + */ + static isFunction(value) { + return this.isA('Function', value); + } + + /** + * 是否是undefined + * @param obj + */ + static isUndefined(obj) { + return obj === void 0; + } + + /** + * 是否是Node + * @param obj + */ + static isDomNode(obj) { + return obj !== null && + typeof obj === 'object' && + typeof obj.nodeType === 'number' && + typeof obj.nodeName === 'string'; + } + + /** + * 是否是promise对象 + * @param obj + */ + static isPromise(obj) { + return !!obj && obj.constructor === Promise; + }; + /** + * 是否是map对象 + * @param obj + */ + static isMap(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + obj.constructor === Map + ); + } + + /** + * 是否是set对象 + * @param obj 对象 + */ + static isSet(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + obj.constructor === Set + ); + } + + /** + * 对象是否有key属性 + * @param obj 对象 + * @param key 对象属性名称 + */ + static has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + } + + /** + * 获取对象的自有属性 + * @param obj 对象 + * @param isArray 是否是数组,[object Array] + */ + static keys(obj, isArray) { + const extraKeys = []; + // 获取对象所有属性 + const allKeys = this.getAllKeys(obj); + if (!isArray) { + return allKeys; + } + if (allKeys.length === 0) { + return allKeys; + } + for (const k of allKeys) { + if (typeof k === 'symbol' || !/^[0-9]+$/.test(k)) { + extraKeys.push(k); + } + } + return extraKeys; + } + + /** + * 获取obj对象的所有属性 + * @param obj obj对象 + */ + static getAllKeys(obj) { + const keys = []; + for (let key in obj) { + if (this.has(obj, key)) { + keys.push(key); + } + } + const symbols = Object.getOwnPropertySymbols(obj); + for (const sym of symbols) { + // obj.propertyIsEnumerable(sym) + if (Object.prototype.propertyIsEnumerable.call(obj, sym)) { + keys.push(sym); + } + } + return keys; + } + +} +export default DeepTypeUtils; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/assertDeepEquals.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/assertDeepEquals.js new file mode 100644 index 0000000..8832a11 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/deepEquals/assertDeepEquals.js @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 DeepTypeUtils from './DeepTypeUtils'; +function assertDeepEquals(actualValue, expected) { + let result = eq(actualValue, expected[0]); + let msg = logMsg(actualValue, expected[0]); + return { + pass: result, + message: msg + }; +} + +function getMapLog(item) { + let valueStr = ''; + let keyValue = ''; + if (item.length > 1) { + let key = item[0]; + let value = item[1]; + if (value !== value) { + valueStr = value; + } else { + valueStr = JSON.stringify(value); + } + keyValue = JSON.stringify(key); + } + return '[' + keyValue + ',' + valueStr + ']'; +} + +function getArrayLog(item) { + // NAN + if (item !== item) { + return item; + } + if (item === undefined) { + return 'undefined'; + } + return JSON.stringify(item); +} + + +function getCollectionLog(data) { + // 获取a的对象名称 + let finallyResult = ''; + const aClassName = Object.prototype.toString.call(data); + if (aClassName === '[object Map]') { + let result = Array.from(data); + finallyResult = result.flatMap((item) => { + return getMapLog(item); + }); + } + if (aClassName === '[object Set]') { + let setArray = Array.from(data); + finallyResult = setArray.flatMap((item) => { + return getArrayLog(item); + }); + } + if (aClassName === '[object Array]') { + finallyResult = data.flatMap((item) => { + return getArrayLog(item); + }); + } + return finallyResult; +} + +function getActualValueLog(actualValue) { + // 获取a的对象名称 + const aClassName = Object.prototype.toString.call(actualValue); + let actualMsg = ''; + if (aClassName === '[object Function]') { + actualMsg = 'actualValue Function'; + } else if (aClassName === '[object Promise]') { + actualMsg = 'actualValue Promise'; + } else if (aClassName === '[object Map]') { + let finallyResult = getCollectionLog(actualValue); + actualMsg = '[' + finallyResult + ']'; + } else if (aClassName === '[object Set]') { + let flatMapResult = getCollectionLog(actualValue); + actualMsg = '[' + flatMapResult + ']'; + } else if (aClassName === '[object Array]') { + let flatMapResult = getCollectionLog(actualValue); + actualMsg = '[' + flatMapResult + ']'; + } else if (aClassName === '[object RegExp]') { + actualMsg = JSON.stringify(actualValue.source.replace('\\', '')); + } else if (aClassName === '[object BigInt]') { + actualMsg = actualValue; + } else if (aClassName === '[object Error]') { + actualMsg = actualValue.message; + } else if (aClassName === '[object ArrayBuffer]') { + actualMsg = actualValue.byteLength; + } + else { + // NAN + if (actualValue !== actualValue) { + actualMsg = actualValue.toString(); + } else { + actualMsg = JSON.stringify(actualValue); + } + } + return actualMsg; +} + +function getExpectedLog(expected) { + const bClassName = Object.prototype.toString.call(expected); + let expectMsg = ''; + if (bClassName === '[object Function]') { + expectMsg = 'expected Function'; + } else if (bClassName === '[object Promise]') { + expectMsg = 'expected Promise'; + } else if (bClassName === '[object Map]') { + let finallyResult = getCollectionLog(expected); + expectMsg = '[' + finallyResult + ']'; + } else if (bClassName === '[object Set]') { + let flatMapResult = getCollectionLog(expected); + expectMsg = '[' + flatMapResult + ']'; + } else if (bClassName === '[object Array]') { + let flatMapResult = getCollectionLog(expected); + expectMsg = '[' + flatMapResult + ']'; + } else if (bClassName === '[object RegExp]') { + expectMsg = JSON.stringify(expected.source.replace('\\', '')); + } else if (bClassName === '[object BigInt]') { + expectMsg = expected; + } else if (bClassName === '[object Error]') { + expectMsg = expected.message; + } else if (bClassName === '[object ArrayBuffer]') { + expectMsg = expected.byteLength; + } + else { + // NAN + if (expected !== expected) { + expectMsg = expected.toString(); + } else { + expectMsg = JSON.stringify(expected); + } + } + return expectMsg; +} + +/** + * 获取失败显示日志 + * @param actualValue 实际对象 + * @param expected 期待比较对象 + */ +function logMsg(actualValue, expected) { + // 获取a的对象名称 + let actualMsg = getActualValueLog(actualValue); + let expectMsg = getExpectedLog(expected); + + return 'expect ' + actualMsg + ' deep equals ' + expectMsg; +} + +function eq(a, b) { + let result = true; + + if (a === b) { + result = a !== 0 || 1 / a === 1 / b; + return result; + } + + if (a === null || b === null) { + result = a === b; + return result; + } + // 获取a的对象名称 + const aClassName = Object.prototype.toString.call(a); + const bClassName = Object.prototype.toString.call(b); + // 不同类型不同对象 + if (aClassName !== bClassName) { + return false; + } + if (aClassName === '[object String]' || aClassName === '[object Number]' || aClassName === '[object Date]' || + aClassName === '[object Boolean]' || aClassName === '[object ArrayBuffer]' || + aClassName === '[object RegExp]' || aClassName === '[object Error]') { + result = isEqualSampleObj(a, b); + return result; + } + + if (typeof a !== 'object' || typeof b !== 'object') { + return false; + } + + if (DeepTypeUtils.isDomNode(a) || DeepTypeUtils.isPromise(a) || DeepTypeUtils.isFunction(a)) { + result = isEqualNodeOrPromiseOrFunction(a, b); + return result; + } + + if (aClassName === '[object Array]' || aClassName === '[object Map]' || aClassName === '[object Set]') { + result = isEqualCollection(a, b); + return result; + } + + result = isEqualObj(a, b); + return result; +} + +function isEqualNodeOrPromiseOrFunction(a, b) { + let equalNodeOrPromiseOrFunction = true; + if (DeepTypeUtils.isDomNode(a) && DeepTypeUtils.isDomNode(b)) { + const aIsDomNode = DeepTypeUtils.isDomNode(a); + const bIsDomNode = DeepTypeUtils.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + equalNodeOrPromiseOrFunction = a.isEqualNode(b); + return equalNodeOrPromiseOrFunction; + } + if (aIsDomNode || bIsDomNode) { + equalNodeOrPromiseOrFunction = false; + return false; + } + } + + if (DeepTypeUtils.isPromise(a) && DeepTypeUtils.isPromise(b)) { + const aIsPromise = DeepTypeUtils.isPromise(a); + const bIsPromise = DeepTypeUtils.isPromise(b); + // 俩个Promise对象 + if (aIsPromise && bIsPromise) { + equalNodeOrPromiseOrFunction = a === b; + return a === b; + } + } + if (DeepTypeUtils.isFunction(a) && DeepTypeUtils.isFunction(b)) { + // 俩个函数对象 + const aCtor = a.constructor, + bCtor = b.constructor; + if ( + aCtor !== bCtor && + DeepTypeUtils.isFunction(aCtor) && + DeepTypeUtils.isFunction(bCtor) && + a instanceof aCtor && + b instanceof bCtor && + !(aCtor instanceof aCtor && bCtor instanceof bCtor) + ) { + equalNodeOrPromiseOrFunction = false; + return false; + } + } + return equalNodeOrPromiseOrFunction; +} + +function isEqualCollection(a, b) { + let equalCollection = true; + // 获取a的对象名称 + const aClassName = Object.prototype.toString.call(a); + const bClassName = Object.prototype.toString.call(b); + // 都是数组 + if (aClassName === '[object Array]') { + equalCollection = isEqualArray(a, b); + return equalCollection; + } + + // 都是Map + if (DeepTypeUtils.isMap(a) && DeepTypeUtils.isMap(b)) { + equalCollection = isEqualMap(a, b); + return equalCollection; + } + + // 都是Set + if (DeepTypeUtils.isSet(a) && DeepTypeUtils.isSet(b)) { + equalCollection = isEqualSet(a, b); + return equalCollection; + } + + return true; +} + +function isEqualSampleObj(a, b) { + let equalSampleObj = true; + const aClassName = Object.prototype.toString.call(a); + const bClassName = Object.prototype.toString.call(b); + // 俩个string对象 + if (aClassName === '[object String]') { + equalSampleObj = a === String(b); + return equalSampleObj; + } + // 俩个Number对象 + if (aClassName === '[object Number]') { + // NAN + if (a !== a && b !== b) { + return a === b; + } + equalSampleObj = a !== +a ? b !== +b : a === 0 && b === 0 ? 1 / a === 1 / b : a === +b; + return equalSampleObj; + } + + // 俩个Date对象/ boolean对象 + if (aClassName === '[object Date]' || aClassName === '[object Boolean]') { + equalSampleObj = +a === +b; + return equalSampleObj; + } + + // 俩个ArrayBuffer + if (aClassName === '[object ArrayBuffer]') { + equalSampleObj = eq(new Uint8Array(a), new Uint8Array(b)); + return equalSampleObj; + } + + // 正则表达式 + if (aClassName === '[object RegExp]') { + return ( + a.source === b.source && + a.global === b.global && + a.multiline === b.multiline && + a.ignoreCase === b.ignoreCase + ); + } + + if (a instanceof Error && b instanceof Error) { + equalSampleObj = a.message === b.message; + return equalSampleObj; + } + + return equalSampleObj; +} + +function isEqualObj(a, b) { + let equalObj = true; + const aClassName = Object.prototype.toString.call(a); + const bClassName = Object.prototype.toString.call(b); + const aKeys = DeepTypeUtils.keys(a, aClassName === '[object Array]'); + let size = aKeys.length; + + // 俩个对象属性长度不一致, 俩对象不相同 + if (DeepTypeUtils.keys(b, bClassName === '[object Array]').length !== size) { + return false; + } + + // 俩对象属性数量相同, 递归比较每个属性值得值 + for (const key of aKeys) { + // b 没有 key 属性 + if (!DeepTypeUtils.has(b, key)) { + equalObj = false; + continue; + } + if (!eq(a[key], b[key])) { + equalObj = false; + } + } + return equalObj; +} + +function isEqualArray(a, b) { + let equalArray = true; + const aLength = a.length; + const bLength = b.length; + if (aLength !== bLength) { + // 数组长度不同,不是同一个对象 + return false; + } + for (let i = 0; i < aLength || i < bLength; i++) { + // 递归每一个元素是否相同 + equalArray = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0) && equalArray; + } + return equalArray; +} + +function isEqualMap(a, b) { + let equalMap = true; + if (a.size !== b.size) { + return false; + } + const keysA = []; + const keysB = []; + a.forEach(function(valueA, keyA) { + keysA.push(keyA); + }); + b.forEach(function(valueB, keyB) { + keysB.push(keyB); + }); + const mapKeys = [keysA, keysB]; + const cmpKeys = [keysB, keysA]; + for (let i = 0; equalMap && i < mapKeys.length; i++) { + const mapIter = mapKeys[i]; + const cmpIter = cmpKeys[i]; + + for (let j = 0; equalMap && j < mapIter.length; j++) { + const mapKey = mapIter[j]; + const cmpKey = cmpIter[j]; + const mapValueA = a.get(mapKey); + let mapValueB; + if (eq(mapKey, cmpKey)) { + mapValueB = b.get(cmpKey); + } else { + mapValueB = b.get(mapKey); + } + equalMap = eq(mapValueA, mapValueB); + } + } + return equalMap; +} + +function isEqualSet(a, b) { + let equalSet = true; + if (a.size !== b.size) { + return false; + } + const valuesA = []; + a.forEach(function(valueA) { + valuesA.push(valueA); + }); + const valuesB = []; + b.forEach(function(valueB) { + valuesB.push(valueB); + }); + const setPairs = [[valuesA, valuesB], [valuesB, valuesA]]; + for (let i = 0; equalSet && i < setPairs.length; i++) { + const baseValues = setPairs[i][0]; + const otherValues = setPairs[i][1]; + for (const baseValue of baseValues) { + let found = false; + for (let j = 0; !found && j < otherValues.length; j++) { + const otherValue = otherValues[j]; + // 深度比较对象 + found = eq(baseValue, otherValue); + } + equalSet = equalSet && found; + } + } + return equalSet; +} + +export default assertDeepEquals; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/isPromiseLike.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/isPromiseLike.js new file mode 100644 index 0000000..1604237 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/assert/isPromiseLike.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +function isPromiseLike(obj) { + return !!obj && isFunction(obj.then); +} + +function isFunction(value) { + return isA('Function', value); +} + +function isA(typeName, value) { + return getType(value) === '[object ' + typeName + ']'; +} + +function getType(value) { + return Object.prototype.toString.apply(value); +} + +export default isPromiseLike; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/DataDriver.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/DataDriver.js new file mode 100644 index 0000000..0d9f9e1 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/DataDriver.js @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +const SUITES_KEY = 'suites'; +const SPECS_KEY = 'items'; +const DESCRIBE_KEY = 'describe'; +const IT_KEY = 'it'; +const PARAMS_KEY = 'params'; +const STRESS_KEY = 'stress'; + +class ObjectUtils { + static get(object, name, defaultValue) { + let result = defaultValue; + for (const key in object) { + if (key === name) { + return object[key]; + } + } + return result; + } + + static has(object, key) { + return Object.prototype.hasOwnProperty.call(object, key); + } +} + +class DataDriver { + constructor(attr) { + this.id = 'dataDriver'; + this.data = attr.data || {}; + } + + init(coreContext) { + this.coreContext = coreContext; + this.suiteService = this.coreContext.getDefaultService('suite'); + this.specService = this.coreContext.getDefaultService('spec'); + } + + getSpecParamsValue(specs) { + let specParams = []; + let specDesc = this.specService.getCurrentRunningSpec().description; + if (specs === null || specs === undefined) { + return specParams; + } + for (const specItem of specs) { + if (ObjectUtils.has(specItem, IT_KEY) && ObjectUtils.get(specItem, IT_KEY) === specDesc) { + return ObjectUtils.get(specItem, PARAMS_KEY, specParams); + } + } + return specParams; + } + + getSpecParams() { + let specParams = []; + let suiteDesc = this.suiteService.getCurrentRunningSuite().description; + let specDesc = this.specService.getCurrentRunningSpec().description; + let suites = ObjectUtils.get(this.data, SUITES_KEY, []); + for (const suiteItem of suites) { + let describeValue = ObjectUtils.get(suiteItem, DESCRIBE_KEY, ''); + if (ObjectUtils.has(suiteItem, DESCRIBE_KEY) && (typeof describeValue === 'object') && describeValue.constructor === Array && describeValue.includes(suiteDesc)) { + let specs = ObjectUtils.get(suiteItem, SPECS_KEY, []); + return this.getSpecParamsValue(specs); + } + } + return specParams; + } + + getSuiteParams() { + let suiteParams = {}; + let suiteDesc = this.suiteService.getCurrentRunningSuite().description; + let suites = ObjectUtils.get(this.data, SUITES_KEY, []); + for (const suiteItem of suites) { + let describeValue = ObjectUtils.get(suiteItem, DESCRIBE_KEY, []); + if (ObjectUtils.has(suiteItem, DESCRIBE_KEY) && (typeof describeValue === 'object') && describeValue.constructor === Array && describeValue.includes(suiteDesc)) { + suiteParams = Object.assign({}, suiteParams, ObjectUtils.get(suiteItem, PARAMS_KEY, suiteParams)); + } + } + return suiteParams; + } + + getStressNum(specs, specDesc) { + let stress = 1; + if (specs === null || specs === undefined) { + return stress; + } + for (const specItem of specs) { + if (ObjectUtils.has(specItem, IT_KEY) && ObjectUtils.get(specItem, IT_KEY) === specDesc) { + let tempStress = ObjectUtils.get(specItem, STRESS_KEY, stress); + return (Number.isInteger(tempStress) && tempStress >= 1) ? tempStress : stress; + } + } + return stress; + } + + getSpecStress(specDesc) { + let stress = 1; + let suiteDesc = this.suiteService.getCurrentRunningSuite().description; + let suites = ObjectUtils.get(this.data, SUITES_KEY, []); + for (const suiteItem of suites) { + let describeValue = ObjectUtils.get(suiteItem, DESCRIBE_KEY, ''); + if (ObjectUtils.has(suiteItem, DESCRIBE_KEY) && (typeof describeValue === 'object') && describeValue.constructor === Array && describeValue.includes(suiteDesc)) { + let specs = ObjectUtils.get(suiteItem, SPECS_KEY, []); + return this.getStressNum(specs, specDesc); + } + } + return stress; + } + + getSuiteStress(suiteDesc) { + let stress = 1; + let suites = ObjectUtils.get(this.data, SUITES_KEY, []); + for (const suiteItem of suites) { + let describeValue = ObjectUtils.get(suiteItem, DESCRIBE_KEY, []); + if (ObjectUtils.has(suiteItem, DESCRIBE_KEY) && (typeof describeValue === 'object') && describeValue.constructor === Array && describeValue.includes(suiteDesc)) { + let tempStress = ObjectUtils.get(suiteItem, STRESS_KEY, stress); + return (Number.isInteger(tempStress) && tempStress >= 1) ? tempStress : stress; + } + } + return stress; + } +} + +export default DataDriver; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/Filter.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/Filter.js new file mode 100644 index 0000000..136e6f8 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/Filter.js @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { LEVEL, SIZE, TESTTYPE } from '../../Constant'; + +class ClassFilter { + constructor(suiteName, itName, params) { + this.suiteName = suiteName; + this.itName = itName; + this.params = params; + } + + filterSuite() { + return !this.params.split(',').map(item => item.split('#')[0]).map(item => item == this.suiteName).reduce((pre, cur) => pre || cur, false); + } + + filterIt() { + let classArray = this.params.split(',') || []; + let suiteFilterResult = classArray.filter(item => !item.includes('#')).map(item => item == this.suiteName).reduce((pre, cur) => pre || cur, false); + let itFilterResult = classArray.filter(item => item.includes('#')).map(item => item == (this.suiteName + '#' + this.itName)).reduce((pre, cur) => pre || cur, false); + return !(suiteFilterResult || itFilterResult); + } +} + +class NotClassFilter { + constructor(suiteName, itName, params) { + this.suiteName = suiteName; + this.itName = itName; + this.params = params; + } + + filterSuite() { + return this.params.split(',').map(item => item == this.suiteName).reduce((pre, cur) => pre || cur, false); + } + + filterIt() { + return this.params.split(',').some(item => item == (this.suiteName + '#' + this.itName)); + } +} + +class SuiteAndItNameFilter { + constructor(suiteName, itName, params) { + this.suiteName = suiteName; + this.itName = itName; + this.params = params; + } + + filterSuite() { + return !this.params.split(',').map(item => item == this.suiteName).reduce((pre, cur) => pre || cur, false); + } + + filterIt() { + return !this.params.split(',').map(item => item == this.itName).reduce((pre, cur) => pre || cur, false); + } +} + + +class TestTypesFilter { + constructor(suiteName, itName, fi, params) { + this.suiteName = suiteName; + this.itName = itName; + this.params = params; + this.fi = fi; + } + + filterIt() { + return !((this.params === (this.fi & this.params)) || this.fi === 0); + } +} + +class NestFilter { + filterNestName(targetSuiteArray, targetSpecArray, suiteStack, desc) { + let targetSuiteName = ''; + for (let key in suiteStack) { + targetSuiteName = targetSuiteName + '.' + suiteStack[key].description; + } + targetSuiteName = targetSuiteName.substring(2); + const targetSpecName = targetSuiteName + '#' + desc; + let isFilter = true; + if (targetSpecArray.includes(targetSpecName)) { + return false; + } + for (let index in targetSuiteArray) { + if (targetSuiteName.startsWith(targetSuiteArray[index])) { + return false; + } + } + return isFilter; + } + + filterNotClass(notClass, suiteStack, desc) { + let filterNotClass = false; + if (notClass != null) { + let notClassArray = notClass.split(','); + let targetSuiteName = ''; + for (let key in suiteStack) { + targetSuiteName = targetSuiteName + '.' + suiteStack[key].description; + } + targetSuiteName = targetSuiteName.substring(2); + const targetSpecName = targetSuiteName + '#' + desc; + if (notClassArray.includes(targetSpecName) || notClassArray.some(key => targetSpecName.startsWith(key))) { + filterNotClass = true; + } + } + return filterNotClass; + } + + filterLevelOrSizeOrTestType(level, size, testType, filter) { + let result = false; + if (filter === 0 || filter === '0') { + return result; + } + if (level == null && size == null && testType == null) { + return result; + } + if (level != null) { + let levelFilter = LEVEL[`${level}`]; + result = result || filter === levelFilter; + } + if (size != null) { + let sizeFilter = SIZE[`${size}`]; + result = result || filter === sizeFilter; + } + if (testType != null) { + let testTypeFilter = TESTTYPE[`${testType}`]; + result = result || filter === testTypeFilter; + } + return !result; + } +} +export { ClassFilter, NotClassFilter, SuiteAndItNameFilter, TestTypesFilter, NestFilter }; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/configService.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/configService.js new file mode 100644 index 0000000..296745d --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/config/configService.js @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { ClassFilter, NotClassFilter, SuiteAndItNameFilter, TestTypesFilter, NestFilter } from './Filter'; +import { TAG, TESTTYPE, LEVEL, SIZE, KEYSET } from '../../Constant'; +const STRESS_RULE = /^[1-9]\d*$/; + +class ConfigService { + constructor(attr) { + this.id = attr.id; + this.supportAsync = true; // 默认异步处理测试用例 + this.random = false; + this.filterValid = []; + this.filter = 0; + this.flag = false; + this.suite = null; + this.itName = null; + this.testType = null; + this.level = null; + this.size = null; + this.class = null; + this.notClass = null; + this.timeout = null; + // 遇错即停模式配置 + this.breakOnError = false; + // 压力测试配置 + this.stress = null; + this.skipMessage = false; + this.runSkipped = ''; + this.filterXdescribe = []; + } + + init(coreContext) { + this.coreContext = coreContext; + } + + isNormalInteger(str) { + const n = Math.floor(Number(str)); + return n !== Infinity && String(n) === String(str) && n >= 0; + } + + + getStress() { + if (this.stress === undefined || this.stress === '' || this.stress === null) { + return 1; + } + return !this.stress.match(STRESS_RULE) ? 1 : Number.parseInt(this.stress); + } + + basicParamValidCheck(params) { + let size = params.size; + if (size !== undefined && size !== '' && size !== null) { + let sizeArray = ['small', 'medium', 'large']; + if (sizeArray.indexOf(size) === -1) { + this.filterValid.push('size:' + size); + } + } + let level = params.level; + if (level !== undefined && level !== '' && level !== null) { + let levelArray = ['0', '1', '2', '3', '4']; + if (levelArray.indexOf(level) === -1) { + this.filterValid.push('level:' + level); + } + } + let testType = params.testType; + if (testType !== undefined && testType !== '' && testType !== null) { + let testTypeArray = ['function', 'performance', 'power', 'reliability', 'security', + 'global', 'compatibility', 'user', 'standard', 'safety', 'resilience']; + if (testTypeArray.indexOf(testType) === -1) { + this.filterValid.push('testType:' + testType); + } + } + } + + filterParamValidCheck(params) { + let timeout = params.timeout; + if (timeout !== undefined && timeout !== '' && timeout !== null) { + if (!this.isNormalInteger(timeout)) { + this.filterValid.push('timeout:' + timeout); + } + } + + let paramKeys = ['dryRun', 'random', 'breakOnError', 'coverage', 'skipMessage']; + for (const key of paramKeys) { + if (params[key] !== undefined && params[key] !== 'true' && params[key] !== 'false') { + this.filterValid.push(`${key}:${params[key]}`); + } + } + + // 压力测试参数验证,正整数 + if (params.stress !== undefined && params.stress !== '' && params.stress !== null) { + if (!params.stress.match(STRESS_RULE)) { + this.filterValid.push('stress:' + params.stress); + } + } + + let nameRule = /^[A-Za-z]{1}[\w#,.]*$/; + let paramClassKeys = ['class', 'notClass']; + for (const key of paramClassKeys) { + if (params[key] !== undefined && params[key] !== '' && params[key] !== null) { + let classArray = params[key].split(','); + classArray.forEach(item => !item.match(nameRule) ? this.filterValid.push(`${key}:${params[key]}`) : null); + } + } + } + + setConfig(params) { + this.basicParamValidCheck(params); + this.filterParamValidCheck(params); + try { + this.class = params.class; + this.notClass = params.notClass; + this.flag = params.flag || { flag: false }; + this.suite = params.suite; + this.itName = params.itName; + this.filter = params.filter; + this.testType = params.testType; + this.level = params.level; + this.size = params.size; + this.timeout = params.timeout; + this.dryRun = params.dryRun; + this.breakOnError = params.breakOnError; + this.random = params.random === 'true' ? true : false; + this.stress = params.stress; + this.coverage = params.coverage; + this.skipMessage = params.skipMessage; + this.runSkipped = params.runSkipped; + this.filterParam = { + testType: TESTTYPE, + level: LEVEL, + size: SIZE + }; + this.parseParams(); + } catch (err) { + console.info(`${TAG}setConfig error: ${err.message}`); + } + } + + parseParams() { + if (this.filter != null) { + return; + } + let testTypeFilter = 0; + let sizeFilter = 0; + let levelFilter = 0; + if (this.testType != null) { + testTypeFilter = this.testType.split(',') + .map(item => this.filterParam.testType[item] || 0) + .reduce((pre, cur) => pre | cur, 0); + } + if (this.level != null) { + levelFilter = this.level.split(',') + .map(item => this.filterParam.level[item] || 0) + .reduce((pre, cur) => pre | cur, 0); + } + if (this.size != null) { + sizeFilter = this.size.split(',') + .map(item => this.filterParam.size[item] || 0) + .reduce((pre, cur) => pre | cur, 0); + } + this.filter = testTypeFilter | sizeFilter | levelFilter; + console.info(`${TAG}filter params:${this.filter}`); + } + + isCurrentSuite(description) { + if (this.suite !== undefined && this.suite !== '' && this.suite !== null) { + let suiteArray = this.suite.split(','); + return suiteArray.indexOf(description) !== -1; + } + return false; + } + + filterSuite(currentSuiteName) { + let filterArray = []; + if (this.suite !== undefined && this.suite !== '' && this.suite !== null) { + filterArray.push(new SuiteAndItNameFilter(currentSuiteName, '', this.suite)); + } + if (this.class !== undefined && this.class !== '' && this.class !== null) { + filterArray.push(new ClassFilter(currentSuiteName, '', this.class)); + } + if (this.notClass !== undefined && this.notClass !== '' && this.notClass !== null) { + filterArray.push(new NotClassFilter(currentSuiteName, '', this.notClass)); + } + + let result = filterArray.map(item => item.filterSuite()).reduce((pre, cur) => pre || cur, false); + return result; + } + + filterDesc(currentSuiteName, desc, fi, coreContext) { + let filterArray = []; + if (this.itName !== undefined && this.itName !== '' && this.itName !== null) { + filterArray.push(new SuiteAndItNameFilter(currentSuiteName, desc, this.itName)); + } + if (this.class !== undefined && this.class !== '' && this.class !== null) { + filterArray.push(new ClassFilter(currentSuiteName, desc, this.class)); + } + if (this.notClass !== undefined && this.notClass !== '' && this.notClass !== null) { + filterArray.push(new NotClassFilter(currentSuiteName, desc, this.notClass)); + } + if (typeof (this.filter) !== 'undefined' && this.filter !== 0 && fi !== 0) { + filterArray.push(new TestTypesFilter('', '', fi, this.filter)); + } + let result = filterArray.map(item => item.filterIt()).reduce((pre, cur) => pre || cur, false); + return result; + } + + filterWithNest(desc, filter) { + let filterArray = []; + const nestFilter = new NestFilter(); + const targetSuiteArray = this.coreContext.getDefaultService('suite').targetSuiteArray; + const targetSpecArray = this.coreContext.getDefaultService('suite').targetSpecArray; + const suiteStack = this.coreContext.getDefaultService('suite').suitesStack; + let isFilter = nestFilter.filterNestName(targetSuiteArray, targetSpecArray, suiteStack, desc); + const isFullRun = this.coreContext.getDefaultService('suite').fullRun; + if (typeof (this.filter) !== 'undefined' && this.filter !== 0 && filter !== 0) { + filterArray.push(new TestTypesFilter('', '', filter, this.filter)); + return filterArray.map(item => item.filterIt()).reduce((pre, cur) => pre || cur, false); + } + if (isFilter && !isFullRun) { + return true; + } + return nestFilter.filterNotClass(this.notClass, suiteStack, desc); + + } + + isRandom() { + return this.random || false; + } + + isBreakOnError() { + return this.breakOnError !== 'true' ? false : true; + } + + setSupportAsync(value) { + this.supportAsync = value; + } + + isSupportAsync() { + return this.supportAsync; + } + + translateParams(parameters) { + const keySet = new Set(KEYSET); + let targetParams = {}; + for (const key in parameters) { + if (keySet.has(key)) { + var newKey = key.replace('-s ', ''); + targetParams[newKey] = parameters[key]; + } + } + return targetParams; + } + translateParamsToString(parameters) { + const keySet = new Set(KEYSET); + let targetParams = ''; + for (const key in parameters) { + if (keySet.has(key)) { + targetParams += ' ' + key + ' ' + parameters[key]; + } + } + return targetParams.trim(); + } + + execute() { + } + + checkIfSuiteInSkipRun(desc) { + return this.runSkipped.split(',').some(item => { + return item === desc || item.startsWith(desc + '.') || item.startsWith(desc + '#') || desc.startsWith(item + '.') || this.runSkipped === 'skipped'; + }); + } + + checkIfSpecInSkipRun(desc) { + return this.runSkipped.split(',').some(item => { + if (item.includes('#')) { + return item === desc; + } else { + return desc.startsWith(item + '.') || desc.startsWith(item + '#') || this.runSkipped === 'skipped'; + } + } + ); + } +} + +export { + ConfigService +}; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/coverage/coverageCollect.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/coverage/coverageCollect.js new file mode 100644 index 0000000..be5a7a8 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/coverage/coverageCollect.js @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 SysTestKit from '../kit/SysTestKit'; +import fs from '@ohos.file.fs'; +import {TAG} from '../../Constant'; + +const jsCoverageFileName = 'js_coverage.json'; + +export async function collectCoverageData() { + if (globalThis.__coverage__ === undefined) { + console.info(`${TAG} globalThis not have coverage`); + return; + } + const strJson = JSON.stringify(globalThis.__coverage__); + let testMode = globalThis.__testMode__; + console.info(`${TAG} coverage data testMode: ${testMode}`); + let savePath = globalThis.__savePath__; + console.info(`${TAG} write coverage data to: ${savePath}`); + let readPath = globalThis.__readPath__; + console.info(`${TAG} read coverage data in: ${readPath}`); + + // run callback mode if local test or (save path and read path ) is not defined + if (!testMode || !isCoveragePathValid(savePath)) { + console.info(`${TAG} run coverage data in call back mode`); + const strLen = strJson.length; + const maxLen = 500; + const maxCount = Math.floor(strLen / maxLen); + const OHOS_REPORT_COVERAGE_DATA = 'OHOS_REPORT_COVERAGE_DATA:'; + for (let count = 0; count <= maxCount; count++) { + console.info(`${OHOS_REPORT_COVERAGE_DATA} ${strJson.substring(count * maxLen, (count + 1) * maxLen)}`); + await SysTestKit.print(`${OHOS_REPORT_COVERAGE_DATA} ${strJson.substring(count * maxLen, (count + 1) * maxLen)}`); + } + return; + } + console.info(`${TAG} run coverage data in save file mode`); + if (fs.accessSync(savePath)) { + fs.unlinkSync(savePath); + } + + let inputPathDir = savePath.substring(0, savePath.length - jsCoverageFileName.length); + if (!fs.accessSync(inputPathDir)) { + console.info(`${TAG} coverage data create dir: ${inputPathDir}`); + fs.mkdirSync(inputPathDir); + } + + let file = fs.openSync(savePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + let writeLen = fs.writeSync(file.fd, strJson, {encoding:'utf-8'}); + console.info(`${TAG} write coverage data success: ${writeLen}`); + fs.closeSync(file); + const OHOS_REPORT_COVERAGE_PATH = 'OHOS_REPORT_COVERAGE_PATH:'; + await SysTestKit.print(`${OHOS_REPORT_COVERAGE_PATH} ${readPath}`); + console.info(`${OHOS_REPORT_COVERAGE_PATH} ${readPath}`); +} + +function isCoveragePathValid(inputPath) { + if (!inputPath) { + return false; + } + if (inputPath.indexOf(jsCoverageFileName) === -1) { + return false; + } + return true; +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/kit/SysTestKit.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/kit/SysTestKit.js new file mode 100644 index 0000000..3bc622d --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/kit/SysTestKit.js @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 {TAG} from '../../Constant'; +import Core from '../../core.js'; + +export default class SysTestKit { + + static delegator = null; + static systemTime = null; + static workerPort = null; + + constructor() { + this.id = 'sysTestKit'; + this.index = 0; + } + + static getDescribeName() { + return Core.getInstance().getDefaultService('suite').getCurrentRunningSuite().description; + } + + static getItName() { + return Core.getInstance().getDefaultService('spec').getCurrentRunningSpec().description; + } + + static getItAttribute() { + return Core.getInstance().getDefaultService('spec').getCurrentRunningSpec().fi; + } + + static actionStart(tag) { + console.info(`${TAG}${JSON.stringify(tag)}`); + var message = '\n' + 'OHOS_REPORT_ACTIONSTART: ' + JSON.stringify(tag) + '\n'; + SysTestKit.print(message); + console.info(`${TAG}${JSON.stringify(tag)} actionStart print success`); + } + + static actionEnd(tag) { + console.info(`${TAG}${JSON.stringify(tag)}`); + var message = '\n' + 'OHOS_REPORT_ACTIONEND: ' + JSON.stringify(tag) + '\n'; + SysTestKit.print(message); + console.info(`${TAG}${JSON.stringify(tag)} actionEnd print success`); + } + + static async existKeyword(keyword, timeout) { + let reg = new RegExp(/^[a-zA-Z0-9]{1,}$/); + if (!reg.test(keyword)) { + throw new Error('keyword must contain more than one string, and only letters and numbers are supported.'); + } + timeout = timeout || 4; + + let searchResult = false; + let cmd = 'hilog -x | grep -i \'' + keyword + '\' | wc -l'; + await executePromise(cmd, timeout).then((data) => { + searchResult = data; + }); + return searchResult; + } + static async print(message) { + if ('printSync' in SysTestKit.delegator) { + console.info(`${TAG}printSync called ...`); + SysTestKit.delegator.printSync(message); + } else { + await SysTestKit.delegator.print(message); + } + } + + static async getRealTime() { + let currentTime = new Date().getTime(); + if (SysTestKit.systemTime !== null && SysTestKit.systemTime !== undefined) { + await SysTestKit.systemTime.getRealTime().then((time) => { + console.info(`${TAG}systemTime.getRealTime success data: ${JSON.stringify(time)}`); + currentTime = time; + }).catch((error) => { + console.error(`${TAG}failed to systemTime.getRealTime because ${JSON.stringify(error)}`); + }); + } + return currentTime; + } +} + +function executePromise(cmd, timeout) { + return new Promise((resolve, reject) => { + SysTestKit.delegator.executeShellCommand(cmd, timeout, + (error, data) => { + console.info(`${TAG}existKeyword CallBack: err : ${JSON.stringify(error)}`); + console.info(`${TAG}existKeyword CallBack: data : ${JSON.stringify(data)}`); + resolve(parseInt(data.stdResult) > 3 ? true : false); + }); + }); +} \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ArgumentMatchers.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ArgumentMatchers.js new file mode 100644 index 0000000..b3fa101 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ArgumentMatchers.js @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class ArgumentMatchers { + ANY = ''; + ANY_STRING = ''; + ANY_BOOLEAN = ''; + ANY_NUMBER = ''; + ANY_OBJECT = ''; + ANY_FUNCTION = ''; + MATCH_REGEXS = ''; + + static any() { + } + + static anyString() { + } + + static anyBoolean() { + } + + static anyNumber() { + } + + static anyObj() { + } + + static anyFunction() { + } + + static matchRegexs() { + let regex = arguments[0]; + if (ArgumentMatchers.isRegExp(regex)) { + return regex; + } + throw Error('not a regex'); + } + + static isRegExp(value) { + return Object.prototype.toString.call(value) === '[object RegExp]'; + } + + matcheReturnKey() { + let arg = arguments[0]; + let regex = arguments[1]; + let stubSetKey = arguments[2]; + + if (stubSetKey && stubSetKey == this.ANY) { + return this.ANY; + } + + if (typeof arg === 'string' && !regex) { + return this.ANY_STRING; + } + + if (typeof arg === 'boolean' && !regex) { + return this.ANY_BOOLEAN; + } + + if (typeof arg === 'number' && !regex) { + return this.ANY_NUMBER; + } + + if (typeof arg === 'object' && !regex) { + return this.ANY_OBJECT; + } + + if (typeof arg === 'function' && !regex) { + return this.ANY_FUNCTION; + } + + if (typeof arg === 'string' && regex) { + return regex.test(arg); + } + + return null; + } + + matcheStubKey() { + let key = arguments[0]; + + if (key === ArgumentMatchers.any) { + return this.ANY; + } + + if (key === ArgumentMatchers.anyString) { + return this.ANY_STRING; + } + if (key === ArgumentMatchers.anyBoolean) { + return this.ANY_BOOLEAN; + } + if (key === ArgumentMatchers.anyNumber) { + return this.ANY_NUMBER; + } + if (key === ArgumentMatchers.anyObj) { + return this.ANY_OBJECT; + } + if (key === ArgumentMatchers.anyFunction) { + return this.ANY_FUNCTION; + } + + if (ArgumentMatchers.isRegExp(key)) { + return key; + } + + return null; + } +} + +export default ArgumentMatchers; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ExtendInterface.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ExtendInterface.js new file mode 100644 index 0000000..2ffc667 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/ExtendInterface.js @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class ExtendInterface { + constructor(mocker) { + this.mocker = mocker; + } + + stub() { + this.params = arguments; + return this; + } + + stubMockedCall(returnInfo) { + this.mocker.stubApply(this, this.params, returnInfo); + } + + afterReturn(value) { + this.stubMockedCall(function () { + return value; + }); + } + + afterReturnNothing() { + this.stubMockedCall(function () { + return undefined; + }); + } + + afterAction(action) { + this.stubMockedCall(action); + } + + afterThrow(msg) { + this.stubMockedCall(function () { + throw msg; + }); + } + + clear() { + this.mocker.clear(); + } +} + +export default ExtendInterface; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/MockKit.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/MockKit.js new file mode 100644 index 0000000..e75f420 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/MockKit.js @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 ExtendInterface from './ExtendInterface'; +import VerificationMode from './VerificationMode'; +import ArgumentMatchers from './ArgumentMatchers'; + +class MockKit { + constructor() { + this.mFunctions = []; + this.stubs = new Map(); + this.recordCalls = new Map(); + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + + init() { + this.reset(); + } + + reset() { + this.mFunctions = []; + this.stubs = {}; + this.recordCalls = {}; + this.currentSetKey = new Map(); + this.mockObj = null; + this.recordMockedMethod = new Map(); + } + + clearAll() { + this.reset(); + var props = Object.keys(this); + for (var i = 0; i < props.length; i++) { + delete this[props[i]]; + } + + var props = Object.getOwnPropertyNames(this); + for (var i = 0; i < props.length; i++) { + delete this[props[i]]; + } + for (var key in this) { + delete this[key]; + } + } + + clear(obj) { + if (!obj) { + throw Error('Please enter an object to be cleaned'); + } + if (typeof obj !== 'object' && typeof obj !== 'function') { + throw new Error('Not a object or static class'); + } + this.recordMockedMethod.forEach(function (value, key, map) { + if (key) { + obj[key] = value; + } + }); + } + + ignoreMock(obj, method) { + if (typeof obj !== 'object' && typeof obj !== 'function') { + throw new Error('Not a object or static class'); + } + if (typeof method !== 'function') { + throw new Error('Not a function'); + } + let og = this.recordMockedMethod.get(method.propName); + if (og) { + obj[method.propName] = og; + this.recordMockedMethod.set(method.propName, undefined); + } + } + + extend(dest, source) { + dest['stub'] = source['stub']; + dest['afterReturn'] = source['afterReturn']; + dest['afterReturnNothing'] = source['afterReturnNothing']; + dest['afterAction'] = source['afterAction']; + dest['afterThrow'] = source['afterThrow']; + dest['stubMockedCall'] = source['stubMockedCall']; + dest['clear'] = source['clear']; + return dest; + } + + stubApply(f, params, returnInfo) { + let values = this.stubs.get(f); + if (!values) { + values = new Map(); + } + let key = params[0]; + if (typeof key === 'undefined') { + key = 'anonymous-mock-' + f.propName; + } else { + key = []; + const matcher = new ArgumentMatchers(); + for (let i = 0; i < params.length; i++) { + const param = params[i]; + const matchKey = matcher.matcheStubKey(param); + if (matchKey) { + key.push(matchKey); + } else { + key.push(param); + } + } + } + this.currentSetKey.set(f, key); + values.set(key, returnInfo); + this.stubs.set(f, values); + } + + getReturnInfo(f, params) { + let values = this.stubs.get(f); + if (!values) { + return undefined; + } + let returnKet = params[0]; + const anonymousName = 'anonymous-mock-' + f.propName; + if (typeof returnKet === 'undefined') { + returnKet = anonymousName; + let stubSetKey = this.currentSetKey.get(f); + + if (stubSetKey && (typeof (returnKet) !== 'undefined')) { + returnKet = stubSetKey; + } + } else { + returnKet = this.getReturnKet(values, params, returnKet, anonymousName); + } + + return values.get(returnKet); + } + + getReturnKet(values, params, defaultValue, anonymousName) { + let flag = true; + let returnKet = defaultValue; + values.forEach((value, paramsKey, map) => { + if ( + flag && + paramsKey !== anonymousName && + paramsKey.length === params.length && + this.checkIsRightValue(paramsKey, params) + ) { + returnKet = paramsKey; + flag = false; + } + }); + return returnKet; + } + + checkIsRightValue(paramsKey, params) { + const matcher = new ArgumentMatchers(); + return paramsKey.every((key, j) => { + if (ArgumentMatchers.isRegExp(key) && typeof params[j] === 'string') { + return key.test(params[j]); + } + const matchKey = matcher.matcheReturnKey(params[j], undefined, key); + if (matchKey && matchKey === key) { + return true; + } else if (this.checkIsEqual(params[j], key)) { + return true; + } + return false; + }); + } + + checkIsEqual(value1, value2) { + if (value1 === value2) { + return true; + } + if (typeof value1 !== typeof value2) { + return false; + } + if (Array.isArray(value1) && Array.isArray(value2)) { + if (value1.length !== value2.length) { + return false; + } + for (let i = 0; i < value1.length; i++) { + if (!this.checkIsEqual(value1[i], value2[i])) { + return false; + } + } + return true; + } + if (Object.prototype.toString.call(value1) === '[object Object]' && + Object.prototype.toString.call(value2) === '[object Object]') { + const keys1 = Object.keys(value1); + const keys2 = Object.keys(value2); + if (keys1.length !== keys2.length) { + return false; + } + for (let key of keys1) { + if (!keys2.includes(key) || !this.checkIsEqual(value1[key], value2[key])) { + return false; + } + } + return true; + } + if (Object.prototype.toString.call(value1) === '[object Date]' && + Object.prototype.toString.call(value2) === '[object Date]' && + value1.getTime() === value2.getTime()) { + return true; + } + if (Object.prototype.toString.call(value1) === '[object RegExp]' && + Object.prototype.toString.call(value2) === '[object RegExp]' && + value1.toString() === value2.toString()) { + return true; + } + return false; + } + + findName(obj, value) { + let properties = this.findProperties(obj); + let name = null; + properties + .filter((item) => item !== 'caller' && item !== 'arguments') + .forEach(function (va1, idx, array) { + if (obj[va1] === value) { + name = va1; + } + }); + return name; + } + + isFunctionFromPrototype(f, container, propName) { + if ( + container.constructor !== Object && + container.constructor.prototype !== container + ) { + return container.constructor.prototype[propName] === f; + } + return false; + } + + findProperties(obj, ...arg) { + function getProperty(newObj) { + if (newObj.__proto__ === null) { + return []; + } + let properties = Object.getOwnPropertyNames(newObj); + return [...properties, ...getProperty(newObj.__proto__)]; + } + return getProperty(obj); + } + + recordMethodCall(originalMethod, args) { + Function.prototype.getName = function () { + return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; + }; + let name = originalMethod.getName(); + let arglistString = name + '(' + Array.from(args).toString() + ')'; + let records = this.recordCalls.get(arglistString); + if (!records) { + records = 0; + } + records++; + this.recordCalls.set(arglistString, records); + } + + mockFunc(originalObject, originalMethod) { + let tmp = this; + this.originalMethod = originalMethod; + let f = function () { + let args = arguments; + let action = tmp.getReturnInfo(f, args); + if (originalMethod) { + tmp.recordMethodCall(originalMethod, args); + } + if (action) { + return action.apply(this, args); + } + }; + + f.container = null || originalObject; + f.original = originalMethod || null; + + if (originalObject && originalMethod) { + if (typeof originalMethod !== 'function') { + throw new Error('Not a function'); + } + var name = this.findName(originalObject, originalMethod); + originalObject[name] = f; + this.recordMockedMethod.set(name, originalMethod); + f.propName = name; + f.originalFromPrototype = this.isFunctionFromPrototype( + f.original, + originalObject, + f.propName + ); + } + f.mocker = this; + this.mFunctions.push(f); + this.extend(f, new ExtendInterface(this)); + return f; + } + + verify(methodName, argsArray) { + if (!methodName) { + throw Error('not a function name'); + } + let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); + return new VerificationMode(a ? a : 0); + } + + mockObject(object) { + if (!object || typeof object === 'string') { + throw Error(`this ${object} cannot be mocked`); + } + const _this = this; + let mockedObject = {}; + let keys = Reflect.ownKeys(object); + keys + .filter((key) => typeof Reflect.get(object, key) === 'function') + .forEach((key) => { + mockedObject[key] = object[key]; + mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); + }); + return mockedObject; + } +} + +function ifMockedFunction(f) { + if ( + Object.prototype.toString.call(f) !== '[object Function]' && + Object.prototype.toString.call(f) !== '[object AsyncFunction]' + ) { + throw Error('not a function'); + } + if (!f.stub) { + throw Error('not a mock function'); + } + return true; +} + +function when(f) { + if (!ifMockedFunction(f)) { + throw Error('not a mock function'); + } + return f.stub.bind(f); +} + +export { MockKit, when }; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/VerificationMode.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/VerificationMode.js new file mode 100644 index 0000000..d0d9b88 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/mock/VerificationMode.js @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 {expect} from '../../interface'; + +class VerificationMode { + constructor(times) { + this.doTimes = times; + } + + times(count) { + expect(count).assertEqual(this.doTimes); + } + + never() { + console.info(this.doTimes); + expect(0).assertEqual(this.doTimes); + } + + once() { + expect(1).assertEqual(this.doTimes); + } + + atLeast(count) { + if (count > this.doTimes) { + throw Error('failed ' + count + ' greater than the actual execution times of method'); + } + } + + atMost(count) { + if (count < this.doTimes) { + throw Error('failed ' + count + ' less than the actual execution times of method'); + } + } +} + +export default VerificationMode; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/LogExpectError.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/LogExpectError.js new file mode 100644 index 0000000..86974b9 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/LogExpectError.js @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class LogExpectError { + static getErrorMsg(matcherName, actualValue, expect, originMsg) { + if (matcherName === 'assertNull') { + return 'expect not null, actualValue is ' + (actualValue); + } + if (matcherName === 'assertTrue') { + return 'expect not true, actualValue is ' + (actualValue); + } + if (matcherName === 'assertFalse') { + return 'expect not false, actualValue is ' + (actualValue); + } + if (matcherName === 'assertEqual') { + return 'expect not Equal, actualValue is ' + actualValue + ' equals ' + expect; + } + if (matcherName === 'assertContain') { + return 'expect not have, ' + actualValue + ' have ' + expect; + } + if (matcherName === 'assertInstanceOf') { + return 'expect not InstanceOf, ' + actualValue + ' is ' + + Object.prototype.toString.call(actualValue) + expect; + } + if (matcherName === 'assertLarger') { + return 'expect not Larger, ' + + (actualValue) + ' is larger than ' + expect; + } + if (matcherName === 'assertLargerOrEqual') { + return 'expect not LargerOrEqual, ' + (actualValue) + ' larger than ' + expect; + } + if (matcherName === 'assertLess') { + return 'expect not Less, ' + (actualValue) + ' less than ' + expect; + } + if (matcherName === 'assertLessOrEqual') { + return 'expect not LessOrEqual, ' + (actualValue) + ' is less than ' + expect; + } + if (matcherName === 'assertNaN') { + return 'expect not NaN, actualValue is ' + (actualValue); + } + if (matcherName === 'assertNegUnlimited') { + return 'expect not NegUnlimited, actualValue is ' + (actualValue); + } + if (matcherName === 'assertPosUnlimited') { + return 'expect not PosUnlimited, actualValue is ' + (actualValue); + } + if (matcherName === 'assertUndefined') { + return 'expect not Undefined, actualValue is ' + (actualValue); + } + return originMsg; + } +} +export default LogExpectError; \ No newline at end of file diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/OhReport.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/OhReport.js new file mode 100644 index 0000000..65f25eb --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/OhReport.js @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 SysTestKit from '../kit/SysTestKit'; +import { collectCoverageData } from '../coverage/coverageCollect'; +import { TAG, PrintTag } from '../../Constant'; + +class OhReport { + constructor(attr) { + this.delegator = attr.delegator; + this.abilityDelegatorArguments = attr.abilityDelegatorArguments; + this.id = 'report'; + this.index = 0; + this.duration = 0; + this.currentThreadName = 'mainThread'; + } + + init(coreContext) { + this.coreContext = coreContext; + this.suiteService = this.coreContext.getDefaultService('suite'); + this.specService = this.coreContext.getDefaultService('spec'); + if (SysTestKit.workerPort !== null) { + this.currentThreadName = SysTestKit.workerPort.name; + } + } + + taskStart() { + } + + async taskDone() { + let summary = this.suiteService.getSummary(); + if (this.abilityDelegatorArguments !== null) { + this.taskDoneTime = new Date().getTime(); + const configService = this.coreContext.getDefaultService('config'); + const suiteService = this.coreContext.getDefaultService('suite'); + const specService = this.coreContext.getDefaultService('spec'); + if (configService['coverage'] === 'true') { + await collectCoverageData(); + } + let message = '\n' + `${PrintTag.OHOS_REPORT_RESULT}: stream=Tests run: ` + summary.total + ', Failure: ' + summary.failure; + message += ', Error: ' + summary.error; + message += ', Pass: ' + summary.pass; + message += ', Ignore: ' + summary.ignore; + if (specService.skipSpecNum > 0) { + message += ', SkipSpec: ' + specService.skipSpecNum; + } + message += '\n' + `${PrintTag.OHOS_REPORT_CODE}: ` + (summary.failure > 0 ? -1 : 0) + '\n'; + let isHasError = summary.failure > 0 || summary.error > 0; + let config = this.coreContext.getDefaultService('config'); + if (config.isBreakOnError() && isHasError) { + // 未执行全部说明 + message += '\n' + `${PrintTag.OHOS_REPORT_RESULT}: breakOnError model, Stopping whole test suite if one specific test case failed or error` + '\n'; + } + message += `${PrintTag.OHOS_REPORT_STATUS}: taskconsuming=` + summary.duration + '\n'; + console.info(`${message}`); + await SysTestKit.print(message); + } + if (SysTestKit.workerPort === null || SysTestKit.workerPort === undefined) { + // 主线程执行完成 结束任务。 + console.info(`${TAG}report print success`); + this.delegator.finishTest('your test finished!!!', 0, () => { }); + } else { + // worker线程执行完成将数据发送到主线程中。 + let sendData = { + currentThreadName: this.currentThreadName, + summary: summary + }; + console.info(`${TAG}, send data to mainThread, ${this.currentThreadName}, ${JSON.stringify(sendData)}`); + SysTestKit.workerPort.postMessage(sendData); + } + } + + incorrectFormat() { + if (this.coreContext.getDefaultService('config').filterValid.length !== 0) { + var value = this.coreContext.getDefaultService('config').filterValid; + var message = 'this param ' + value.join(',') + ' is invalid' + '\n'; + this.delegator.finishTest(message, 0, () => { + }); + } + } + + incorrectTestSuiteFormat() { + if (this.coreContext.getDefaultService('config').filterXdescribe.length !== 0) { + let value = this.coreContext.getDefaultService('config').filterXdescribe; + let message = 'xdescribe ' + value.join(',') + ' should not contain it' + '\n'; + this.delegator.finishTest(message, 0, () => { + }); + } + } + async suiteStart() { + if (this.abilityDelegatorArguments !== null) { + let specArr = []; + this.suiteService.getAllChildSuiteNum(this.suiteService.getCurrentRunningSuite(), specArr); + let message = '\n' + `${PrintTag.OHOS_REPORT_SUM}: ` + specArr.length; + this.suiteService.setCurrentRunningSuiteDesc(this.suiteService.getRootSuite(), this.suiteService.getCurrentRunningSuite(), ''); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: class=` + this.suiteService.getCurrentRunningSuiteDesc() + '\n'; + if (this.suiteService.currentRunningSuite.isSkip) { + message += `${PrintTag.OHOS_REPORT_STATUS}: skipReason=` + this.suiteService.currentRunningSuite.skipReason + '\n'; + } + if (SysTestKit.workerPort !== null) { + message += `${PrintTag.OHOS_REPORT_STATUS}: currentWorkerName=` + this.currentThreadName; + } + console.info(`${message}`); + await SysTestKit.print(message); + console.info(`${TAG}${this.suiteService.getCurrentRunningSuite().description} suiteStart print success`); + } + } + + async suiteDone() { + if (this.abilityDelegatorArguments !== null) { + const currentRunningSuite = this.suiteService.getCurrentRunningSuite(); + this.suiteService.setCurrentRunningSuiteDesc(this.suiteService.getRootSuite(), currentRunningSuite, ''); + let message = '\n' + `${PrintTag.OHOS_REPORT_STATUS}: class=` + this.suiteService.getCurrentRunningSuiteDesc(); + if (this.suiteService.currentRunningSuite.isSkip && this.suiteService.currentRunningSuite.skipReason !== '') { + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: skipReason=` + this.suiteService.currentRunningSuite.skipReason; + } + const isPromiseError = currentRunningSuite.isPromiseError; + if (isPromiseError) { + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: shortMsg=Promise(async, await) in describe is not allowed!`; + } + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: suiteconsuming=` + currentRunningSuite.duration; + if (currentRunningSuite.hookError) { + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: ${currentRunningSuite.hookError.message}`; + } + message += '\n'; + if (SysTestKit.workerPort !== null) { + message += `${PrintTag.OHOS_REPORT_STATUS}: currentWorkerName=` + this.currentThreadName; + } + console.info(`${message}`); + await SysTestKit.print(message); + console.info(`${TAG}${currentRunningSuite.description} suiteDone print success`); + } + } + + async specStart() { + if (this.abilityDelegatorArguments !== null) { + let message = '\n' + `${PrintTag.OHOS_REPORT_STATUS}: class=` + this.suiteService.getCurrentRunningSuiteDesc(); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: current=` + (++this.index); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: id=JS`; + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: numtests=` + this.specService.getTestTotal(); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: stream=`; + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: test=` + this.specService.currentRunningSpec.description; + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS_CODE}: 1` + '\n'; + if (this.specService.currentRunningSpec.isSkip) { + message += `${PrintTag.OHOS_REPORT_STATUS}: skipReason=` + this.specService.currentRunningSpec.skipReason + '\n'; + } + if (SysTestKit.workerPort !== null) { + message += `${PrintTag.OHOS_REPORT_STATUS}: currentWorkerName=` + this.currentThreadName; + } + console.info(`${message}`); + await SysTestKit.print(message); + console.info(`${TAG}${this.specService.currentRunningSpec.description} specStart start print success`); + } + } + + async specDone() { + if (this.abilityDelegatorArguments !== null) { + let message = '\n' + `${PrintTag.OHOS_REPORT_STATUS}: class=` + this.suiteService.getCurrentRunningSuiteDesc(); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: current=` + (this.index); + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: id=JS`; + message += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: numtests=` + this.specService.getTestTotal(); + let messageStack = ''; + let messageCode = ''; + if (this.specService.currentRunningSpec.error) { + messageStack = `${PrintTag.OHOS_REPORT_STATUS}: stack=` + this.specService.currentRunningSpec.error?.stack?.slice(0, -1); + messageCode += `${PrintTag.OHOS_REPORT_STATUS}: stream=`; + messageCode += this.specService.currentRunningSpec.expectMsg !== '' ? + `message: ${this.specService.currentRunningSpec.expectMsg}, Error in ${this.specService.currentRunningSpec.description}, ${this.specService.currentRunningSpec.error?.message}` : + `Error in ${this.specService.currentRunningSpec.description}, ${this.specService.currentRunningSpec.error?.message}`; + messageCode += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: test=` + this.specService.currentRunningSpec.description; + messageCode += '\n' + `${PrintTag.OHOS_REPORT_STATUS_CODE}: -1` + '\n'; + } else if (this.specService.currentRunningSpec) { + if (this.specService.currentRunningSpec.fail) { + messageStack += `${PrintTag.OHOS_REPORT_STATUS}: stack=` + this.specService.currentRunningSpec.fail?.stack?.slice(0, -1); + messageCode += `${PrintTag.OHOS_REPORT_STATUS}: stream=`; + messageCode += this.specService.currentRunningSpec.expectMsg !== '' ? + `message: ${this.specService.currentRunningSpec.expectMsg}, Error in ${this.specService.currentRunningSpec.description}, ${this.specService.currentRunningSpec.fail?.message}` : + `Error in ${this.specService.currentRunningSpec.description}, ${this.specService.currentRunningSpec.fail?.message}`; + messageCode += '\n' + `${PrintTag.OHOS_REPORT_STATUS}: test=` + this.specService.currentRunningSpec.description; + messageCode += '\n' + `${PrintTag.OHOS_REPORT_STATUS_CODE}: -2` + '\n'; + } else { + messageStack += `${PrintTag.OHOS_REPORT_STATUS}: stream=`; + messageCode += `${PrintTag.OHOS_REPORT_STATUS}: test=` + this.specService.currentRunningSpec.description; + messageCode += '\n' + `${PrintTag.OHOS_REPORT_STATUS_CODE}: 0` + '\n'; + messageCode += this.specService.currentRunningSpec.isSkip ? (`${PrintTag.OHOS_REPORT_STATUS}: skipReason=` + this.specService.currentRunningSpec.skipReason + '\n') : ''; + } + } else { + messageCode += '\n'; + } + messageCode += `${PrintTag.OHOS_REPORT_STATUS}: consuming=` + this.specService.currentRunningSpec.duration + '\n'; + if (SysTestKit.workerPort !== null) { + messageCode += `${PrintTag.OHOS_REPORT_STATUS}: currentWorkerName=` + this.currentThreadName; + } + console.info(`${message}`); + console.info(`\n${messageStack}`); + console.info(`\n${messageCode}`); + await SysTestKit.print(message); + await SysTestKit.print(messageStack); + await SysTestKit.print(messageCode); + console.info(`${TAG}${this.specService.currentRunningSpec.description} specDone end print success`); + } + } +} + +export default OhReport; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/ReportExtend.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/ReportExtend.js new file mode 100644 index 0000000..e973388 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/module/report/ReportExtend.js @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +class ReportExtend { + constructor(fileModule) { + this.id = 'extend'; + this.fileModule = fileModule; + } + + init(coreContext) { + this.coreContext = coreContext; + this.suiteService = this.coreContext.getDefaultService('suite'); + } + + taskStart() { + + } + + handleSpecs(specs, report, suiteReport, testsuite) { + for (let testcase of specs) { + report.tests++; + suiteReport.tests++; + let caseReport = { + tag: 'testcase', + name: testcase.description, + status: 'run', + time: '0.0', + classname: testsuite.description + }; + if (testcase.error) { + caseReport.result = false; + caseReport.children = [{ + tag: 'error', + type: '', + message: testcase.error.message + }]; + report.errors++; + suiteReport.errors++; + } else if (testcase.result.failExpects.length > 0) { + caseReport.result = false; + let message = ''; + testcase.result.failExpects.forEach(failExpect => { + message += failExpect.message || ('expect ' + failExpect.actualValue + ' ' + failExpect.checkFunc + ' ' + (failExpect.expectValue || '')) + ';'; + }); + caseReport.children = [{ + tag: 'failure', + type: '', + message: message + }]; + report.failures++; + suiteReport.failures++; + } else { + caseReport.result = true; + } + suiteReport.children.push(caseReport); + } + } + + taskDone() { + const report = { + tag: 'testsuites', + name: 'summary_report', + timestamp: new Date().toDateString(), + time: '1', + errors: 0, + failures: 0, + tests: 0, + children: [] + }; + const rootSuite = this.suiteService.rootSuite; + if (rootSuite && rootSuite.childSuites) { + for (let testsuite of rootSuite.childSuites) { + let suiteReport = { + tag: 'testsuite', + name: testsuite['description'], + errors: 0, + tests: 0, + failures: 0, + time: '0.1', + children: [] + }; + let specs = testsuite['specs']; + this.handleSpecs(specs, report, suiteReport, testsuite); + report.children.push(suiteReport); + } + } + + writeXmlReport(report); + } +} + +function writeXmlReport(report) { + let reportXml = '\n' + json2xml(report); + this.fileModule.writeText({ + uri: 'internal://app/report.xml', + text: reportXml, + success: function () { + console.info('call success callback success'); + }, + fail: function (data, code) { + console.info('call fail callback success:'); + }, + complete: function () { + console.info('call complete callback success'); + } + }); +} + +function handleChild(json, key, hasChildren, childrenStr) { + if (json[key].length > 0) { + hasChildren = true; + for (let child of json[key]) { + childrenStr += json2xml(child); + } + } +} + +function json2xml(json) { + let tagName; + let hasChildren = false; + let childrenStr = ''; + let attrStr = ''; + for (let key in json) { + if (key === 'tag') { + tagName = json[key]; + } else if (key === 'children') { + handleChild(json, key, hasChildren, childrenStr); + } else { + attrStr += ` ${key}="${json[key]}"`; + } + } + let xml = `<${tagName}${attrStr}`; + xml += hasChildren ? `>${childrenStr}` : '/>'; + return xml; +} + +export default ReportExtend; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/service.js b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/service.js new file mode 100644 index 0000000..9b711aa --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/service.js @@ -0,0 +1,1265 @@ +/* + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 SysTestKit from './module/kit/SysTestKit'; +import { TAG } from './Constant'; +import LogExpectError from './module/report/LogExpectError'; +import { NestFilter } from './module/config/Filter'; + +function assertTrueFun(actualValue) { + let result = { + pass: (actualValue) === true, + message: 'expect true, actualValue is ' + actualValue + }; + return result; +} + +function assertEqualFun(actualValue, args) { + let msg = 'expect ' + actualValue + ' equals ' + args[0]; + if (actualValue === args[0]) { // 数值相同,提示数据类型 + const aClassName = Object.prototype.toString.call(actualValue); + const bClassName = Object.prototype.toString.call(args[0]); + msg = 'expect ' + actualValue + aClassName + ' equals ' + args[0] + bClassName + 'strict mode inspect type'; + } + let result = { + pass: (actualValue) === args[0], + expectValue: args[0], + message: msg + }; + return result; +} + +function assertThrowFun(actual, args) { + const result = { + pass: false + }; + if (typeof actual !== 'function') { + result.message = 'toThrow\'s Actual should be a Function'; + } else { + let hasThrow = false; + let throwError; + try { + actual(); + } catch (e) { + hasThrow = true; + throwError = e; + } + if (!hasThrow) { + result.message = 'function did not throw an exception'; + } else if (throwError && throwError.message === args[0]) { + result.pass = true; + } else { + result.message = `expect to throw ${args[0]} , actual throw ${throwError.message}`; + } + } + return result; +} + +class AssertException extends Error { + constructor(message) { + super(); + this.name = 'AssertException'; + this.message = message; + } +} + +function getFuncWithArgsZero(func, timeout, isStressTest) { + return new Promise(async (resolve, reject) => { + let timer = null; + if (!isStressTest) { + timer = setTimeout(() => { + reject(new Error('execute timeout ' + timeout + 'ms')); + }, timeout); + } + try { + await func(); + } catch (err) { + reject(err); + } + timer !== null ? clearTimeout(timer) : null; + resolve(); + }); +} + +function getFuncWithArgsOne(func, timeout, isStressTest) { + return new Promise(async (resolve, reject) => { + let timer = null; + if (!isStressTest) { + timer = setTimeout(() => { + reject(new Error('execute timeout ' + timeout + 'ms')); + }, timeout); + } + + function done() { + timer !== null ? clearTimeout(timer) : null; + resolve(); + } + + try { + await func(done); + } catch (err) { + timer !== null ? clearTimeout(timer) : null; + reject(err); + } + }); +} + +function getFuncWithArgsTwo(func, timeout, paramItem, isStressTest) { + return new Promise(async (resolve, reject) => { + let timer = null; + if (!isStressTest) { + timer = setTimeout(() => { + reject(new Error('execute timeout ' + timeout + 'ms')); + }, timeout); + } + + function done() { + timer !== null ? clearTimeout(timer) : null; + resolve(); + } + + try { + await func(done, paramItem); + } catch (err) { + timer !== null ? clearTimeout(timer) : null; + reject(err); + } + }); +} + +function processFunc(coreContext, func) { + let argNames = ((func || '').toString() + .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '') + .match(/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m) || ['', '', ''])[2] + .split(',') // split parameters + .map(item => item.replace(/^\s*(_?)(.+?)\1\s*$/, name => name.split('=')[0].trim())) + .filter(String); + let funcLen = func.length; + let processedFunc; + const config = coreContext.getDefaultService('config'); + config.setSupportAsync(true); + const timeout = + (config.timeout === undefined ? 5000 : config.timeout); + const isStressTest = (coreContext.getServices('dataDriver') !== undefined || config.getStress() > 1); + switch (funcLen) { + case 0: { + processedFunc = function () { + return getFuncWithArgsZero(func, timeout, isStressTest); + }; + break; + } + case 1: { + processedFunc = function () { + return getFuncWithArgsOne(func, timeout, isStressTest); + }; + break; + } + default: { + processedFunc = function (paramItem) { + return getFuncWithArgsTwo(func, timeout, paramItem, isStressTest); + }; + break; + } + } + return processedFunc; +} + +function secureRandomNumber() { + return crypto.randomBytes(8).readUInt32LE() / 0xffffffff; +} + + + +class SuiteService { + constructor(attr) { + this.id = attr.id; + this.rootSuite = new SuiteService.Suite({}); + this.currentRunningSuite = this.rootSuite; + this.suitesStack = [this.rootSuite]; + this.targetSuiteArray = []; + this.targetSpecArray = []; + this.currentRunningSuiteDesc = null; + this.fullRun = false; + this.isSkipSuite = false; + this.suiteSkipReason = null; + } + + describe(desc, func) { + const configService = this.coreContext.getDefaultService('config'); + if (this.suitesStack.some(suite => { return suite.description === desc })) { + console.error(`${TAG} Loop nesting occurs : ${desc}`); + this.suiteSkipReason = ''; + this.isSkipSuite = false; + return; + } + let isFilter = this.analyzeConfigServiceClass(configService.class, desc); + if (configService.filterSuite(desc) && isFilter) { + if (this.currentRunningSuite.description === '' || this.currentRunningSuite.description == null) { + console.info(`${TAG}filter suite : ${desc}`); + this.suiteSkipReason = ''; + this.isSkipSuite = false; + return; + } + } + const suite = new SuiteService.Suite({ description: desc }); + if (this.isSkipSuite) { + suite.isSkip = true; + suite.skipReason = this.suiteSkipReason; + } + this.suiteSkipReason = ''; + this.isSkipSuite = false; + if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') { + let suiteStress = this.coreContext.getServices('dataDriver').dataDriver.getSuiteStress(desc); + for (let i = 1; i < suiteStress; i++) { + this.currentRunningSuite.childSuites.push(suite); + } + } + this.currentRunningSuite.childSuites.push(suite); + this.currentRunningSuite = suite; + this.suitesStack.push(suite); + const res = func.call(); + if (Object.prototype.toString.call(res) === '[object Promise]') { + suite.isPromiseError = true; + } + this.suitesStack.pop(); + this.currentRunningSuite = this.suitesStack.pop(); + this.suitesStack.push(this.currentRunningSuite); + } + xdescribe(desc, func, reason) { + const configService = this.coreContext.getDefaultService('config'); + if (!configService.skipMessage && configService.runSkipped !== 'all') { + if (configService.runSkipped != null && configService.runSkipped !== '') { + let finalDesc = ''; + this.suitesStack.map(suite => { + finalDesc = finalDesc + '.' + suite.description; + }); + finalDesc = (finalDesc + '.' + desc).substring(2); + console.info(`${TAG} finalDesc ${finalDesc}`); + if (configService.checkIfSuiteInSkipRun(finalDesc)) { + console.info(`${TAG} runSkipped suite: ${desc}`); + } else { + console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`); + return; + } + } else { + console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`); + return; + } + } + this.isSkipSuite = true; + this.suiteSkipReason = reason; + this.describe(desc, func); + } + + beforeAll(func) { + this.currentRunningSuite.beforeAll.push(processFunc(this.coreContext, func)); + } + + beforeEach(func) { + this.currentRunningSuite.beforeEach.push(processFunc(this.coreContext, func)); + } + + beforeItSpecified(itDescs, func) { + this.currentRunningSuite.beforeItSpecified.set(itDescs, processFunc(this.coreContext, func)); + } + + afterItSpecified(itDescs, func) { + this.currentRunningSuite.afterItSpecified.set(itDescs, processFunc(this.coreContext, func)); + } + + afterAll(func) { + this.currentRunningSuite.afterAll.push(processFunc(this.coreContext, func)); + } + + afterEach(func) { + this.currentRunningSuite.afterEach.push(processFunc(this.coreContext, func)); + } + + getCurrentRunningSuite() { + return this.currentRunningSuite; + } + + setCurrentRunningSuite(suite) { + this.currentRunningSuite = suite; + } + + getRootSuite() { + return this.rootSuite; + } + + getCurrentRunningSuiteDesc() { + return this.currentRunningSuiteDesc; + } + + + setCurrentRunningSuiteDesc(suite, currentSuite, prefix) { + if (suite != null && suite === currentSuite) { + this.currentRunningSuiteDesc = prefix; + } else if (suite != null && suite !== currentSuite) { + suite.childSuites.forEach(it => { + let temp = prefix; + if (it.description != null || it.description !== '') { + temp = prefix === '' ? it.description : prefix + '.' + it.description; + } + this.setCurrentRunningSuiteDesc(it, currentSuite, temp); + } + ); + } + } + analyzeConfigServiceClass(configServiceClass, desc) { + if (configServiceClass == null || configServiceClass === '') { + this.fullRun = true; + return false; + } + const targetArray = configServiceClass.split(',').map(item => item.trim()).filter(item => item !== ''); + if (this.targetSuiteArray.length === 0) { + for (let index in targetArray) { + if (targetArray[index].includes('#')) { + this.targetSpecArray.push(targetArray[index]); + } else { + this.targetSuiteArray.push(targetArray[index]); + } + } + + } + return targetArray.indexOf(desc) === -1; + + } + traversalResults(suite, obj, breakOnError) { + if (suite.childSuites.length === 0 && suite.specs.length === 0) { + return; + } + if (suite.specs.length > 0) { + for (const itItem of suite.specs) { + obj.total++; + let itInfo = { + currentThreadName: 'mainThread', + description: suite.description + '#' + itItem.description, + result: -3 + }; + if (SysTestKit.workerPort !== null) { + itInfo.currentThreadName = SysTestKit.workerPort.name; + } + obj.itItemList.push(itInfo); + if (breakOnError && (obj.error > 0 || obj.failure > 0)) { // breakOnError模式 + continue; + } + if (itItem.error) { + obj.error++; + itInfo.result = -1; + } else if (itItem.fail) { + obj.failure++; + itInfo.result = -2; + } else if (itItem.pass === true) { + obj.pass++; + itInfo.result = 0; + } + } + } + + obj.duration += suite.duration; + + if (suite.childSuites.length > 0) { + for (const suiteItem of suite.childSuites) { + this.traversalResults(suiteItem, obj, breakOnError); + } + } + + } + + async setSuiteResults(suite, error, coreContext) { + if (suite.childSuites.length === 0 && suite.specs.length === 0) { + return; + } + if (suite.specs.length > 0) { + const specService = coreContext.getDefaultService('spec'); + for (const specItem of suite.specs) { + specService.setCurrentRunningSpec(specItem); + if (error instanceof AssertException) { + specItem.fail = error; + } else { + specItem.error = error; + } + await coreContext.fireEvents('spec', 'specStart', specItem); + await coreContext.fireEvents('spec', 'specDone', specItem); + } + } + if (suite.childSuites.length > 0) { + for (const suiteItem of suite.childSuites) { + await this.setSuiteResults(suiteItem, error, coreContext); + } + } + } + + getSummary() { + let suiteService = this.coreContext.getDefaultService('suite'); + let rootSuite = suiteService.rootSuite; + const specService = this.coreContext.getDefaultService('spec'); + const configService = this.coreContext.getDefaultService('config'); + let breakOnError = configService.isBreakOnError(); + let isError = specService.getStatus(); + let isBreaKOnError = breakOnError && isError; + // itItemList 保存当前用例执行情况, 发送到主线程用例计算最终结果 + let obj = { total: 0, failure: 0, error: 0, pass: 0, ignore: 0, duration: 0, itItemList: []}; + for (const suiteItem of rootSuite.childSuites) { + this.traversalResults(suiteItem, obj, isBreaKOnError); + } + obj.ignore = obj.total - obj.pass - obj.failure - obj.error; + return obj; + } + + init(coreContext) { + this.coreContext = coreContext; + } + + traversalSuites(suite, obj, configService) { + if (suite.childSuites.length === 0 && suite.specs.length === 0) { + return []; + } + if (suite.specs.length > 0) { + let itArray = []; + for (const itItem of suite['specs']) { + if (!configService.filterDesc(suite.description, itItem.description, itItem.fi, null)) { + itArray.push({ 'itName': itItem.description }); + } + } + obj[suite.description] = itArray; + } + if (suite.childSuites.length > 0) { + let suiteArray = []; + for (const suiteItem of suite.childSuites) { + let suiteObj = {}; + this.traversalSuites(suiteItem, suiteObj, configService); + if (!configService.filterSuite(suiteItem.description)) { + suiteArray.push(suiteObj); + } + } + obj.suites = suiteArray; + } + } + + async dryRun(abilityDelegator) { + console.info(`${TAG} rootSuite : ` + JSON.stringify(this.rootSuite)); + let obj = this.rootSuite; + let prefixStack = []; + let suiteArray = []; + let skipSuiteArray = []; + this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj); + const configService = this.coreContext.getDefaultService('config'); + let result; + if (configService.skipMessage) { + result = { 'suites': suiteArray, 'skipSuites': skipSuiteArray }; + } else { + result = { 'suites': suiteArray }; + } + let strJson = JSON.stringify(result); + let strLen = strJson.length; + let maxLen = 500; + let maxCount = Math.floor(strLen / maxLen); + for (let count = 0; count <= maxCount; count++) { + await SysTestKit.print(strJson.substring(count * maxLen, (count + 1) * maxLen)); + } + console.info(`${TAG}dryRun print success`); + abilityDelegator.finishTest('dry run finished!!!', 0, () => { }); + } + + //将suitesArray的嵌套结构展开成三层结构 + analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj) { + obj.childSuites.map(suite => { + if (suite.description != null && suite.description !== '') { + let prefix = ''; + if (prefixStack.length > 0) { + prefix = prefixStack.join('.') + '.' + suite.description; + } else { + prefix = suite.description; + } + prefixStack.push(suite.description); + let temp = {}; + temp[prefix] = []; + let skipTemp = {}; + skipTemp[prefix] = []; + suite.specs.map(spec => { + let it = { 'itName': spec.description }; + spec.isSkip ? skipTemp[prefix].push(it) : temp[prefix].push(it); + }); + suiteArray.push(temp); + skipSuiteArray.push(skipTemp); + } + this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, suite); + prefixStack.pop(); + }); + } + //获取当前测试套下的所有测试用例数量 + getAllChildSuiteNum(suite, specArray) { + if (suite.specs != null) { + suite.specs.forEach(spec => specArray.push(spec)); + } + if (suite.childSuites != null) { + suite.childSuites.forEach(it => this.getAllChildSuiteNum(it, specArray)); + } + } + + execute() { + const configService = this.coreContext.getDefaultService('config'); + if (configService.filterValid.length !== 0) { + this.coreContext.fireEvents('task', 'incorrectFormat'); + return; + } + if (configService.filterXdescribe.length !== 0) { + this.coreContext.fireEvents('task', 'incorrectTestSuiteFormat'); + return; + } + if (configService.isRandom() && this.rootSuite.childSuites.length > 0) { + this.rootSuite.childSuites.sort(function () { + return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1; + }); + this.currentRunningSuite = this.rootSuite.childSuites[0]; + } + if (configService.isSupportAsync()) { + console.info(`${TAG} rootSuite:` + JSON.stringify(this.rootSuite)); + let asyncExecute = async () => { + await this.coreContext.fireEvents('task', 'taskStart'); + await this.rootSuite.asyncRun(this.coreContext); + }; + asyncExecute().then(async () => { + await this.coreContext.fireEvents('task', 'taskDone'); + }); + } else { + console.info('${TAG} rootSuite:' + JSON.stringify(this.rootSuite)); + this.coreContext.fireEvents('task', 'taskStart'); + this.rootSuite.run(this.coreContext); + this.coreContext.fireEvents('task', 'taskDone'); + } + } + + apis() { + const _this = this; + return { + describe: function (desc, func) { + return _this.describe(desc, func); + }, + xdescribe: function (desc, func, reason) { + return _this.xdescribe(desc, func, reason); + }, + beforeItSpecified: function (itDescs, func) { + return _this.beforeItSpecified(itDescs, func); + }, + afterItSpecified: function (itDescs, func) { + return _this.afterItSpecified(itDescs, func); + }, + beforeAll: function (func) { + return _this.beforeAll(func); + }, + beforeEach: function (func) { + return _this.beforeEach(func); + }, + afterAll: function (func) { + return _this.afterAll(func); + }, + afterEach: function (func) { + return _this.afterEach(func); + } + }; + } +} + +SuiteService.Suite = class { + constructor(attrs) { + this.description = attrs.description || ''; + this.childSuites = []; + this.specs = []; + this.beforeAll = []; + this.afterAll = []; + this.beforeItSpecified = new Map(); + this.afterItSpecified = new Map(); + this.beforeEach = []; + this.afterEach = []; + this.duration = 0; + this.hookError = null; + this.isSkip = false; + this.skipReason = ''; + } + + pushSpec(spec) { + this.specs.push(spec); + } + + removeSpec(desc) { + this.specs = this.specs.filter((item, index) => { + return item.description !== desc; + }); + } + + getSpecsNum() { + return this.specs.length; + } + + isRun(coreContext) { + const configService = coreContext.getDefaultService('config'); + const suiteService = coreContext.getDefaultService('suite'); + const specService = coreContext.getDefaultService('spec'); + let breakOnError = configService.isBreakOnError(); + let isError = specService.getStatus(); + return breakOnError && isError; + } + + run(coreContext) { + const suiteService = coreContext.getDefaultService('suite'); + suiteService.setCurrentRunningSuite(this); + if (this.description !== '') { + coreContext.fireEvents('suite', 'suiteStart', this); + } + this.runHookFunc('beforeAll'); + if (this.specs.length > 0) { + const configService = coreContext.getDefaultService('config'); + if (configService.isRandom()) { + this.specs.sort(function () { + return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1; + }); + } + for (let spec in this.specs) { + let isBreakOnError = this.isRun(coreContext); + if (isBreakOnError) { + break; + } + this.runHookFunc('beforeEach'); + spec.run(coreContext); + this.runHookFunc('afterEach'); + } + } + if (this.childSuites.length > 0) { + for (let suite in this.childSuites) { + let isBreakOnError = this.isRun(coreContext); + if (isBreakOnError) { + break; + } + suite.run(coreContext); + suiteService.setCurrentRunningSuite(suite); + } + } + this.runHookFunc('afterAll'); + if (this.description !== '') { + coreContext.fireEvents('suite', 'suiteDone'); + } + } + + async runBeforeItSpecified(beforeItSpecified, specItem) { + for (const [itNames, hookFunc] of beforeItSpecified) { + if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) || + (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) { + await Reflect.apply(hookFunc, null, []); + } + break; + } + } + + async runAfterItSpecified(beforeItSpecified, specItem) { + for (const [itNames, hookFunc] of beforeItSpecified) { + if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) || + (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) { + await Reflect.apply(hookFunc, null, []); + } + break; + } + } + + async asyncRunSpecs(coreContext) { + const configService = coreContext.getDefaultService('config'); + if (configService.isRandom()) { + this.specs.sort(function () { + return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1; + }); + } + const specService = coreContext.getDefaultService('spec'); + for (let specItem of this.specs) { + specService.setCurrentRunningSpec(specItem); + // 遇错即停模式,发现用例有问题,直接返回,不在执行后面的it + let isBreakOnError = this.isRun(coreContext); + if (isBreakOnError) { + console.info('break description :' + this.description); + break; + } + await coreContext.fireEvents('spec', 'specStart', specItem); + try { + await this.runBeforeItSpecified(this.beforeItSpecified, specItem); + await this.runAsyncHookFunc('beforeEach'); + await specItem.asyncRun(coreContext); + await this.runAfterItSpecified(this.afterItSpecified, specItem); + await this.runAsyncHookFunc('afterEach'); + } catch (e) { + console.error(`${TAG}stack:${e?.stack}`); + console.error(`${TAG}stack end`); + if (e instanceof AssertException) { + specItem.fail = e; + } else { + specItem.error = e; + } + specService.setStatus(true); + } + specItem.setResult(); + await coreContext.fireEvents('spec', 'specDone', specItem); + specService.setCurrentRunningSpec(null); + } + } + + async asyncRunChildSuites(coreContext) { + for (let i = 0; i < this.childSuites.length; i++) { + // 遇错即停模式, 发现用例有问题,直接返回,不在执行后面的description + let isBreakOnError = this.isRun(coreContext); + if (isBreakOnError) { + console.info(`${TAG}break description : ${this.description}`); + break; + } + await this.childSuites[i].asyncRun(coreContext); + } + } + + async asyncRun(coreContext) { + const suiteService = coreContext.getDefaultService('suite'); + const specService = coreContext.getDefaultService('spec'); + + suiteService.setCurrentRunningSuite(this); + suiteService.suitesStack.push(this); + if (this.description !== '') { + await coreContext.fireEvents('suite', 'suiteStart', this); + } + + try { + await this.runAsyncHookFunc('beforeAll'); + } catch (error) { + console.error(`${TAG}${error?.stack}`); + this.hookError = error; + } + + if (this.hookError !== null) { + specService.setStatus(true); + await suiteService.setSuiteResults(this, this.hookError, coreContext); + } + + if (this.specs.length > 0 && this.hookError === null) { + await this.asyncRunSpecs(coreContext); + } + + if (this.childSuites.length > 0 && this.hookError === null) { + await this.asyncRunChildSuites(coreContext); + } + + try { + await this.runAsyncHookFunc('afterAll'); + } catch (error) { + console.error(`${TAG}${error?.stack}`); + this.hookError = error; + specService.setStatus(true); + } + + if (this.description !== '') { + await coreContext.fireEvents('suite', 'suiteDone'); + let childSuite = suiteService.suitesStack.pop(); + let currentRunningSuite = suiteService.suitesStack.pop(); + suiteService.setCurrentRunningSuite(currentRunningSuite); + suiteService.suitesStack.push(currentRunningSuite); + } + } + + runHookFunc(hookName) { + if (this[hookName] && this[hookName].length > 0) { + this[hookName].forEach(func => { + try { + func(); + } catch (e) { + console.error(`${TAG}${e.stack}`); + } + }); + } + } + + async runAsyncHookFunc(hookName) { + for (const hookItem of this[hookName]) { + try { + await hookItem(); + } catch (error) { + error['message'] += `, error in ${hookName} function`; + throw error; + } + + } + } +}; + +class SpecService { + constructor(attr) { + this.id = attr.id; + this.totalTest = 0; + this.hasError = false; + this.skipSpecNum = 0; + this.isSkipSpec = false; + this.specSkipReason = ''; + } + + init(coreContext) { + this.coreContext = coreContext; + } + + setCurrentRunningSpec(spec) { + this.currentRunningSpec = spec; + } + + setStatus(obj) { + this.hasError = obj; + } + + getStatus() { + return this.hasError; + } + + getTestTotal() { + return this.totalTest; + } + + getCurrentRunningSpec() { + return this.currentRunningSpec; + } + + + getSkipSpecNum() { + return this.skipSpecNum; + } + + initSpecService() { + this.isSkipSpec = false; + this.specSkipReason = ''; + } + + it(desc, filter, func) { + const suiteService = this.coreContext.getDefaultService('suite'); + const configService = this.coreContext.getDefaultService('config'); + let isFilter = new NestFilter().filterNestName(suiteService.targetSuiteArray, suiteService.targetSpecArray, suiteService.suitesStack, desc); + if (configService.filterWithNest(desc, filter)) { + console.info(`${TAG}filter it :${desc}`); + this.initSpecService(); + return; + } + if (configService.filterDesc(suiteService.currentRunningSuite.description, desc, filter, this.coreContext) && isFilter && !suiteService.fullRun) { + console.info(`${TAG}filter it :${desc}`); + this.initSpecService(); + } else { + let processedFunc = processFunc(this.coreContext, func); + const spec = new SpecService.Spec({ description: desc, fi: filter, fn: processedFunc }); + if (this.isSkipSpec) { + spec.isSkip = true; + spec.skipReason = this.specSkipReason; + } + this.initSpecService(); + if (configService.runSkipped === 'skipped' && !spec.isSkip) { + console.info(`${TAG} runSkipped is skipped , just run xit, don't run it: ${spec.description}`); + return; + } + if (suiteService.getCurrentRunningSuite().isSkip && !spec.isSkip) { + configService.filterXdescribe.push(suiteService.getCurrentRunningSuite().description); + } + if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') { + let specStress = this.coreContext.getServices('dataDriver').dataDriver.getSpecStress(desc); + for (let i = 1; i < specStress; i++) { + this.totalTest++; + suiteService.getCurrentRunningSuite().pushSpec(spec); + } + } + // dryRun 状态下不统计压力测试重复数据 + if (configService['dryRun'] !== 'true') { + let stress = configService.getStress(); // 命令配置压力测试 + console.info(`${TAG}stress length : ${stress}`); + for (let i = 1; i < stress; i++) { + this.totalTest++; + suiteService.getCurrentRunningSuite().pushSpec(spec); + } + } + this.totalTest++; + suiteService.getCurrentRunningSuite().pushSpec(spec); + } + } + + xit(desc, filter, func, reason) { + const configService = this.coreContext.getDefaultService('config'); + const suiteService = this.coreContext.getDefaultService('suite'); + if (!configService.skipMessage && configService.runSkipped !== 'all') { + if (configService.runSkipped != null && configService.runSkipped !== '') { + let finalDesc = ''; + suiteService.suitesStack.map(suite => { + finalDesc = finalDesc + '.' + suite.description; + }); + finalDesc = (finalDesc + '#' + desc).substring(2); + if (configService.checkIfSpecInSkipRun(finalDesc)) { + console.info(`${TAG} runSkipped spec: ${desc}`); + } else { + console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`); + return; + } + } else { + console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`); + return; + } + } + this.skipSpecNum++; + this.isSkipSpec = true; + this.specSkipReason = reason; + this.it(desc, filter, func); + } + + apis() { + const _this = this; + return { + it: function (desc, filter, func) { + return _this.it(desc, filter, func); + }, + xit: function (desc, filter, func, reason) { + return _this.xit(desc, filter, func, reason); + } + }; + } +} + +SpecService.Spec = class { + constructor(attrs) { + this.description = attrs.description || ''; + this.fi = attrs.fi; + this.fn = attrs.fn || function () { + }; + this.fail = undefined; + this.error = undefined; + this.duration = 0; + this.startTime = 0; + this.isExecuted = false; // 当前用例是否执行 + this.isSkip = false; + this.skipReason = ''; + this.expectMsg = ''; + } + + setResult() { + if (this.fail) { + this.pass = false; + } else { + this.pass = true; + } + } + + run(coreContext) { + const specService = coreContext.getDefaultService('spec'); + specService.setCurrentRunningSpec(this); + coreContext.fireEvents('spec', 'specStart', this); + this.isExecuted = true; + try { + let dataDriver = coreContext.getServices('dataDriver'); + if (typeof dataDriver === 'undefined') { + this.fn(); + } else { + let suiteParams = dataDriver.dataDriver.getSuiteParams(); + let specParams = dataDriver.dataDriver.getSpecParams(); + console.info(`${TAG}[suite params] ${JSON.stringify(suiteParams)}`); + console.info(`${TAG}[spec params] ${JSON.stringify(specParams)}`); + if (this.fn.length === 0) { + this.fn(); + } else if (specParams.length === 0) { + this.fn(suiteParams); + } else { + specParams.forEach(paramItem => this.fn(Object.assign({}, paramItem, suiteParams))); + } + } + this.setResult(); + } catch (e) { + this.error = e; + specService.setStatus(true); + } + coreContext.fireEvents('spec', 'specDone', this); + } + + async asyncRun(coreContext) { + const dataDriver = coreContext.getServices('dataDriver'); + if (typeof dataDriver === 'undefined') { + await this.fn(); + } else { + const suiteParams = dataDriver.dataDriver.getSuiteParams(); + const specParams = dataDriver.dataDriver.getSpecParams(); + console.info(`[suite params] ${JSON.stringify(suiteParams)}`); + console.info(`[spec params] ${JSON.stringify(specParams)}`); + if (this.fn.length === 0) { + await this.fn(); + } else if (specParams.length === 0) { + await this.fn(suiteParams); + } else { + for (const paramItem of specParams) { + await this.fn(Object.assign({}, paramItem, suiteParams)); + } + } + } + + this.isExecuted = true; + } + + filterCheck(coreContext) { + const specService = coreContext.getDefaultService('spec'); + specService.setCurrentRunningSpec(this); + return true; + } +}; + +class ExpectService { + constructor(attr) { + this.id = attr.id; + this.matchers = {}; + this.customMatchers = []; + } + + expect(actualValue) { + return this.wrapMatchers(actualValue); + } + + init(coreContext) { + this.coreContext = coreContext; + this.addMatchers(this.basicMatchers()); + } + + addMatchers(matchers) { + for (const matcherName in matchers) { + if (Object.prototype.hasOwnProperty.call(matchers, matcherName)) { + this.matchers[matcherName] = matchers[matcherName]; + } + } + } + + removeMatchers(customAssertionName) { + if (customAssertionName === 'all') { + for (const matcherName in this.matchers) { + this.matchers[matcherName] = this.customMatchers.includes(matcherName) + ? (() => {throw new Error(`${matcherName} is unregistered`)}) : undefined; + } + } else { + this.matchers[customAssertionName] = () => { + throw new Error(`${customAssertionName} is unregistered`); + }; + } + } + + basicMatchers() { + return { + assertTrue: assertTrueFun, + assertEqual: assertEqualFun, + assertThrow: assertThrowFun + }; + } + + initWrapMatchers(currentRunningSpec) { + return { + // 翻转标识 + isNot: false, + // 翻转方法 + not: function () { + this.isNot = true; + return this; + }, + message: function (msg) { + currentRunningSpec.expectMsg = msg; + console.info(`${TAG} msg: ${msg}`); + return this; + } + }; + + } + + handleWithAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite) { + wrappedMatchers[matcherName] = async function (...args) { + await _this.matchers[matcherName](actualValue, args).then(function (result) { + if (wrappedMatchers.isNot) { + result.pass = !result.pass; + } + result.actualValue = actualValue; + result.checkFunc = matcherName; + if (!result.pass) { + const assertError = new AssertException(result.message); + currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError; + throw assertError; + } + }); + }; + } + + handleWithoutAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite) { + wrappedMatchers[matcherName] = function (...args) { + const result = _this.customMatchers.includes(matcherName) + ? _this.matchers[matcherName](actualValue, args[0]) : _this.matchers[matcherName](actualValue, args); + if (wrappedMatchers.isNot) { + result.pass = !result.pass; + result.message = LogExpectError.getErrorMsg(matcherName, actualValue, args[0], result.message); + } + result.actualValue = actualValue; + result.checkFunc = matcherName; + if (!result.pass) { + const assertError = new AssertException(result.message); + currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError; + throw assertError; + } + }; + } + + addAssert(wrappedMatchers, matcherName, actualValue) { + const _this = this; + const specService = _this.coreContext.getDefaultService('spec'); + const currentRunningSpec = specService.getCurrentRunningSpec(); + const currentRunningSuite = _this.coreContext.getDefaultService('suite').getCurrentRunningSuite(); + if (matcherName.search('assertPromise') === 0) { + this.handleWithAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite); + } else { + this.handleWithoutAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite); + } + } + + wrapMatchers(actualValue) { + const _this = this; + const specService = _this.coreContext.getDefaultService('spec'); + const currentRunningSpec = specService.getCurrentRunningSpec(); + const wrappedMatchers = this.initWrapMatchers(currentRunningSpec); + const currentRunningSuite = _this.coreContext.getDefaultService('suite').getCurrentRunningSuite(); + for (const matcherName in this.matchers) { + let result = Object.prototype.hasOwnProperty.call(this.matchers, matcherName); + if (!result) { + continue; + } + this.addAssert(wrappedMatchers, matcherName, actualValue); + } + return wrappedMatchers; + } + + apis() { + const _this = this; + return { + expect: function (actualValue) { + return _this.expect(actualValue); + } + }; + } +} + +class ReportService { + constructor(attr) { + this.id = attr.id; + } + + init(coreContext) { + this.coreContext = coreContext; + this.specService = this.coreContext.getDefaultService('spec'); + this.suiteService = this.coreContext.getDefaultService('suite'); + this.duration = 0; + } + + taskStart() { + console.info(`${TAG}[start] start run suites`); + } + + async suiteStart() { + console.info(`${TAG}[suite start]${this.suiteService.getCurrentRunningSuite().description}`); + } + + async specStart() { + console.info(`${TAG}start running case '${this.specService.currentRunningSpec.description}'`); + this.index = this.index + 1; + let spec = this.specService.currentRunningSpec; + spec.startTime = await SysTestKit.getRealTime(); + } + + async specDone() { + let msg = ''; + let spec = this.specService.currentRunningSpec; + let suite = this.suiteService.currentRunningSuite; + spec.duration = await SysTestKit.getRealTime() - spec.startTime; + suite.duration += spec.duration; + if (spec.error) { + this.formatPrint('error', spec.description + ' ; consuming ' + spec.duration + 'ms'); + this.formatPrint('errorDetail', spec.error); + } else if (spec.fail) { + this.formatPrint('fail', spec.description + ' ; consuming ' + spec.duration + 'ms'); + this.formatPrint('failDetail', spec.fail?.message); + } else { + this.formatPrint('pass', spec.description + ' ; consuming ' + spec.duration + 'ms'); + } + this.formatPrint(this.specService.currentRunningSpec.error, msg); + } + + suiteDone() { + let suite = this.suiteService.currentRunningSuite; + let message = suite.hookError ? `, ${suite.hookError?.message}` : ''; + console.info(`[suite end] ${suite.description} consuming ${suite.duration} ms${message}`); + } + + taskDone() { + let msg = ''; + let summary = this.suiteService.getSummary(); + msg = 'total cases:' + summary.total + ';failure ' + summary.failure + ',' + 'error ' + summary.error; + msg += ',pass ' + summary.pass + '; consuming ' + summary.duration + 'ms'; + console.info(`${TAG}${msg}`); + console.info(`${TAG}[end] run suites end`); + } + + incorrectFormat() { + if (this.coreContext.getDefaultService('config').filterValid.length !== 0) { + this.coreContext.getDefaultService('config').filterValid.forEach(function (item) { + console.info(`${TAG}this param ${item} is invalid`); + }); + } + } + + incorrectTestSuiteFormat() { + if (this.coreContext.getDefaultService('config').filterXdescribe.length !== 0) { + this.coreContext.getDefaultService('config').filterXdescribe.forEach(function (item) { + console.info(`${TAG}xdescribe: ${item} should not contain it`); + }); + } + } + + formatPrint(type, msg) { + switch (type) { + case 'pass': + console.info(`${TAG}[pass]${msg}`); + break; + case 'fail': + console.info(`${TAG}[fail]${msg}`); + break; + case 'failDetail': + console.info(`${TAG}[failDetail]${msg}`); + break; + case 'error': + console.info(`${TAG}[error]${msg}`); + break; + case 'errorDetail': + console.info(`${TAG}[errorDetail]${msg}`); + break; + } + } + + sleep(numberMillis) { + var now = new Date(); + var exitTime = now.getTime() + numberMillis; + while (true) { + now = new Date(); + if (now.getTime() > exitTime) { + return; + } + } + } +} + +export { + SuiteService, + SpecService, + ExpectService, + ReportService +}; diff --git a/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/testrunner/OpenHarmonyTestRunner.ts b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000..a2fe902 --- /dev/null +++ b/oh_modules/.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium/src/main/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { util } from '@kit.ArkTS'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; + +let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; +let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; +let jsonPath: string = 'mock/mock-config.json'; +let domain: number = 0x0000; //日志标识,0x0000作为测试框架的业务标识 +let tag: string = 'testTag'; //日志标识字符串,作为tag标识当前runner类下的测试行为 + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(domain, tag, '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); + } + + async onRun() { + hilog.info(domain, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments(); + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); + let moduleName = abilityDelegatorArguments.parameters['-m']; + let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); + let mResourceManager = context.resourceManager; + await checkMock(abilityDelegator, mResourceManager); + hilog.info(domain, tag, '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + hilog.info(domain, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} + +async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { + let rawFile: Uint8Array; + try { + rawFile = resourceManager.getRawFileContentSync(jsonPath); + hilog.info(domain, tag, 'MockList file exists'); + let mockStr: string = util.TextDecoder.create("utf-8", { ignoreBOM: true }).decodeWithStream(rawFile); + let mockMap: Record = getMockList(mockStr); + try { + abilityDelegator.setMockList(mockMap); + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(domain, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); + } + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(domain, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); + } +} + +function getMockList(jsonStr: string) { + let jsonObj: Record = JSON.parse(jsonStr); + let map: Map = new Map(Object.entries(jsonObj)); + let mockList: Record = {}; + map.forEach((value: object, key: string) => { + let realValue: string = value['source'].toString(); + mockList[key] = realValue; + }); + hilog.info(domain, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); + return mockList; +} \ No newline at end of file diff --git a/oh_modules/.ohpm/lock.json5 b/oh_modules/.ohpm/lock.json5 new file mode 100644 index 0000000..d7c73f4 --- /dev/null +++ b/oh_modules/.ohpm/lock.json5 @@ -0,0 +1,153 @@ +{ + "lockVersion": "1.0", + "settings": { + "resolveConflict": true, + "resolveConflictStrict": false, + "installAll": true + }, + "overrides": {}, + "overrideDependencyMap": {}, + "modules": { + ".": { + "name": "", + "dependencies": {}, + "devDependencies": { + "@ohos/hypium": { + "specifier": "1.0.24", + "version": "1.0.24" + }, + "@ohos/hamock": { + "specifier": "1.0.0", + "version": "1.0.0" + } + }, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + }, + "common": { + "name": "common", + "dependencies": {}, + "devDependencies": {}, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + }, + "features/chat": { + "name": "chat", + "dependencies": { + "@ohos/common": { + "specifier": "file:common", + "version": "file:common" + } + }, + "devDependencies": {}, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + }, + "features/graph": { + "name": "graph", + "dependencies": { + "@ohos/common": { + "specifier": "file:common", + "version": "file:common" + } + }, + "devDependencies": {}, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + }, + "features/settings": { + "name": "settings", + "dependencies": { + "@ohos/common": { + "specifier": "file:common", + "version": "file:common" + } + }, + "devDependencies": {}, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + }, + "products/phone": { + "name": "phone", + "dependencies": { + "@ohos/common": { + "specifier": "file:common", + "version": "file:common" + }, + "@ohos/graph": { + "specifier": "file:features/graph", + "version": "file:features/graph" + }, + "@ohos/chat": { + "specifier": "file:features/chat", + "version": "file:features/chat" + }, + "@ohos/settings": { + "specifier": "file:features/settings", + "version": "file:features/settings" + } + }, + "devDependencies": {}, + "dynamicDependencies": {}, + "maskedByOverrideDependencyMap": false + } + }, + "packages": { + "@ohos/common@file:common": { + "storePath": "common", + "dependencies": {}, + "dynamicDependencies": {}, + "dev": false, + "dynamic": false, + "maskedByOverrideDependencyMap": false + }, + "@ohos/hypium@1.0.24": { + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "storePath": "oh_modules/.ohpm/@ohos+hypium@1.0.24", + "dependencies": {}, + "dynamicDependencies": {}, + "dev": true, + "dynamic": false, + "maskedByOverrideDependencyMap": false + }, + "@ohos/hamock@1.0.0": { + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "storePath": "oh_modules/.ohpm/@ohos+hamock@1.0.0", + "dependencies": {}, + "dynamicDependencies": {}, + "dev": true, + "dynamic": false, + "maskedByOverrideDependencyMap": false + }, + "@ohos/graph@file:features/graph": { + "storePath": "features/graph", + "dependencies": { + "@ohos/common": "common" + }, + "dynamicDependencies": {}, + "dev": false, + "dynamic": false, + "maskedByOverrideDependencyMap": false + }, + "@ohos/settings@file:features/settings": { + "storePath": "features/settings", + "dependencies": { + "@ohos/common": "common" + }, + "dynamicDependencies": {}, + "dev": false, + "dynamic": false, + "maskedByOverrideDependencyMap": false + }, + "@ohos/chat@file:features/chat": { + "storePath": "features/chat", + "dependencies": { + "@ohos/common": "common" + }, + "dynamicDependencies": {}, + "dev": false, + "dynamic": false, + "maskedByOverrideDependencyMap": false + } + } +} \ No newline at end of file diff --git a/oh_modules/.ohpm/oh_modules/@ohos/hamock b/oh_modules/.ohpm/oh_modules/@ohos/hamock new file mode 120000 index 0000000..b4b328a --- /dev/null +++ b/oh_modules/.ohpm/oh_modules/@ohos/hamock @@ -0,0 +1 @@ +../../@ohos+hamock@1.0.0/oh_modules/@ohos/hamock \ No newline at end of file diff --git a/oh_modules/.ohpm/oh_modules/@ohos/hypium b/oh_modules/.ohpm/oh_modules/@ohos/hypium new file mode 120000 index 0000000..3f5fefa --- /dev/null +++ b/oh_modules/.ohpm/oh_modules/@ohos/hypium @@ -0,0 +1 @@ +../../@ohos+hypium@1.0.24/oh_modules/@ohos/hypium \ No newline at end of file diff --git a/oh_modules/@ohos/hamock b/oh_modules/@ohos/hamock new file mode 120000 index 0000000..56a64d0 --- /dev/null +++ b/oh_modules/@ohos/hamock @@ -0,0 +1 @@ +../.ohpm/@ohos+hamock@1.0.0/oh_modules/@ohos/hamock \ No newline at end of file diff --git a/oh_modules/@ohos/hypium b/oh_modules/@ohos/hypium new file mode 120000 index 0000000..a377cd1 --- /dev/null +++ b/oh_modules/@ohos/hypium @@ -0,0 +1 @@ +../.ohpm/@ohos+hypium@1.0.24/oh_modules/@ohos/hypium \ No newline at end of file diff --git a/products/phone/build-profile.json5 b/products/phone/build-profile.json5 index b823c6e..b48e33e 100644 --- a/products/phone/build-profile.json5 +++ b/products/phone/build-profile.json5 @@ -1,5 +1,17 @@ { "apiType": "stageMode", + "buildOption": { + "arkOptions": { + "runtimeOnly": { + "packages": [ + "@ohos/common", + "@ohos/graph", + "@ohos/chat", + "@ohos/settings" + ] + } + } + }, "targets": [ { "name": "default" diff --git a/entry/hvigorfile.ts b/products/phone/hvigorfile.ts similarity index 63% rename from entry/hvigorfile.ts rename to products/phone/hvigorfile.ts index 567099d..603072c 100644 --- a/entry/hvigorfile.ts +++ b/products/phone/hvigorfile.ts @@ -1,6 +1,6 @@ import { hapTasks } from '@ohos/hvigor-ohos-plugin'; export default { - system: hapTasks, - plugins: [] -}; + system: hapTasks, + plugins: [] +}; \ No newline at end of file diff --git a/products/phone/oh-package-lock.json5 b/products/phone/oh-package-lock.json5 new file mode 100644 index 0000000..42de8b1 --- /dev/null +++ b/products/phone/oh-package-lock.json5 @@ -0,0 +1,49 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/chat@../../features/chat": "@ohos/chat@../../features/chat", + "@ohos/common@../../common": "@ohos/common@../../common", + "@ohos/graph@../../features/graph": "@ohos/graph@../../features/graph", + "@ohos/settings@../../features/settings": "@ohos/settings@../../features/settings" + }, + "packages": { + "@ohos/chat@../../features/chat": { + "name": "@ohos/chat", + "version": "1.0.0", + "resolved": "../../features/chat", + "registryType": "local", + "dependencies": { + "@ohos/common": "file:../../common" + } + }, + "@ohos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local" + }, + "@ohos/graph@../../features/graph": { + "name": "@ohos/graph", + "version": "1.0.0", + "resolved": "../../features/graph", + "registryType": "local", + "dependencies": { + "@ohos/common": "file:../../common" + } + }, + "@ohos/settings@../../features/settings": { + "name": "@ohos/settings", + "version": "1.0.0", + "resolved": "../../features/settings", + "registryType": "local", + "dependencies": { + "@ohos/common": "file:../../common" + } + } + } +} \ No newline at end of file diff --git a/products/phone/oh_modules/@ohos/chat b/products/phone/oh_modules/@ohos/chat new file mode 120000 index 0000000..28c4809 --- /dev/null +++ b/products/phone/oh_modules/@ohos/chat @@ -0,0 +1 @@ +../../../../features/chat \ No newline at end of file diff --git a/products/phone/oh_modules/@ohos/common b/products/phone/oh_modules/@ohos/common new file mode 120000 index 0000000..3da9bd8 --- /dev/null +++ b/products/phone/oh_modules/@ohos/common @@ -0,0 +1 @@ +../../../../common \ No newline at end of file diff --git a/products/phone/oh_modules/@ohos/graph b/products/phone/oh_modules/@ohos/graph new file mode 120000 index 0000000..1af869c --- /dev/null +++ b/products/phone/oh_modules/@ohos/graph @@ -0,0 +1 @@ +../../../../features/graph \ No newline at end of file diff --git a/products/phone/oh_modules/@ohos/settings b/products/phone/oh_modules/@ohos/settings new file mode 120000 index 0000000..ba44257 --- /dev/null +++ b/products/phone/oh_modules/@ohos/settings @@ -0,0 +1 @@ +../../../../features/settings \ No newline at end of file diff --git a/products/phone/src/main/ets/entryability/EntryAbility.ets b/products/phone/src/main/ets/entryability/EntryAbility.ets index 29799e4..e6dcdcf 100644 --- a/products/phone/src/main/ets/entryability/EntryAbility.ets +++ b/products/phone/src/main/ets/entryability/EntryAbility.ets @@ -1,14 +1,15 @@ import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; +import { defaultLogger } from '@ohos/common'; export default class EntryAbility extends UIAbility { onCreate(want: Want, param: AbilityConstant.LaunchParam): void { - console.info('EntryAbility onCreate'); + defaultLogger.info('EntryAbility onCreate'); } onDestroy(): void { - console.info('EntryAbility onDestroy'); + defaultLogger.info('EntryAbility onDestroy'); } onWindowStageCreate(windowStage: window.WindowStage): void { @@ -16,7 +17,7 @@ export default class EntryAbility extends UIAbility { try { windowClass.setWindowBackgroundColor('#00000000'); } catch (e) { - console.error('Failed to set background color: ' + (e as BusinessError).message); + defaultLogger.error('Failed to set background color: ' + (e as BusinessError).message); } try { windowClass.setWindowSystemBarProperties({ @@ -24,28 +25,28 @@ export default class EntryAbility extends UIAbility { navigationBarColor: '#00000000' }); } catch (e) { - console.error('Failed to set system bar properties: ' + (e as BusinessError).message); + defaultLogger.error('Failed to set system bar properties: ' + (e as BusinessError).message); } - console.info('EntryAbility onWindowStageCreate'); + defaultLogger.info('EntryAbility onWindowStageCreate'); windowStage.loadContent('pages/SplashPage', (err, data) => { if (err.code) { - console.error('Failed to load the content. Cause: ' + JSON.stringify(err)); + defaultLogger.error('Failed to load the content. Cause: ' + JSON.stringify(err)); return; } - console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data)); + defaultLogger.info('Succeeded in loading the content. Data: ' + JSON.stringify(data)); }); } onWindowStageDestroy(): void { - console.info('EntryAbility onWindowStageDestroy'); + defaultLogger.info('EntryAbility onWindowStageDestroy'); } onForeground(): void { - console.info('EntryAbility onForeground'); + defaultLogger.info('EntryAbility onForeground'); } onBackground(): void { - console.info('EntryAbility onBackground'); + defaultLogger.info('EntryAbility onBackground'); } -} +} \ No newline at end of file diff --git a/products/phone/src/main/ets/pages/Index.ets b/products/phone/src/main/ets/pages/Index.ets index 96318d0..3bac397 100644 --- a/products/phone/src/main/ets/pages/Index.ets +++ b/products/phone/src/main/ets/pages/Index.ets @@ -1,4 +1,4 @@ -import { GraphDatabase } from '@ohos/common'; + import { GraphDatabase, Logger } from '@ohos/common'; import { MainPage } from './MainPage'; import display from '@ohos.display'; @@ -9,14 +9,18 @@ struct Index { private displayCallback?: Callback; aboutToAppear() { - this.db.init(getContext(this)); - try { - this.displayCallback = (size: number): void => { - }; - display.on('change', this.displayCallback); - } catch (e) { - console.error('display.on error: ' + JSON.stringify(e)); + const ctx = this.getUIContext()?.getHostContext(); + if (ctx) { + this.db.init(ctx); } + // TODO: display.on('change') API 在 API 23 中已废弃,应替换为窗口尺寸监听 + // 当前保留以兼容旧代码,后续应使用 window.on('windowSizeChange') 或断点系统替代 + // try { + // this.displayCallback = (size: number): void => { }; + // display.on('change', this.displayCallback); + // } catch (e) { + // Logger.error('display.on error: ' + JSON.stringify(e)); + // } } aboutToDisappear() { @@ -26,6 +30,12 @@ struct Index { } build() { - MainPage({ db: this.db }).width('100%').height('100%'); + Column() { + MainPage({ db: this.db }) + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') } } diff --git a/products/phone/src/main/ets/pages/MainPage.ets b/products/phone/src/main/ets/pages/MainPage.ets index 2a3a5b0..3fb49a0 100644 --- a/products/phone/src/main/ets/pages/MainPage.ets +++ b/products/phone/src/main/ets/pages/MainPage.ets @@ -1,22 +1,26 @@ -import { GraphDatabase } from '@ohos/common'; +import { GraphDatabase, Logger, ImmersiveTabNavigation } from '@ohos/common'; import { GraphPage } from '@ohos/graph'; import { ChatPage } from '@ohos/chat'; import { SettingsPage } from '@ohos/settings'; import display from '@ohos.display'; +@Builder +export function MainPageBuilder() { + MainPage({ db: new GraphDatabase() }) +} + @Component export struct MainPage { @Prop db: GraphDatabase; @State isWide: boolean = false; @State currentIndex: number = 0; - private tabController: TabsController = new TabsController(); aboutToAppear() { this.updateBreakpoint(); try { display.on('change', () => { this.updateBreakpoint(); }); } catch (e) { - console.error('display.on error: ' + JSON.stringify(e)); + Logger.error('display.on error: ' + JSON.stringify(e)); } } @@ -25,82 +29,77 @@ export struct MainPage { const defaultWindow = display.getDefaultDisplaySync(); this.isWide = defaultWindow.width > 520; } catch (e) { - console.error('updateBreakpoint error: ' + JSON.stringify(e)); + Logger.error('updateBreakpoint error: ' + JSON.stringify(e)); } } - @Builder - TabContentsBuilder() { - TabContent() { - if (this.isWide) { - Row() { - GraphPage({ db: this.db }) - .layoutWeight(1) - .height('100%') - .clip(true) - .borderRadius(12) - .margin({ left: 4, right: 2 }) + @Builder + TrulyMEMContent() { + if (this.isWide) { + Row() { + GraphPage({ db: this.db }) + .layoutWeight(1) + .height('100%') + .clip(true) + .borderRadius(12) + .margin({ left: 4, right: 2 }) - ChatPage({ db: this.db }) - .width(380) - .height('100%') - .clip(true) - .borderRadius(12) - .margin({ left: 2, right: 4 }) - } - .width('100%') - .height('100%') - .padding(4) - .backgroundColor('#1A1B2E') - } else { - Column() { - Stack() { GraphPage({ db: this.db }) } - .height('55%') - .width('100%') - .clip(true) - .borderRadius(12) - .margin({ top: 2, left: 4, right: 4, bottom: 2 }) + ChatPage({ db: this.db }) + .width(380) + .height('100%') + .clip(true) + .borderRadius(12) + .margin({ left: 2, right: 4 }) + } + .width('100%') + .height('100%') + .padding(4) + .backgroundColor('#1A1B2E') + } else { + Column() { + Stack() { GraphPage({ db: this.db }) } + .height('55%') + .width('100%') + .clip(true) + .borderRadius(12) + .margin({ top: 2, left: 4, right: 4, bottom: 2 }) - Stack() { ChatPage({ db: this.db }) } - .height('45%') - .width('100%') - .clip(true) - .borderRadius(12) - .margin({ top: 2, left: 4, right: 4, bottom: 2 }) - } - .width('100%') - .height('100%') - .padding(2) - .backgroundColor('#1A1B2E') - } - } - .tabBar(this.bottomTabBarStyle('TrulyMEM', '\ue8b6')) - - TabContent() { - SettingsPage() - } - .tabBar(this.bottomTabBarStyle('设置', '\ue8b8')) + Stack() { ChatPage({ db: this.db }) } + .height('45%') + .width('100%') + .clip(true) + .borderRadius(12) + .margin({ top: 2, left: 4, right: 4, bottom: 2 }) + } + .width('100%') + .height('100%') + .padding(2) + .backgroundColor('#1A1B2E') } + } - private bottomTabBarStyle(title: string, icon: string): BottomTabBarStyle { - return new BottomTabBarStyle({ - normal: new SymbolGlyphModifier(icon) - }, title) - .padding({ bottom: 4 }); - } + @Builder + SettingsContent() { + SettingsPage() + } - build() { - Tabs({ controller: this.tabController, index: this.currentIndex }) { - this.TabContentsBuilder() - } - .width('100%') - .height('100%') - .backgroundColor('#0A0A1A') - .barPosition(BarPosition.End) - .scrollable(false) - .animationMode(AnimationMode.NO_ANIMATION) - .onChange((index: number) => { - this.currentIndex = index; - }) + @Builder + TabContentBuilder() { + if (this.currentIndex === 0) { + this.TrulyMEMContent() + } else { + this.SettingsContent() } + } + + build() { + ImmersiveTabNavigation({ + contentBuilder: this.TabContentBuilder, + onTabChange: (index: number) => { + this.currentIndex = index; + } + }) + .width('100%') + .height('100%') + } } \ No newline at end of file diff --git a/products/phone/src/main/ets/pages/SplashPage.ets b/products/phone/src/main/ets/pages/SplashPage.ets index 386d617..a141935 100644 --- a/products/phone/src/main/ets/pages/SplashPage.ets +++ b/products/phone/src/main/ets/pages/SplashPage.ets @@ -1,11 +1,15 @@ -import router from '@ohos.router'; +import { PromptAction } from '@kit.ArkUI'; @Entry @Component struct SplashPage { aboutToAppear() { setTimeout(() => { - router.replaceUrl({ url: 'pages/Index' }); + const uiContext = this.getUIContext(); + if (uiContext) { + const router = uiContext.getRouter(); + router.replaceUrl({ url: 'pages/Index' }); + } }, 2000); } diff --git a/products/phone/src/main/module.json5 b/products/phone/src/main/module.json5 index 9c4fcde..37a311c 100644 --- a/products/phone/src/main/module.json5 +++ b/products/phone/src/main/module.json5 @@ -12,12 +12,12 @@ "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", + "routerMap": "$profile:router_map", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", - "icon": "$media:layered_image", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", diff --git a/entry/src/main/resources/base/element/color.json b/products/phone/src/main/resources/base/element/color.json similarity index 74% rename from entry/src/main/resources/base/element/color.json rename to products/phone/src/main/resources/base/element/color.json index 162a7b6..0268da8 100644 --- a/entry/src/main/resources/base/element/color.json +++ b/products/phone/src/main/resources/base/element/color.json @@ -2,7 +2,7 @@ "color": [ { "name": "start_window_background", - "value": "#FFFFFF" + "value": "#0A0A1A" } ] } diff --git a/entry/src/main/resources/base/element/string.json b/products/phone/src/main/resources/base/element/string.json similarity index 59% rename from entry/src/main/resources/base/element/string.json rename to products/phone/src/main/resources/base/element/string.json index 951fcd4..a1a95a4 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/products/phone/src/main/resources/base/element/string.json @@ -6,15 +6,23 @@ }, { "name": "module_desc", - "value": "True Memory Engine" - }, - { - "name": "EntryAbility_desc", - "value": "TrulyMEM Main Ability" + "value": "TrulyMEM Phone Module" }, { "name": "EntryAbility_label", "value": "TrulyMEM" + }, + { + "name": "EntryAbility_desc", + "value": "TrulyMEM Entry Ability" + }, + { + "name": "MainAbility_label", + "value": "TrulyMEM" + }, + { + "name": "MainAbility_desc", + "value": "TrulyMEM Main Ability" } ] } diff --git a/products/phone/src/main/resources/base/media/layered_image.json b/products/phone/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000..6f9c8fa --- /dev/null +++ b/products/phone/src/main/resources/base/media/layered_image.json @@ -0,0 +1 @@ +{"layered": true, "background": ":startIcon", "foreground": ":startIcon"} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/media/startIcon.png b/products/phone/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..08cd6f2bfd1b53ec5a4db72bed55f40907e8bdfa GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9xZY8JuI3K{zz}&{z5M@%E Q4U}N;boFyt=akR{0J void; - -export class RConClient -{ - public isActive: boolean; //是否可用标志位,随tcp状态更改 - private tcp: socket.TCPSocket; //socket连接 - private ipAddress: socket.NetAddress; //ip地址 - private passWord: string; //密码 - private isConnecting: boolean = false; // 标记连接是否正在进行中,防止并发连接 - private closeHandler: (() => void) | null = null; // 保存 close 事件处理器引用以便清理 - private authResolve: (() => void) | null = null; // 认证完成信号 - private nextPacketId: number = 1; // 递增的包序号,避免重复 - - constructor(ip:string,LPort:number,PassWord:string) { - this.isActive = false; - this.tcp = socket.constructTCPSocketInstance(); - this.ipAddress = { - address: ip, - port: LPort - } - this.passWord = PassWord;//初始化tcp连接相关配置 - } - //连接服务器 - async setConnect(timeOut:number,callBack:RConCallback)//异步获取服务器连接 - { - if (this.isConnecting) { - // Already connecting, return an error via callback - callBack(RConResult.error("ALREADY_CONNECTING", "A connection attempt is already in progress")); - return; - } - this.isConnecting = true; - let finished = false; // To ensure we set isConnecting to false only once - const setFinished = () => { - if (!finished) { - finished = true; - this.isConnecting = false; - } - }; - - // 清理旧的事件监听器 - this.cleanupListeners(); - - let connectOptions: socket.TCPConnectOptions = { - address: this.ipAddress, - timeout: timeOut - }; - - await this.tcp.connect(connectOptions).then(() => { - console.info('Connection established'); - let result:RConResult = RConResult.result(undefined,"tcp_ok") - // 注册 close 事件并保存引用 - this.closeHandler = () => { - this.isActive = false; - }; - this.tcp.on('close', this.closeHandler); - //发送认证信息 - GetAuth(this.tcp,this.passWord,3,(result:RConResult)=>{ - setFinished(); - if(!result.success) { - callBack(result); - // 通知等待的 sendAfterAuth 认证失败 - this.notifyAuthComplete(); - } - else - { - this.isActive = true; - callBack(result); - // 通知等待的 sendAfterAuth 认证成功 - this.notifyAuthComplete(); - } - - }) - }).catch((err: BusinessError) => { - setFinished(); - let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "tcp_fail"); - callBack(result); - this.notifyAuthComplete(); - }); - } - - /** - * 通知认证流程完成(供 sendAfterAuth 等待机制使用) - */ - private notifyAuthComplete(): void { - if (this.authResolve) { - this.authResolve(); - this.authResolve = null; - } - } - - /** - * 获取下一个递增包序号(跳过 -1,因为 -1 在 RCON 协议中表示认证失败) - */ - private getNextPacketId(): number { - const id = this.nextPacketId; - this.nextPacketId++; - if (this.nextPacketId === -1 || this.nextPacketId > 2147483646) { - this.nextPacketId = 1; - } - return id; - } - - /** - * 清理所有事件监听器 - */ - private cleanupListeners(): void { - this.tcp.off("close"); - this.tcp.off("message"); - this.closeHandler = null; - } - - async sendMessage(message:string,type:number,callBack:RConCallback) - {//发送一条指令 - if(!this.isActive)//错误重连机制 - { - await this.setConnect(1000,(result:RConResult)=>{ - if (!result.success) { - // Connection failed, call the outer callback with the error - callBack(result); - return; - } - // 重连后需要重新认证,不能直接发送命令 - // 等待认证完成后再发送消息 - this.sendAfterAuth(message, type, callBack); - }) - } - else { - this.sendWithResponse(message, type, callBack); - } - } - - /** - * 发送消息并等待响应(正常路径) - */ - private sendWithResponse(message: string, type: number, callBack: RConCallback): void { - const packetId = this.getNextPacketId(); - const messageHandler = (info: socket.SocketMessageInfo) => { - const buffer: ArrayBuffer = info.message; - const result = parseRCONBuffer(buffer); - callBack(result); - this.tcp.off("message", messageHandler); - }; - this.tcp.on("message", messageHandler); - - let sendOption: socket.TCPSendOptions = { data: buildPacket(packetId, type, message) }; - this.tcp.send(sendOption).catch((err: BusinessError) => { - let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "send_auth_fail"); - callBack(result); - this.tcp.off("message", messageHandler); - this.setDisconnect(); - }); - } - - /** - * 重连后等待认证完成再发送消息 - * 使用 Promise 等待机制替代轮询 - */ - private sendAfterAuth(message: string, type: number, callBack: RConCallback): void { - // 创建认证等待 Promise(最多 5 秒) - const authPromise = new Promise((resolve) => { - this.authResolve = () => resolve(this.isActive); - // 超时兜底 - setTimeout(() => { - if (this.authResolve) { - this.authResolve = null; - resolve(false); - } - }, 5000); - }); - - authPromise.then((authSuccess: boolean) => { - if (authSuccess) { - this.sendWithResponse(message, type, callBack); - } else { - let result = RConResult.error("AUTH_TIMEOUT", "Authentication timed out after reconnect"); - callBack(result); - } - }); - } - - async resetServer(ip:string,password:string,port:number,timeout:number,callback:RConCallback) - {//重新设置服务器配置 - this.ipAddress = { - address:ip, - port:port - } - this.passWord = password; - this.setDisconnect();//断开放弃使用的tcp连接 - await this.setConnect(timeout,callback); - } - setDisconnect() - { - // 清理所有事件监听器 - this.cleanupListeners(); - // 断链 - try { - this.tcp.close(); - } catch { - // Socket already closed - } - this.isActive = false; - } - -} diff --git a/trulymem-core/src/main/ets/components/tools.ets b/trulymem-core/src/main/ets/components/tools.ets deleted file mode 100644 index 50f7a39..0000000 --- a/trulymem-core/src/main/ets/components/tools.ets +++ /dev/null @@ -1,194 +0,0 @@ -import { socket } from '@kit.NetworkKit'; -import { BusinessError } from '@kit.BasicServicesKit'; -import { util } from '@kit.ArkTS'; - -const MAXLENGTH:number = 1447; - -/** - * 手动 UTF-8 编码(替代已弃用的 util.TextEncoder.encode/encodeInto) - */ -function utf8Encode(str: string): Uint8Array { - const bytes: number[] = []; - for (let i = 0; i < str.length; i++) { - const code = str.charCodeAt(i); - if (code <= 0x7F) { - bytes.push(code); - } else if (code <= 0x07FF) { - bytes.push(0xC0 | (code >> 6)); - bytes.push(0x80 | (code & 0x3F)); - } else if (code <= 0xFFFF) { - bytes.push(0xE0 | (code >> 12)); - bytes.push(0x80 | ((code >> 6) & 0x3F)); - bytes.push(0x80 | (code & 0x3F)); - } else { - bytes.push(0xF0 | (code >> 18)); - bytes.push(0x80 | ((code >> 12) & 0x3F)); - bytes.push(0x80 | ((code >> 6) & 0x3F)); - bytes.push(0x80 | (code & 0x3F)); - } - } - return new Uint8Array(bytes); -} - -export class RConResult { - success: boolean = false; // 操作是否成功 - errorCode?: string; // 错误码 - errorMessage?: string; // 错误描述 - rawData?: ArrayBuffer; - resultText ?:string; // 附加数据 - type ?:number; - id ?:number; -static result(data?:ArrayBuffer,stringResult?:string,type?:number,id?:number):RConResult{ - const result = new RConResult(); - result.success = true; - if(data !== undefined){result.rawData = data;} - if(stringResult !== undefined){result.resultText = stringResult;} - if(type !== undefined){result.type = type;} - if(id !== undefined){result.id = id;} - return result; -} - static error(code: string, message: string): RConResult { - const result = new RConResult(); - result.success = false; - result.errorCode = code; - result.errorMessage = message; - return result; - } -} - -export function buildPacket(packetId: number, type: number, payload: string): ArrayBuffer -{ - // 使用自定义 UTF-8 编码器替代已弃用的 util.TextEncoder - const encodedPayload: Uint8Array = utf8Encode(payload); - const payloadWithTerminator: Uint8Array = new Uint8Array(encodedPayload.length + 1); - payloadWithTerminator.set(encodedPayload); - payloadWithTerminator[encodedPayload.length] = 0x00; // 添加空终止符 - - // 检查编码后的字节长度是否符合 RCON 协议限制(payload + null terminator + padding <= 1460) - // 即 encodedPayload.length + 1 (null) + 1 (padding) <= 1460 - 4 (length) - 4 (id) - 4 (type) - // 即 encodedPayload.length <= 1446 - if (encodedPayload.length > MAXLENGTH) { - console.warn(`[RCON] Payload truncated from ${encodedPayload.length} to ${MAXLENGTH} bytes (RCON protocol limit)`); - // 截断到最大字节长度 - const truncated = new Uint8Array(MAXLENGTH); - truncated.set(encodedPayload.subarray(0, MAXLENGTH)); - payloadWithTerminator.set(truncated); - } - - // 计算各字段长度(单位:字节) - const idFieldSize: number = 4; - const typeFieldSize: number = 4; - const payloadSize: number = payloadWithTerminator.byteLength; - const paddingSize: number = 1; - - // 总长度计算(不包含长度字段自身) - const packetLength: number = - idFieldSize + - typeFieldSize + - payloadSize + - paddingSize; - - // 创建二进制缓冲区 - const buffer: ArrayBuffer = new ArrayBuffer( - packetLength + 4 // 包含长度字段自身 - ); - const view: DataView = new DataView(buffer); - - // 写入字段(小端序) - view.setInt32(0, packetLength, true); // 长度字段 - view.setInt32(4, packetId, true); // ID字段 - view.setInt32(8, type, true); // 类型字段 - - // 写入负载内容 - const payloadStartOffset: number = 12; - new Uint8Array(buffer, payloadStartOffset).set(payloadWithTerminator); - - // 添加填充字节 - const paddingOffset: number = payloadStartOffset + payloadSize; - new Uint8Array(buffer, paddingOffset, paddingSize).fill(0x00); - - return buffer; -} - -export function parseRCONBuffer(buffer: ArrayBuffer): RConResult -{ - const view: DataView = new DataView(buffer); - - // 基础头校验 - if (buffer.byteLength < 12) { - return RConResult.error("INVALID_HEADER", "Packet length less than 12 bytes"); - } - - // 小端序解析头字段 - const length: number = view.getInt32(0, true); - const requestId: number = view.getInt32(4, true); - const type: number = view.getInt32(8, true); - - // 负载终止符定位 - let payloadEnd: number = 12; - const maxPosition: number = Math.min(12 + length - 10, buffer.byteLength); - while (payloadEnd < maxPosition) { - if (view.getUint8(payloadEnd) === 0x00) break; - payloadEnd++; - } - - // 提取有效负载 - const payloadBytes: Uint8Array = new Uint8Array( - buffer.slice(12, payloadEnd) - ); - // 解码 UTF-8 字符串 - const textDecoderOptions: util.TextDecoderOptions = { - fatal: false, - ignoreBOM: true - }; - const decodeToStringOptions: util.DecodeToStringOptions = { - stream: false - }; - const textDecoder: util.TextDecoder = util.TextDecoder.create('utf-8', textDecoderOptions); - const payloadString: string = textDecoder.decodeToString(payloadBytes, decodeToStringOptions); - const result: RConResult = RConResult.result(buffer, payloadString, type, requestId); - return result; -} - -export async function GetAuth(Connection:socket.TCPSocket,password:string,authRequestId:number,callBack :Function) -{ - // 先注册监听器,再发送数据,防止快速响应丢失 - let messageHandlerRegistered = true; - const messageHandler = (info: socket.SocketMessageInfo) => { - const buffer:ArrayBuffer = info.message; - const result = parseRCONBuffer(buffer); - // 认证响应处理 - if (result.type === 2) { - if (result?.id === authRequestId) { - console.log("Authentication success"); - callBack(result); - if (messageHandlerRegistered) { - Connection.off('message', messageHandler); - messageHandlerRegistered = false; - } - } else if (result?.id === -1) { - console.error("Authentication failed"); - callBack(RConResult.error("Authentication failed","password wrong")); - if (messageHandlerRegistered) { - Connection.off('message', messageHandler); - messageHandlerRegistered = false; - } - } - } - }; - Connection.on('message', messageHandler); - - // 修复:packetId 必须与 authRequestId 一致,服务器会回显此 ID - let sendOption:socket.TCPSendOptions = {data : buildPacket(authRequestId, 3, password)}; - await Connection.send(sendOption).then(()=>{ - let result:RConResult = RConResult.result(undefined,"send_auth_ok") - callBack(result); - }).catch((err: BusinessError) => { - let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "send_auth_fail"); - callBack(result); - if (messageHandlerRegistered) { - Connection.off('message', messageHandler); - messageHandlerRegistered = false; - } - }) -} diff --git a/trulymem-core/src/main/resources/base/element/float.json b/trulymem-core/src/main/resources/base/element/float.json deleted file mode 100644 index 33ea223..0000000 --- a/trulymem-core/src/main/resources/base/element/float.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "float": [ - { - "name": "page_text_font_size", - "value": "50fp" - } - ] -} diff --git a/trulymem-core/src/main/resources/base/element/string.json b/trulymem-core/src/main/resources/base/element/string.json deleted file mode 100644 index 6d6e189..0000000 --- a/trulymem-core/src/main/resources/base/element/string.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "string": [ - { - "name": "page_show", - "value": "page from package" - }, - { - "name": "permmsion_for_tcp", - "value": "to connect with rcon port opened in game servers" - } - ] -} diff --git a/trulymem-core/src/ohosTest/ets/test/List.test.ets b/trulymem-core/src/ohosTest/ets/test/List.test.ets deleted file mode 100644 index 794c7dc..0000000 --- a/trulymem-core/src/ohosTest/ets/test/List.test.ets +++ /dev/null @@ -1,5 +0,0 @@ -import abilityTest from './Ability.test'; - -export default function testsuite() { - abilityTest(); -} \ No newline at end of file diff --git a/trulymem-core/src/ohosTest/module.json5 b/trulymem-core/src/ohosTest/module.json5 deleted file mode 100644 index 86d0db6..0000000 --- a/trulymem-core/src/ohosTest/module.json5 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "module": { - "name": "librcon_test", - "type": "feature", - - "deviceTypes": [ - "default", - "tablet", - "2in1" - ], - "deliveryWithInstall": true, - "installationFree": false - } -} diff --git a/trulymem-core/src/test/List.test.ets b/trulymem-core/src/test/List.test.ets deleted file mode 100644 index bb5b5c3..0000000 --- a/trulymem-core/src/test/List.test.ets +++ /dev/null @@ -1,5 +0,0 @@ -import localUnitTest from './LocalUnit.test'; - -export default function testsuite() { - localUnitTest(); -} \ No newline at end of file diff --git a/trulymem-core/src/test/LocalUnit.test.ets b/trulymem-core/src/test/LocalUnit.test.ets deleted file mode 100644 index 165fc16..0000000 --- a/trulymem-core/src/test/LocalUnit.test.ets +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; - -export default function localUnitTest() { - describe('localUnitTest', () => { - // Defines a test suite. Two parameters are supported: test suite name and test suite function. - beforeAll(() => { - // Presets an action, which is performed only once before all test cases of the test suite start. - // This API supports only one parameter: preset action function. - }); - beforeEach(() => { - // Presets an action, which is performed before each unit test case starts. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: preset action function. - }); - afterEach(() => { - // Presets a clear action, which is performed after each unit test case ends. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: clear action function. - }); - afterAll(() => { - // Presets a clear action, which is performed after all test cases of the test suite end. - // This API supports only one parameter: clear action function. - }); - it('assertContain', 0, () => { - // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. - let a = 'abc'; - let b = 'b'; - // Defines a variety of assertion methods, which are used to declare expected boolean conditions. - expect(a).assertContain(b); - expect(a).assertEqual(a); - }); - }); -} \ No newline at end of file diff --git a/trulymem-core/src/test/unittest/PacketBuilder.test.ets b/trulymem-core/src/test/unittest/PacketBuilder.test.ets deleted file mode 100644 index 95b8ce7..0000000 --- a/trulymem-core/src/test/unittest/PacketBuilder.test.ets +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, expect } from '@ohos/hypium'; -import { buildPacket } from '../../main/ets/components/tools'; -import { util } from '@kit.ArkTS'; - -export default function packetBuilderTest() { - describe('buildPacket', () => { - describe('packet structure', () => { - it('should create packet with correct header format', 0, () => { - const packet = buildPacket(1, 2, "test"); - const view = new DataView(packet); - - // 验证长度字段(小端序) - const length = view.getInt32(0, true); - expect(length).assertEqual(10); // 4 (id) + 4 (type) + 5 (payload) + 1 (padding) - - // 验证ID字段 - const id = view.getInt32(4, true); - expect(id).assertEqual(1); - - // 验证类型字段 - const type = view.getInt32(8, true); - expect(type).assertEqual(2); - - // 验证载荷(包含空终止符) - const payloadBytes = new Uint8Array(packet, 12, 4); - const decoder = new util.TextDecoder('utf-8'); - const payload = decoder.decode(payloadBytes); - expect(payload).assertEqual("test"); - - // 验证填充字节 - const padding = view.getUint8(16); - expect(padding).assertEqual(0); - }); - - it('should handle empty payload', 0, () => { - const packet = buildPacket(1, 2, ""); - const view = new DataView(packet); - - const length = view.getInt32(0, true); - expect(length).assertEqual(10); // 4+4+1+1 - - const payload = view.getUint8(12); - expect(payload).assertEqual(0); // 空终止符 - }); - - it('should truncate payload to MAXLENGTH (1447)', 0, () => { - const longPayload = "x".repeat(2000); - const packet = buildPacket(1, 2, longPayload); - const view = new DataView(packet); - - const length = view.getInt32(0, true); - // 4 + 4 + 1447 + 1 = 1456 - expect(length).assertEqual(1456); - }); - - it('should handle payload at MAXLENGTH boundary', 0, () => { - const exactPayload = "x".repeat(1447); - const packet = buildPacket(1, 2, exactPayload); - const view = new DataView(packet); - - const length = view.getInt32(0, true); - expect(length).assertEqual(1456); - }); - - it('should handle payload just under MAXLENGTH', 0, () => { - const payload = "x".repeat(1446); - const packet = buildPacket(1, 2, payload); - const view = new DataView(packet); - - const length = view.getInt32(0, true); - expect(length).assertEqual(1455); - }); - }); - - describe('edge cases', () => { - it('should handle special characters', 0, () => { - const packet = buildPacket(1, 2, "测试中文"); - const view = new DataView(packet); - - const length = view.getInt32(0, true); - // 中文字符是多字节的 - expect(length > 10).assertTrue(); - }); - - it('should handle unicode characters', 0, () => { - const packet = buildPacket(1, 2, "🎮🎮🎮"); - const view = new DataView(packet); - - expect(view.getInt32(4, true)).assertEqual(1); - }); - - it('should handle large packet IDs', 0, () => { - const packet = buildPacket(2147483647, 2, "test"); - const view = new DataView(packet); - - expect(view.getInt32(4, true)).assertEqual(2147483647); - }); - - it('should handle zero packet ID', 0, () => { - const packet = buildPacket(0, 2, "test"); - const view = new DataView(packet); - - expect(view.getInt32(4, true)).assertEqual(0); - }); - }); - }); -} diff --git a/trulymem-core/src/test/unittest/PacketParser.test.ets b/trulymem-core/src/test/unittest/PacketParser.test.ets deleted file mode 100644 index f6fa868..0000000 --- a/trulymem-core/src/test/unittest/PacketParser.test.ets +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect } from '@ohos/hypium'; -import { parseRCONBuffer, buildPacket } from '../../main/ets/components/tools'; - -export default function packetParserTest() { - describe('parseRCONBuffer', () => { - describe('valid packet parsing', () => { - it('should parse valid packet correctly', 0, () => { - const originalPacket = buildPacket(1, 2, "test"); - const result = parseRCONBuffer(originalPacket); - - expect(result.success).assertTrue(); - expect(result.id).assertEqual(1); - expect(result.type).assertEqual(2); - expect(result.resultText).assertEqual("test"); - }); - - it('should parse packet with long payload', 0, () => { - const longText = "a".repeat(1000); - const originalPacket = buildPacket(1, 2, longText); - const result = parseRCONBuffer(originalPacket); - - expect(result.success).assertTrue(); - expect(result.resultText).assertEqual(longText); - }); - - it('should parse packet with empty payload', 0, () => { - const originalPacket = buildPacket(1, 2, ""); - const result = parseRCONBuffer(originalPacket); - - expect(result.success).assertTrue(); - expect(result.resultText).assertEqual(""); - }); - - it('should parse packet with unicode payload', 0, () => { - const unicodeText = "🎮测试"; - const originalPacket = buildPacket(1, 2, unicodeText); - const result = parseRCONBuffer(originalPacket); - - expect(result.success).assertTrue(); - expect(result.resultText).assertEqual(unicodeText); - }); - }); - - describe('error handling', () => { - it('should fail on packet less than 12 bytes', 0, () => { - const smallBuffer = new ArrayBuffer(11); - const result = parseRCONBuffer(smallBuffer); - - expect(result.success).assertFalse(); - expect(result.errorCode).assertEqual("INVALID_HEADER"); - }); - - it('should handle zero-length packet', 0, () => { - const emptyBuffer = new ArrayBuffer(0); - const result = parseRCONBuffer(emptyBuffer); - - expect(result.success).assertFalse(); - }); - - it('should handle corrupted packet', 0, () => { - const corruptedBuffer = new ArrayBuffer(20); - const view = new DataView(corruptedBuffer); - // 设置无效的长度值 - view.setInt32(0, 1000, true); - - const result = parseRCONBuffer(corruptedBuffer); - // 应该能处理,但可能得到不完整的结果 - expect(result).not.assertNull(); - }); - }); - - describe('round-trip tests', () => { - it('should correctly round-trip standard packet', 0, () => { - const originalPacket = buildPacket(123, 2, "hello world"); - const parsed = parseRCONBuffer(originalPacket); - - expect(parsed.id).assertEqual(123); - expect(parsed.type).assertEqual(2); - expect(parsed.resultText).assertEqual("hello world"); - }); - - it('should correctly round-trip authentication packet', 0, () => { - const originalPacket = buildPacket(1, 3, "password123"); - const parsed = parseRCONBuffer(originalPacket); - - expect(parsed.id).assertEqual(1); - expect(parsed.type).assertEqual(3); - expect(parsed.resultText).assertEqual("password123"); - }); - }); - }); -} diff --git a/trulymem-core/src/test/unittest/RConClient.test.ets b/trulymem-core/src/test/unittest/RConClient.test.ets deleted file mode 100644 index 2bff141..0000000 --- a/trulymem-core/src/test/unittest/RConClient.test.ets +++ /dev/null @@ -1,148 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from '@ohos/hypium'; -import { RConClient } from '../../main/ets/components/RconClient'; -import { RConResult } from '../../main/ets/components/tools'; - -export default function rConClientTest() { - describe('RConClient', () => { - let client: RConClient; - - beforeEach(() => { - client = new RConClient("127.0.0.1", 25575, "password"); - }); - - // ===== 构造函数测试 ===== - describe('constructor', () => { - it('should initialize with correct properties', 0, () => { - expect(client.isActive).assertFalse(); - }); - - it('should accept different configurations', 0, () => { - const customClient = new RConClient("192.168.1.100", 25575, "secret"); - expect(customClient.isActive).assertFalse(); - }); - }); - - // ===== setConnect 测试 ===== - describe('setConnect', () => { - it('should call callback multiple times during connection', 0, () => { - let callbackCount = 0; - const results: RConResult[] = []; - - client.setConnect(5000, (result: RConResult) => { - callbackCount++; - results.push(result); - }); - - // 注意:这是异步操作,实际测试需要等待 - // 这里只是演示测试结构 - }); - - it('should handle connection timeout', 0, () => { - let errorReceived = false; - - client.setConnect(1, (result: RConResult) => { - if (!result.success && result.errorCode === "tcp_fail") { - errorReceived = true; - } - }); - - // 验证超时处理 - }); - - it('should prevent concurrent connections', 0, () => { - let firstCallback = false; - let secondCallback = false; - - // 第一次连接 - client.setConnect(5000, (result: RConResult) => { - firstCallback = true; - }); - - // 第二次连接应该被拒绝 - client.setConnect(5000, (result: RConResult) => { - secondCallback = true; - if (!result.success && result.errorCode === "ALREADY_CONNECTING") { - // 预期的错误 - } - }); - }); - }); - - // ===== sendMessage 测试 ===== - describe('sendMessage', () => { - it('should send message with correct format', 0, () => { - // 模拟已连接状态 - // client.setConnect(...) 完成后 - - client.sendMessage("/list", 2, (result: RConResult) => { - if (result.success) { - expect(result.resultText).not.assertNull(); - } - }); - }); - - it('should handle message send errors', 0, () => { - client.sendMessage("/invalid-command", 2, (result: RConResult) => { - // 验证错误处理 - }); - }); - - it('should trigger auto-reconnect when inactive', 0, () => { - // 测试自动重连机制 - }); - }); - - // ===== resetServer 测试 ===== - describe('resetServer', () => { - it('should reset server configuration', 0, () => { - let reconnectSuccessful = false; - - client.resetServer("192.168.1.200", "newpassword", 25575, 5000, (result: RConResult) => { - reconnectSuccessful = result.success; - }); - }); - - it('should update internal state after reset', 0, () => { - // 验证状态更新 - }); - }); - - // ===== setDisconnect 测试 ===== - describe('setDisconnect', () => { - it('should disconnect when active', 0, () => { - // 先连接 - client.setConnect(5000, (result: RConResult) => { - if (result.success && result.type === 2) { - client.setDisconnect(); - expect(client.isActive).assertFalse(); - } - }); - }); - - it('should handle disconnect when not connected', 0, () => { - // 不应该抛出异常 - client.setDisconnect(); - expect(client.isActive).assertFalse(); - }); - }); - - // ===== 状态管理测试 ===== - describe('state management', () => { - it('should track active state correctly', 0, () => { - expect(client.isActive).assertFalse(); - - // 连接后应该是true - client.setConnect(5000, (result: RConResult) => { - if (result.success && result.type === 2) { - expect(client.isActive).assertTrue(); - } - }); - }); - - it('should handle state after disconnect', 0, () => { - client.setDisconnect(); - expect(client.isActive).assertFalse(); - }); - }); - }); -} diff --git a/trulymem-core/src/test/unittest/RConResult.test.ets b/trulymem-core/src/test/unittest/RConResult.test.ets deleted file mode 100644 index 7002372..0000000 --- a/trulymem-core/src/test/unittest/RConResult.test.ets +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, it, expect } from '@ohos/hypium'; -import { RConResult } from '../../main/ets/components/tools'; - -export default function rConResultTest() { - describe('RConResult', () => { - // ===== 静态方法测试 ===== - describe('static result()', () => { - it('should create successful result with all parameters', 0, () => { - const data = new ArrayBuffer(10); - const result = RConResult.result(data, "test", 2, 1); - - expect(result.success).assertTrue(); - expect(result.resultText).assertEqual("test"); - expect(result.type).assertEqual(2); - expect(result.id).assertEqual(1); - expect(result.rawData).assertEqual(data); - }); - - it('should create successful result with optional parameters', 0, () => { - const result = RConResult.result(); - - expect(result.success).assertTrue(); - expect(result.rawData).assertUndefined(); - expect(result.resultText).assertUndefined(); - expect(result.type).assertUndefined(); - expect(result.id).assertUndefined(); - }); - - it('should create result with only data', 0, () => { - const data = new ArrayBuffer(5); - const result = RConResult.result(data); - - expect(result.success).assertTrue(); - expect(result.rawData).assertEqual(data); - expect(result.resultText).assertUndefined(); - }); - - it('should create result with only string', 0, () => { - const result = RConResult.result(undefined, "hello"); - - expect(result.success).assertTrue(); - expect(result.resultText).assertEqual("hello"); - expect(result.rawData).assertUndefined(); - }); - }); - - describe('static error()', () => { - it('should create error result with code and message', 0, () => { - const result = RConResult.error("TCP_FAIL", "Connection failed"); - - expect(result.success).assertFalse(); - expect(result.errorCode).assertEqual("TCP_FAIL"); - expect(result.errorMessage).assertEqual("Connection failed"); - }); - - it('should create error with empty strings', 0, () => { - const result = RConResult.error("", ""); - - expect(result.success).assertFalse(); - expect(result.errorCode).assertEqual(""); - expect(result.errorMessage).assertEqual(""); - }); - }); - - // ===== 边界值测试 ===== - describe('edge cases', () => { - it('should handle long strings', 0, () => { - const longStr = "a".repeat(10000); - const result = RConResult.result(undefined, longStr); - - expect(result.resultText).assertEqual(longStr); - }); - - it('should handle negative id', 0, () => { - const result = RConResult.result(undefined, undefined, undefined, -1); - - expect(result.id).assertEqual(-1); - }); - - it('should handle negative type', 0, () => { - const result = RConResult.result(undefined, undefined, -1); - - expect(result.type).assertEqual(-1); - }); - }); - }); -}