1/* 2 * Copyright (c) 2021 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 testViewState = 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 super(compilerAssignedUniqueChildId, parent); 58 // tsc will add initialization of forLink_ and forProp_ with defauilt values here 59 this.updateWithValueParams(params); 60 } 61 62 // generated based on state definitions 63 public updateWithValueParams(params: { 64 forLink_?: boolean, 65 forProp_?: string, 66 forReg_?: string 67 }): void { 68 console.debug(`${this.id()}:${this.constructor.name}: updateWithValueParams ...`); 69 if (params.forLink_) { 70 this.forLink_ = params.forLink_; 71 } 72 if (params.forProp_) { 73 this.forProp_ = params.forProp_; 74 } 75 if (params.forReg_) { 76 this.forReg_ = params.forReg_; 77 } 78 console.debug(`${this.id()}:${this.constructor.name}: updateWithValueParams done`); 79 } 80 81 // generated based on state definitions 82 public aboutToBeDeleted() { 83 console.debug(`${this.id()}:${this.constructor.name}: abouttoBeDeleted`); 84 // tell the ObservedProperty this View is to be deleted 85 this.__forLink_.aboutToBeDeleted(this); 86 this.__forProp_.aboutToBeDeleted(this); 87 SubscriberManager.Get().delete(this.id()); 88 } 89 90 // added just to make things work, not in production when this func is implemented in C++ 91 findChildById(compilerAssignedUniqueChildId: string): View { 92 console.log(`${this.id()}:${this.constructor.name}: findChildById ${compilerAssignedUniqueChildId}. Will not work!`); 93 return childView; 94 } 95 96 public render() { 97 // dump state 98 console.debug(`${this.id()}:${this.constructor.name}: render: state is: forLink_: ${this.forLink_}, forProp_: ${this.forProp_}`); 99 100 const compilerAssignedChildId = "1"; 101 /* global var */ childView = this.findChildById(compilerAssignedChildId) as Child; 102 if (!childView) { 103 childView = new Child(compilerAssignedChildId, this, { 104 link_: this.__forLink_, 105 prop_: this.__forProp_, 106 reg_: this.forReg_ 107 }); 108 } else { 109 childView.updateWithValueParams({ reg_: this.forReg_ }); 110 } 111 } 112 } 113 114 // Child ---------------------------------------- 115 class Child extends View 116 implements IPropertySubscriber { 117 118 // generated from 119 // @State private num_ : number = 1; 120 private __num_: ObservedPropertySimple<number> 121 = new ObservedPropertySimple<number>(1, this, "__num_"); 122 /* private changed to for testing: */ public get num_(): number { 123 return this["__num_"].get(); 124 } 125 /* private changed to for testing: */ public set num_(newValue: number) { 126 this["__num_"].set(newValue); 127 } 128 129 // generated from 130 // @Link private link_ : boolean 131 /* private changed to for testing: */ __link_: ObservedPropertySimpleAbstract<boolean>; 132 /* private changed to for testing: */ public get link_(): boolean { 133 return this.__link_.get(); 134 } 135 /* private changed to for testing: */ public set link_(newValue: boolean) { 136 this.__link_.set(newValue); 137 } 138 139 // generated from 140 // @Prop private prop_ : string 141 /* private changed to for testing: */ __prop_: ObservedPropertySimpleAbstract<string>; 142 /* private changed to for testing: */ public get prop_(): string { 143 return this.__prop_.get(); 144 } 145 /* private changed to for testing: */ public set prop_(newValue: string) { 146 this.__prop_.set(newValue); 147 } 148 149 // eDSL compiler makes no chnage 150 /* private changed to for testing: */ public reg_: string = "initial value" 151 152 constructor(compilerAssignedUniqueChildId: string, parent: View, 153 params: { 154 num_?: number, 155 link_: ObservedPropertySimpleAbstract<boolean>, 156 prop_: ObservedPropertySimpleAbstract<string>, 157 reg_?: string 158 }) { 159 super(compilerAssignedUniqueChildId, parent); 160 // tsc will add initialization of num_ and reg_ with defauilt values here 161 this.__link_ = params.link_.createLink(this, "__link_"); 162 this.__prop_ = params.prop_.createProp(this, "__prop_"); 163 this.updateWithValueParams(params); 164 } 165 166 // generated 167 public updateWithValueParams(params: { num_?: number, reg_?: string }): void { 168 console.debug(`${this.id()}:${this.constructor.name}: updateWithValueParams start`); 169 if (params.num_) { 170 this.num_ = params.num_; 171 } 172 if (params.reg_) { 173 this.reg_ = params.reg_; 174 } 175 console.debug(`${this.constructor.name}: updateWithValueParams done`); 176 } 177 178 // generated based on state definitions 179 // notify all SubscribedAbstractProperty objects they will be deleted soon 180 public aboutToBeDeleted() { 181 console.debug(`${this.id()}:${this.constructor.name}: aboutToBeDeleted`); 182 // notify @State 183 // this View unsubscribes 184 this.__num_.aboutToBeDeleted(this); 185 186 // notify for @Link and @Prop 187 // to allow them to unsubscribe from their source properties 188 // this View unsubscribes as well 189 this.__link_.aboutToBeDeleted(); 190 this.__prop_.aboutToBeDeleted(); 191 192 SubscriberManager.Get().delete(this.id()); 193 } 194 195 196 // transpiled from build() 197 public render() { 198 // dump state 199 console.debug(`${this.id()}:${this.constructor.name}: render: state is: num_: ${this.num_}, link_: ${this.link_}, prop_: ${this.prop_}, reg_=${this.reg_}`); 200 } 201 } 202 203 204 // simulate the process 205 console.debug("create Parent ...") 206 let parentView: Parent = new Parent("0", undefined, {}) 207 208 209 tcase("Parent.render #1 ...", () => { 210 211 // to test the recording of get access / View.propsNeededForRender 212 parentView.aboutToRender(); 213 parentView.render(); 214 parentView.onRenderDone(); 215 216 test(`get access recording during parent render, recorded: ${JSON.stringify(Array.from(parentView.propertiesNeededToRender()))}`, 217 eqSet(parentView.propertiesNeededToRender(), new Set<string>(["forLink_", "forProp_"]))); 218 219 childView.render(); 220 221 test(`parentView.forLink_ value ok`, parentView.forLink_ == true); 222 test(`parentView.forProp_ value ok`, parentView.forProp_ == "ForProp Value OK"); 223 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg Orig"); 224 225 test(`childView.num_ value ok`, childView.num_ == 1); 226 test(`childView.link_ value ok`, childView.link_ == true); 227 test(`childView.prop_ value ok`, childView.prop_ == "ForProp Value OK"); 228 test(`childView.reg_ value ok`, childView.reg_ == "forReg Orig"); 229 230 test(`Check @Link and @State subscribed ok: parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 2); 231 test(`Check @Link and @State subscribed ok: parent.__forProp_ subscribers`, parentView.__forProp_.numberOfSubscrbers() == 2); 232 233 SubscriberManager.Get().dumpSubscriberInfo(); 234 // parent view 235 // parent view 2 state vard 236 // child view 237 // child view 3 state vars 238 // 7 subscribers total 239 test(`SubscriberManager num of subscribers, is ${SubscriberManager.Get().numberOfSubscrbers()} should be 7`, SubscriberManager.Get().numberOfSubscrbers() == 7); 240 }); 241 242 tcase("Simulate an event handler mutating parent's @State props", () => { 243 244 let spyParentPropertyHasChanged = spyOn(parentView, "propertyHasChanged"); 245 let spyChildPropertyHasChanged = spyOn(childView, "propertyHasChanged"); 246 247 let spyParentPropertyHasBeenRead = spyOn(parentView, "propertyRead"); 248 let spyChildPropertyHasBeenRead = spyOn(childView, "propertyRead"); 249 250 parentView.forLink_ = false; 251 parentView.forProp_ = "_forProp changed value OK"; 252 // render will do the updating of the prop value: 253 childView.prop_ = parentView.forProp_; 254 255 test(`parentView.forLink_ value ok`, parentView.forLink_ == false); 256 test(`parentView.forProp_ value ok`, parentView.forProp_ == "_forProp changed value OK"); 257 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg Orig"); 258 259 test(`childView.num_ unchanged`, childView.num_ == 1); 260 test(`childView.link_ updated ok`, childView.link_ == false); 261 test(`childView.prop_ updated ok`, childView.prop_ == "_forProp changed value OK"); 262 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 263 264 test(`parentView.aPropertyHasChanged was called`, spyParentPropertyHasChanged.called) 265 test(`childView.aPropertyHasChanged was called`, spyChildPropertyHasChanged.called) 266 267 test(`parentView.aPropertyHasBeenRead was called`, spyParentPropertyHasBeenRead.called) 268 test(`childView.aPropertyHasBeenRead was called`, spyChildPropertyHasBeenRead.called) 269 }); 270 271 tcase("Parent.render #2 ...", () => { 272 273 parentView.render(); 274 childView.render(); 275 276 test(`childView.num_ unchanged`, childView.num_ == 1); 277 test(`childView.link_ unchanged`, childView.link_ == false); 278 test(`childView.prop_ unchanged`, childView.prop_ == "_forProp changed value OK"); 279 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 280 }); 281 282 tcase("Simulate an event handler mutating parent's regular (unobserved) varible", () => { 283 284 let spyParentPropertyHasChanged = spyOn(parentView, "propertyHasChanged"); 285 286 parentView.forReg_ = "forReg_ chanaged"; 287 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg_ chanaged"); 288 test(`childView.reg_ unchanged`, childView.reg_ == "forReg Orig"); 289 test(`parentView.propertyHasChanged was NOT called`, !spyParentPropertyHasChanged.called) 290 }); 291 292 tcase("Parent.render #3 ...", () => { 293 294 parentView.render(); 295 childView.render(); 296 297 let spyChildaPropertyHasChanged = spyOn(childView, "aPropertyHasChanged"); 298 299 test(`parentView.forReg_ value ok`, parentView.forReg_ == "forReg_ chanaged"); 300 test(`childView.reg_ changed`, childView.reg_ == "forReg_ chanaged"); 301 test(`childView.aPropertyHasChanged was NOT called`, !spyChildaPropertyHasChanged.called) 302 }); 303 304 tcase("Notify about deletion, first child ...", () => { 305 306 let spyChildLinkAboutToBeDeleted = spyOn(childView.__prop_, "aboutToBeDeleted"); 307 let spyChildPropAboutToBeDeleted = spyOn(childView.__link_, "aboutToBeDeleted"); 308 309 childView.aboutToBeDeleted(); 310 test(`aboutToBeDeleted was called on childView.__link`, spyChildLinkAboutToBeDeleted.called); 311 test(`aboutToBeDeleted was called on childView.__prop`, spyChildPropAboutToBeDeleted.called); 312 test(`parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 1); 313 314 SubscriberManager.Get().dumpSubscriberInfo(); 315 // parent view 316 // parent view forLink_, forProp_ state vars 317 test(`SubscriberManager num of subscribers is ${SubscriberManager.Get().numberOfSubscrbers()} should be 3`, SubscriberManager.Get().numberOfSubscrbers() == 3); 318 }); 319 320 tcase("Notify about deletion, second parent ...", () => { 321 parentView.aboutToBeDeleted(); 322 test(`parent.__forLink_ subscribers`, parentView.__forLink_.numberOfSubscrbers() == 0); 323 324 SubscriberManager.Get().dumpSubscriberInfo(); 325 326 test(`SubscriberManager num of subscribers is ${SubscriberManager.Get().numberOfSubscrbers()} should be 0 .`, SubscriberManager.Get().numberOfSubscrbers() == 0); 327 }); 328 329}); 330