From 06f94fbbb5d5afc5de81e5a07b679de9ce62a3ac Mon Sep 17 00:00:00 2001 From: Roman Sedaikin Date: Tue, 11 Feb 2025 17:45:38 +0300 Subject: [PATCH 1/2] ets-harness impl Signed-off-by: Roman Sedaikin --- arkoala-arkts/arkui/src/generated/index.ts | 3 + arkoala-arkts/ets-harness/.mocharc.json | 4 + arkoala-arkts/ets-harness/package.json | 36 ++++++ .../ets-harness/src/TestApplication.ts | 119 ++++++++++++++++++ .../ets-harness/src/ets/etsconfig.json | 21 ++++ .../ets-harness/src/ets/pages/case1.ets | 8 ++ arkoala-arkts/ets-harness/src/loader.ts | 18 +++ arkoala-arkts/ets-harness/tsconfig.json | 59 +++++++++ arkoala-arkts/package.json | 1 + arkoala/framework/src/Application.ts | 2 +- arkoala/package.json | 1 + 11 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 arkoala-arkts/ets-harness/.mocharc.json create mode 100644 arkoala-arkts/ets-harness/package.json create mode 100644 arkoala-arkts/ets-harness/src/TestApplication.ts create mode 100644 arkoala-arkts/ets-harness/src/ets/etsconfig.json create mode 100644 arkoala-arkts/ets-harness/src/ets/pages/case1.ets create mode 100644 arkoala-arkts/ets-harness/src/loader.ts create mode 100644 arkoala-arkts/ets-harness/tsconfig.json diff --git a/arkoala-arkts/arkui/src/generated/index.ts b/arkoala-arkts/arkui/src/generated/index.ts index 6607c0e11..ce9440534 100644 --- a/arkoala-arkts/arkui/src/generated/index.ts +++ b/arkoala-arkts/arkui/src/generated/index.ts @@ -416,3 +416,6 @@ export * from "./ArkCustomSpanMaterialized" export * from "./ArkLinearIndicatorControllerMaterialized" export * from "./ArkGlobalScopeInspectorMaterialized" export * from "./GlobalScope" +export * from "./Events" +export * from "./PeerNode" +export * from "./peers/CallbacksChecker" diff --git a/arkoala-arkts/ets-harness/.mocharc.json b/arkoala-arkts/ets-harness/.mocharc.json new file mode 100644 index 000000000..6bb9dcc6e --- /dev/null +++ b/arkoala-arkts/ets-harness/.mocharc.json @@ -0,0 +1,4 @@ +{ + "ui": "tdd", + "spec": "./build/lib/src/launcher.js" +} diff --git a/arkoala-arkts/ets-harness/package.json b/arkoala-arkts/ets-harness/package.json new file mode 100644 index 000000000..5626a0cdf --- /dev/null +++ b/arkoala-arkts/ets-harness/package.json @@ -0,0 +1,36 @@ +{ + "name": "@koalaui/ets-harness", + "version": "1.4.8+devel", + "description": "", + "main": "./build/lib/src/launcher.js", + "types": "./build/lib/src/launcher.d.ts", + "files": [ + "build/lib/**/*.js", + "build/lib/**/*.d.ts" + ], + "scripts": { + "clean": "rimraf build", + "compile": "ets-tsc -b ./tsconfig.json", + "compile:plugin": "cd ../../arkoala/ets-plugin && npm run compile", + "compile:ets": "npm run compile:plugin && cd src/ets && ets-tsc -p ./etsconfig.json", + "test:compile": "npm run compile:ets && npm run compile", + "test:run": "npm run test:compile && mocha build/lib/src/launcher.js" + }, + "keywords": [], + "dependencies": { + "@koalaui/common": "1.4.8+devel", + "@koalaui/compat": "1.4.8+devel", + "@koalaui/harness": "1.4.8+devel" + }, + "devDependencies": { + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.0", + "@typescript-eslint/eslint-plugin": "^5.20.0", + "@typescript-eslint/parser": "^5.20.0", + "chai": "^4.3.6", + "eslint": "^8.13.0", + "eslint-plugin-unused-imports": "^2.0.0", + "mocha": "^9.2.2", + "source-map-support": "^0.5.21" + } +} \ No newline at end of file diff --git a/arkoala-arkts/ets-harness/src/TestApplication.ts b/arkoala-arkts/ets-harness/src/TestApplication.ts new file mode 100644 index 000000000..a944a216a --- /dev/null +++ b/arkoala-arkts/ets-harness/src/TestApplication.ts @@ -0,0 +1,119 @@ +import { checkArkoalaCallbacks, checkEvents, PeerNode, setCustomEventsChecker, UserViewBuilder } from "@koalaui/arkts-arkui" +import { int64 } from "@koalaui/common" +import { InteropNativeModule, KPointer } from "@koalaui/interop" +import { ComputableState, createAnimationTimer, GlobalStateManager, memoEntry, MutableState, StateContext, StateManager } from "@koalaui/runtime" + +setCustomEventsChecker(checkArkoalaCallbacks) + +class PartialUpdateRecord { + public update: () => void + public context: Object + public callback: (before: boolean) => void + + constructor(update: () => void, context: Object, callback: (before: boolean) => void) { + this.callback = callback + this.context = context + this.update = update + } +} + +let partialUpdates = new Array() +let _currentPartialUpdateContext: Object | undefined = undefined + +/** + * Provide partial update lambda and context. + * + * @param update - function that performs state update + * @param context - context available to UI code when state update effect happens + */ +export function addPartialUpdate(update: () => void, context: T, callback: (before: boolean) => void): void { + partialUpdates.push(new PartialUpdateRecord(update, context as Object, callback)) +} + +/** + * Current partial update context or undefined. + * + * @returns current partial update context + */ +export function currentPartialUpdateContext(): T | undefined { + return _currentPartialUpdateContext as (T | undefined) +} + +let detachedRoots: Map> = new Map>() + +function createMemoRoot( + manager: StateManager, + /** @memo */ + builder: UserViewBuilder +): ComputableState { + const peer = PeerNode.generateRootPeer() + const node = manager.updatableNode(peer, (context: StateContext) => { + const frozen = manager.frozen + manager.frozen = true + memoEntry(context, 0, builder) + manager.frozen = frozen + }) + node.value + return node +} + + +export class TestApplication { + private manager: StateManager | undefined = undefined + private root: ComputableState | undefined = undefined + private timer: MutableState | undefined = undefined + + constructor() {} + + nextFrame() { + this.timer!.value = Date.now() as int64 + checkEvents() + this.updateStates(this.manager!, this.root!) + } + + run( + /** @memo */ + content: () => void + ) { + this.manager = GlobalStateManager.instance + this.timer = createAnimationTimer(this.manager!) + this.root = createMemoRoot(this.manager, content) + this.nextFrame() + } + + updateStates(manager: StateManager, root: ComputableState ) { + // Ensure all current state updates took effect. + manager.syncChanges() + manager.updateSnapshot() + root.value + for (const detachedRoot of detachedRoots.values()) + detachedRoot.value + if (partialUpdates.length > 0) { + // If there are pending partial updates - we apply them one by one and provide update context. + for (let update of partialUpdates) { + // Set the context available via currentPartialUpdateContext() to @memo code. + _currentPartialUpdateContext = update.context + // Update states. + update.update() + // Propagate changes. + manager.updateSnapshot() + // Notify subscriber. + update.callback(true) + // Compute new tree state + try { + root.value + for (const detachedRoot of detachedRoots.values()) + detachedRoot.value + } catch (error) { + InteropNativeModule._NativeLog('has error in partialUpdates') + } + // Notify subscriber. + update.callback(false) + // Clear context. + _currentPartialUpdateContext = undefined + } + // Clear partial updates list. + partialUpdates.splice(0, partialUpdates.length) + } + } +} diff --git a/arkoala-arkts/ets-harness/src/ets/etsconfig.json b/arkoala-arkts/ets-harness/src/ets/etsconfig.json new file mode 100644 index 000000000..bf870ef43 --- /dev/null +++ b/arkoala-arkts/ets-harness/src/ets/etsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../arkoala-arkts/arkui/config/etsconfig-base.json", + "include": [ + "./**/*.ets" + ], + "compilerOptions": { + "types": [], + "baseUrl": ".", + "rootDirs": [ + "." + ], + "outDir": "../../build/ets-junk", + "plugins": [ + { + "transform": "@koalaui/ets-plugin/build/lib/src/ArkExpander.js", + "destination": "../../build/generated", + "arkui": "@koalaui/arkts-arkui" + } + ] + } +} diff --git a/arkoala-arkts/ets-harness/src/ets/pages/case1.ets b/arkoala-arkts/ets-harness/src/ets/pages/case1.ets new file mode 100644 index 000000000..2bd6f39d3 --- /dev/null +++ b/arkoala-arkts/ets-harness/src/ets/pages/case1.ets @@ -0,0 +1,8 @@ +//@Entry +@Component +struct Case1 { + @State x:number = 0 + build() { + Column() {} + } +} \ No newline at end of file diff --git a/arkoala-arkts/ets-harness/src/loader.ts b/arkoala-arkts/ets-harness/src/loader.ts new file mode 100644 index 000000000..2e0885861 --- /dev/null +++ b/arkoala-arkts/ets-harness/src/loader.ts @@ -0,0 +1,18 @@ +import { Case1, Case1Options } from "../build/generated/pages/case1" +import { Assert } from "@koalaui/harness" +import { TestApplication } from "./TestApplication" + +suite("Case1", () => { + test("StateChange", () => { + // run app with Case1() + const app = new TestApplication() + app.run(Case1) + // nextFrame + // change state + // log result + // assert log + let x = 42 + let y = 24 + Assert(x == 42, "StateChange test is failed!\n expected: " + x + "\ncurrent: " + y ) + }) +}) diff --git a/arkoala-arkts/ets-harness/tsconfig.json b/arkoala-arkts/ets-harness/tsconfig.json new file mode 100644 index 000000000..da6d365a5 --- /dev/null +++ b/arkoala-arkts/ets-harness/tsconfig.json @@ -0,0 +1,59 @@ +{ + "extends": "@koalaui/build-common/tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "module": "CommonJS", + "plugins": [ + { + "transform": "@koalaui/compiler-plugin/build/lib/src/koala-transformer.js", + "trace": false, + "only_unmemoize": true, + "unmemoizeDir": "./build/unmemoized" + } + ], + "outDir": "./build/lib", + "baseUrl": ".", + "paths": { + "@koalaui/arkui-common": [ + "../../arkoala/arkui-common/src/arkts" + ], + "@koalaui/runtime": [ + "../../incremental/runtime/src" + ], + "@koalaui/harness": [ + "../../incremental/harness" + ], + "@koalaui/arkts-arkui": [ + "../arkui/src" + ], + "@koalaui/arkts-arkui/ohos.router": [ + "../arkui/src/ohos.router.ts" + ], + "#arkcompat": [ + "../../arkoala/arkui-common/src/arkts" + ], + "app/*": [ + "./build/generated/*" + ] + } + }, + "files": [ + "../../incremental/tools/panda/arkts/std-lib/global.d.ts" + ], + "include": [ + "./build/generated", + "./src/**/*.ts" + ], + "exclude": [ + ], + "references": [ + { "path": "../arkui" }, + { "path": "../../arkoala/arkui-common" }, + { "path": "../../arkoala/arkui-common/tsconfig-unmemoize.json" }, + { "path": "../arkui/tsconfig-unmemoize.json" }, + { "path": "../../incremental/runtime" }, + { "path": "../../incremental/compiler-plugin" }, + { "path": "../../incremental/common" }, + { "path": "../../incremental/harness" } + ] +} diff --git a/arkoala-arkts/package.json b/arkoala-arkts/package.json index 2210b9013..ee2066012 100644 --- a/arkoala-arkts/package.json +++ b/arkoala-arkts/package.json @@ -8,6 +8,7 @@ "./har", "../arkoala/arkui-common", "../arkoala/ets-plugin", + "./ets-harness", "../incremental/build-common", "../incremental/compiler-plugin", "../incremental/common", diff --git a/arkoala/framework/src/Application.ts b/arkoala/framework/src/Application.ts index 07af09551..c0975259a 100644 --- a/arkoala/framework/src/Application.ts +++ b/arkoala/framework/src/Application.ts @@ -127,7 +127,7 @@ function uiRoot( ui: () => void, control: ArkoalaControl ): State { - const node = rootConstruct() + const node: PeerNode = rootConstruct() const root: ComputableState = memoRoot(node!, () => withControl(ui, control) diff --git a/arkoala/package.json b/arkoala/package.json index de8e2bbed..45b62727c 100644 --- a/arkoala/package.json +++ b/arkoala/package.json @@ -18,6 +18,7 @@ "../incremental/common", "../incremental/runtime", "../incremental/compiler-plugin", + "../incremental/harness", "../interop", "../arkoala-arkts/arkui" ], -- Gitee From b6d29ec8a5dd838384ec21037dcd53fda89474d8 Mon Sep 17 00:00:00 2001 From: Roman Sedaikin Date: Thu, 13 Feb 2025 18:08:50 +0300 Subject: [PATCH 2/2] Stage 2 Signed-off-by: Roman Sedaikin --- .../arkui/config/etsconfig-base.json | 9 +- arkoala-arkts/arkui/src/ArkTestComponent.ts | 84 +++++++++++++ arkoala-arkts/arkui/src/generated/PeerNode.ts | 10 ++ arkoala-arkts/arkui/src/index-test.d.ts | 27 +++++ arkoala-arkts/arkui/src/index.ts | 4 +- .../arkui/src/peers/ArkTestComponentPeer.ts | 51 ++++++++ arkoala-arkts/arkui/tsconfig.json | 1 + arkoala-arkts/ets-harness/.mocharc.json | 4 +- arkoala-arkts/ets-harness/arktsconfig.json | 29 +++++ arkoala-arkts/ets-harness/package.json | 11 +- ...pplication.ts => EtsHarnessApplication.ts} | 83 +++++++++++-- arkoala-arkts/ets-harness/src/Page.ts | 28 +++++ .../ets-harness/src/ets/pages/case1.ets | 6 +- .../ets-harness/src/ets/pages/case2.ets | 12 ++ arkoala-arkts/ets-harness/src/loader.ts | 114 +++++++++++++++--- arkoala-arkts/ets-harness/src/test_entry.ts | 33 +++++ .../ets-harness/tsconfig-unmemoize.json | 56 +++++++++ arkoala-arkts/ets-harness/tsconfig.json | 67 +++------- .../ets-harness/webpack.config.node.js | 104 ++++++++++++++++ .../native/src/generated/bridge_custom.cc | 2 + arkoala/framework/src/Application.ts | 2 +- interop/src/arkts/InteropNativeModule.sts | 1 + interop/src/cpp/common-interop.cc | 8 ++ interop/src/cpp/vmloader.cc | 70 ++++++++++- 24 files changed, 722 insertions(+), 94 deletions(-) create mode 100644 arkoala-arkts/arkui/src/ArkTestComponent.ts create mode 100644 arkoala-arkts/arkui/src/index-test.d.ts create mode 100644 arkoala-arkts/arkui/src/peers/ArkTestComponentPeer.ts create mode 100644 arkoala-arkts/ets-harness/arktsconfig.json rename arkoala-arkts/ets-harness/src/{TestApplication.ts => EtsHarnessApplication.ts} (58%) create mode 100644 arkoala-arkts/ets-harness/src/Page.ts create mode 100644 arkoala-arkts/ets-harness/src/ets/pages/case2.ets create mode 100644 arkoala-arkts/ets-harness/src/test_entry.ts create mode 100644 arkoala-arkts/ets-harness/tsconfig-unmemoize.json create mode 100644 arkoala-arkts/ets-harness/webpack.config.node.js diff --git a/arkoala-arkts/arkui/config/etsconfig-base.json b/arkoala-arkts/arkui/config/etsconfig-base.json index a1bc1c4e3..e8d063d61 100644 --- a/arkoala-arkts/arkui/config/etsconfig-base.json +++ b/arkoala-arkts/arkui/config/etsconfig-base.json @@ -1,5 +1,6 @@ { "files": [ + "../../../arkoala-arkts/arkui/src/index-test.d.ts", "../../../arkoala-arkts/arkui/types/index-full.d.ts", "../../../incremental/tools/panda/arkts/std-lib/global.d.ts" ], @@ -225,7 +226,8 @@ "UIExtensionComponent", "RichEditor", "CachedImage", - "MediaCachedImage" + "MediaCachedImage", + "TestComponent" ], "extend": { "decorator": [ @@ -802,6 +804,11 @@ "name": "MediaCachedImage", "type": "MediaCachedImageAttribute", "instance": "MediaCachedImageInstance" + }, + { + "name": "TestComponent", + "type": "TestComponentAttribute", + "instance": "TestComponentInstance" } ] }, diff --git a/arkoala-arkts/arkui/src/ArkTestComponent.ts b/arkoala-arkts/arkui/src/ArkTestComponent.ts new file mode 100644 index 000000000..ecf7628af --- /dev/null +++ b/arkoala-arkts/arkui/src/ArkTestComponent.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024-2025 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 { NodeAttach, remember } from "@koalaui/runtime" +import { ArkCommonMethodComponent } from "./generated/ArkCommon" +import { ArkTestComponentPeer } from "./peers/ArkTestComponentPeer" +import { CommonMethod } from "./generated/ArkCommonInterfaces" +import { InteropNativeModule } from "@koalaui/interop" + +/** @memo:stable */ +export class ArkTestComponentComponent extends ArkCommonMethodComponent { + getPeer(): ArkTestComponentPeer { + return (this.peer as ArkTestComponentPeer) + } + /** @memo */ + setTestComponentOptions(options?: TestComponentOptions): this { + if (this.checkPriority("setColumnOptions")) { + const options_casted = options as (TestComponentOptions | undefined) + this.getPeer()?.setTestComponentOptionsAttribute(options_casted) + return this + } + return this + } + /** @memo */ + onChange(value: Function0): this { + if (this.checkPriority("onChange")) { + this.getPeer()?.onChangeAttribute(value) + return this + } + return this + } + /** @memo */ + log(message: string): this { + if (this.checkPriority("log")) { + this.getPeer()?.logAttribute(message) + return this + } + return this + } + public applyAttributesFinish(): void { + // we calls this function outside of class, so need to make it public + super.applyAttributesFinish() + } +} + +/** @memo */ +export function ArkTestComponent( + /** @memo */ + style: ((attributes: ArkTestComponentComponent) => void) | undefined, + /** @memo */ + content_: (() => void) | undefined, + options?: TestComponentOptions | undefined +) { + const receiver = remember(() => { + return new ArkTestComponentComponent() + }) + NodeAttach((): ArkTestComponentPeer => ArkTestComponentPeer.create(receiver), (_: ArkTestComponentPeer) => { + receiver.setTestComponentOptions(options) + style?.(receiver) + content_?.() + receiver.applyAttributesFinish() + }) +} + +export interface TestComponentOptions { + id?: number; +} +export type TestComponentInterface = (options?: TestComponentOptions) => TestComponentAttribute; +export interface TestComponentAttribute extends CommonMethod { + onChange?: Function0; + log?: string +} diff --git a/arkoala-arkts/arkui/src/generated/PeerNode.ts b/arkoala-arkts/arkui/src/generated/PeerNode.ts index d39f4d705..7a30ef6f0 100644 --- a/arkoala-arkts/arkui/src/generated/PeerNode.ts +++ b/arkoala-arkts/arkui/src/generated/PeerNode.ts @@ -16,6 +16,16 @@ export class PeerNode extends IncrementalNode { static nextId(): int32 { return ++PeerNode.currentId } private id: int32 + setId(id: int32) { + PeerNode.peerNodeMap.delete(this.id) + this.id = id + PeerNode.peerNodeMap.set(this.id, this) + } + + getId(): int32 { + return this.id + } + private static peerNodeMap = new Map() static findPeerByNativeId(id: number): PeerNode | undefined { diff --git a/arkoala-arkts/arkui/src/index-test.d.ts b/arkoala-arkts/arkui/src/index-test.d.ts new file mode 100644 index 000000000..1a192fbe6 --- /dev/null +++ b/arkoala-arkts/arkui/src/index-test.d.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024-2025 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. + */ + +declare interface TestComponentOptions { + id?: (number); +} +declare interface TestComponentInterface { + (options?: TestComponentOptions): TestComponentAttribute; +} +declare class TestComponentAttribute extends CommonMethod { + onChange(callback: () => void): TestComponentAttribute; + log(message: string): TestComponentAttribute; +} +declare const TestComponent: TestComponentInterface +declare const TestComponentInstance: TestComponentAttribute diff --git a/arkoala-arkts/arkui/src/index.ts b/arkoala-arkts/arkui/src/index.ts index b8ebda64d..2945b8f24 100644 --- a/arkoala-arkts/arkui/src/index.ts +++ b/arkoala-arkts/arkui/src/index.ts @@ -25,4 +25,6 @@ export * from "./stateOf" export * from "./ForEach" export * from "./LazyForEach" export * from "./ohos.router" -export * from "./ArkNavigation" \ No newline at end of file +export * from "./ArkNavigation" +export * from "./peers/ArkTestComponentPeer" +export * from "./ArkTestComponent" \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/peers/ArkTestComponentPeer.ts b/arkoala-arkts/arkui/src/peers/ArkTestComponentPeer.ts new file mode 100644 index 000000000..431e97dff --- /dev/null +++ b/arkoala-arkts/arkui/src/peers/ArkTestComponentPeer.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024-2025 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 { int32 } from "@koalaui/common" +import { InteropNativeModule, KPointer } from "@koalaui/interop" +import { ComponentBase } from "../generated/ComponentBase" +import { ArkUIGeneratedNativeModule } from "#components" +import { ArkCommonMethodPeer, ArkCommonMethodAttributes } from "../generated/peers/ArkCommonPeer" +import { PeerNode } from "../generated/PeerNode" +import { TestComponentOptions } from "../ArkTestComponent" +import { UserView } from "../UserView" + +export class ArkTestComponentPeer extends ArkCommonMethodPeer { + protected constructor(peerPtr: KPointer, id: int32, name: string = "", flags: int32 = 0) { + super(peerPtr, id, name, flags) + } + public static create(component?: ComponentBase, flags: int32 = 0): ArkTestComponentPeer { + const peerId = PeerNode.nextId() + const _peerPtr = ArkUIGeneratedNativeModule._Blank_construct(peerId, flags) + const _peer = new ArkTestComponentPeer(_peerPtr, peerId, "Blank", flags) + component?.setPeer(_peer) + return _peer + } + setTestComponentOptionsAttribute(option?: TestComponentOptions): void { + if (option !== undefined) { + this.setId(option!.id! as int32) + } + } + onChangeCallback: Function0 | undefined = undefined + onChangeAttribute(callback: Function0): void { + this.onChangeCallback = callback + } + logAttribute(message: string): void { + InteropNativeModule._AppendGroupedLog(1, message) + } +} +export interface ArkTestComponentAttributes extends ArkCommonMethodAttributes { + onChange?: Function0 +} diff --git a/arkoala-arkts/arkui/tsconfig.json b/arkoala-arkts/arkui/tsconfig.json index dde0d9dc1..341b6914c 100644 --- a/arkoala-arkts/arkui/tsconfig.json +++ b/arkoala-arkts/arkui/tsconfig.json @@ -24,6 +24,7 @@ "../../arkoala/arkui-common/ohos-sdk-ets/HarmonyOS-NEXT-DB1/openharmony/ets/component/index-full.d.ts", "../../arkoala/arkui-common/ohos-sdk-ets/HarmonyOS-NEXT-DB1/openharmony/ets/component/koala-extensions.d.ts", "../../arkoala/arkui-common/ohos-sdk-ets/HarmonyOS-NEXT-DB1/openharmony/ets/api/@internal/full/global.d.ts", + "../../arkoala-arkts/arkui/src/index-test.d.ts", "../../incremental/tools/panda/arkts/std-lib/global.d.ts" ], // TODO: maybe delete this section "references": [ diff --git a/arkoala-arkts/ets-harness/.mocharc.json b/arkoala-arkts/ets-harness/.mocharc.json index 6bb9dcc6e..1235551ee 100644 --- a/arkoala-arkts/ets-harness/.mocharc.json +++ b/arkoala-arkts/ets-harness/.mocharc.json @@ -1,4 +1,4 @@ { "ui": "tdd", - "spec": "./build/lib/src/launcher.js" -} + "spec": "../build/index.js" +} \ No newline at end of file diff --git a/arkoala-arkts/ets-harness/arktsconfig.json b/arkoala-arkts/ets-harness/arktsconfig.json new file mode 100644 index 000000000..e58b64958 --- /dev/null +++ b/arkoala-arkts/ets-harness/arktsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "package": "@koalaui/ets-harness", + "outDir": "build/abc", + "rootDirs": ".", + "baseUrl": ".", + "paths": { + "#components": ["../arkui/build/unmemoized/src/generated/arkts"], + "#arkcompat/*": ["../../arkoala/arkui-common/build/unmemoized/src/arkts/*"], + "#arkstate": ["../../arkoala/arkui-common/build/unmemoized/src"], + "@koalaui/arkui-common": ["../../arkoala/arkui-common/build/unmemoized/src"], + "@koalaui/arkts-arkui": ["../arkui/build/unmemoized/src", "../arkui/build/unmemoized/src/peers"], + "@koalaui/interop": ["../../interop/src/arkts"], + "@koalaui/common": ["../../incremental/common/src"], + "@koalaui/compat": ["../../incremental/compat/src/arkts"], + "@koalaui/runtime": ["../../incremental/runtime/build/unmemoized/src"], + "@koalaui/harness": ["../../incremental/harness/src/arkts"] + } + }, + "exclude": [ + "build/unmemoized/src/loader.ts", + "build/unmemoized/src/test_entry.ts" + ], + "include": [ + "src/EtsHarnessApplication.ts", + "build/unmemoized/src/*.ts", + "build/unmemoized/build/generated/pages/*.ts" + ] +} diff --git a/arkoala-arkts/ets-harness/package.json b/arkoala-arkts/ets-harness/package.json index 5626a0cdf..2138ab059 100644 --- a/arkoala-arkts/ets-harness/package.json +++ b/arkoala-arkts/ets-harness/package.json @@ -10,11 +10,16 @@ ], "scripts": { "clean": "rimraf build", - "compile": "ets-tsc -b ./tsconfig.json", "compile:plugin": "cd ../../arkoala/ets-plugin && npm run compile", "compile:ets": "npm run compile:plugin && cd src/ets && ets-tsc -p ./etsconfig.json", - "test:compile": "npm run compile:ets && npm run compile", - "test:run": "npm run test:compile && mocha build/lib/src/launcher.js" + "compile:unmemoize": "ets-tsc -p ./tsconfig-unmemoize.json", + "compile:loader": "WEBPACK_NO_MINIMIZE=true webpack --config webpack.config.node.js", + "compile:abc": "fast-arktsc --input-files ./arktsconfig.json --output-dir ./build --compiler ../../incremental/tools/panda/arkts/arktsc --link-name ets-harness && ninja ${NINJA_OPTIONS} -f build/build.ninja", + "compile:all": "npm run compile:ets && npm run compile:unmemoize && npm run compile:loader && npm run compile:abc", + "test:compile": "npm run compile:all && cp -r build/ets-harness.abc ../build/", + "run:pure": "ACE_LIBRARY_PATH=../build PANDA_HOME=../../incremental/tools/panda/node_modules/@panda/sdk bash ../../incremental/tools/panda/arkts/ark build/ets-harness.abc --ark-boot-files ../build/arkoala.abc:../../incremental/harness/build/harness.abc --ark-entry-point @koalaui.ets-harness.build.unmemoized.src.loader.ETSGLOBAL::main", + "run": "ACE_LIBRARY_PATH=../build PANDA_HOME=../../incremental/tools/panda/node_modules/@panda/sdk mocha ../build/index.js panda:EtsHarness", + "test:run": "npm run test:compile && npm run run" }, "keywords": [], "dependencies": { diff --git a/arkoala-arkts/ets-harness/src/TestApplication.ts b/arkoala-arkts/ets-harness/src/EtsHarnessApplication.ts similarity index 58% rename from arkoala-arkts/ets-harness/src/TestApplication.ts rename to arkoala-arkts/ets-harness/src/EtsHarnessApplication.ts index a944a216a..38bf4ba7c 100644 --- a/arkoala-arkts/ets-harness/src/TestApplication.ts +++ b/arkoala-arkts/ets-harness/src/EtsHarnessApplication.ts @@ -1,7 +1,9 @@ -import { checkArkoalaCallbacks, checkEvents, PeerNode, setCustomEventsChecker, UserViewBuilder } from "@koalaui/arkts-arkui" +import { ArkTestComponentPeer, checkArkoalaCallbacks, checkEvents, PeerNode, setCustomEventsChecker, UserView, UserViewBuilder } from "@koalaui/arkts-arkui" import { int64 } from "@koalaui/common" -import { InteropNativeModule, KPointer } from "@koalaui/interop" +import { InteropNativeModule, KPointer, pointer, registerNativeModuleLibraryName } from "@koalaui/interop" import { ComputableState, createAnimationTimer, GlobalStateManager, memoEntry, MutableState, StateContext, StateManager } from "@koalaui/runtime" +import { ArkUINativeModule } from "#components" +import { int32 } from "@koalaui/common" setCustomEventsChecker(checkArkoalaCallbacks) @@ -58,27 +60,82 @@ function createMemoRoot( } -export class TestApplication { +export class EtsHarnessApplication { private manager: StateManager | undefined = undefined private root: ComputableState | undefined = undefined private timer: MutableState | undefined = undefined + private userView: UserView | undefined = undefined + private appId: string = "EtsHarness" - constructor() {} + constructor(app: string, userView: UserView, useNativeLog: boolean) { + this.userView = userView + this.appId = app + } - nextFrame() { - this.timer!.value = Date.now() as int64 - checkEvents() - this.updateStates(this.manager!, this.root!) + static createApplication(app: string, page: string, useNativeLog: boolean): EtsHarnessApplication { + registerNativeModuleLibraryName("InteropNativeModule", "ArkoalaNative_ark") + registerNativeModuleLibraryName("ArkUINativeModule", "ArkoalaNative_ark") + registerNativeModuleLibraryName("ArkUIGeneratedNativeModule", "ArkoalaNative_ark") + registerNativeModuleLibraryName("TestNativeModule", "ArkoalaNative_ark") + const userView = ArkUINativeModule._LoadUserView(app, page) + if (userView == undefined) throw new Error("Cannot load user view"); + return new EtsHarnessApplication(app, userView as UserView, useNativeLog) } - run( + restartWith(page: string) { + if (this.manager === undefined) { + return + } + this.manager!.reset() + const userView = ArkUINativeModule._LoadUserView(this.appId, page) + if (userView == undefined) throw new Error("Cannot load user view"); + this.userView = userView as UserView + let /** @memo */ - content: () => void - ) { + content = this.userView!.getBuilder() + this.root = createMemoRoot(this.manager!, content) + } + + start(): pointer { this.manager = GlobalStateManager.instance this.timer = createAnimationTimer(this.manager!) - this.root = createMemoRoot(this.manager, content) - this.nextFrame() + let + /** @memo */ + content = this.userView!.getBuilder() + this.root = createMemoRoot(this.manager!, content) + return this.root!.value.peer.ptr + } + + enter(arg0: int32, arg1: int32): boolean { + this.timer!.value = Date.now() as int64 + checkEvents() + this.updateStates(this.manager!, this.root!) + return true + } + + emitEvent(type: int32, target: int32, arg0: int32, arg1: int32) { + const node = PeerNode.findPeerByNativeId(target) + switch (type) { + case 1: { + if (node != undefined) { + const peer = node as ArkTestComponentPeer + peer.onChangeCallback?.() + } + break + } + case 2: { + UserView.startNativeLog(1) + break; + } + case 3: { + UserView.stopNativeLog(1) + break; + } + default: { + InteropNativeModule._NativeLog(">>> Unsupported event type: " + type) + break; + } + } } updateStates(manager: StateManager, root: ComputableState ) { diff --git a/arkoala-arkts/ets-harness/src/Page.ts b/arkoala-arkts/ets-harness/src/Page.ts new file mode 100644 index 000000000..36611291b --- /dev/null +++ b/arkoala-arkts/ets-harness/src/Page.ts @@ -0,0 +1,28 @@ +import { Case1 } from "../build/generated/pages/case1" +import { Case2 } from "../build/generated/pages/case2" +import { UserView, UserViewBuilder } from "@koalaui/arkts-arkui" + +export class EtsHarness extends UserView { + private params: String + constructor(params: String) { + super() + this.params = params + } + getBuilder(): UserViewBuilder { + switch (this.params) { + case "Case1": { + /** @memo */ + const wrapper = () => { Case1() } + return wrapper + } + case "Case2": { + /** @memo */ + const wrapper = () => { Case2() } + return wrapper + } + default: { + throw new Error("No test case provided!") + } + } + } +} diff --git a/arkoala-arkts/ets-harness/src/ets/pages/case1.ets b/arkoala-arkts/ets-harness/src/ets/pages/case1.ets index 2bd6f39d3..8594a5214 100644 --- a/arkoala-arkts/ets-harness/src/ets/pages/case1.ets +++ b/arkoala-arkts/ets-harness/src/ets/pages/case1.ets @@ -3,6 +3,10 @@ struct Case1 { @State x:number = 0 build() { - Column() {} + TestComponent({ id: 42 }).onChange(() => { + this.x++ + console.log("Case1 - value:" + this.x) + }) + .log("Case1 - value:" + this.x) } } \ No newline at end of file diff --git a/arkoala-arkts/ets-harness/src/ets/pages/case2.ets b/arkoala-arkts/ets-harness/src/ets/pages/case2.ets new file mode 100644 index 000000000..30529d8ff --- /dev/null +++ b/arkoala-arkts/ets-harness/src/ets/pages/case2.ets @@ -0,0 +1,12 @@ +//@Entry +@Component +struct Case2 { + @State x:number = 0 + build() { + TestComponent({ id: 42 }).onChange(() => { + this.x-- + console.log("Case2 - value:" + this.x) + }) + .log("Case2 - value:" + this.x) + } +} \ No newline at end of file diff --git a/arkoala-arkts/ets-harness/src/loader.ts b/arkoala-arkts/ets-harness/src/loader.ts index 2e0885861..586a48ad7 100644 --- a/arkoala-arkts/ets-harness/src/loader.ts +++ b/arkoala-arkts/ets-harness/src/loader.ts @@ -1,18 +1,96 @@ -import { Case1, Case1Options } from "../build/generated/pages/case1" -import { Assert } from "@koalaui/harness" -import { TestApplication } from "./TestApplication" - -suite("Case1", () => { - test("StateChange", () => { - // run app with Case1() - const app = new TestApplication() - app.run(Case1) - // nextFrame - // change state - // log result - // assert log - let x = 42 - let y = 24 - Assert(x == 42, "StateChange test is failed!\n expected: " + x + "\ncurrent: " + y ) - }) -}) +import { int8Array } from "@koalaui/compat" +import { entry } from "./test_entry" + +type int32 = number +type int64 = number +type float32 = number + +export type KStringPtr = string +export type KStringArrayPtr = Uint8Array +export type KInt32ArrayPtr = Int32Array +export type KFloat32ArrayPtr = Float32Array +export type KUint8ArrayPtr = Uint8Array +export type KInt = int32 +export type KUInt = int32 +export type KLong = int64 +export type KFloat = float32 +export type KBoolean = int32 +export type KPointer = bigint +export type pointer = KPointer +export type KNativePointer = KPointer + +export interface LoaderOps { + _LoadVirtualMachine(vmKind: int32, appClassPath: string, appLibPath: string): int32 + _StartApplication(appUrl: string, appParams: string): KPointer + _RunApplication(arg0: int32, arg1: int32): boolean +} + +export interface NativeControl extends LoaderOps { + _EmitEvent(type: int32, target: int32, arg0: int32, arg1: int32): void + _RestartWith(page: string): void +} + +let theModule: NativeControl | undefined = undefined + +declare const LOAD_NATIVE: object + +export function nativeModule(): NativeControl { + if (theModule) return theModule + theModule = new Object() as NativeControl + const modules = LOAD_NATIVE as any + if (!modules) + throw new Error("Cannot load native module") + for (const moduleName of Object.keys(modules)) { + Object.assign(theModule, modules[moduleName]) // TODO freeze? + } + return theModule +} + +export class AppControl { + getLog(): string { + return "" + } + emitTask(type: int32, target: int32, arg1: int32, arg2: int32): AppControl { + nativeModule()._EmitEvent(type, target, arg1, arg2) + return this + } + start(): AppControl { + nativeModule()._EmitEvent(2, -1, 0, 0) + return this + } + stop(): AppControl { + nativeModule()._EmitEvent(3, -1, 0, 0) + return this + } + nextFrame(): AppControl { + nativeModule()._RunApplication(0, 0) + return this + } + loadPage(page: string): AppControl { + nativeModule()._RestartWith(page) + return this + } +} + +let vm = "panda" +let app = "EtsHarness" +let params = "Case1" + +export function loadVM(variant: string, app: string, params: string, loopIterations?: number): void { + let classPath = __dirname + let nativePath = __dirname + + let vmKind = 2 // panda + const control = new AppControl() + + const result = nativeModule()._LoadVirtualMachine(vmKind, classPath, nativePath) + if (result == 0) { + nativeModule()._StartApplication(app, params); + control.nextFrame() + } else { + throw new Error(`Cannot start VM: ${result}`) + } + entry(control) +} + +loadVM(vm, app, params, (process && process.argv.length > 4) ? parseInt(process.argv[4]) : undefined) diff --git a/arkoala-arkts/ets-harness/src/test_entry.ts b/arkoala-arkts/ets-harness/src/test_entry.ts new file mode 100644 index 000000000..ec112fe5e --- /dev/null +++ b/arkoala-arkts/ets-harness/src/test_entry.ts @@ -0,0 +1,33 @@ +import { Assert } from "@koalaui/harness" +import { AppControl } from "./loader" + +export function entry(control: AppControl) { + suite("Case1", () => { + test("StateChange:Increment", () => { + // onChange + control + .loadPage("Case1") + .start() + .emitTask(1, 42, 0, 0).nextFrame() + .emitTask(1, 42, 0, 0).nextFrame() + .emitTask(1, 42, 0, 0).nextFrame() + .stop() + let x = 3 + Assert.equal(x, 3, "StateChange test is failed!\n expected: " + 3 + "\ncurrent: " + x) + }) + }) + suite("Case2", () => { + test("StateChange:Decrement", () => { + // onChange + control + .loadPage("Case2") + .start() + .emitTask(1, 42, 0, 0).nextFrame() + .emitTask(1, 42, 0, 0).nextFrame() + .emitTask(1, 42, 0, 0).nextFrame() + .stop() + let x = -3 + Assert.equal(x, -3, "StateChange test is failed!\n expected: " + (-3) + "\ncurrent: " + x) + }) + }) +} diff --git a/arkoala-arkts/ets-harness/tsconfig-unmemoize.json b/arkoala-arkts/ets-harness/tsconfig-unmemoize.json new file mode 100644 index 000000000..d0290c754 --- /dev/null +++ b/arkoala-arkts/ets-harness/tsconfig-unmemoize.json @@ -0,0 +1,56 @@ +{ + "extends": "@koalaui/build-common/tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "module": "CommonJS", + "plugins": [ + { + "transform": "@koalaui/compiler-plugin/build/lib/src/koala-transformer.js", + "trace": false, + "only_unmemoize": true, + "unmemoizeDir": "./build/unmemoized" + } + ], + "outDir": "./build/lib", + "baseUrl": ".", + "paths": { + "@koalaui/arkui-common": [ + "../../arkoala/arkui-common/src/arkts" + ], + "@koalaui/runtime": [ + "../../incremental/runtime/src" + ], + "@koalaui/harness": [ + "../../incremental/harness" + ], + "@koalaui/arkts-arkui": [ + "../arkui/src" + ], + "@koalaui/arkts-arkui/ohos.router": [ + "../arkui/src/ohos.router.ts" + ], + "#arkcompat": [ + "../../arkoala/arkui-common/src/arkts" + ], + "#components": [ + "../arkui/src/generated/ts" + ], + } + }, + "include": [ + "./build/generated", + "./src/Page.ts" + ], + "exclude": [ + ], + "references": [ + { "path": "../arkui" }, + { "path": "../../arkoala/arkui-common" }, + { "path": "../../arkoala/arkui-common/tsconfig-unmemoize.json" }, + { "path": "../arkui/tsconfig-unmemoize.json" }, + { "path": "../../incremental/runtime" }, + { "path": "../../incremental/compiler-plugin" }, + { "path": "../../incremental/common" }, + { "path": "../../incremental/harness" } + ] +} diff --git a/arkoala-arkts/ets-harness/tsconfig.json b/arkoala-arkts/ets-harness/tsconfig.json index da6d365a5..98d0c0fbb 100644 --- a/arkoala-arkts/ets-harness/tsconfig.json +++ b/arkoala-arkts/ets-harness/tsconfig.json @@ -1,59 +1,20 @@ { - "extends": "@koalaui/build-common/tsconfig.json", "compilerOptions": { - "rootDir": ".", - "module": "CommonJS", - "plugins": [ - { - "transform": "@koalaui/compiler-plugin/build/lib/src/koala-transformer.js", - "trace": false, - "only_unmemoize": true, - "unmemoizeDir": "./build/unmemoized" - } - ], - "outDir": "./build/lib", - "baseUrl": ".", - "paths": { - "@koalaui/arkui-common": [ - "../../arkoala/arkui-common/src/arkts" - ], - "@koalaui/runtime": [ - "../../incremental/runtime/src" - ], - "@koalaui/harness": [ - "../../incremental/harness" - ], - "@koalaui/arkts-arkui": [ - "../arkui/src" - ], - "@koalaui/arkts-arkui/ohos.router": [ - "../arkui/src/ohos.router.ts" - ], - "#arkcompat": [ - "../../arkoala/arkui-common/src/arkts" - ], - "app/*": [ - "./build/generated/*" - ] - } + "target": "es2017", + "moduleResolution": "node", + "composite": true, + "incremental": true, + "declarationMap": true, + "sourceMap": true, + "declaration": true, + "noEmitOnError": true, + "strict": true, + "skipLibCheck": true, + "removeComments": false, + "outDir": "build", }, - "files": [ - "../../incremental/tools/panda/arkts/std-lib/global.d.ts" - ], "include": [ - "./build/generated", - "./src/**/*.ts" - ], - "exclude": [ - ], - "references": [ - { "path": "../arkui" }, - { "path": "../../arkoala/arkui-common" }, - { "path": "../../arkoala/arkui-common/tsconfig-unmemoize.json" }, - { "path": "../arkui/tsconfig-unmemoize.json" }, - { "path": "../../incremental/runtime" }, - { "path": "../../incremental/compiler-plugin" }, - { "path": "../../incremental/common" }, - { "path": "../../incremental/harness" } + "src/loader.ts", + "src/test_entry.ts" ] } diff --git a/arkoala-arkts/ets-harness/webpack.config.node.js b/arkoala-arkts/ets-harness/webpack.config.node.js new file mode 100644 index 000000000..47c01bb62 --- /dev/null +++ b/arkoala-arkts/ets-harness/webpack.config.node.js @@ -0,0 +1,104 @@ +/* + * 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. + */ + +const path = require("path") +const CopyPlugin = require("copy-webpack-plugin") +const DefinePlugin = require("webpack").DefinePlugin + +const minimize = !process.env.WEBPACK_NO_MINIMIZE + +/** @returns {import("webpack").WebpackOptionsNormalized} */ +const makeConfig = ({ os, arch, tsconfig }) => ({ + target: "node", + entry: `./src/loader.ts`, + output: { + filename: `index.js`, + path: path.resolve(__dirname, `../build`), + libraryTarget: "commonjs2", + }, + resolve: { + extensions: [".ts", ".node", "..."] + }, + + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + options: { + "projectReferences": true, + configFile: tsconfig, + compiler: "@koalaui/ets-tsc" + } + }, + ] + }, + + plugins: [ + new CopyPlugin({ + patterns: copyPluginPatterns(os, arch) + }), + new DefinePlugin({ + 'LOAD_NATIVE': `require("./ArkoalaLoader.node")` + }) + ], + + mode: minimize ? "production" : "development", + devtool: minimize ? false : "inline-source-map" +}) + +function getExt(os) { + switch (os) { + case "linux": return "so" + case "windows": return "dll" + case "macos": return "dylib" + default: return "so" + } +} + +function copyPluginPatterns(os, arch) { + const patterns = [] + patterns.push({ + from: path.resolve(`../../arkoala/framework/native/build-node-host-vmloader/ArkoalaLoader.node`), + to: "." + }) + patterns.push({ + from: path.resolve(`../../arkoala/framework/native/build-node-host-vmloader/libvmloader.${ getExt(os) }`), + to: "." + }) + patterns.push({ + from: path.resolve(`../../arkoala/framework/native/build-panda-host/libArkoalaNative_${os}_${arch}_ark.${ getExt(os) }`), + to: `./libArkoalaNative_ark.${ getExt(os) }` + }) + patterns.push({ + from: path.resolve(`../../arkoala/framework/native/build-panda-host/libace_compatible_mock.${ getExt(os) }`), + to: `./libace_compatible_mock.${ getExt(os) }` + }) + return patterns +} + +module.exports = env => { + const oses = { + 'win32': 'windows', + 'darwin': 'macos', + } + + const os = env.os || oses[process.platform] || process.platform + const arch = (env.arch || process.arch) + + const tsconfig = env.tsconfig || "" + + return makeConfig({os, arch, tsconfig}) +} diff --git a/arkoala/framework/native/src/generated/bridge_custom.cc b/arkoala/framework/native/src/generated/bridge_custom.cc index ea077265d..75972006f 100644 --- a/arkoala/framework/native/src/generated/bridge_custom.cc +++ b/arkoala/framework/native/src/generated/bridge_custom.cc @@ -465,6 +465,8 @@ KVMObjectHandle impl_LoadUserView(KVMContext vm, const KStringPtr& viewClass, co // TODO: hack, fix it! if (className == "ViewLoaderApp") { className = "Page.App"; + } else if (className == "EtsHarness") { + className = "@koalaui.ets-harness.build.unmemoized.src.Page.EtsHarness"; } else { className = "@koalaui.user.build.unmemoized.src.Page." + className; } diff --git a/arkoala/framework/src/Application.ts b/arkoala/framework/src/Application.ts index c0975259a..07af09551 100644 --- a/arkoala/framework/src/Application.ts +++ b/arkoala/framework/src/Application.ts @@ -127,7 +127,7 @@ function uiRoot( ui: () => void, control: ArkoalaControl ): State { - const node: PeerNode = rootConstruct() + const node = rootConstruct() const root: ComputableState = memoRoot(node!, () => withControl(ui, control) diff --git a/interop/src/arkts/InteropNativeModule.sts b/interop/src/arkts/InteropNativeModule.sts index 9b74e2e83..87d73e4c8 100644 --- a/interop/src/arkts/InteropNativeModule.sts +++ b/interop/src/arkts/InteropNativeModule.sts @@ -38,5 +38,6 @@ export class InteropNativeModule { native static _StartApplication(appUrl: string, appParams: string): KPointer native static _EmitEvent(eventType: int32, target: int32, arg0: int32, arg1: int32): void native static _CallForeignVM(context:KPointer, callback: int32, data: KUint8ArrayPtr, dataLength: int32): int32 + native static _RestartWith(page: string): void } diff --git a/interop/src/cpp/common-interop.cc b/interop/src/cpp/common-interop.cc index ff493c06e..c522cc84a 100644 --- a/interop/src/cpp/common-interop.cc +++ b/interop/src/cpp/common-interop.cc @@ -260,6 +260,7 @@ typedef KInt (*LoadVirtualMachine_t)(KInt vmKind, const char* classPath, const c typedef KNativePointer (*StartApplication_t)(const char* appUrl, const char* appParams); typedef KBoolean (*RunApplication_t)(const KInt arg0, const KInt arg1); typedef void (*EmitEvent_t)(const KInt type, const KInt target, const KInt arg0, const KInt arg1); +typedef void (*RestartWith_t)(const char* page); void* getImpl(const char* path, const char* name) { static void* lib = nullptr; @@ -316,6 +317,13 @@ void impl_EmitEvent(const KInt type, const KInt target, const KInt arg0, const K } KOALA_INTEROP_V4(EmitEvent, KInt, KInt, KInt, KInt) +void impl_RestartWith(const KStringPtr& page) { + static RestartWith_t impl = nullptr; + if (!impl) impl = reinterpret_cast(getImpl(nullptr, "RestartWith")); + impl(page.c_str()); +} +KOALA_INTEROP_V1(RestartWith, KStringPtr) + static Callback_Caller_t g_callbackCaller = nullptr; void setCallbackCaller(Callback_Caller_t callbackCaller) { g_callbackCaller = callbackCaller; diff --git a/interop/src/cpp/vmloader.cc b/interop/src/cpp/vmloader.cc index dcd9bd6e2..d409a8780 100644 --- a/interop/src/cpp/vmloader.cc +++ b/interop/src/cpp/vmloader.cc @@ -140,6 +140,7 @@ struct VMEntry { void* app; void* enter; void* emitEvent; + void* restartWith; ForeignVMContext foreignVMContext; }; @@ -295,6 +296,8 @@ struct AppInfo { const char* enterMethodSig; const char* emitEventMethodName; const char* emitEventMethodSig; + const char* restartWithMethodName; + const char* restartWithMethodSig; }; #ifdef KOALA_JNI @@ -323,15 +326,29 @@ const AppInfo pandaAppInfo = { "emitEvent", "IIII:V", }; +const AppInfo harnessAppInfo = { + "@koalaui/ets-harness/src/EtsHarnessApplication/EtsHarnessApplication", + "createApplication", + "Lstd/core/String;Lstd/core/String;Z:L@koalaui/ets-harness/src/EtsHarnessApplication/EtsHarnessApplication;", + "start", + ":J", + "enter", + "II:Z", + "emitEvent", + "IIII:V", + "restartWith", + "Lstd/core/String;:V" +}; #endif extern "C" DLL_EXPORT KNativePointer StartApplication(const char* appUrl, const char* appParams) { + const auto isTestEnv = std::string(appUrl) == "EtsHarness"; const AppInfo* appInfo = #ifdef KOALA_JNI (g_vmEntry.vmKind == JAVA_VM_KIND) ? &javaAppInfo : #endif #ifdef KOALA_ETS_NAPI - (g_vmEntry.vmKind == PANDA_VM_KIND) ? &pandaAppInfo : + (g_vmEntry.vmKind == PANDA_VM_KIND) ? isTestEnv ? &harnessAppInfo : &pandaAppInfo : #endif nullptr; @@ -431,6 +448,17 @@ extern "C" DLL_EXPORT KNativePointer StartApplication(const char* appUrl, const } return nullptr; } + if (isTestEnv) { + g_vmEntry.restartWith = (void*)(etsEnv->Getp_method(appClass, appInfo->restartWithMethodName, appInfo->restartWithMethodSig)); + if (!g_vmEntry.restartWith) { + LOGE("Cannot find enter restartWith %" LOG_PUBLIC "s", appInfo->restartWithMethodSig); + if (etsEnv->ErrorCheck()) { + etsEnv->ErrorDescribe(); + etsEnv->ErrorClear(); + } + return nullptr; + } + } // TODO: pass app entry point! return reinterpret_cast(etsEnv->CallLongMethod((ets_object)(app), start)); } @@ -527,6 +555,46 @@ extern "C" DLL_EXPORT void EmitEvent(const KInt type, const KInt target, const K #endif } +extern "C" DLL_EXPORT void RestartWith(const char* page) { +#ifdef KOALA_JNI + if (g_vmEntry.vmKind == JAVA_VM_KIND) { + JNIEnv* jEnv = (JNIEnv*)(g_vmEntry.env); + if (!g_vmEntry.restartWith) { + LOGE("Cannot find restartWith method"); + return; + } + jEnv->CallVoidMethod( + (jobject)(g_vmEntry.app), + (jmethodID)(g_vmEntry.restartWith), + jEnv->NewStringUTF(page) + ); + if (jEnv->ExceptionCheck()) { + jEnv->ExceptionDescribe(); + jEnv->ExceptionClear(); + } + } +#endif +#ifdef KOALA_ETS_NAPI + if (g_vmEntry.vmKind == PANDA_VM_KIND) { + EtsEnv* etsEnv = (EtsEnv*)(g_vmEntry.env); + if (!g_vmEntry.restartWith) { + LOGE("Cannot find restartWith method"); + return; + } + etsEnv->CallVoidMethod( + (ets_object)(g_vmEntry.app), + (ets_method)(g_vmEntry.restartWith), + etsEnv->NewStringUTF(page) + ); + if (etsEnv->ErrorCheck()) { + LOGE("Calling restartWith() method gave an error"); + etsEnv->ErrorDescribe(); + etsEnv->ErrorClear(); + } + } + #endif +} + void traverseDir(std::string root, std::vector& paths, int depth) { if (depth >= 50) { return; -- Gitee