From 2ff79be888f1ffe0dd2dd1d779c773cea248e9ed Mon Sep 17 00:00:00 2001 From: chengongping Date: Tue, 17 Jun 2025 00:51:12 +0800 Subject: [PATCH] add the ut for case Signed-off-by: chengongping --- .../src/main/ets/pages/utForCase/builder.ts | 162 ++++++++ .../main/ets/pages/utForCase/makeObserved.ts | 389 ++++++++++++++++++ .../main/ets/pages/utForCase/persistencev2.ts | 376 +++++++++++++++++ .../src/main/ets/pages/utForCase/repeat.ts | 350 ++++++++++++++++ .../main/ets/pages/utForCase/reusablev2.ts | 248 +++++++++++ .../src/main/ets/pages/utForCase/state.ts | 248 +++++++++++ 6 files changed, 1773 insertions(+) create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/builder.ts create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/makeObserved.ts create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/persistencev2.ts create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/repeat.ts create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/reusablev2.ts create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/state.ts diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/builder.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/builder.ts new file mode 100644 index 000000000..7286b8044 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/builder.ts @@ -0,0 +1,162 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +@Entry +@Component +struct PrivateBuilder { + @State builder_value: string = 'Hello'; + + @Builder + builder() { + Column() { + Text(this.builder_value) + .width(230) + .height(40) + .backgroundColor('#ffeae5e5') + .borderRadius(20) + .margin(12) + .textAlign(TextAlign.Center) + } + } + + aboutToAppear(): void { + setTimeout(() => { + this.builder_value = 'Hello World'; + }, 2000); + } + + build() { + Row() { + Column() { + Text(this.builder_value) + .width(230) + .height(40) + .backgroundColor('#ffeae5e5') + .borderRadius(20) + .textAlign(TextAlign.Center) + this.builder() + Button('点击改变builder_value内容') + .onClick(() => { + this.builder_value = 'builder_value被点击了'; + }) + } + .height('100%') + .width('100%') + } + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +interface PrivateBuilder_Params { + builder_value?: string; +} +class PrivateBuilder extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.__builder_value = new ObservedPropertySimplePU('Hello', this, "builder_value"); + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: PrivateBuilder_Params) { + if (params.builder_value !== undefined) { + this.builder_value = params.builder_value; + } + } + updateStateVars(params: PrivateBuilder_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__builder_value.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__builder_value.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + private __builder_value: ObservedPropertySimplePU; + get builder_value() { + return this.__builder_value.get(); + } + set builder_value(newValue: string) { + this.__builder_value.set(newValue); + } + builder(parent = null) { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create(this.builder_value); + Text.width(230); + Text.height(40); + Text.backgroundColor('#ffeae5e5'); + Text.borderRadius(20); + Text.margin(12); + Text.textAlign(TextAlign.Center); + }, Text); + Text.pop(); + Column.pop(); + } + aboutToAppear(): void { + setTimeout(() => { + this.builder_value = 'Hello World'; + }, 2000); + } + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Row.create(); + }, Row); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + Column.height('100%'); + Column.width('100%'); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create(this.builder_value); + Text.width(230); + Text.height(40); + Text.backgroundColor('#ffeae5e5'); + Text.borderRadius(20); + Text.textAlign(TextAlign.Center); + }, Text); + Text.pop(); + this.builder.bind(this)(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Button.createWithLabel('点击改变builder_value内容'); + Button.onClick(() => { + this.builder_value = 'builder_value被点击了'; + }); + }, Button); + Button.pop(); + Column.pop(); + Row.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "PrivateBuilder"; + } +} +registerNamedRoute(() => new PrivateBuilder(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/makeObserved.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/makeObserved.ts new file mode 100644 index 000000000..335b7f7fc --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/makeObserved.ts @@ -0,0 +1,389 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +import { collections } from '@kit.ArkTS'; +import { UIUtils } from '@kit.ArkUI'; + +@Sendable +class Info { + id: number = 0; + name: string = 'cc'; + + constructor(id: number) { + this.id = id; + } +} + + +@Entry +@ComponentV2 +struct Index { + scroller: Scroller = new Scroller(); + @Local arrCollect: collections.Array = + UIUtils.makeObserved(new collections.Array(new Info(1), new Info(2))); + + build() { + Column() { + // ForEach接口仅支持Array,不支持collections.Array。 + // 但ForEach的实现用到的Array的API,collections.Array都有提供。所以可以使用as类型断言Array。 + // 需要注意断言并不会改变原本的数据类型。 + ForEach(this.arrCollect as object as Array, (item: Info) => { + Text('' + item.id).onClick(() => { + item.id++; + }) + }, (item: Info, index) => item.id.toString() + index.toString()) + Divider() + .color('blue') + if (this.arrCollect.length > 0) { + Text('the first one ' + this.arrCollect[this.arrCollect.length - this.arrCollect.length].id) + Text('the last one ' + this.arrCollect[this.arrCollect.length - 1].id) + } + Divider() + .color('blue') + + /****************************改变数据长度的api**************************/ + Scroll(this.scroller) { + Column({space: 10}) { + // push: 新增新元素 + Button('push').onClick(() => { + this.arrCollect.push(new Info(30)); + }) + // pop: 删除最后一个 + Button('pop').onClick(() => { + this.arrCollect.pop(); + }) + // shift: 删除第一个 + Button('shift').onClick(() => { + this.arrCollect.shift(); + }) + // unshift: 在数组的开头插入新项 + Button('unshift').onClick(() => { + this.arrCollect.unshift(new Info(50)); + }) + // splice: 从数组的指定位置删除元素 + Button('splice').onClick(() => { + this.arrCollect.splice(1); + }) + + // shrinkTo: 将数组长度缩小到给定的长度 + Button('shrinkTo').onClick(() => { + this.arrCollect.shrinkTo(1); + }) + // extendTo: 将数组长度扩展到给定的长度 + Button('extendTo').onClick(() => { + this.arrCollect.extendTo(6, new Info(20)); + }) + + Divider() + .color('blue') + + /****************************************改变数组item本身*****************/ + // sort:从大到小排序 + Button('sort').onClick(() => { + this.arrCollect.sort((a: Info, b: Info) => b.id - a.id); + }) + // fill: 用值填充指定部分 + Button('fill').onClick(() => { + this.arrCollect.fill(new Info(5), 0, 2); + }) + + /*****************************不会改变数组本身API***************************/ + // slice:返回新的数组,根据start end对原数组的拷贝,不会改变原数组,所以直接调用slice不会触发UI刷新 + // 可以构建用例为返回的浅拷贝的数据赋值给this.arrCollect,需要注意这里依然要调用makeObserved,否则this.arr被普通变量赋值后,会丧失观察能力 + Button('slice').onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.slice(0, 1)); + }) + // map:原理同上 + Button('map').onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.map((value) => { + value.id += 10; + return value; + })) + }) + // filter:原理同上 + Button('filter').onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.filter((value: Info) => value.id % 2 === 0)); + }) + + // concat:原理同上 + Button('concat').onClick(() => { + let array1 = new collections.Array(new Info(100)) + this.arrCollect = UIUtils.makeObserved(this.arrCollect.concat(array1)); + }) + }.height('200%') + }.height('60%') + } + .height('100%') + .width('100%') + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +import collections from "@ohos:arkts.collections"; +import { UIUtils as UIUtils } from "@ohos:arkui.StateManagement"; +class Info { + id: number = 0; + name: string = 'cc'; + constructor(id: number) { + "use sendable"; + this.id = id; + } +} +class Index extends ViewV2 { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda, extraInfo) { + super(parent, elmtId, extraInfo); + this.scroller = new Scroller(); + this.arrCollect = UIUtils.makeObserved(new collections.Array(new Info(1), new Info(2))); + this.finalizeConstruction(); + } + public resetStateVarsOnReuse(params: Object): void { + this.arrCollect = UIUtils.makeObserved(new collections.Array(new Info(1), new Info(2))); + } + scroller: Scroller; + @Local + arrCollect: collections.Array; + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + Column.height('100%'); + Column.width('100%'); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // ForEach接口仅支持Array,不支持collections.Array。 + // 但ForEach的实现用到的Array的API,collections.Array都有提供。所以可以使用as类型断言Array。 + // 需要注意断言并不会改变原本的数据类型。 + ForEach.create(); + const forEachItemGenFunction = _item => { + const item = _item; + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('' + item.id); + Text.onClick(() => { + item.id++; + }); + }, Text); + Text.pop(); + }; + this.forEachUpdateFunction(elmtId, this.arrCollect as object as Array, forEachItemGenFunction, (item: Info, index) => item.id.toString() + index.toString(), false, true); + }, ForEach); + // ForEach接口仅支持Array,不支持collections.Array。 + // 但ForEach的实现用到的Array的API,collections.Array都有提供。所以可以使用as类型断言Array。 + // 需要注意断言并不会改变原本的数据类型。 + ForEach.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Divider.create(); + Divider.color('blue'); + }, Divider); + this.observeComponentCreation2((elmtId, isInitialRender) => { + If.create(); + if (this.arrCollect.length > 0) { + this.ifElseBranchUpdateFunction(0, () => { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('the first one ' + this.arrCollect[this.arrCollect.length - this.arrCollect.length].id); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('the last one ' + this.arrCollect[this.arrCollect.length - 1].id); + }, Text); + Text.pop(); + }); + } + else { + this.ifElseBranchUpdateFunction(1, () => { + }); + } + }, If); + If.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Divider.create(); + Divider.color('blue'); + }, Divider); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /****************************改变数据长度的api**************************/ + Scroll.create(this.scroller); + /****************************改变数据长度的api**************************/ + Scroll.height('60%'); + }, Scroll); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create({ space: 10 }); + Column.height('200%'); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // push: 新增新元素 + Button.createWithLabel('push'); + // push: 新增新元素 + Button.onClick(() => { + this.arrCollect.push(new Info(30)); + }); + }, Button); + // push: 新增新元素 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // pop: 删除最后一个 + Button.createWithLabel('pop'); + // pop: 删除最后一个 + Button.onClick(() => { + this.arrCollect.pop(); + }); + }, Button); + // pop: 删除最后一个 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // shift: 删除第一个 + Button.createWithLabel('shift'); + // shift: 删除第一个 + Button.onClick(() => { + this.arrCollect.shift(); + }); + }, Button); + // shift: 删除第一个 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // unshift: 在数组的开头插入新项 + Button.createWithLabel('unshift'); + // unshift: 在数组的开头插入新项 + Button.onClick(() => { + this.arrCollect.unshift(new Info(50)); + }); + }, Button); + // unshift: 在数组的开头插入新项 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // splice: 从数组的指定位置删除元素 + Button.createWithLabel('splice'); + // splice: 从数组的指定位置删除元素 + Button.onClick(() => { + this.arrCollect.splice(1); + }); + }, Button); + // splice: 从数组的指定位置删除元素 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // shrinkTo: 将数组长度缩小到给定的长度 + Button.createWithLabel('shrinkTo'); + // shrinkTo: 将数组长度缩小到给定的长度 + Button.onClick(() => { + this.arrCollect.shrinkTo(1); + }); + }, Button); + // shrinkTo: 将数组长度缩小到给定的长度 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // extendTo: 将数组长度扩展到给定的长度 + Button.createWithLabel('extendTo'); + // extendTo: 将数组长度扩展到给定的长度 + Button.onClick(() => { + this.arrCollect.extendTo(6, new Info(20)); + }); + }, Button); + // extendTo: 将数组长度扩展到给定的长度 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Divider.create(); + Divider.color('blue'); + }, Divider); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /****************************************改变数组item本身*****************/ + // sort:从大到小排序 + Button.createWithLabel('sort'); + /****************************************改变数组item本身*****************/ + // sort:从大到小排序 + Button.onClick(() => { + this.arrCollect.sort((a: Info, b: Info) => b.id - a.id); + }); + }, Button); + /****************************************改变数组item本身*****************/ + // sort:从大到小排序 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // fill: 用值填充指定部分 + Button.createWithLabel('fill'); + // fill: 用值填充指定部分 + Button.onClick(() => { + this.arrCollect.fill(new Info(5), 0, 2); + }); + }, Button); + // fill: 用值填充指定部分 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /*****************************不会改变数组本身API***************************/ + // slice:返回新的数组,根据start end对原数组的拷贝,不会改变原数组,所以直接调用slice不会触发UI刷新 + // 可以构建用例为返回的浅拷贝的数据赋值给this.arrCollect,需要注意这里依然要调用makeObserved,否则this.arr被普通变量赋值后,会丧失观察能力 + Button.createWithLabel('slice'); + /*****************************不会改变数组本身API***************************/ + // slice:返回新的数组,根据start end对原数组的拷贝,不会改变原数组,所以直接调用slice不会触发UI刷新 + // 可以构建用例为返回的浅拷贝的数据赋值给this.arrCollect,需要注意这里依然要调用makeObserved,否则this.arr被普通变量赋值后,会丧失观察能力 + Button.onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.slice(0, 1)); + }); + }, Button); + /*****************************不会改变数组本身API***************************/ + // slice:返回新的数组,根据start end对原数组的拷贝,不会改变原数组,所以直接调用slice不会触发UI刷新 + // 可以构建用例为返回的浅拷贝的数据赋值给this.arrCollect,需要注意这里依然要调用makeObserved,否则this.arr被普通变量赋值后,会丧失观察能力 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // map:原理同上 + Button.createWithLabel('map'); + // map:原理同上 + Button.onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.map((value) => { + value.id += 10; + return value; + })); + }); + }, Button); + // map:原理同上 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // filter:原理同上 + Button.createWithLabel('filter'); + // filter:原理同上 + Button.onClick(() => { + this.arrCollect = UIUtils.makeObserved(this.arrCollect.filter((value: Info) => value.id % 2 === 0)); + }); + }, Button); + // filter:原理同上 + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // concat:原理同上 + Button.createWithLabel('concat'); + // concat:原理同上 + Button.onClick(() => { + let array1 = new collections.Array(new Info(100)); + this.arrCollect = UIUtils.makeObserved(this.arrCollect.concat(array1)); + }); + }, Button); + // concat:原理同上 + Button.pop(); + Column.pop(); + /****************************改变数据长度的api**************************/ + Scroll.pop(); + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "Index"; + } +} +registerNamedRoute(() => new Index(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/persistencev2.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/persistencev2.ts new file mode 100644 index 000000000..429427523 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/persistencev2.ts @@ -0,0 +1,376 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +import { PersistenceV2, Type, ConnectOptions } from '@kit.ArkUI'; +import { contextConstant } from '@kit.AbilityKit'; + +// 接受序列化失败的回调 +PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => { + console.error(''); +}); + +@ObservedV2 +class SampleChild { + @Trace childId: number = 0; + groupId: number = 1; +} + +@ObservedV2 +export class Sample { + // 对于复杂对象需要@Type修饰,确保序列化成功 + @Type(SampleChild) + @Trace father: SampleChild = new SampleChild(); +} + +@Entry +@ComponentV2 +struct Page1 { + @Local refresh: number = 0; + // key不传入尝试用为type的name作为key,加密参数不传入默认加密等级为EL2 + @Local p: Sample = PersistenceV2.globalConnect({type: Sample, defaultCreator:() => new Sample()})!; + + // 使用key:global1连接,传入加密等级为EL1 + @Local p1: Sample = PersistenceV2.globalConnect({type: Sample, key:'global1', defaultCreator:() => new Sample(), areaMode: contextConstant.AreaMode.EL1})!; + + // 使用key:global2连接,使用构造函数形式,加密参数不传入默认加密等级为EL2 + options: ConnectOptions = {type: Sample, key: 'global2', defaultCreator:() => new Sample()}; + @Local p2: Sample = PersistenceV2.globalConnect(this.options)!; + + // 使用key:global3连接,直接写加密数值,范围只能在0-4,否则运行会crash,例如加密设置为EL3 + @Local p3: Sample = PersistenceV2.globalConnect({type: Sample, key:'global3', defaultCreator:() => new Sample(), areaMode: 3})!; + + build() { + Column() { + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text('Key Sample: ' + this.p.father.childId.toString()) + .onClick(()=> { + this.p.father.childId += 1; + }) + .fontSize(25) + .fontColor(Color.Red) + Text('Key global1: ' + this.p1.father.childId.toString()) + .onClick(()=> { + this.p1.father.childId += 1; + }) + .fontSize(25) + .fontColor(Color.Red) + Text('Key global2: ' + this.p2.father.childId.toString()) + .onClick(()=> { + this.p2.father.childId += 1; + }) + .fontSize(25) + .fontColor(Color.Red) + Text('Key global3: ' + this.p3.father.childId.toString()) + .onClick(()=> { + this.p3.father.childId += 1; + }) + .fontSize(25) + .fontColor(Color.Red) + /**************************** keys接口 **************************/ + // keys 本身不会刷新,需要借助状态变量刷新 + Text('Persist keys: ' + PersistenceV2.keys().toString() + ' refresh: ' + this.refresh) + .onClick(() => { + this.refresh += 1; + }) + .fontSize(25) + + /**************************** remove接口 **************************/ + Text('Remove key Sample: ' + 'refresh: ' + this.refresh) + .onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove(Sample); + this.refresh += 1; + }) + .fontSize(25) + Text('Remove key global1: ' + 'refresh: ' + this.refresh) + .onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global1'); + this.refresh += 1; + }) + .fontSize(25) + Text('Remove key global2: ' + 'refresh: ' + this.refresh) + .onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global2'); + this.refresh += 1; + }) + .fontSize(25) + Text('Remove key global3: ' + 'refresh: ' + this.refresh) + .onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global3'); + this.refresh += 1; + }) + .fontSize(25) + /**************************** reConnect **************************/ + // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据 + Text('ReConnect key global2: ' + 'refresh: ' + this.refresh) + .onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.globalConnect(this.options); + this.refresh += 1; + }) + .fontSize(25) + + /**************************** save接口 **************************/ + Text('not save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh) + .onClick(() => { + // 未被@Trace保存的对象无法自动存储 + this.p.father.groupId += 1; + this.refresh += 1; + }) + .fontSize(25) + Text('save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh) + .onClick(() => { + // 未被@Trace保存的对象无法自动存储,需要调用key存储 + this.p.father.groupId += 1; + PersistenceV2.save(Sample); + this.refresh += 1; + }) + .fontSize(25) + } + .width('100%') + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +import { PersistenceV2 as PersistenceV2 } from "@ohos:arkui.StateManagement"; +import { Type as Type } from "@ohos:arkui.StateManagement"; +import type { ConnectOptions as ConnectOptions } from "@ohos:arkui.StateManagement"; +import contextConstant from "@ohos:app.ability.contextConstant"; +// 接受序列化失败的回调 +PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => { + console.error(''); +}); +@ObservedV2 +class SampleChild { + @Trace + childId: number = 0; + groupId: number = 1; +} +@ObservedV2 +export class Sample { + // 对于复杂对象需要@Type修饰,确保序列化成功 + @Type(SampleChild) + @Trace + father: SampleChild = new SampleChild(); +} +class Page1 extends ViewV2 { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda, extraInfo) { + super(parent, elmtId, extraInfo); + this.refresh = 0; + this.p = PersistenceV2.globalConnect({ type: Sample, defaultCreator: () => new Sample() })!; + this.p1 = PersistenceV2.globalConnect({ type: Sample, key: 'global1', defaultCreator: () => new Sample(), areaMode: contextConstant.AreaMode.EL1 })!; + this.options = { type: Sample, key: 'global2', defaultCreator: () => new Sample() }; + this.p2 = PersistenceV2.globalConnect(this.options)!; + this.p3 = PersistenceV2.globalConnect({ type: Sample, key: 'global3', defaultCreator: () => new Sample(), areaMode: 3 })!; + this.finalizeConstruction(); + } + public resetStateVarsOnReuse(params: Object): void { + this.refresh = 0; + this.p = PersistenceV2.globalConnect({ type: Sample, defaultCreator: () => new Sample() })!; + this.p1 = PersistenceV2.globalConnect({ type: Sample, key: 'global1', defaultCreator: () => new Sample(), areaMode: contextConstant.AreaMode.EL1 })!; + this.p2 = PersistenceV2.globalConnect(this.options)!; + this.p3 = PersistenceV2.globalConnect({ type: Sample, key: 'global3', defaultCreator: () => new Sample(), areaMode: 3 })!; + } + @Local + refresh: number; + // key不传入尝试用为type的name作为key,加密参数不传入默认加密等级为EL2 + @Local + p: Sample; + // 使用key:global1连接,传入加密等级为EL1 + @Local + p1: Sample; + // 使用key:global2连接,使用构造函数形式,加密参数不传入默认加密等级为EL2 + options: ConnectOptions; + @Local + p2: Sample; + // 使用key:global3连接,直接写加密数值,范围只能在0-4,否则运行会crash,例如加密设置为EL3 + @Local + p3: Sample; + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + Column.width('100%'); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text.create('Key Sample: ' + this.p.father.childId.toString()); + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text.onClick(() => { + this.p.father.childId += 1; + }); + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text.fontSize(25); + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text.fontColor(Color.Red); + }, Text); + /**************************** 显示数据 **************************/ + // 被@Trace修饰的数据可以自动持久化进磁盘 + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Key global1: ' + this.p1.father.childId.toString()); + Text.onClick(() => { + this.p1.father.childId += 1; + }); + Text.fontSize(25); + Text.fontColor(Color.Red); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Key global2: ' + this.p2.father.childId.toString()); + Text.onClick(() => { + this.p2.father.childId += 1; + }); + Text.fontSize(25); + Text.fontColor(Color.Red); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Key global3: ' + this.p3.father.childId.toString()); + Text.onClick(() => { + this.p3.father.childId += 1; + }); + Text.fontSize(25); + Text.fontColor(Color.Red); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /**************************** keys接口 **************************/ + // keys 本身不会刷新,需要借助状态变量刷新 + Text.create('Persist keys: ' + PersistenceV2.keys().toString() + ' refresh: ' + this.refresh); + /**************************** keys接口 **************************/ + // keys 本身不会刷新,需要借助状态变量刷新 + Text.onClick(() => { + this.refresh += 1; + }); + /**************************** keys接口 **************************/ + // keys 本身不会刷新,需要借助状态变量刷新 + Text.fontSize(25); + }, Text); + /**************************** keys接口 **************************/ + // keys 本身不会刷新,需要借助状态变量刷新 + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /**************************** remove接口 **************************/ + Text.create('Remove key Sample: ' + 'refresh: ' + this.refresh); + /**************************** remove接口 **************************/ + Text.onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove(Sample); + this.refresh += 1; + }); + /**************************** remove接口 **************************/ + Text.fontSize(25); + }, Text); + /**************************** remove接口 **************************/ + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Remove key global1: ' + 'refresh: ' + this.refresh); + Text.onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global1'); + this.refresh += 1; + }); + Text.fontSize(25); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Remove key global2: ' + 'refresh: ' + this.refresh); + Text.onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global2'); + this.refresh += 1; + }); + Text.fontSize(25); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('Remove key global3: ' + 'refresh: ' + this.refresh); + Text.onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.remove('global3'); + this.refresh += 1; + }); + Text.fontSize(25); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /**************************** reConnect **************************/ + // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据 + Text.create('ReConnect key global2: ' + 'refresh: ' + this.refresh); + /**************************** reConnect **************************/ + // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据 + Text.onClick(() => { + // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect + PersistenceV2.globalConnect(this.options); + this.refresh += 1; + }); + /**************************** reConnect **************************/ + // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据 + Text.fontSize(25); + }, Text); + /**************************** reConnect **************************/ + // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据 + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + /**************************** save接口 **************************/ + Text.create('not save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh); + /**************************** save接口 **************************/ + Text.onClick(() => { + // 未被@Trace保存的对象无法自动存储 + this.p.father.groupId += 1; + this.refresh += 1; + }); + /**************************** save接口 **************************/ + Text.fontSize(25); + }, Text); + /**************************** save接口 **************************/ + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh); + Text.onClick(() => { + // 未被@Trace保存的对象无法自动存储,需要调用key存储 + this.p.father.groupId += 1; + PersistenceV2.save(Sample); + this.refresh += 1; + }); + Text.fontSize(25); + }, Text); + Text.pop(); + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "Page1"; + } +} +registerNamedRoute(() => new Page1(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/repeat.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/repeat.ts new file mode 100644 index 000000000..0d70ce7f3 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/repeat.ts @@ -0,0 +1,350 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +@Observed +class Article { + id: string; + title: string; + brief: string; + isLiked: boolean; + likesCount: number; + + constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) { + this.id = id; + this.title = title; + this.brief = brief; + this.isLiked = isLiked; + this.likesCount = likesCount; + } +} + +@Entry +@Component +struct ArticleListView { + @State articleList: Array
= [ + new Article('001', '第0篇文章', '文章简介内容', false, 100), + new Article('002', '第1篇文章', '文章简介内容', false, 100), + new Article('003', '第2篇文章', '文章简介内容', false, 100), + new Article('004', '第4篇文章', '文章简介内容', false, 100), + new Article('005', '第5篇文章', '文章简介内容', false, 100), + new Article('006', '第6篇文章', '文章简介内容', false, 100), + ]; + + build() { + List() { + ForEach(this.articleList, (item: Article) => { + ListItem() { + ArticleCard({ + article: item + }) + .margin({ top: 20 }) + } + }, (item: Article) => item.id) + } + .padding(20) + .scrollBar(BarState.Off) + .backgroundColor(0xF1F3F5) + } +} + +@Component +struct ArticleCard { + @ObjectLink article: Article; + + handleLiked() { + this.article.isLiked = !this.article.isLiked; + this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1; + } + + build() { + Row() { + // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image($r('app.media.icon')) + .width(80) + .height(80) + .margin({ right: 20 }) + + Column() { + Text(this.article.title) + .fontSize(20) + .margin({ bottom: 8 }) + Text(this.article.brief) + .fontSize(16) + .fontColor(Color.Gray) + .margin({ bottom: 8 }) + + Row() { + // 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked')) + .width(24) + .height(24) + .margin({ right: 8 }) + Text(this.article.likesCount.toString()) + .fontSize(16) + } + .onClick(() => this.handleLiked()) + .justifyContent(FlexAlign.Center) + } + .alignItems(HorizontalAlign.Start) + .width('80%') + .height('100%') + } + .padding(20) + .borderRadius(12) + .backgroundColor('#FFECECEC') + .height(120) + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +interface ArticleCard_Params { + article?: Article; +} +interface ArticleListView_Params { + articleList?: Array
; +} +@Observed +class Article { + id: string; + title: string; + brief: string; + isLiked: boolean; + likesCount: number; + constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) { + this.id = id; + this.title = title; + this.brief = brief; + this.isLiked = isLiked; + this.likesCount = likesCount; + } +} +class ArticleListView extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.__articleList = new ObservedPropertyObjectPU([ + new Article('001', '第0篇文章', '文章简介内容', false, 100), + new Article('002', '第1篇文章', '文章简介内容', false, 100), + new Article('003', '第2篇文章', '文章简介内容', false, 100), + new Article('004', '第4篇文章', '文章简介内容', false, 100), + new Article('005', '第5篇文章', '文章简介内容', false, 100), + new Article('006', '第6篇文章', '文章简介内容', false, 100), + ], this, "articleList"); + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: ArticleListView_Params) { + if (params.articleList !== undefined) { + this.articleList = params.articleList; + } + } + updateStateVars(params: ArticleListView_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__articleList.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__articleList.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + private __articleList: ObservedPropertyObjectPU>; + get articleList() { + return this.__articleList.get(); + } + set articleList(newValue: Array
) { + this.__articleList.set(newValue); + } + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + List.create(); + List.padding(20); + List.scrollBar(BarState.Off); + List.backgroundColor(0xF1F3F5); + }, List); + this.observeComponentCreation2((elmtId, isInitialRender) => { + ForEach.create(); + const forEachItemGenFunction = _item => { + const item = _item; + { + const itemCreation = (elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + ListItem.create(deepRenderFunction, true); + if (!isInitialRender) { + ListItem.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }; + const itemCreation2 = (elmtId, isInitialRender) => { + ListItem.create(deepRenderFunction, true); + }; + const deepRenderFunction = (elmtId, isInitialRender) => { + itemCreation(elmtId, isInitialRender); + this.observeComponentCreation2((elmtId, isInitialRender) => { + __Common__.create(); + __Common__.margin({ top: 20 }); + }, __Common__); + { + this.observeComponentCreation2((elmtId, isInitialRender) => { + if (isInitialRender) { + let componentCall = new ArticleCard(this, { + article: item + }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 34, col: 11 }); + ViewPU.create(componentCall); + let paramsLambda = () => { + return { + article: item + }; + }; + componentCall.paramsGenerator_ = paramsLambda; + } + else { + this.updateStateVarsOfChildByElmtId(elmtId, { + article: item + }); + } + }, { name: "ArticleCard" }); + } + __Common__.pop(); + ListItem.pop(); + }; + this.observeComponentCreation2(itemCreation2, ListItem); + ListItem.pop(); + } + }; + this.forEachUpdateFunction(elmtId, this.articleList, forEachItemGenFunction, (item: Article) => item.id, false, false); + }, ForEach); + ForEach.pop(); + List.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "ArticleListView"; + } +} +class ArticleCard extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.__article = new SynchedPropertyNesedObjectPU(params.article, this, "article"); + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: ArticleCard_Params) { + this.__article.set(params.article); + } + updateStateVars(params: ArticleCard_Params) { + this.__article.set(params.article); + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__article.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__article.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + private __article: SynchedPropertyNesedObjectPU
; + get article() { + return this.__article.get(); + } + handleLiked() { + this.article.isLiked = !this.article.isLiked; + this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1; + } + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Row.create(); + Row.padding(20); + Row.borderRadius(12); + Row.backgroundColor('#FFECECEC'); + Row.height(120); + Row.width('100%'); + Row.justifyContent(FlexAlign.SpaceBetween); + }, Row); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.create({ "id": 16777225, "type": 20000, params: [], "bundleName": "com.example.myapplication", "moduleName": "entry" }); + // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.width(80); + // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.height(80); + // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.margin({ right: 20 }); + }, Image); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + Column.alignItems(HorizontalAlign.Start); + Column.width('80%'); + Column.height('100%'); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create(this.article.title); + Text.fontSize(20); + Text.margin({ bottom: 8 }); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create(this.article.brief); + Text.fontSize(16); + Text.fontColor(Color.Gray); + Text.margin({ bottom: 8 }); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Row.create(); + Row.onClick(() => this.handleLiked()); + Row.justifyContent(FlexAlign.Center); + }, Row); + this.observeComponentCreation2((elmtId, isInitialRender) => { + // 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.create(this.article.isLiked ? { "id": 16777225, "type": 20000, params: [], "bundleName": "com.example.myapplication", "moduleName": "entry" } : { "id": 16777225, "type": 20000, params: [], "bundleName": "com.example.myapplication", "moduleName": "entry" }); + // 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.width(24); + // 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.height(24); + // 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 + Image.margin({ right: 8 }); + }, Image); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create(this.article.likesCount.toString()); + Text.fontSize(16); + }, Text); + Text.pop(); + Row.pop(); + Column.pop(); + Row.pop(); + } + rerender() { + this.updateDirtyElements(); + } +} +registerNamedRoute(() => new ArticleListView(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; \ No newline at end of file diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/reusablev2.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/reusablev2.ts new file mode 100644 index 000000000..15828fbd4 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/reusablev2.ts @@ -0,0 +1,248 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +class Model { + public value: string; + + constructor(value: string) { + this.value = value; + } +} + +@Entry +@Component +struct EntryComponent { + build() { + Column() { + // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化 + MyComponent({ count: 1, increaseBy: 2 }) + .width(300) + MyComponent({ title: new Model('Hello World 2'), count: 7 }) + } + } +} + +@Component +struct MyComponent { + @State title: Model = new Model('Hello World'); + @State count: number = 0; + increaseBy: number = 1; + + build() { + Column() { + Text('' + this.title.value) + .margin(10) + Button('Click to change title') + .onClick(() => { + // @State变量的更新将触发上面的Text组件内容更新 + this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI'; + }) + .width(300) + .margin(10) + + Button('Click to increase count =' + this.count) + .onClick(() => { + // @State变量的更新将触发该Button组件的内容更新 + this.count += this.increaseBy; + }) + .width(300) + .margin(10) + } + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +interface MyComponent_Params { + title?: Model; + count?: number; + increaseBy?: number; +} +interface EntryComponent_Params { +} +class Model { + public value: string; + constructor(value: string) { + this.value = value; + } +} +class EntryComponent extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: EntryComponent_Params) { + } + updateStateVars(params: EntryComponent_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + } + aboutToBeDeleted() { + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + __Common__.create(); + __Common__.width(300); + }, __Common__); + { + this.observeComponentCreation2((elmtId, isInitialRender) => { + if (isInitialRender) { + let componentCall = new + // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化 + MyComponent(this, { count: 1, increaseBy: 2 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 15, col: 7 }); + ViewPU.create(componentCall); + let paramsLambda = () => { + return { + count: 1, + increaseBy: 2 + }; + }; + componentCall.paramsGenerator_ = paramsLambda; + } + else { + this.updateStateVarsOfChildByElmtId(elmtId, {}); + } + }, { name: "MyComponent" }); + } + __Common__.pop(); + { + this.observeComponentCreation2((elmtId, isInitialRender) => { + if (isInitialRender) { + let componentCall = new MyComponent(this, { title: new Model('Hello World 2'), count: 7 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 17, col: 7 }); + ViewPU.create(componentCall); + let paramsLambda = () => { + return { + title: new Model('Hello World 2'), + count: 7 + }; + }; + componentCall.paramsGenerator_ = paramsLambda; + } + else { + this.updateStateVarsOfChildByElmtId(elmtId, {}); + } + }, { name: "MyComponent" }); + } + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "EntryComponent"; + } +} +class MyComponent extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.__title = new ObservedPropertyObjectPU(new Model('Hello World'), this, "title"); + this.__count = new ObservedPropertySimplePU(0, this, "count"); + this.increaseBy = 1; + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: MyComponent_Params) { + if (params.title !== undefined) { + this.title = params.title; + } + if (params.count !== undefined) { + this.count = params.count; + } + if (params.increaseBy !== undefined) { + this.increaseBy = params.increaseBy; + } + } + updateStateVars(params: MyComponent_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__title.purgeDependencyOnElmtId(rmElmtId); + this.__count.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__title.aboutToBeDeleted(); + this.__count.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + private __title: ObservedPropertyObjectPU; + get title() { + return this.__title.get(); + } + set title(newValue: Model) { + this.__title.set(newValue); + } + private __count: ObservedPropertySimplePU; + get count() { + return this.__count.get(); + } + set count(newValue: number) { + this.__count.set(newValue); + } + private increaseBy: number; + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('' + this.title.value); + Text.margin(10); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Button.createWithLabel('Click to change title'); + Button.onClick(() => { + // @State变量的更新将触发上面的Text组件内容更新 + this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI'; + }); + Button.width(300); + Button.margin(10); + }, Button); + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Button.createWithLabel('Click to increase count =' + this.count); + Button.onClick(() => { + // @State变量的更新将触发该Button组件的内容更新 + this.count += this.increaseBy; + }); + Button.width(300); + Button.margin(10); + }, Button); + Button.pop(); + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } +} +registerNamedRoute(() => new EntryComponent(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/state.ts b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/state.ts new file mode 100644 index 000000000..15828fbd4 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForCase/state.ts @@ -0,0 +1,248 @@ +/* + * Copyright (c) 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. + */ + +exports.source = +` +class Model { + public value: string; + + constructor(value: string) { + this.value = value; + } +} + +@Entry +@Component +struct EntryComponent { + build() { + Column() { + // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化 + MyComponent({ count: 1, increaseBy: 2 }) + .width(300) + MyComponent({ title: new Model('Hello World 2'), count: 7 }) + } + } +} + +@Component +struct MyComponent { + @State title: Model = new Model('Hello World'); + @State count: number = 0; + increaseBy: number = 1; + + build() { + Column() { + Text('' + this.title.value) + .margin(10) + Button('Click to change title') + .onClick(() => { + // @State变量的更新将触发上面的Text组件内容更新 + this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI'; + }) + .width(300) + .margin(10) + + Button('Click to increase count =' + this.count) + .onClick(() => { + // @State变量的更新将触发该Button组件的内容更新 + this.count += this.increaseBy; + }) + .width(300) + .margin(10) + } + } +} +`; + +exports.expectResult = +` +if (!("finalizeConstruction" in ViewPU.prototype)) { + Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); +} +interface MyComponent_Params { + title?: Model; + count?: number; + increaseBy?: number; +} +interface EntryComponent_Params { +} +class Model { + public value: string; + constructor(value: string) { + this.value = value; + } +} +class EntryComponent extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: EntryComponent_Params) { + } + updateStateVars(params: EntryComponent_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + } + aboutToBeDeleted() { + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + __Common__.create(); + __Common__.width(300); + }, __Common__); + { + this.observeComponentCreation2((elmtId, isInitialRender) => { + if (isInitialRender) { + let componentCall = new + // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化 + MyComponent(this, { count: 1, increaseBy: 2 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 15, col: 7 }); + ViewPU.create(componentCall); + let paramsLambda = () => { + return { + count: 1, + increaseBy: 2 + }; + }; + componentCall.paramsGenerator_ = paramsLambda; + } + else { + this.updateStateVarsOfChildByElmtId(elmtId, {}); + } + }, { name: "MyComponent" }); + } + __Common__.pop(); + { + this.observeComponentCreation2((elmtId, isInitialRender) => { + if (isInitialRender) { + let componentCall = new MyComponent(this, { title: new Model('Hello World 2'), count: 7 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 17, col: 7 }); + ViewPU.create(componentCall); + let paramsLambda = () => { + return { + title: new Model('Hello World 2'), + count: 7 + }; + }; + componentCall.paramsGenerator_ = paramsLambda; + } + else { + this.updateStateVarsOfChildByElmtId(elmtId, {}); + } + }, { name: "MyComponent" }); + } + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } + static getEntryName(): string { + return "EntryComponent"; + } +} +class MyComponent extends ViewPU { + constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) { + super(parent, __localStorage, elmtId, extraInfo); + if (typeof paramsLambda === "function") { + this.paramsGenerator_ = paramsLambda; + } + this.__title = new ObservedPropertyObjectPU(new Model('Hello World'), this, "title"); + this.__count = new ObservedPropertySimplePU(0, this, "count"); + this.increaseBy = 1; + this.setInitiallyProvidedValue(params); + this.finalizeConstruction(); + } + setInitiallyProvidedValue(params: MyComponent_Params) { + if (params.title !== undefined) { + this.title = params.title; + } + if (params.count !== undefined) { + this.count = params.count; + } + if (params.increaseBy !== undefined) { + this.increaseBy = params.increaseBy; + } + } + updateStateVars(params: MyComponent_Params) { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__title.purgeDependencyOnElmtId(rmElmtId); + this.__count.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__title.aboutToBeDeleted(); + this.__count.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + private __title: ObservedPropertyObjectPU; + get title() { + return this.__title.get(); + } + set title(newValue: Model) { + this.__title.set(newValue); + } + private __count: ObservedPropertySimplePU; + get count() { + return this.__count.get(); + } + set count(newValue: number) { + this.__count.set(newValue); + } + private increaseBy: number; + initialRender() { + this.observeComponentCreation2((elmtId, isInitialRender) => { + Column.create(); + }, Column); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Text.create('' + this.title.value); + Text.margin(10); + }, Text); + Text.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Button.createWithLabel('Click to change title'); + Button.onClick(() => { + // @State变量的更新将触发上面的Text组件内容更新 + this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI'; + }); + Button.width(300); + Button.margin(10); + }, Button); + Button.pop(); + this.observeComponentCreation2((elmtId, isInitialRender) => { + Button.createWithLabel('Click to increase count =' + this.count); + Button.onClick(() => { + // @State变量的更新将触发该Button组件的内容更新 + this.count += this.increaseBy; + }); + Button.width(300); + Button.margin(10); + }, Button); + Button.pop(); + Column.pop(); + } + rerender() { + this.updateDirtyElements(); + } +} +registerNamedRoute(() => new EntryComponent(undefined, {}), "", { bundleName: "com.example.myapplication", moduleName: "entry", pagePath: "pages/Index", pageFullPath: "entry/src/main/ets/pages/Index", integratedHsp: "false", moduleType: "followWithHap" }); +`; -- Gitee