已合并
Add Cangjie-ArkTS-FFI Examples for 6.0 #392
Nixiaoxiang创建于 2025年12月23日
Add Cangjie-ArkTS-FFI Examples for 6.0 #392
已合并
共 35 个文件变更+1272-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +/node_modules | ||
| 2 | +/oh_modules | ||
| 3 | +/local.properties | ||
| 4 | +/.idea | ||
| 5 | +**/build | ||
| 6 | +/.hvigor | ||
| 7 | +.cxx | ||
| 8 | +/.clangd | ||
| 9 | +/.clang-format | ||
| 10 | +/.clang-tidy | ||
| 11 | +**/.test | ||
| 12 | +/.appanalyzer | ||
| 13 | +**/cj_res | ||
| 14 | +**/*.cj.macrocall | ||
| 15 | +**/ability_mainability_entry.cj | ||
| 16 | +**/module_**_entry.cj | ||
| 17 | +**/IDL_Dependencies_List~ | ||
| 18 | +**/build-script-cache | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +{ | ||
| 2 | + "app": { | ||
| 3 | + "bundleName": "com.example.cjcallarktsdemo", | ||
| 4 | + "vendor": "example", | ||
| 5 | + "versionCode": 1000000, | ||
| 6 | + "versionName": "1.0.0", | ||
| 7 | + "icon": "$media:calendar", | ||
| 8 | + "label": "$string:app_name" | ||
| 9 | + } | ||
| 10 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +{ | ||
| 2 | + "string": [ | ||
| 3 | + { | ||
| 4 | + "name": "app_name", | ||
| 5 | + "value": "CJCallArkTSDemo" | ||
| 6 | + } | ||
| 7 | + ] | ||
| 8 | +} | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | +{ | ||
| 2 | + "app": { | ||
| 3 | + "signingConfigs": [], | ||
| 4 | + "products": [ | ||
| 5 | + { | ||
| 6 | + "name": "default", | ||
| 7 | + "signingConfig": "default", | ||
| 8 | + "targetSdkVersion": "6.0.0(20)", | ||
| 9 | + "compatibleSdkVersion": "6.0.0(20)", | ||
| 10 | + "runtimeOS": "HarmonyOS", | ||
| 11 | + "buildOption": { | ||
| 12 | + "strictMode": { | ||
| 13 | + "caseSensitiveCheck": true, | ||
| 14 | + "useNormalizedOHMUrl": true | ||
| 15 | + } | ||
| 16 | + } | ||
| 17 | + } | ||
| 18 | + ], | ||
| 19 | + "buildModeSet": [ | ||
| 20 | + { | ||
| 21 | + "name": "debug", | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + "name": "release" | ||
| 25 | + } | ||
| 26 | + ] | ||
| 27 | + }, | ||
| 28 | + "modules": [ | ||
| 29 | + { | ||
| 30 | + "name": "entry", | ||
| 31 | + "srcPath": "./entry", | ||
| 32 | + "targets": [ | ||
| 33 | + { | ||
| 34 | + "name": "default", | ||
| 35 | + "applyToProducts": [ | ||
| 36 | + "default" | ||
| 37 | + ] | ||
| 38 | + } | ||
| 39 | + ] | ||
| 40 | + } | ||
| 41 | + ] | ||
| 42 | +} | ||
| @@ -0,0 +1,33 @@ | |||
| 1 | +{ | ||
| 2 | + "files": [ | ||
| 3 | + "**/*.ets", | ||
| 4 | + "**/*.cj" | ||
| 5 | + ], | ||
| 6 | + "ignore": [ | ||
| 7 | + "**/src/ohosTest/**/*", | ||
| 8 | + "**/src/test/**/*", | ||
| 9 | + "**/src/mock/**/*", | ||
| 10 | + "**/node_modules/**/*", | ||
| 11 | + "**/oh_modules/**/*", | ||
| 12 | + "**/build/**/*", | ||
| 13 | + "**/.preview/**/*" | ||
| 14 | + ], | ||
| 15 | + "ruleSet": [ | ||
| 16 | + "plugin:@performance/recommended", | ||
| 17 | + "plugin:@typescript-eslint/recommended" | ||
| 18 | + ], | ||
| 19 | + "rules": { | ||
| 20 | + "@security/no-unsafe-aes": "error", | ||
| 21 | + "@security/no-unsafe-hash": "error", | ||
| 22 | + "@security/no-unsafe-mac": "warn", | ||
| 23 | + "@security/no-unsafe-dh": "error", | ||
| 24 | + "@security/no-unsafe-dsa": "error", | ||
| 25 | + "@security/no-unsafe-ecdsa": "error", | ||
| 26 | + "@security/no-unsafe-rsa-encrypt": "error", | ||
| 27 | + "@security/no-unsafe-rsa-sign": "error", | ||
| 28 | + "@security/no-unsafe-rsa-key": "error", | ||
| 29 | + "@security/no-unsafe-dsa-key": "error", | ||
| 30 | + "@security/no-unsafe-dh-key": "error", | ||
| 31 | + "@security/no-unsafe-3des": "error" | ||
| 32 | + } | ||
| 33 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +/node_modules | ||
| 2 | +/oh_modules | ||
| 3 | +/.preview | ||
| 4 | +/build | ||
| 5 | +/.cxx | ||
| 6 | +/.test | ||
| 7 | +**/IDL_Dependencies_List~ | ||
| 8 | +**/build-script-cache | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | +{ | ||
| 2 | + "apiType": "stageMode", | ||
| 3 | + "buildOption": { | ||
| 4 | + "cangjieOptions": { | ||
| 5 | + "path": "./cjpm.toml" | ||
| 6 | + }, | ||
| 7 | + "nativeLib": { | ||
| 8 | + "filter": { | ||
| 9 | + "enableOverride": true | ||
| 10 | + } | ||
| 11 | + } | ||
| 12 | + }, | ||
| 13 | + "buildOptionSet": [ | ||
| 14 | + ], | ||
| 15 | + "targets": [ | ||
| 16 | + { | ||
| 17 | + "name": "default" | ||
| 18 | + } | ||
| 19 | + ] | ||
| 20 | +} | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +version = 0 | ||
| 2 | + | ||
| 3 | +[requires] | ||
| @@ -0,0 +1,35 @@ | |||
| 1 | +[package] | ||
| 2 | + cjc-version = "0.48.2" | ||
| 3 | + compile-option = "--dy-std --cfg=\"${COMPILE_CONDITION_ENTRY}\"" | ||
| 4 | + description = "CangjieUI Application" | ||
| 5 | + link-option = "" | ||
| 6 | + name = "ohos_app_cangjie_entry" | ||
| 7 | + output-type = "dynamic" | ||
| 8 | + src-dir = "./src/main/cangjie" | ||
| 9 | + target-dir = "" | ||
| 10 | + version = "1.0.0" | ||
| 11 | + package-configuration = {} | ||
| 12 | + scripts = {} | ||
| 13 | + | ||
| 14 | +[profile] | ||
| 15 | + [profile.build] | ||
| 16 | + incremental = true | ||
| 17 | + lto = "" | ||
| 18 | + [profile.build.combined] | ||
| 19 | + ohos_app_cangjie_entry = "dynamic" | ||
| 20 | + [profile.customized-option] | ||
| 21 | + debug = "-g -Woff all -Won apilevel-check" | ||
| 22 | + release = "--fast-math -O2 -s -Woff all -Won apilevel-check" | ||
| 23 | + [profile.test] | ||
| 24 | + | ||
| 25 | +[target.aarch64-linux-ohos] | ||
| 26 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/aarch64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 27 | +[target.aarch64-linux-ohos.bin-dependencies] | ||
| 28 | + path-option = ["${AARCH64_LIBS}", "${AARCH64_MACRO_LIBS}", "${AARCH64_KIT_LIBS}"] | ||
| 29 | + package-option = {} | ||
| 30 | + | ||
| 31 | +[target.x86_64-linux-ohos] | ||
| 32 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/x86_64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 33 | +[target.x86_64-linux-ohos.bin-dependencies] | ||
| 34 | + path-option = ["${X86_64_OHOS_LIBS}", "${X86_64_OHOS_MACRO_LIBS}", "${X86_64_OHOS_KIT_LIBS}"] | ||
| 35 | + | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; | ||
| 2 | + | ||
| 3 | +export default { | ||
| 4 | + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ | ||
| 5 | + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ | ||
| 6 | +} | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +{ | ||
| 2 | + "name": "entry", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "Please describe the basic information.", | ||
| 5 | + "main": "", | ||
| 6 | + "author": "", | ||
| 7 | + "license": "", | ||
| 8 | + "dependencies": {} | ||
| 9 | +} | ||
| 10 | + | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +package ohos_app_cangjie_entry | ||
| 2 | + | ||
| 3 | +import ohos.ability.AbilityStage | ||
| 4 | +import ohos_app_cangjie_entry.utils.appLog | ||
| 5 | + | ||
| 6 | +class MyAbilityStage <: AbilityStage { | ||
| 7 | + public override func onCreate(): Unit { | ||
| 8 | + appLog.info("MyAbilityStage onCreated.") | ||
| 9 | + } | ||
| 10 | +} | ||
| @@ -0,0 +1,27 @@ | |||
| 1 | +package ohos_app_cangjie_entry | ||
| 2 | + | ||
| 3 | +import ohos.base.* | ||
| 4 | +import ohos.ability.* | ||
| 5 | +import ohos.window.* | ||
| 6 | +import ohos_app_cangjie_entry.services.CalendarService | ||
| 7 | + | ||
| 8 | +class MainAbility <: UIAbility { | ||
| 9 | + public init() { | ||
| 10 | + super() | ||
| 11 | + registerSelf() | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + public override func onCreate(want: Want, launchParam: LaunchParam): Unit { | ||
| 15 | + AppLog.info("MainAbility OnCreated.${want.abilityName}") | ||
| 16 | + match (launchParam.launchReason) { | ||
| 17 | + case LaunchReason.START_ABILITY => AppLog.info("START_ABILITY") | ||
| 18 | + case _ => () | ||
| 19 | + } | ||
| 20 | + CalendarService.initialize(context) | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public override func onWindowStageCreate(windowStage: WindowStage): Unit { | ||
| 24 | + AppLog.info("MainAbility onWindowStageCreate.") | ||
| 25 | + windowStage.loadContent("EntryView") | ||
| 26 | + } | ||
| 27 | +} | ||
| @@ -0,0 +1,243 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/12/22 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry.services | ||
| 5 | + | ||
| 6 | +import std.time.DateTime | ||
| 7 | +import std.collection.ArrayList | ||
| 8 | + | ||
| 9 | +import ohos.ark_interop.* | ||
| 10 | +import ohos.base.AsyncError | ||
| 11 | +import ohos.concurrency.launch | ||
| 12 | +import ohos.ability_access_ctrl.* | ||
| 13 | +import ohos.ability.UIAbilityContext | ||
| 14 | +import ohos.ability.getStageContext | ||
| 15 | +import ohos.ark_interop_helper.getJSContext | ||
| 16 | + | ||
| 17 | +import ohos_app_cangjie_entry.utils.appLog | ||
| 18 | + | ||
| 19 | +public class EventInfo <: Hashable & Equatable<EventInfo> { | ||
| 20 | + public let id: Int64 | ||
| 21 | + public let title: String | ||
| 22 | + public let startTime: Int64 | ||
| 23 | + | ||
| 24 | + public init(id: Int64, title: String, startTime: Int64) { | ||
| 25 | + this.id = id | ||
| 26 | + this.title = title | ||
| 27 | + this.startTime = startTime | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + // 实现Hashable和Equatable接口,以便在ForEach等组件中使用 | ||
| 31 | + public func hashCode(): Int64 { | ||
| 32 | + this.id | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public operator func ==(that: EventInfo): Bool { | ||
| 36 | + this.id == that.id | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public operator func !=(that: EventInfo): Bool { | ||
| 40 | + this.id != that.id | ||
| 41 | + } | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +public class CalendarService { | ||
| 45 | + private static var abilityCtx: ?UIAbilityContext = None | ||
| 46 | + private static var runtime: JSRuntime = JSRuntime() | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 初始化服务,获取并保存 UIAbilityContext | ||
| 50 | + */ | ||
| 51 | + public static func initialize(abilityContext: UIAbilityContext): Unit { | ||
| 52 | + abilityCtx = abilityContext | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * 申请日历读写权限 | ||
| 57 | + * 以下设置必须被添加到 "module"@module.json5 | ||
| 58 | + "requestPermissions": [ | ||
| 59 | + { | ||
| 60 | + "name": "ohos.permission.READ_CALENDAR", | ||
| 61 | + "reason": "$string:app_name", | ||
| 62 | + "usedScene": { | ||
| 63 | + "when": "always" | ||
| 64 | + } | ||
| 65 | + }, | ||
| 66 | + { | ||
| 67 | + "name": "ohos.permission.WRITE_CALENDAR", | ||
| 68 | + "reason": "$string:app_name", | ||
| 69 | + "usedScene": { | ||
| 70 | + "when": "always" | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + ] | ||
| 74 | + */ | ||
| 75 | + public static func requestPermissions(onComplete: (Bool) -> Unit): Unit { | ||
| 76 | + let abiCtx: UIAbilityContext = abilityCtx ?? throw Exception("failed to fetch UIAbilityContext, run initialize() first.") | ||
| 77 | + | ||
| 78 | + let permissions: Array<String> = [ | ||
| 79 | + "ohos.permission.READ_CALENDAR", | ||
| 80 | + "ohos.permission.WRITE_CALENDAR" | ||
| 81 | + ] | ||
| 82 | + | ||
| 83 | + let atManager: AtManager = AbilityAccessCtrl.createAtManager() | ||
| 84 | + | ||
| 85 | + atManager.requestPermissionsFromUser(getStageContext(abiCtx), permissions) { | ||
| 86 | + err: ?AsyncError, data: ?PermissionRequestResult => | ||
| 87 | + if (err.isSome() || data.isNone()) { | ||
| 88 | + appLog.error("failed to require permissions.") | ||
| 89 | + launch { onComplete(false) } | ||
| 90 | + return | ||
| 91 | + } | ||
| 92 | + let result: PermissionRequestResult = data.getOrThrow() | ||
| 93 | + var allGranted: Bool = true | ||
| 94 | + for (i in 0..result.permissions.size) { | ||
| 95 | + let p: String = result.permissions[i] | ||
| 96 | + let r: Int32 = result.authResults[i] | ||
| 97 | + if (r == 0) { | ||
| 98 | + appLog.info("permission ${p} is required.") | ||
| 99 | + } else { | ||
| 100 | + appLog.warn("permission ${p} is rejected.") | ||
| 101 | + allGranted = false | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | + let res: Bool = allGranted | ||
| 105 | + launch { onComplete(res) } | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + public static func addEvent(title: String, description: String, onComplete: (Int64) -> Unit): Unit { | ||
| 110 | + appLog.info("try to add an event: ${title}") | ||
| 111 | + let callback = { ctx: JSContext, calendar: JSObject => | ||
| 112 | + // 调用 ArkTS 类成员方法 Calendar.addEvent 来在日历中添加事件。 | ||
| 113 | + // 根据 Event 的 API 文档,创建一个事件对象,作为传入的参数。 | ||
| 114 | + let eventObj = createEventObject(title, description, ctx) | ||
| 115 | + // 该方法是异步方法,需要异步回调。 | ||
| 116 | + let promise = calendar.callMethod("addEvent", eventObj.toJSValue()).asPromise() | ||
| 117 | + promise.then( | ||
| 118 | + // onFulfilled | ||
| 119 | + ctx.function { innerCtx: JSContext, innerCallInfo: JSCallInfo => | ||
| 120 | + // 从 JSCallInfo 中获取方法返回的事件 ID,是整型的。 | ||
| 121 | + let eventId = Int64(innerCallInfo[0].asNumber().toFloat64()) | ||
| 122 | + appLog.info("successfully added an event. event id: ${eventId}") | ||
| 123 | + onComplete(eventId) | ||
| 124 | + innerCtx.undefined().toJSValue() | ||
| 125 | + }, | ||
| 126 | + // onRejected | ||
| 127 | + onRejected: ctx.function { innerCtx: JSContext, _: JSCallInfo => | ||
| 128 | + appLog.error("failed to add an event!") | ||
| 129 | + onComplete(-1) | ||
| 130 | + innerCtx.undefined().toJSValue() | ||
| 131 | + } | ||
| 132 | + ) | ||
| 133 | + } | ||
| 134 | + withCalendar(onSuccess: callback, onFailure: { => onComplete(-1) }) | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + public static func getEvents(onResult: (ArrayList<EventInfo>) -> Unit): Unit { | ||
| 138 | + appLog.info("try to get events...") | ||
| 139 | + let callback = { ctx: JSContext, calendar: JSObject => | ||
| 140 | + let promise: JSPromise = calendar.callMethod("getEvents").asPromise() | ||
| 141 | + promise.then( | ||
| 142 | + ctx.function { innerCtx: JSContext, innerCallinfo: JSCallInfo => | ||
| 143 | + let jsEvents: JSArray = innerCallinfo[0].asArray() | ||
| 144 | + let events: ArrayList<EventInfo> = ArrayList<EventInfo>() | ||
| 145 | + | ||
| 146 | + for (i in 0..jsEvents.size) { | ||
| 147 | + let jsEvent: JSObject = jsEvents[i].asObject() | ||
| 148 | + let id: Int64 = Int64(jsEvent.getProperty("id").asNumber().toFloat64()) | ||
| 149 | + let title: String = jsEvent.getProperty("title").asString().toString() | ||
| 150 | + let startTime: Int64 = Int64(jsEvent.getProperty("startTime").asNumber().toFloat64()) | ||
| 151 | + events.add(EventInfo(id, title, startTime)) | ||
| 152 | + } | ||
| 153 | + appLog.info("successfully got ${events.size} events.") | ||
| 154 | + onResult(events) | ||
| 155 | + innerCtx.undefined().toJSValue() | ||
| 156 | + }, | ||
| 157 | + onRejected: ctx.function { innerCtx: JSContext, _: JSCallInfo => | ||
| 158 | + appLog.error("failed to get events!") | ||
| 159 | + onResult(ArrayList<EventInfo>()) | ||
| 160 | + innerCtx.undefined().toJSValue() | ||
| 161 | + } | ||
| 162 | + ) | ||
| 163 | + } | ||
| 164 | + withCalendar(onSuccess: callback, onFailure: { => onResult(ArrayList<EventInfo>()) }) | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + public static func deleteEvent(eventId: Int64, onComplete: (Bool) -> Unit): Unit { | ||
| 168 | + appLog.info("try to delete event: ${eventId}") | ||
| 169 | + let callback = { ctx: JSContext, calendar: JSObject => | ||
| 170 | + let eventIdValue: JSValue = ctx.number(Float64(eventId)).toJSValue() | ||
| 171 | + let promise: JSPromise = calendar.callMethod("deleteEvent", eventIdValue).asPromise() | ||
| 172 | + promise.then( | ||
| 173 | + ctx.function { innerCtx: JSContext, _: JSCallInfo => | ||
| 174 | + appLog.info("successfully deleted event id: ${eventId}") | ||
| 175 | + onComplete(true) | ||
| 176 | + innerCtx.undefined().toJSValue() | ||
| 177 | + }, | ||
| 178 | + onRejected: ctx.function { innerCtx: JSContext, _: JSCallInfo => | ||
| 179 | + appLog.error("failed to delete event id: ${eventId}") | ||
| 180 | + onComplete(false) | ||
| 181 | + innerCtx.undefined().toJSValue() | ||
| 182 | + } | ||
| 183 | + ) | ||
| 184 | + } | ||
| 185 | + withCalendar(onSuccess: callback, onFailure: { => onComplete(false) }) | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + private static func withCalendar(onSuccess!: (JSContext, JSObject) -> Unit, onFailure!: () -> Unit): Unit { | ||
| 189 | + let localAbilityCtx = abilityCtx ?? throw Exception("AbilityContext is not initialized!") | ||
| 190 | + | ||
| 191 | + let jsContext: JSContext = runtime.mainContext // 互操作上下文 | ||
| 192 | + | ||
| 193 | + // 加载 calendarManager 模块 | ||
| 194 | + let module: JSValue = jsContext.requireSystemNativeModule("calendarManager") | ||
| 195 | + if (module.isUndefined()) { | ||
| 196 | + appLog.error("load module 'calendarManager' failed!") | ||
| 197 | + onFailure() | ||
| 198 | + return | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + // 调用 JS 同步接口 getCalendarManager 获取 CalendarManager 对象 | ||
| 202 | + let tsContext: JSValue = getJSContext(runtime, localAbilityCtx) // 应用上下文 | ||
| 203 | + let callResult: JSValue = module.asObject().callMethod("getCalendarManager", tsContext) | ||
| 204 | + if (callResult.isUndefined()) { | ||
| 205 | + appLog.error("getCalendarManager failed!") | ||
| 206 | + onFailure() | ||
| 207 | + return | ||
| 208 | + } | ||
| 209 | + let manager: JSObject = callResult.asObject() | ||
| 210 | + | ||
| 211 | + // 调用 JS 异步接口 getCalendar,得到 JS Promise 对象,最终在 then 回调中获取 Calendar 对象 | ||
| 212 | + let calendarPromise: JSPromise = manager.callMethod("getCalendar").asPromise() | ||
| 213 | + calendarPromise.then( | ||
| 214 | + // onFulfilled | ||
| 215 | + jsContext.function { ctx, callInfo => | ||
| 216 | + let calendar: JSObject = callInfo[0].asObject() | ||
| 217 | + onSuccess(ctx, calendar) | ||
| 218 | + ctx.undefined().toJSValue() | ||
| 219 | + }, | ||
| 220 | + // onRejected | ||
| 221 | + onRejected: jsContext.function { ctx, _ => | ||
| 222 | + appLog.error("failed to get calendar!") | ||
| 223 | + onFailure() | ||
| 224 | + ctx.undefined().toJSValue() | ||
| 225 | + } | ||
| 226 | + ) | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + private static func createEventObject(title: String, description: String, ctx: JSContext): JSObject { | ||
| 230 | + let eventObj: JSObject = ctx.object() | ||
| 231 | + | ||
| 232 | + let curTimeStamp: Int64 = DateTime.now().toUnixTimeStamp().toMilliseconds() | ||
| 233 | + let oneHour: Int64 = 3600 * 1000 | ||
| 234 | + | ||
| 235 | + eventObj.setProperty("title", ctx.string(title).toJSValue()) | ||
| 236 | + eventObj.setProperty("description", ctx.string(description).toJSValue()) | ||
| 237 | + eventObj.setProperty("type", ctx.number(0.0).toJSValue()) // 0 代表 EventType.NORMAL | ||
| 238 | + eventObj.setProperty("startTime", ctx.number(Float64(curTimeStamp)).toJSValue()) | ||
| 239 | + eventObj.setProperty("endTime", ctx.number(Float64(curTimeStamp + oneHour)).toJSValue()) | ||
| 240 | + | ||
| 241 | + eventObj | ||
| 242 | + } | ||
| 243 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/12/22 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry.utils | ||
| 5 | + | ||
| 6 | +import ohos.hilog.HilogChannel | ||
| 7 | + | ||
| 8 | +public let appLog: HilogChannel = HilogChannel(0, 0, "CJ_DEMO") | ||
| @@ -0,0 +1,69 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/12/22 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry.views | ||
| 5 | + | ||
| 6 | +import ohos.base.* | ||
| 7 | +import ohos.component.* | ||
| 8 | + | ||
| 9 | +extend Column { | ||
| 10 | + // 卡片容器样式 | ||
| 11 | + func sectionCard() { | ||
| 12 | + this.width(100.percent) | ||
| 13 | + .padding(20) | ||
| 14 | + .borderRadius(15) | ||
| 15 | + .margin(bottom: 15) | ||
| 16 | + .backgroundColor(Color.WHITE) | ||
| 17 | + .alignItems(HorizontalAlign.Start) | ||
| 18 | + } | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +extend Text { | ||
| 22 | + // 标题样式 | ||
| 23 | + func sectionTitle() { | ||
| 24 | + this.fontSize(20) | ||
| 25 | + .fontWeight(FontWeight.Medium) | ||
| 26 | + .margin(bottom: 5) | ||
| 27 | + } | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +extend TextInput { | ||
| 31 | + // 文本输入框样式 | ||
| 32 | + func inputFieldStyle() { | ||
| 33 | + this.fontSize(16) | ||
| 34 | + .backgroundColor(0xF1F3F5) | ||
| 35 | + .borderRadius(10) | ||
| 36 | + .padding(10) | ||
| 37 | + .margin(bottom: 10) | ||
| 38 | + .height(50) | ||
| 39 | + } | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +extend Button { | ||
| 43 | + // 操作按钮样式 | ||
| 44 | + func actionButton() { | ||
| 45 | + this.width(100.percent) | ||
| 46 | + .height(50) | ||
| 47 | + .fontSize(18) | ||
| 48 | + .fontWeight(FontWeight.Medium) | ||
| 49 | + .borderRadius(12) | ||
| 50 | + } | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +extend Divider { | ||
| 54 | + // 标准分割线样式 | ||
| 55 | + func standardDivider() { | ||
| 56 | + this.strokeWidth(1) | ||
| 57 | + .color(0xEFEFEF) | ||
| 58 | + .margin(top: 10, bottom: 10) | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +extend ListItem { | ||
| 63 | + // 列表项样式 | ||
| 64 | + func listItemStyle() { | ||
| 65 | + this.padding(10) | ||
| 66 | + .borderRadius(8) | ||
| 67 | + .backgroundColor(0xFAFAFA) | ||
| 68 | + } | ||
| 69 | +} | ||
| @@ -0,0 +1,151 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/12/22 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry.views | ||
| 5 | + | ||
| 6 | +import std.collection.ArrayList | ||
| 7 | + | ||
| 8 | +import ohos.base.* | ||
| 9 | +import ohos.component.* | ||
| 10 | +import ohos.state_manage.* | ||
| 11 | +import ohos.state_macro_manage.* | ||
| 12 | +import ohos_app_cangjie_entry.services.* | ||
| 13 | +import ohos_app_cangjie_entry.utils.appLog | ||
| 14 | + | ||
| 15 | +@Entry | ||
| 16 | +@Component | ||
| 17 | +public class EntryView { | ||
| 18 | + @State var eventTitle: String = "" | ||
| 19 | + @State var eventDescription: String = "" | ||
| 20 | + @State var eventList: ObservedArrayList<EventInfo> = ObservedArrayList<EventInfo>() | ||
| 21 | + @State var isLoading: Bool = true | ||
| 22 | + | ||
| 23 | + let scroller: Scroller = Scroller() | ||
| 24 | + | ||
| 25 | + protected override func aboutToAppear() { | ||
| 26 | + loadEvents() | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + func loadEvents() { | ||
| 30 | + isLoading = true | ||
| 31 | + CalendarService.requestPermissions { granted: Bool => | ||
| 32 | + if (granted) { | ||
| 33 | + CalendarService.getEvents() { events: ArrayList<EventInfo> => | ||
| 34 | + eventList.clear() | ||
| 35 | + for (event in events) { | ||
| 36 | + eventList.append(event) | ||
| 37 | + } | ||
| 38 | + isLoading = false | ||
| 39 | + } | ||
| 40 | + } else { | ||
| 41 | + appLog.warn("Permissions denied by user. Cannot load events.") | ||
| 42 | + eventList.clear() | ||
| 43 | + isLoading = false | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + func addEventToList() { | ||
| 49 | + var title: String = this.eventTitle | ||
| 50 | + if (title == "") { | ||
| 51 | + title = "默认事件" | ||
| 52 | + } | ||
| 53 | + let description: String = this.eventDescription | ||
| 54 | + | ||
| 55 | + this.eventTitle = "" | ||
| 56 | + this.eventDescription = "" | ||
| 57 | + | ||
| 58 | + CalendarService.addEvent(title, description) { id: Int64 => | ||
| 59 | + if (id > 0) { | ||
| 60 | + appLog.info("Event added, refreshing list...") | ||
| 61 | + this.loadEvents() | ||
| 62 | + } else { | ||
| 63 | + appLog.error("Failed to add event from UI.") | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + func deleteEventFromList(eventId: Int64) { | ||
| 69 | + CalendarService.deleteEvent(eventId) { success: Bool => | ||
| 70 | + if (success) { | ||
| 71 | + appLog.info("Event deleted, refreshing list...") | ||
| 72 | + this.loadEvents() | ||
| 73 | + } else { | ||
| 74 | + appLog.error("Failed to delete event from UI.") | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + func build() { | ||
| 80 | + Column { | ||
| 81 | + Text("日程管理演示") | ||
| 82 | + .fontSize(22) | ||
| 83 | + .width(100.percent) | ||
| 84 | + .textAlign(TextAlign.Center) | ||
| 85 | + .margin(top: 10, bottom: 20) | ||
| 86 | + .fontWeight(FontWeight.Medium) | ||
| 87 | + | ||
| 88 | + Column { | ||
| 89 | + Text("添加新日程") | ||
| 90 | + .sectionTitle() | ||
| 91 | + | ||
| 92 | + Divider() | ||
| 93 | + .standardDivider() | ||
| 94 | + | ||
| 95 | + TextInput(placeholder: "请输入日程标题", text: eventTitle) | ||
| 96 | + .inputFieldStyle() | ||
| 97 | + .onChange { value: String => this.eventTitle = value } | ||
| 98 | + | ||
| 99 | + TextInput(placeholder: "请输入日程描述", text: eventDescription) | ||
| 100 | + .inputFieldStyle() | ||
| 101 | + .onChange { value: String => this.eventDescription = value } | ||
| 102 | + | ||
| 103 | + Button("立即添加") | ||
| 104 | + .actionButton() | ||
| 105 | + .backgroundColor(0x007DFF) | ||
| 106 | + .onClick { _ => addEventToList() } | ||
| 107 | + } | ||
| 108 | + .sectionCard() | ||
| 109 | + | ||
| 110 | + Column { | ||
| 111 | + Text("日程列表") | ||
| 112 | + .sectionTitle() | ||
| 113 | + | ||
| 114 | + Divider() | ||
| 115 | + .standardDivider() | ||
| 116 | + | ||
| 117 | + Column { | ||
| 118 | + if (isLoading) { | ||
| 119 | + Text("正在加载日程...") | ||
| 120 | + .margin(20) | ||
| 121 | + .height(100.percent) | ||
| 122 | + .fontColor(Color.GRAY) | ||
| 123 | + } else if (eventList.isEmpty()) { | ||
| 124 | + Text("暂无日程") | ||
| 125 | + .margin(20) | ||
| 126 | + .height(100.percent) | ||
| 127 | + .fontColor(Color.GRAY) | ||
| 128 | + } else { | ||
| 129 | + List(space: 10, scroller: scroller) { | ||
| 130 | + ForEach(eventList, { item: EventInfo, _: Int64 => | ||
| 131 | + ListItem { | ||
| 132 | + EventListItem(item: item, onDelete: { => deleteEventFromList(item.id) }) | ||
| 133 | + } | ||
| 134 | + .listItemStyle() | ||
| 135 | + }) | ||
| 136 | + } | ||
| 137 | + .width(100.percent) | ||
| 138 | + .scrollBar(BarState.Off) | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + .height(49.percent) | ||
| 142 | + .width(100.percent) | ||
| 143 | + } | ||
| 144 | + .sectionCard() | ||
| 145 | + } | ||
| 146 | + .width(100.percent) | ||
| 147 | + .height(100.percent) | ||
| 148 | + .backgroundColor(0xF1F3F5) | ||
| 149 | + .padding(top: 20, bottom: 10.vp, left: 10.vp, right: 10.vp) | ||
| 150 | + } | ||
| 151 | +} | ||
| @@ -0,0 +1,46 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/12/22 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry.views | ||
| 5 | + | ||
| 6 | +import std.time.* | ||
| 7 | + | ||
| 8 | +import ohos.base.* | ||
| 9 | +import ohos.component.* | ||
| 10 | +import ohos.state_manage.* | ||
| 11 | +import ohos.state_macro_manage.* | ||
| 12 | + | ||
| 13 | +import ohos_app_cangjie_entry.services.EventInfo | ||
| 14 | + | ||
| 15 | +@Component | ||
| 16 | +class EventListItem { | ||
| 17 | + @Prop var item: EventInfo | ||
| 18 | + var onDelete: () -> Unit | ||
| 19 | + | ||
| 20 | + let timeFormat: String = "yyyy/MM/dd HH:mm" | ||
| 21 | + | ||
| 22 | + func build() { | ||
| 23 | + Row { | ||
| 24 | + Column { | ||
| 25 | + Text(item.title) | ||
| 26 | + .fontSize(16) | ||
| 27 | + .fontWeight(FontWeight.Medium) | ||
| 28 | + Text("${DateTime.fromUnixTimeStamp(Duration.millisecond * item.startTime).format(timeFormat)}") | ||
| 29 | + .fontSize(12) | ||
| 30 | + .margin(top: 4) | ||
| 31 | + .fontColor(Color.GRAY) | ||
| 32 | + } | ||
| 33 | + .alignItems(HorizontalAlign.Start) | ||
| 34 | + | ||
| 35 | + Button("删除") | ||
| 36 | + .height(36) | ||
| 37 | + .borderRadius(8) | ||
| 38 | + .fontColor(Color.WHITE) | ||
| 39 | + .backgroundColor(0xec0000) | ||
| 40 | + .onClick { _ => onDelete() } | ||
| 41 | + } | ||
| 42 | + .height(48) | ||
| 43 | + .width(100.percent) | ||
| 44 | + .justifyContent(FlexAlign.SpaceBetween) | ||
| 45 | + } | ||
| 46 | +} | ||
| @@ -0,0 +1,54 @@ | |||
| 1 | +{ | ||
| 2 | + "module": { | ||
| 3 | + "name": "entry", | ||
| 4 | + "type": "entry", | ||
| 5 | + "description": "$string:module_desc", | ||
| 6 | + "mainElement": "EntryAbility", | ||
| 7 | + "deviceTypes": [ | ||
| 8 | + "phone", | ||
| 9 | + "tablet", | ||
| 10 | + "2in1" | ||
| 11 | + ], | ||
| 12 | + "requestPermissions": [ | ||
| 13 | + { | ||
| 14 | + "name": "ohos.permission.READ_CALENDAR", | ||
| 15 | + "reason": "$string:app_name", | ||
| 16 | + "usedScene": { | ||
| 17 | + "when": "always" | ||
| 18 | + } | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + "name": "ohos.permission.WRITE_CALENDAR", | ||
| 22 | + "reason": "$string:app_name", | ||
| 23 | + "usedScene": { | ||
| 24 | + "when": "always" | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | + ], | ||
| 28 | + "deliveryWithInstall": true, | ||
| 29 | + "installationFree": false, | ||
| 30 | + "srcEntry": "ohos_app_cangjie_entry.MyAbilityStage", | ||
| 31 | + "abilities": [ | ||
| 32 | + { | ||
| 33 | + "name": "EntryAbility", | ||
| 34 | + "srcEntry": "ohos_app_cangjie_entry.MainAbility", | ||
| 35 | + "description": "$string:EntryAbility_desc", | ||
| 36 | + "icon": "$media:calendar", | ||
| 37 | + "label": "$string:EntryAbility_label", | ||
| 38 | + "startWindowIcon": "$media:calendar", | ||
| 39 | + "startWindowBackground": "$color:start_window_background", | ||
| 40 | + "exported": true, | ||
| 41 | + "skills": [ | ||
| 42 | + { | ||
| 43 | + "entities": [ | ||
| 44 | + "entity.system.home" | ||
| 45 | + ], | ||
| 46 | + "actions": [ | ||
| 47 | + "action.system.home" | ||
| 48 | + ] | ||
| 49 | + } | ||
| 50 | + ] | ||
| 51 | + } | ||
| 52 | + ] | ||
| 53 | + } | ||
| 54 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +{ | ||
| 2 | + "color": [ | ||
| 3 | + { | ||
| 4 | + "name": "start_window_background", | ||
| 5 | + "value": "#FFFFFF" | ||
| 6 | + } | ||
| 7 | + ] | ||
| 8 | +} | ||
| @@ -0,0 +1,16 @@ | |||
| 1 | +{ | ||
| 2 | + "string": [ | ||
| 3 | + { | ||
| 4 | + "name": "module_desc", | ||
| 5 | + "value": "module description" | ||
| 6 | + }, | ||
| 7 | + { | ||
| 8 | + "name": "EntryAbility_desc", | ||
| 9 | + "value": "description" | ||
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + "name": "EntryAbility_label", | ||
| 13 | + "value": "Calendar" | ||
| 14 | + } | ||
| 15 | + ] | ||
| 16 | +} | ||
| @@ -0,0 +1,2 @@ | |||
| 1 | +{ | ||
| 2 | +} | ||
| @@ -0,0 +1,40 @@ | |||
| 1 | +[package] | ||
| 2 | + cjc-version = "0.48.2" | ||
| 3 | + compile-option = "--dy-std --cfg=\"${COMPILE_CONDITION_ENTRY}\"" | ||
| 4 | + description = "CangjieUI Application" | ||
| 5 | + link-option = "--unresolved-symbols=ignore-all" | ||
| 6 | + name = "ohos_app_cangjie_entry_test" | ||
| 7 | + output-type = "dynamic" | ||
| 8 | + src-dir = "." | ||
| 9 | + target-dir = "" | ||
| 10 | + version = "1.0.0" | ||
| 11 | + package-configuration = {} | ||
| 12 | + scripts = {} | ||
| 13 | + | ||
| 14 | +[dependencies] | ||
| 15 | + [dependencies.ohos_app_cangjie_entry] | ||
| 16 | + path = "../../../" | ||
| 17 | + version = "1.0.0" | ||
| 18 | + | ||
| 19 | +[profile] | ||
| 20 | + [profile.build] | ||
| 21 | + incremental = true | ||
| 22 | + lto = "" | ||
| 23 | + [profile.customized-option] | ||
| 24 | + debug = "-g -Woff all -Won apilevel-check" | ||
| 25 | + release = "--fast-math -O2 -s -Woff all -Won apilevel-check" | ||
| 26 | + withoutCoverage = "--cfg=\"coverage=false\"" | ||
| 27 | + withCoverage = "--cfg=\"coverage=true\"" | ||
| 28 | + [profile.test] | ||
| 29 | + | ||
| 30 | +[target.aarch64-linux-ohos] | ||
| 31 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/aarch64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 32 | +[target.aarch64-linux-ohos.bin-dependencies] | ||
| 33 | + path-option = ["${AARCH64_LIBS}", "${AARCH64_MACRO_LIBS}", "${AARCH64_KIT_LIBS}"] | ||
| 34 | + package-option = {} | ||
| 35 | + | ||
| 36 | +[target.x86_64-linux-ohos] | ||
| 37 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/x86_64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 38 | +[target.x86_64-linux-ohos.bin-dependencies] | ||
| 39 | + path-option = ["${X86_64_OHOS_LIBS}", "${X86_64_OHOS_MACRO_LIBS}", "${X86_64_OHOS_KIT_LIBS}"] | ||
| 40 | + | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/2/26 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry_test | ||
| 5 | + | ||
| 6 | +import std.unittest.expectEqual | ||
| 7 | +import std.unittest.testmacro.Expect | ||
| 8 | + | ||
| 9 | +func unittest() { | ||
| 10 | + let a = "abc" | ||
| 11 | + let b = "b" | ||
| 12 | + @Expect(a.contains(b)) | ||
| 13 | + @Expect(a, a) | ||
| 14 | +} | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | +package ohos_app_cangjie_entry_test | ||
| 2 | + | ||
| 3 | +import std.unittest.TestClass | ||
| 4 | +import std.unittest.TestPackage | ||
| 5 | +import std.unittest.TestSuite | ||
| 6 | +import std.unittest.UnitTestCase | ||
| 7 | +import std.unittest.common.Configuration | ||
| 8 | +import std.unittest.testmacro.Test | ||
| 9 | +import std.unittest.testmacro.TestCase | ||
| 10 | + | ||
| 11 | +@Test | ||
| 12 | +class TestExample00 { | ||
| 13 | + @TestCase | ||
| 14 | + func test00(): Unit { | ||
| 15 | + unittest() | ||
| 16 | + } | ||
| 17 | +} | ||
| @@ -0,0 +1,9 @@ | |||
| 1 | +package ohos_app_cangjie_entry_test | ||
| 2 | + | ||
| 3 | +import ohos_app_cangjie_entry_test.unittest_support.registerTestSuite | ||
| 4 | + | ||
| 5 | +private let register = testsuite() | ||
| 6 | + | ||
| 7 | +func testsuite() { | ||
| 8 | + registerTestSuite {TestExample00()} | ||
| 9 | +} | ||
A19-CangjiexArkTS/CangjieCallArkTS/6.0/entry/src/ohosTest/cangjie/unittest_support/unittest_engine.cj+246-0
| @@ -0,0 +1,246 @@ | |||
| 1 | +package ohos_app_cangjie_entry_test.unittest_support | ||
| 2 | + | ||
| 3 | +import ohos.test_runner.TestRunner | ||
| 4 | +import ohos.hilog.HilogChannel | ||
| 5 | +import ohos.ability_delegator_registry.AbilityDelegatorRegistry | ||
| 6 | +import std.unittest.XmlReporter | ||
| 7 | +import std.fs.Path | ||
| 8 | +import std.fs.Directory | ||
| 9 | +import std.collection.filter | ||
| 10 | +import std.fs.FileInfo | ||
| 11 | +import std.collection.first | ||
| 12 | +import std.fs.File | ||
| 13 | +import std.collection.forEach | ||
| 14 | +import std.convert.Parsable | ||
| 15 | +import ohos.bundle_manager.BundleManager | ||
| 16 | +import ohos.bundle_manager.BundleFlag | ||
| 17 | +import std.collection.any | ||
| 18 | +import std.collection.HashMap | ||
| 19 | +import std.unittest.TestClass | ||
| 20 | +import std.collection.ArrayList | ||
| 21 | +import std.unittest.TestSuite | ||
| 22 | +import ohos.ability.Want | ||
| 23 | +import std.process.Process | ||
| 24 | +import ohos.ui_test.UITest | ||
| 25 | +import std.unittest.common.Configuration | ||
| 26 | + | ||
| 27 | +@When[coverage == "true"] | ||
| 28 | +foreign func __gcov_dump(): Unit | ||
| 29 | + | ||
| 30 | +@When[coverage == "true"] | ||
| 31 | +public func gcov_dump(coveragePath: String): Unit { | ||
| 32 | + let logger = HilogChannel(0, 0, "CangjieTest") | ||
| 33 | + logger.info("execute test with coverage") | ||
| 34 | + unsafe { __gcov_dump() } | ||
| 35 | + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() | ||
| 36 | + abilityDelegator.print("GENERATE_COVERAGE: ${coveragePath}") | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +@When[coverage == "false"] | ||
| 40 | +public func gcov_dump(coveragePath: String): Unit { | ||
| 41 | + let logger = HilogChannel(0, 0, "CangjieTest") | ||
| 42 | + logger.info("execute test without coverage") | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +public func registerTestSuite(testClassInstanceGenerator: () -> TestClass): Unit { | ||
| 46 | + OpenHarmonyTestRunner | ||
| 47 | + .testClassInstanceGenerators | ||
| 48 | + .add(testClassInstanceGenerator) | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +public class OpenHarmonyTestRunner <: TestRunner { | ||
| 52 | + private static let logger = HilogChannel(0, 0, "CangjieTest") | ||
| 53 | + public static let testSuiteMap = HashMap<String, TestSuite>() | ||
| 54 | + public static let testClassInstanceGenerators = ArrayList<() -> TestClass>() | ||
| 55 | + private static func getDetailedInfoOf(e: Exception): String { | ||
| 56 | + let sb = StringBuilder("exception: ${e.toString()}\n") | ||
| 57 | + e.getStackTrace() |> | ||
| 58 | + forEach { | ||
| 59 | + s => sb.append(" at ${s.declaringClass}.${s.methodName}(${s.fileName}:${s.lineNumber})\n") | ||
| 60 | + } | ||
| 61 | + sb.toString() | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + private func executeTestClass(testClassName: String): Unit { | ||
| 65 | + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() | ||
| 66 | + logger.info("EXECUTE_CLASS_START: \"${testClassName}\"") | ||
| 67 | + abilityDelegator.print("EXECUTE_CLASS_START: \"${testClassName}\"") | ||
| 68 | + let testSuite = match (OpenHarmonyTestRunner | ||
| 69 | + .testSuiteMap | ||
| 70 | + .get(testClassName)) { | ||
| 71 | + case None => return | ||
| 72 | + case Some(testClassInstanceGenerator) => testClassInstanceGenerator | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + testSuite | ||
| 76 | + .runTests() | ||
| 77 | + .reportTo(XmlReporter(Path("/data/storage/el1/base"))) | ||
| 78 | + | ||
| 79 | + let testReportXmlFileName = "test-default.${testClassName}.xml" | ||
| 80 | + match (Directory.readFrom("/data/storage/el1/base/tests") |> | ||
| 81 | + filter {fileInfo => fileInfo.name == testReportXmlFileName} |> first) { | ||
| 82 | + case None => logger.error("${testReportXmlFileName} not found.") | ||
| 83 | + case Some(testReportXmlFileInfo) => | ||
| 84 | + let testReportXmlFilePath = testReportXmlFileInfo.path | ||
| 85 | + let testReportXmlFileContent = File.readFrom(testReportXmlFilePath) | ||
| 86 | + let xmlReport = String.fromUtf8(testReportXmlFileContent) | ||
| 87 | + logger.info("XML_REPORT_CONTENT_LENGTH: ${xmlReport.size}") | ||
| 88 | + logger.info(xmlReport) | ||
| 89 | + abilityDelegator.print("XML_REPORT_CONTENT_LENGTH: ${xmlReport.size}") | ||
| 90 | + abilityDelegator.print(xmlReport) | ||
| 91 | + logger.info("EXECUTE_CLASS_END: \"${testClassName}\"") | ||
| 92 | + abilityDelegator.print("EXECUTE_CLASS_END: \"${testClassName}\"") | ||
| 93 | + reportTestReport("test-default.${testClassName}.xml") | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + private func executeAllTestClasses(): Unit { | ||
| 97 | + for (testClassName in OpenHarmonyTestRunner | ||
| 98 | + .testSuiteMap | ||
| 99 | + .keys()) { | ||
| 100 | + executeTestClass(testClassName) | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + private func executeTestCase(testClassName: String, testCaseName: String): Unit { | ||
| 104 | + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() | ||
| 105 | + abilityDelegator.print("EXECUTE_CLASS_START: \"${testClassName}\"") | ||
| 106 | + logger.info("EXECUTE_METHOD_START: \"${testClassName}#${testCaseName}\"") | ||
| 107 | + let testSuite = match (OpenHarmonyTestRunner | ||
| 108 | + .testSuiteMap | ||
| 109 | + .get(testClassName)) { | ||
| 110 | + case None => return | ||
| 111 | + case Some(testClassInstanceGenerator) => testClassInstanceGenerator | ||
| 112 | + } | ||
| 113 | + let configuration = Configuration() | ||
| 114 | + configuration.setByName("filter", "${testClassName}.${testCaseName}") | ||
| 115 | + testSuite | ||
| 116 | + .runTests(configuration) | ||
| 117 | + .reportTo(XmlReporter(Path("/data/storage/el1/base"))) | ||
| 118 | + | ||
| 119 | + let testReportXmlFileName = "test-default.${testClassName}.xml" | ||
| 120 | + match (Directory.readFrom("/data/storage/el1/base/tests") |> | ||
| 121 | + filter {fileInfo => fileInfo.name == testReportXmlFileName} |> first) { | ||
| 122 | + case None => logger.error("${testReportXmlFileName} not found.") | ||
| 123 | + case Some(testReportXmlFileInfo) => | ||
| 124 | + let testReportXmlFilePath = testReportXmlFileInfo.path | ||
| 125 | + let testReportXmlFileContent = File.readFrom(testReportXmlFilePath) | ||
| 126 | + let xmlReport = String.fromUtf8(testReportXmlFileContent) | ||
| 127 | + logger.info("XML_REPORT_CONTENT_LENGTH: ${xmlReport.size}") | ||
| 128 | + logger.info(xmlReport) | ||
| 129 | + abilityDelegator.print("XML_REPORT_CONTENT_LENGTH: ${xmlReport.size}") | ||
| 130 | + abilityDelegator.print(xmlReport) | ||
| 131 | + logger.info("EXECUTE_METHOD_END: \"${testClassName}#${testCaseName}\"") | ||
| 132 | + abilityDelegator.print("EXECUTE_CLASS_END: \"${testClassName}\"") | ||
| 133 | + reportTestReport("test-default.${testClassName}.xml") | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + private func reportTestReport(testReportName: String): Unit { | ||
| 137 | + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() | ||
| 138 | + let abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() | ||
| 139 | + let bundleName = abilityDelegatorArguments.bundleName | ||
| 140 | + abilityDelegator.print("REPORT_TEST_RESULT: /data/app/el1/100/base/${bundleName}/tests/${testReportName}") | ||
| 141 | + } | ||
| 142 | + public override func onPrepare(): Unit { | ||
| 143 | + logger.info("OpenHarmonyTestRunner onPrepare") | ||
| 144 | + for (testClassInstanceGenerator in OpenHarmonyTestRunner.testClassInstanceGenerators) { | ||
| 145 | + let testClassInstance = try { | ||
| 146 | + testClassInstanceGenerator() | ||
| 147 | + } catch (e: Exception) { | ||
| 148 | + logger.error("exception occurred during testsuite generation:\n${getDetailedInfoOf(e)}") | ||
| 149 | + continue | ||
| 150 | + } | ||
| 151 | + let testSuite = testClassInstance.asTestSuite() | ||
| 152 | + let testSuiteName = testSuite.name | ||
| 153 | + logger.info("Registering TestSuite ${testSuiteName}") | ||
| 154 | + OpenHarmonyTestRunner | ||
| 155 | + .testSuiteMap | ||
| 156 | + .add(testSuiteName, testSuite) | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + public override func onRun(): Unit { | ||
| 160 | + logger.info("OpenHarmonyTestRunner onRun") | ||
| 161 | + let abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() | ||
| 162 | + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() | ||
| 163 | + UITest.setup() | ||
| 164 | + logger.info("bundleName = ${abilityDelegatorArguments.bundleName}") | ||
| 165 | + logger.info("abilityDelegatorArguments.testRunnerClassName = ${abilityDelegatorArguments.testRunnerClassName}") | ||
| 166 | + logger.info("abilityDelegatorArguments.testCaseNames = ${abilityDelegatorArguments.testCaseNames}") | ||
| 167 | + logger.info("abilityDelegatorArguments.parameters = ${abilityDelegatorArguments.parameters}") | ||
| 168 | + logger.info("Started running tests.") | ||
| 169 | + abilityDelegator.print("OpenHarmonyTestRunner onRun") | ||
| 170 | + let parameters = abilityDelegatorArguments.parameters | ||
| 171 | + let bundleName = abilityDelegatorArguments.bundleName | ||
| 172 | + let bundleInfo = BundleManager.getBundleInfoForSelf( | ||
| 173 | + BundleFlag | ||
| 174 | + .GET_BUNDLE_INFO_WITH_HAP_MODULE | ||
| 175 | + .getValue() | BundleFlag | ||
| 176 | + .GET_BUNDLE_INFO_WITH_APPLICATION | ||
| 177 | + .getValue()) | ||
| 178 | + let moduleName = match (parameters.get("-m")) { | ||
| 179 | + case None => | ||
| 180 | + abilityDelegator.finishTest("Module name was not specified.", -1) | ||
| 181 | + return | ||
| 182 | + case Some(moduleName) => | ||
| 183 | + if (bundleInfo.hapModulesInfo |> any {hapModuleInfo => hapModuleInfo.name == moduleName}) { | ||
| 184 | + moduleName | ||
| 185 | + } else { | ||
| 186 | + abilityDelegator.finishTest( | ||
| 187 | + "No module with name \"${moduleName}\" was found in bundle ${bundleName}.", -1) | ||
| 188 | + return | ||
| 189 | + } | ||
| 190 | + } | ||
| 191 | + let abilityName = match (bundleInfo.hapModulesInfo |> filter {hapModuleInfo => hapModuleInfo.name == moduleName} |> | ||
| 192 | + first) { | ||
| 193 | + case None => | ||
| 194 | + abilityDelegator.finishTest("No module with name \"${moduleName}\" was found in bundle ${bundleName}.", | ||
| 195 | + -1) | ||
| 196 | + return | ||
| 197 | + case Some(hapModuleInfo) => | ||
| 198 | + let mainElementName = hapModuleInfo.mainElementName | ||
| 199 | + logger.info("mainElementName = ${mainElementName}") | ||
| 200 | + mainElementName | ||
| 201 | + } | ||
| 202 | + let want = Want(bundleName: bundleName, moduleName: moduleName, abilityName: abilityName) | ||
| 203 | + abilityDelegator | ||
| 204 | + .startAbility(want) | ||
| 205 | + .get() | ||
| 206 | + let timeOut = match (parameters.get("-s timeout")) { | ||
| 207 | + case None => None<Int64> | ||
| 208 | + case Some(timeOutText) => match (Int64.tryParse(timeOutText)) { | ||
| 209 | + case None => | ||
| 210 | + abilityDelegator.finishTest("The parameter \"timeout\": ${timeOutText} is invalid", -1) | ||
| 211 | + return | ||
| 212 | + case Some(timeOutValue) => timeOutValue | ||
| 213 | + } | ||
| 214 | + } | ||
| 215 | + spawn { | ||
| 216 | + try { | ||
| 217 | + match (parameters.get("-s class")) { | ||
| 218 | + case None => executeAllTestClasses() | ||
| 219 | + case Some(text) => text.split(",", removeEmpty: true) |> | ||
| 220 | + forEach { | ||
| 221 | + t: String => | ||
| 222 | + let pair = t.split("#", removeEmpty: true) | ||
| 223 | + let testClassName = match (pair.get(0)) { | ||
| 224 | + case None => return | ||
| 225 | + case Some(text) => text.trimAscii() | ||
| 226 | + } | ||
| 227 | + match (pair.get(1)) { | ||
| 228 | + case None => executeTestClass(testClassName) | ||
| 229 | + case Some(testCaseName) => executeTestCase(testClassName, testCaseName) | ||
| 230 | + } | ||
| 231 | + } | ||
| 232 | + } | ||
| 233 | + } catch (e: Exception) { | ||
| 234 | + logger.error("an exception occurred:\n${getDetailedInfoOf(e)}") | ||
| 235 | + } | ||
| 236 | + Process | ||
| 237 | + .current | ||
| 238 | + .setEnv("GCOV_PREFIX", "/data/storage/el1/base/cov") | ||
| 239 | + gcov_dump("/data/app/el1/100/base/${bundleName}/cov/") | ||
| 240 | + | ||
| 241 | + abilityDelegator.finishTest("Test finished.", 0) | ||
| 242 | + } | ||
| 243 | + } | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +let _ = TestRunner.registerCreator("OpenHarmonyTestRunner") {OpenHarmonyTestRunner()} | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | +[package] | ||
| 2 | + cjc-version = "0.48.2" | ||
| 3 | + compile-option = "--dy-std --cfg=\"${COMPILE_CONDITION_ENTRY}\"" | ||
| 4 | + description = "CangjieUI Application" | ||
| 5 | + link-option = "" | ||
| 6 | + name = "ohos_app_cangjie_entry_local_test" | ||
| 7 | + output-type = "dynamic" | ||
| 8 | + src-dir = "." | ||
| 9 | + target-dir = "" | ||
| 10 | + version = "1.0.0" | ||
| 11 | + package-configuration = {} | ||
| 12 | + scripts = {} | ||
| 13 | + | ||
| 14 | +[profile] | ||
| 15 | + [profile.build] | ||
| 16 | + incremental = true | ||
| 17 | + lto = "" | ||
| 18 | + [profile.customized-option] | ||
| 19 | + debug = "-g -Woff all -Won apilevel-check" | ||
| 20 | + release = "--fast-math -O2 -s -Woff all -Won apilevel-check" | ||
| 21 | + [profile.test] | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | +/** | ||
| 2 | + * Created on 2025/2/26 | ||
| 3 | + */ | ||
| 4 | +package ohos_app_cangjie_entry_local_test | ||
| 5 | + | ||
| 6 | +import std.unittest.expectEqual | ||
| 7 | +import std.unittest.testmacro.Expect | ||
| 8 | + | ||
| 9 | +func unittest() { | ||
| 10 | + let a = "abc" | ||
| 11 | + let b = "b" | ||
| 12 | + @Expect(a.contains(b)) | ||
| 13 | + @Expect(a, a) | ||
| 14 | +} | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | +package ohos_app_cangjie_entry_local_test | ||
| 2 | + | ||
| 3 | +import std.unittest.TestClass | ||
| 4 | +import std.unittest.TestPackage | ||
| 5 | +import std.unittest.TestSuite | ||
| 6 | +import std.unittest.UnitTestCase | ||
| 7 | +import std.unittest.common.Configuration | ||
| 8 | +import std.unittest.testmacro.Test | ||
| 9 | +import std.unittest.testmacro.TestCase | ||
| 10 | + | ||
| 11 | +@Test | ||
| 12 | +class TestExample00 { | ||
| 13 | + @TestCase | ||
| 14 | + func test00(): Unit { | ||
| 15 | + unittest() | ||
| 16 | + } | ||
| 17 | +} | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | +{ | ||
| 2 | + "modelVersion": "6.0.0", | ||
| 3 | + "dependencies": { | ||
| 4 | + }, | ||
| 5 | + "execution": { | ||
| 6 | + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */ | ||
| 7 | + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ | ||
| 8 | + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ | ||
| 9 | + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ | ||
| 10 | + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ | ||
| 11 | + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ | ||
| 12 | + }, | ||
| 13 | + "logging": { | ||
| 14 | + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ | ||
| 15 | + }, | ||
| 16 | + "debugging": { | ||
| 17 | + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ | ||
| 18 | + }, | ||
| 19 | + "nodeOptions": { | ||
| 20 | + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ | ||
| 21 | + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ | ||
| 22 | + } | ||
| 23 | +} | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | +import { appTasks } from '@ohos/hvigor-ohos-plugin'; | ||
| 2 | + | ||
| 3 | +export default { | ||
| 4 | + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ | ||
| 5 | + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ | ||
| 6 | +} | ||
| @@ -0,0 +1,28 @@ | |||
| 1 | +{ | ||
| 2 | + "meta": { | ||
| 3 | + "stableOrder": true, | ||
| 4 | + "enableUnifiedLockfile": false | ||
| 5 | + }, | ||
| 6 | + "lockfileVersion": 3, | ||
| 7 | + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", | ||
| 8 | + "specifiers": { | ||
| 9 | + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", | ||
| 10 | + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" | ||
| 11 | + }, | ||
| 12 | + "packages": { | ||
| 13 | + "@ohos/hamock@1.0.0": { | ||
| 14 | + "name": "", | ||
| 15 | + "version": "1.0.0", | ||
| 16 | + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", | ||
| 17 | + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", | ||
| 18 | + "registryType": "ohpm" | ||
| 19 | + }, | ||
| 20 | + "@ohos/hypium@1.0.24": { | ||
| 21 | + "name": "", | ||
| 22 | + "version": "1.0.24", | ||
| 23 | + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", | ||
| 24 | + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.24.har", | ||
| 25 | + "registryType": "ohpm" | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | +} | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +{ | ||
| 2 | + "modelVersion": "6.0.0", | ||
| 3 | + "description": "Please describe the basic information.", | ||
| 4 | + "dependencies": { | ||
| 5 | + }, | ||
| 6 | + "devDependencies": { | ||
| 7 | + "@ohos/hypium": "1.0.24", | ||
| 8 | + "@ohos/hamock": "1.0.0" | ||
| 9 | + } | ||
| 10 | +} | ||