1/* 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const testViewStateFU = tsuite("View State", () => { 17 18 // instead of a register, use glbal variables 19 let childView: Child; 20 21 class Parent extends View { 22 // generated from 23 // @State private forLink_ : boolean = 1 24 // Info is the public property not the name of the prop in backing store 25 /* private, for test purposes: */ public __forLink_: ObservedPropertySimple<boolean> 26 = new ObservedPropertySimple<boolean>(true, this, "forLink_"); 27 public get forLink_(): boolean { 28 return this.__forLink_.get(); 29 } 30 public set forLink_(newValue: boolean) { 31 this.__forLink_.set(newValue); 32 } 33 34 // generated from 35 // @State private forProp_ : string 36 // Info is the public property not the name of the prop in backing store 37 /* private, for test purposes: */ public __forProp_: ObservedPropertySimple<string> 38 = new ObservedPropertySimple<string>("ForProp Value OK", this, "forProp_"); 39 public get forProp_(): string { 40 return this.__forProp_.get(); 41 } 42 public set forProp_(newValue: string) { 43 this.__forProp_.set(newValue); 44 } 45 46 // generated from 47 // private forReg_ : string = "forReg Orig"; 48 /* private, for test purposes: */ public forReg_: string = "forReg Orig"; 49 50 // generated based on state definitions 51 constructor(compilerAssignedUniqueChildId: string, parent: View, 52 params: { 53 forLink_?: boolean, 54 forProp_?: string, 55 forReg_?: string; 56 }, 57 uri : string, 58 localStorage : LocalStorage) { 59 super(compilerAssignedUniqueChildId, parent, localStorage); 60 // tsc will add initialization of forLink_ and forProp_ with defauilt values here 61 this.updateWithValueParams(params); 62 } 63 64 // generated based on state definitions 65 public updateWithValueParams(params: { 66 forLink_?: boolean, 67 forProp_?: string, 68 forReg_?: string 69 }): void { 70 console.debug(`${this.id__()}:${this.constructor.name}: updateWithValueParams ...`); 71 if (params.forLink_) { 72 this.forLink_ = params.forLink_; 73 } 74 if (params.forProp_) { 75 this.forProp_ = params.forProp_; 76 } 77 if (params.forReg_) { 78 this.forReg_ = params.forReg_; 79 } 80 console.debug(`${this.id__()}:${this.constructor.name}: updateWithValueParams done`); 81 } 82 83 // generated based on state definitions 84 public aboutToBeDeleted() { 85 console.debug(`${this.id__()}:${this.constructor.name}: abouttoBeDeleted`); 86 // tell the ObservedProperty this View is to be deleted 87 this.__forLink_.aboutToBeDeleted(this); 88 this.__forProp_.aboutToBeDeleted(this); 89 SubscriberManager.Delete(this.id__()); 90 } 91 92 // added just to make things work, not in production when this func is implemented in C++ 93 findChildById(compilerAssignedUniqueChildId: string): View { 94 console.log(`${this.id__()}:${this.constructor.name}: findChildById ${compilerAssignedUniqueChildId}. Will not work!`); 95 return childView; 96 } 97 98 public render() { 99 // dump state 100 console.debug(`${this.id__()}:${this.constructor.name}: render: state is: forLink_: ${this.forLink_}, forProp_: ${this.forProp_}`); 101 102 const compilerAssignedChildId = "1"; 103 /* global var */ childView = this.findChildById(compilerAssignedChildId) as Child; 104 if (!childView) { 105 childView = new Child(compilerAssignedChildId, this, { 106 link_: this.__forLink_, 107 prop_: this.__forProp_, 108 reg_: this.forReg_ 109 }); 110 } else { 111 childView.updateWithValueParams({ reg_: this.forReg_ }); 112 } 113 } 114 } 115 116 // Child ---------------------------------------- 117 class Child extends View 118 implements IPropertySubscriber { 119 120 // generated from 121 // @State private num_ : number = 1; 122 private __num_: ObservedPropertySimple<number> 123 = new ObservedPropertySimple<number>(1, this, "__num_"); 124 /* private changed to for testing: */ public get num_(): number { 125 return this["__num_"].get(); 126 } 127 /* private changed to for testing: */ public set num_(newValue: number) { 128 this["__num_"].set(newValue); 129 } 130 131 // generated from 132 // @Link private link_ : boolean 133 /* private changed to for testing: */ __link_: ObservedPropertySimpleAbstract<boolean>; 134 /* private changed to for testing: */ public get link_(): boolean { 135 return this.__link_.get(); 136 } 137 /* private changed to for testing: */ public set link_(newValue: boolean) { 138 this.__link_.set(newValue); 139 } 140 141 // generated from 142 // @Prop private prop_ : string 143 /* private changed to for testing: */ __prop_: ObservedPropertySimpleAbstract<string>; 144 /* private changed to for testing: */ public get prop_(): string { 145 return this.__prop_.get(); 146 } 147 /* private changed to for testing: */ public set prop_(newValue: string) { 148 this.__prop_.set(newValue); 149 } 150 151 // eDSL compiler makes no change 152 /* private changed to for testing: */ public reg_: string = "initial value" 153 154 constructor(compilerAssignedUniqueChildId: string, parent: View, 155 params: { 156 num_?: number, 157 link_: ObservedPropertySimpleAbstract<boolean>, 158 prop_: ObservedPropertySimpleAbstract<string>, 159 reg_?: string 160 }) { 161 super(compilerAssignedUniqueChildId, parent); 162 // tsc will add initialization of num_ and reg_ with defauilt values here 163 this.__link_ = params.link_.createLink(this, "__link_"); 164 this.__prop_ = params.prop_.createProp(this, "__prop_"); 165 this.updateWithValueParams(params); 166 } 167 168 // generated 169 public updateWithValueParams(params: { num_?: number, reg_?: string }): void { 170 console.debug(`${this.id__()}:${this.constructor.name}: updateWithValueParams start`); 171 if (params.num_) { 172 this.num_ = params.num_; 173 } 174 if (params.reg_) { 175 this.reg_ = params.reg_; 176 } 177 console.debug(`${this.constructor.name}: updateWithValueParams done`); 178 } 179 180 // generated based on state definitions 181 // notify all SubscribedAbstractProperty objects they will be deleted soon 182 public aboutToBeDeleted() { 183 console.debug(`${this.id__()}:${this.constructor.name}: aboutToBeDeleted`); 184 // notify @State 185 // this View unsubscribes 186 this.__num_.aboutToBeDeleted(this); 187 188 // notify for @Link and @Prop 189 // to allow them to unsubscribe from their source properties 190 // this View unsubscribes as well 191 this.__link_.aboutToBeDeleted(); 192 this.__prop_.aboutToBeDeleted(); 193 194 SubscriberManager.Delete(this.id__()); 195 } 196 197 198 // transpiled from build() 199 public render() { 200 // dump state 201 console.debug(`${this.id__()}:${this.constructor.name}: render: state is: num_: ${this.num_}, link_: ${this.link_}, prop_: ${this.prop_}, reg_=${this.reg_}`); 202 } 203 } 204 205 206 // simulate the process 207 208 console.debug("create LocalStorage ...") 209 const uri = "stateMgmt/src/utest/view_test.ts"; 210 let localStorageForView : LocalStorage = new LocalStorage({}); 211 212 console.debug("create Parent ..."); 213 let parentView: Parent = new Parent("0", undefined, {}, uri, localStorageForView); 214 215 216 tcase("Parent.render #1 ...", () => { 217 218 // to test the recording of get access / View.propsNeededForRender 219 parentView.aboutToRender(); 220 parentView.render(); 221 parentView.onRenderDone(); 222 223 test(`get access recording during parent render, recorded: ${JSON.stringify(Array.from(parentView.propertiesNeededToRender()))}`, 224 eqSet(parentView.propertiesNeededToRender(), new Set<string>(["forLink_", "forProp_"]))); 225 226 childView.render(); 227 228 test(`parentView.forLink_ value ok`, parentView.forLink_ == true); 229 test(`parentView.forProp_ value ok`, parentView.forProp_ == "ForProp Value OK"); 230 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg Orig"); 231 232 test(`childView.num_ value ok`, childView.num_ == 1); 233 test(`childView.link_ value ok`, childView.link_ == true); 234 test(`childView.prop_ value ok`, childView.prop_ == "ForProp Value OK"); 235 test(`childView.reg_ value ok`, childView.reg_ == "forReg Orig"); 236 237 test(`Check @Link and @State subscribed ok: parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 2); 238 test(`Check @Link and @State subscribed ok: parent.__forProp_ subscribers`, parentView.__forProp_.numberOfSubscrbers() == 2); 239 240 SubscriberManager.DumpSubscriberInfo(); 241 242 // parent view 243 // parent view 2 state vard 244 // child view 245 // child view 3 state vars 246 // 7 subscribers total 247 test(`SubscriberManager num of subscribers, is ${SubscriberManager.NumberOfSubscribers()} should be 7`, SubscriberManager.NumberOfSubscribers() == 7); 248 }); 249 250 tcase("Simulate an event handler mutating parent's @State props", () => { 251 252 let spyParentPropertyHasChanged = spyOn(parentView, "propertyHasChanged"); 253 let spyChildPropertyHasChanged = spyOn(childView, "propertyHasChanged"); 254 255 let spyParentPropertyHasBeenRead = spyOn(parentView, "propertyRead"); 256 let spyChildPropertyHasBeenRead = spyOn(childView, "propertyRead"); 257 258 parentView.forLink_ = false; 259 parentView.forProp_ = "_forProp changed value OK"; 260 // render will do the updating of the prop value: 261 childView.prop_ = parentView.forProp_; 262 263 test(`parentView.forLink_ value ok`, parentView.forLink_ == false); 264 test(`parentView.forProp_ value ok`, parentView.forProp_ == "_forProp changed value OK"); 265 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg Orig"); 266 267 test(`childView.num_ unchanged`, childView.num_ == 1); 268 test(`childView.link_ updated ok`, childView.link_ == false); 269 test(`childView.prop_ updated ok`, childView.prop_ == "_forProp changed value OK"); 270 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 271 272 test(`parentView.aPropertyHasChanged was called`, spyParentPropertyHasChanged.called) 273 test(`childView.aPropertyHasChanged was called`, spyChildPropertyHasChanged.called) 274 275 test(`parentView.aPropertyHasBeenRead was called`, spyParentPropertyHasBeenRead.called) 276 test(`childView.aPropertyHasBeenRead was called`, spyChildPropertyHasBeenRead.called) 277 }); 278 279 tcase("Parent.render #2 ...", () => { 280 281 parentView.render(); 282 childView.render(); 283 284 test(`childView.num_ unchanged`, childView.num_ == 1); 285 test(`childView.link_ unchanged`, childView.link_ == false); 286 test(`childView.prop_ unchanged`, childView.prop_ == "_forProp changed value OK"); 287 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 288 }); 289 290 tcase("Simulate an event handler mutating parent's regular (unobserved) variable", () => { 291 292 let spyParentPropertyHasChanged = spyOn(parentView, "propertyHasChanged"); 293 294 parentView.forReg_ = "forReg_ chanaged"; 295 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg_ chanaged"); 296 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 297 test(`parentView.propertyHasChanged was NOT called`, !spyParentPropertyHasChanged.called) 298 }); 299 300 tcase("Parent.render #3 ...", () => { 301 302 parentView.render(); 303 childView.render(); 304 305 let spyChildaPropertyHasChanged = spyOn(childView, "aPropertyHasChanged"); 306 307 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg_ chanaged"); 308 test(`childView.reg_ changed`, childView.reg_ == "forReg_ chanaged"); 309 test(`childView.aPropertyHasChanged was NOT called`, !spyChildaPropertyHasChanged.called) 310 }); 311 312 tcase("Notify about deletion, first child ...", () => { 313 314 let spyChildLinkAboutToBeDeleted = spyOn(childView.__prop_, "aboutToBeDeleted"); 315 let spyChildPropAboutToBeDeleted = spyOn(childView.__link_, "aboutToBeDeleted"); 316 317 childView.aboutToBeDeleted(); 318 test(`aboutToBeDeleted was called on childView.__link`, spyChildLinkAboutToBeDeleted.called); 319 test(`aboutToBeDeleted was called on childView.__prop`, spyChildPropAboutToBeDeleted.called); 320 test(`parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 1); 321 322 SubscriberManager.DumpSubscriberInfo(); 323 324 // parent view 325 // parent view forLink_, forProp_ state vars 326 test(`SubscriberManager num of subscribers is ${SubscriberManager.NumberOfSubscribers()} should be 3`, SubscriberManager.NumberOfSubscribers() == 3); 327 }); 328 329 tcase("Notify about deletion, second parent ...", () => { 330 parentView.aboutToBeDeleted(); 331 test(`parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 0); 332 333 SubscriberManager.DumpSubscriberInfo(); 334 335 test(`SubscriberManager num of subscribers is ${SubscriberManager.NumberOfSubscribers()} should be 0 .`, SubscriberManager.NumberOfSubscribers() == 0); 336 }); 337 338}); 339