@qooxdoo/framework 8.0.0-beta.2 → 8.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/Manifest.json +1 -1
  3. package/lib/compiler/compile-info.json +42 -43
  4. package/lib/compiler/index.js +555 -645
  5. package/package.json +1 -1
  6. package/source/class/qx/Class.js +23 -4
  7. package/source/class/qx/Mixin.js +15 -6
  8. package/source/class/qx/core/check/AbstractCheck.js +5 -1
  9. package/source/class/qx/core/check/CheckFactory.js +6 -0
  10. package/source/class/qx/core/check/DynamicTypeCheck.js +5 -0
  11. package/source/class/qx/core/property/ExplicitPropertyStorage.js +7 -19
  12. package/source/class/qx/core/property/IPropertyStorage.js +2 -21
  13. package/source/class/qx/core/property/Property.js +96 -72
  14. package/source/class/qx/core/property/SimplePropertyStorage.js +2 -18
  15. package/source/class/qx/data/MBinding.js +1 -1
  16. package/source/class/qx/data/SingleValueBinding.js +58 -100
  17. package/source/class/qx/data/binding/AbstractSegment.js +15 -10
  18. package/source/class/qx/data/binding/PropNameSegment.js +34 -11
  19. package/source/class/qx/test/Mixin.js +219 -0
  20. package/source/class/qx/test/Promise.js +10 -11
  21. package/source/class/qx/test/core/Property.js +22 -16
  22. package/source/class/qx/test/data/singlevalue/Async.js +17 -4
  23. package/source/class/qx/test/performance/Property.js +0 -1
  24. package/source/class/qx/test/ui/core/SingleSelectionManager.js +150 -0
  25. package/source/class/qx/tool/compiler/cli/api/CompilerApi.js +1 -2
  26. package/source/class/qx/ui/core/SingleSelectionManager.js +4 -4
  27. package/source/class/qx/ui/form/validation/Manager.js +1 -1
  28. package/source/resource/qx/decoration/Modern/table/boolean-false.png +0 -0
  29. package/source/resource/qx/decoration/Modern/table/boolean-true.png +0 -0
@@ -97,6 +97,12 @@ qx.Class.define("qx.data.SingleValueBinding", {
97
97
  * be called if the set of the value fails.
98
98
  * @property {string} ignoreConverter A string which will be matched using the current
99
99
  * property chain. If it matches, the converter will not be called.
100
+ * @property {boolean} async If true, the following will happen:
101
+ * - The binding's init promise (returned by `getInitPromise`) will resolve only after the initial value has been set on the target.
102
+ * If any properties in the source and target chains have async getters,
103
+ * the initial set may happen on a later tick.
104
+ * - The binding will call setAsync on the target value instead of the synchronous `set` and the init promise will resolve after the initial setAsync call has resolved.
105
+ *
100
106
  *
101
107
  *
102
108
  * @callback converter
@@ -126,6 +132,7 @@ qx.Class.define("qx.data.SingleValueBinding", {
126
132
  throw new Error("SourcePath and targetPath must be specified");
127
133
  }
128
134
  this.__options = options ?? {};
135
+ this.__async = !!this.__options.async;
129
136
  let tracker = {};
130
137
 
131
138
  const Utils = qx.event.Utils;
@@ -169,6 +176,10 @@ qx.Class.define("qx.data.SingleValueBinding", {
169
176
  },
170
177
 
171
178
  members: {
179
+ /**
180
+ * Whether this binding is asynchronous
181
+ */
182
+ __async: false,
172
183
  /**
173
184
  * @type {*}
174
185
  */
@@ -202,6 +213,14 @@ qx.Class.define("qx.data.SingleValueBinding", {
202
213
  */
203
214
  __options: null,
204
215
 
216
+ /**
217
+ *
218
+ * @returns {boolean}
219
+ */
220
+ isAsync() {
221
+ return this.__async;
222
+ },
223
+
205
224
  /**
206
225
  * Promises/A+ thenable compliance, this means that you can await the binding for initialisation
207
226
  * https://promisesaplus.com/
@@ -320,7 +339,7 @@ qx.Class.define("qx.data.SingleValueBinding", {
320
339
  }
321
340
  this.__record = null; // invalidate record representation cache
322
341
  this.__targetSegments = qx.data.SingleValueBinding.__parseSegments(this, value);
323
- this.__targetSegments.at(-1).addListener("changeInput", this.__updateTarget, this);
342
+ this.__targetSegments.at(-1).setChangeInputCallback(() => this.__updateTarget());
324
343
  },
325
344
 
326
345
  /**
@@ -577,27 +596,6 @@ qx.Class.define("qx.data.SingleValueBinding", {
577
596
  return out;
578
597
  },
579
598
 
580
- /**
581
- * Splits a property path into segments.
582
- * @param {string} path
583
- * @returns {string[]} an array of segments, split by dot and square brackets.
584
- * For example, splitSegments(`a.b[0].c`) will return `["a", "b", "[0]", "c"]`
585
- */
586
- splitSegments(path) {
587
- let out = [];
588
- for (let dotSplit of path.split(".")) {
589
- let bracketSplits = dotSplit.split("[");
590
- for (let i = 0; i < bracketSplits.length; i++) {
591
- let bracketSplit = bracketSplits[i];
592
- if (i > 0) {
593
- bracketSplit = "[" + bracketSplit;
594
- }
595
- out.push(bracketSplit);
596
- }
597
- }
598
- return out.filter(s => s.length > 0);
599
- },
600
-
601
599
  /**
602
600
  * Finds all bindings for an object, as either a source or target
603
601
  *
@@ -673,72 +671,6 @@ qx.Class.define("qx.data.SingleValueBinding", {
673
671
  }
674
672
  },
675
673
 
676
- /**
677
- * Helper method that sets a value for a named property
678
- *
679
- * @param {qx.core.Object} target object to have a property set
680
- * @param {String} propertyName the name of the property
681
- * @param {Object?} value the value to set
682
- */
683
- set(target, propertyName, value) {
684
- let prop = qx.util.PropertyUtil.getProperty(target.constructor, propertyName);
685
- if (!prop) {
686
- let setFuncName = "set" + qx.lang.String.firstUp(propertyName);
687
- if (typeof target[setFuncName] == "function") {
688
- return target[setFuncName](value);
689
- } else {
690
- throw new Error(`Property ${propertyName} not found on ${target.classname}`);
691
- }
692
- }
693
-
694
- if (prop.isAsync()) {
695
- return prop.setAsync(target, value);
696
- } else {
697
- return prop.set(target, value);
698
- }
699
- },
700
-
701
- /**
702
- * Helper method to get a value of a named property
703
- *
704
- * @param {qx.core.Object} target object to have a property get
705
- * @param {String} propertyName the name of the property
706
- * @returns {Object?} the property value
707
- */
708
- get(target, propertyName) {
709
- let prop = qx.util.PropertyUtil.getProperty(target.constructor, propertyName);
710
- if (prop.isAsync()) {
711
- return prop.getAsync(target);
712
- } else {
713
- return prop.get(target);
714
- }
715
- },
716
-
717
- /**
718
- * Helper method that resets a named property
719
- *
720
- * @param {qx.core.Object} target object to have a property set
721
- * @param {String} propertyName the name of the property
722
-
723
- */
724
- reset(target, propertyName) {
725
- let prop = qx.util.PropertyUtil.getProperty(target.constructor, propertyName);
726
- if (!prop) {
727
- let setFuncName = "reset" + qx.lang.String.firstUp(propertyName);
728
- if (typeof target[setFuncName] == "function") {
729
- return target[setFuncName]();
730
- } else {
731
- throw new Error(`Property ${propertyName} not found on ${target.classname}`);
732
- }
733
- }
734
-
735
- if (prop.isAsync()) {
736
- return prop.resetAsync(target);
737
- } else {
738
- return prop.reset(target);
739
- }
740
- },
741
-
742
674
  /**
743
675
  *
744
676
  * Internal helper for getting the current set value at the property chain.
@@ -783,26 +715,52 @@ qx.Class.define("qx.data.SingleValueBinding", {
783
715
  * @return {qx.data.binding.AbstractSegment[]?} the new array of segments
784
716
  */
785
717
  __parseSegments(binding, path) {
786
- let segsStrings = qx.data.SingleValueBinding.splitSegments(path);
718
+ let segsStrings = this.__splitSegments(path);
719
+ //should be let segsStrings = path.split(/\.|(?=\[)|(?<=\])/g);// split by dot or square brackets, but keep the brackets as part of the segments
720
+ //but this breaks our Rhino because it's really old
721
+ //TODO update Rhino
722
+
787
723
  let segments = [];
788
724
 
789
- for (let seg of segsStrings) {
725
+ let previous;
726
+ for (let segStr of segsStrings) {
727
+ let seg;
790
728
  //if it's an array index:
791
- if (seg.startsWith("[")) {
792
- segments.push(new qx.data.binding.ArrayIndexSegment(binding, seg));
729
+ if (segStr.startsWith("[")) {
730
+ seg = new qx.data.binding.ArrayIndexSegment(binding, segStr);
793
731
  } else {
794
732
  //otherwise, it's a normal path
795
- segments.push(new qx.data.binding.PropNameSegment(binding, seg));
733
+ seg = new qx.data.binding.PropNameSegment(binding, segStr);
796
734
  }
797
- }
798
-
799
- segments.forEach((seg, index) => {
800
- if (index < segments.length - 1) {
801
- seg.setOutputReceiver(segments[index + 1]);
735
+ if (previous) {
736
+ previous.setOutputReceiver(seg);
802
737
  }
803
- });
738
+ previous = seg;
739
+ segments.push(seg);
740
+ }
804
741
 
805
742
  return segments;
806
- }
743
+ },
744
+
745
+ /**
746
+ * Splits a property path into segments.
747
+ * @param {string} path
748
+ * @returns {string[]} an array of segments, split by dot and square brackets.
749
+ * For example, splitSegments(`a.b[0].c`) will return `["a", "b", "[0]", "c"]`
750
+ */
751
+ __splitSegments(path) {
752
+ let out = [];
753
+ for (let dotSplit of path.split(".")) {
754
+ let bracketSplits = dotSplit.split("[");
755
+ for (let i = 0; i < bracketSplits.length; i++) {
756
+ let bracketSplit = bracketSplits[i];
757
+ if (i > 0) {
758
+ bracketSplit = "[" + bracketSplit;
759
+ }
760
+ out.push(bracketSplit);
761
+ }
762
+ }
763
+ return out.filter(s => s.length > 0);
764
+ },
807
765
  }
808
766
  });
@@ -47,15 +47,12 @@ qx.Class.define("qx.data.binding.AbstractSegment", {
47
47
  }
48
48
  },
49
49
 
50
- /**
51
- * Fired when this segment's input changed, and it has been received and fully processed by the next segments in the chain,
52
- * i.e. any async set/get operations have completed.
53
- */
54
- events: {
55
- changeInput: "qx.event.type.Data"
56
- },
57
-
58
50
  members: {
51
+ /**
52
+ * Called when this segment's input changed, and it has been received and fully processed by the next segments in the chain,
53
+ * i.e. any async set/get operations have completed.
54
+ */
55
+ __changeInputCallback: () => {},
59
56
  /**
60
57
  * @type {qx.data.SingleValueBinding}
61
58
  */
@@ -70,6 +67,14 @@ qx.Class.define("qx.data.binding.AbstractSegment", {
70
67
  */
71
68
  __outputReceiver: null,
72
69
 
70
+ /**
71
+ *
72
+ * @param {Function} callback
73
+ */
74
+ setChangeInputCallback(callback) {
75
+ this.__changeInputCallback = callback;
76
+ },
77
+
73
78
  /**
74
79
  *
75
80
  * @returns {qx.data.SingleValueBinding} the binding that this segment belongs to. Useful for debugging purposes.
@@ -125,9 +130,9 @@ qx.Class.define("qx.data.binding.AbstractSegment", {
125
130
 
126
131
  let out = this._applyInput(value, old);
127
132
  if (qx.lang.Type.isPromise(out)) {
128
- return out.then(() => this.fireDataEvent("changeInput", value, old));
133
+ return out.then(() => this.__changeInputCallback(value, old));
129
134
  } else {
130
- return this.fireDataEvent("changeInput", value, old);
135
+ return this.__changeInputCallback(value, old);
131
136
  }
132
137
  },
133
138
 
@@ -69,6 +69,10 @@ qx.Class.define("qx.data.binding.PropNameSegment", {
69
69
  * Apply for `input`
70
70
  */
71
71
  _applyInput(value, oldValue) {
72
+ if (!this.getOutputReceiver()) {
73
+ return;
74
+ }
75
+
72
76
  if (oldValue) {
73
77
  let eventName = this.__getEventName(oldValue);
74
78
  eventName && oldValue.removeListener(eventName, this.__onChangeInputProperty, this);
@@ -81,9 +85,7 @@ qx.Class.define("qx.data.binding.PropNameSegment", {
81
85
  this.setEventName(eventName);
82
86
  }
83
87
 
84
- if (this.getOutputReceiver()) {
85
- return this.updateOutput();
86
- }
88
+ return this.updateOutput();
87
89
  },
88
90
 
89
91
  updateOutput() {
@@ -95,10 +97,15 @@ qx.Class.define("qx.data.binding.PropNameSegment", {
95
97
  if (property === null) {
96
98
  return this._setOutput(null);
97
99
  }
98
- if (!property.isAsync() || property.isInitialized(input)) {
99
- let nextInput = property.get(input, this.__propName);
100
+ if (!property.supportsGetAsync() || property.hasLocalValue(input)) {
101
+ let nextInput = property.get(input);
100
102
  return this._setOutput(nextInput);
101
103
  } else {
104
+ if (qx.core.Environment.get("qx.debug")) {
105
+ if (!(this.getBinding()?.isAsync())) {
106
+ this.warn(`In binding ${this}, property "${this.__propName}" wasn't available synchronously but the binding is not async. This will cause the target to be updated in a later tick. If you want to await the initial set, use 'object.bindAsync' or add 'async: true' in the binding options.`);
107
+ }
108
+ }
102
109
  let promise = property.getAsync(input);
103
110
  return promise.then(nextInput => this._setOutput(nextInput));
104
111
  }
@@ -109,13 +116,31 @@ qx.Class.define("qx.data.binding.PropNameSegment", {
109
116
  * @override
110
117
  */
111
118
  setTargetValue(targetValue) {
112
- if (this.getInput() == null || this.getInput() === undefined) {
119
+ let input = this.getInput();
120
+ if (input == null || input === undefined) {
113
121
  return;
114
122
  }
123
+
124
+ let async = this.getBinding()?.isAsync();
125
+
126
+ //get the setter method name
127
+ let upname = qx.lang.String.firstUp(this.__propName);
128
+
129
+ let method;
115
130
  if (targetValue !== undefined) {
116
- return qx.data.SingleValueBinding.set(this.getInput(), this.__propName, targetValue);
131
+ if (async) {
132
+ method = `set${upname}Async`;
133
+ } else {
134
+ method = `set${upname}`;
135
+ }
136
+ return input[method](targetValue);
117
137
  } else {
118
- return qx.data.SingleValueBinding.reset(this.getInput(), this.__propName);
138
+ if (async) {
139
+ method = `reset${upname}Async`;
140
+ } else {
141
+ method = `reset${upname}`;
142
+ }
143
+ return input[method]();
119
144
  }
120
145
  },
121
146
 
@@ -125,9 +150,7 @@ qx.Class.define("qx.data.binding.PropNameSegment", {
125
150
  * @param {qx.event.type.Data} evt
126
151
  */
127
152
  __onChangeInputProperty(evt) {
128
- if (this.getOutputReceiver()) {
129
- return this._setOutput(evt.getData());
130
- }
153
+ return this._setOutput(evt.getData());
131
154
  },
132
155
 
133
156
  /**
@@ -631,6 +631,225 @@ qx.Class.define("qx.test.Mixin", {
631
631
  o.dispose();
632
632
  },
633
633
 
634
+ /**
635
+ * Primary bug: when the same mixin is included in two different classes,
636
+ * addMembers() overwrites the shared mixin function's .base pointer on the
637
+ * second include, causing the first class's super() call to resolve to the
638
+ * wrong base method. Per-mixin-per-class $mixinBases storage fixes this.
639
+ */
640
+ testMixinInMultipleClasses() {
641
+ // Two unrelated base classes, each with a 'describe' method
642
+ qx.Class.define("qx.MBase1", {
643
+ extend: qx.core.Object,
644
+ members: {
645
+ describe() {
646
+ return "Base1";
647
+ }
648
+ }
649
+ });
650
+ qx.Class.define("qx.MBase2", {
651
+ extend: qx.core.Object,
652
+ members: {
653
+ describe() {
654
+ return "Base2";
655
+ }
656
+ }
657
+ });
658
+
659
+ // Same mixin included in both classes
660
+ qx.Mixin.define("qx.MDual", {
661
+ members: {
662
+ describe() {
663
+ return super.describe() + " + Mixin";
664
+ }
665
+ }
666
+ });
667
+
668
+ qx.Class.define("qx.MChild1", { extend: qx.MBase1, include: [qx.MDual] });
669
+ qx.Class.define("qx.MChild2", { extend: qx.MBase2, include: [qx.MDual] });
670
+
671
+ var c1 = new qx.MChild1();
672
+ var c2 = new qx.MChild2();
673
+
674
+ // Without the fix, c1.describe() would call Base2 (last include clobbered fn.base)
675
+ this.assertEquals("Base1 + Mixin", c1.describe());
676
+ this.assertEquals("Base2 + Mixin", c2.describe());
677
+
678
+ c1.dispose();
679
+ c2.dispose();
680
+ qx.Class.undefine("qx.MChild1");
681
+ qx.Class.undefine("qx.MChild2");
682
+ qx.Class.undefine("qx.MDual");
683
+ qx.Class.undefine("qx.MBase1");
684
+ qx.Class.undefine("qx.MBase2");
685
+ },
686
+
687
+ /**
688
+ * Regression test: when two different mixins both override the same method
689
+ * and are both patched into the same class, a naive per-class base cache
690
+ * (keyed by method name only) would be overwritten by the second patch,
691
+ * making the first mixin's super() resolve back to itself → infinite recursion.
692
+ * The per-mixin-per-class $mixinBases Map avoids this by using the mixin as
693
+ * the lookup key, so each mixin retains its own base reference.
694
+ */
695
+ testPatchTwoMixinsSameMethodNoInfiniteRecursion() {
696
+ qx.Class.define("qx.IRBase", {
697
+ extend: qx.core.Object,
698
+ members: {
699
+ describe() {
700
+ return "base";
701
+ }
702
+ }
703
+ });
704
+
705
+ qx.Mixin.define("qx.MIR1", {
706
+ members: {
707
+ describe() {
708
+ return super.describe() + " [m1]";
709
+ }
710
+ }
711
+ });
712
+
713
+ qx.Mixin.define("qx.MIR2", {
714
+ members: {
715
+ describe() {
716
+ return super.describe() + " [m2]";
717
+ }
718
+ }
719
+ });
720
+
721
+ qx.Class.patch(qx.IRBase, qx.MIR1);
722
+ qx.Class.patch(qx.IRBase, qx.MIR2);
723
+
724
+ var obj = new qx.IRBase();
725
+ // Without per-mixin base storage, MIR1's super() resolves to MIR1 itself → stack overflow
726
+ this.assertEquals("base [m1] [m2]", obj.describe());
727
+
728
+ obj.dispose();
729
+ qx.Class.undefine("qx.IRBase");
730
+ qx.Class.undefine("qx.MIR1");
731
+ qx.Class.undefine("qx.MIR2");
732
+ },
733
+
734
+ /**
735
+ * Mixin-in-mixin: an outer mixin composes an inner mixin (via include:)
736
+ * with no members of its own. When the outer mixin is applied to two
737
+ * different classes, the inner mixin's super() call must resolve to each
738
+ * class's own base — not the last class to include it (which would clobber
739
+ * the shared .base pointer on the inner mixin's function object).
740
+ */
741
+ testMixinInMixinMultipleClasses() {
742
+ // Inner mixin with a greet() calling super
743
+ qx.Mixin.define("qx.MInner", {
744
+ members: {
745
+ greet() {
746
+ return super.greet() + " [inner]";
747
+ }
748
+ }
749
+ });
750
+
751
+ // Pure composition mixin: no own members, just includes MInner
752
+ qx.Mixin.define("qx.MOuter", {
753
+ include: [qx.MInner]
754
+ });
755
+
756
+ qx.Class.define("qx.MIMBase1", {
757
+ extend: qx.core.Object,
758
+ members: {
759
+ greet() {
760
+ return "Alpha";
761
+ }
762
+ }
763
+ });
764
+ qx.Class.define("qx.MIMBase2", {
765
+ extend: qx.core.Object,
766
+ members: {
767
+ greet() {
768
+ return "Beta";
769
+ }
770
+ }
771
+ });
772
+
773
+ // Both classes include MOuter — MInner.greet's .base gets clobbered on the
774
+ // second include without per-mixin-per-class storage
775
+ qx.Class.define("qx.MIMClass1", { extend: qx.MIMBase1, include: [qx.MOuter] });
776
+ qx.Class.define("qx.MIMClass2", { extend: qx.MIMBase2, include: [qx.MOuter] });
777
+
778
+ var c1 = new qx.MIMClass1();
779
+ var c2 = new qx.MIMClass2();
780
+ // MInner.greet's super must resolve to each class's own base, not the last one applied
781
+ this.assertEquals("Alpha [inner]", c1.greet());
782
+ this.assertEquals("Beta [inner]", c2.greet());
783
+
784
+ c1.dispose();
785
+ c2.dispose();
786
+ qx.Class.undefine("qx.MIMClass1");
787
+ qx.Class.undefine("qx.MIMClass2");
788
+ qx.Class.undefine("qx.MIMBase1");
789
+ qx.Class.undefine("qx.MIMBase2");
790
+ qx.Class.undefine("qx.MOuter");
791
+ qx.Class.undefine("qx.MInner");
792
+ },
793
+
794
+ /**
795
+ * Deep inheritance: the mixin is included in an ancestor class several
796
+ * levels up. A second unrelated class also includes the same mixin,
797
+ * clobbering the shared .base pointer. The baseClassMethod lookup must
798
+ * traverse the inheritance chain to find $mixinBases on the ancestor and
799
+ * return the correct (per-class) base function.
800
+ */
801
+ testDeepInheritanceMixinBaseResolution() {
802
+ qx.Class.define("qx.DIBase1", {
803
+ extend: qx.core.Object,
804
+ members: {
805
+ describe() {
806
+ return "Base1";
807
+ }
808
+ }
809
+ });
810
+ qx.Class.define("qx.DIBase2", {
811
+ extend: qx.core.Object,
812
+ members: {
813
+ describe() {
814
+ return "Base2";
815
+ }
816
+ }
817
+ });
818
+
819
+ qx.Mixin.define("qx.MDI", {
820
+ members: {
821
+ describe() {
822
+ return super.describe() + " [mixin]";
823
+ }
824
+ }
825
+ });
826
+
827
+ // Mixin applied at Parent1; Child1 and Grandchild1 inherit it
828
+ qx.Class.define("qx.DIParent1", { extend: qx.DIBase1, include: [qx.MDI] });
829
+ qx.Class.define("qx.DIChild1", { extend: qx.DIParent1 });
830
+ qx.Class.define("qx.DIGrandchild1", { extend: qx.DIChild1 });
831
+
832
+ // Second unrelated class including the same mixin — clobbers MDI.$$members.describe.base
833
+ qx.Class.define("qx.DIParent2", { extend: qx.DIBase2, include: [qx.MDI] });
834
+
835
+ var deep = new qx.DIGrandchild1();
836
+ var other = new qx.DIParent2();
837
+
838
+ // $mixinBases must be found on DIParent1 (ancestor), not on the instance's own class
839
+ this.assertEquals("Base1 [mixin]", deep.describe());
840
+ this.assertEquals("Base2 [mixin]", other.describe());
841
+
842
+ deep.dispose();
843
+ other.dispose();
844
+ qx.Class.undefine("qx.DIGrandchild1");
845
+ qx.Class.undefine("qx.DIChild1");
846
+ qx.Class.undefine("qx.DIParent1");
847
+ qx.Class.undefine("qx.DIParent2");
848
+ qx.Class.undefine("qx.MDI");
849
+ qx.Class.undefine("qx.DIBase1");
850
+ qx.Class.undefine("qx.DIBase2");
851
+ },
852
+
634
853
  testDoubleMixin() {
635
854
  qx.Class.define("qx.D", {
636
855
  extend: qx.core.Object,
@@ -566,7 +566,7 @@ qx.Class.define("qx.test.Promise", {
566
566
 
567
567
  /**
568
568
  * Tests that a property apply method can return a promise; in this case, the
569
- * property *is* marked as async, and we use the setAlphaAsync to test chaining
569
+ * and we use the setAlphaAsync to test chaining
570
570
  */
571
571
  testPropertySetValueAsyncApply2() {
572
572
  var t = this;
@@ -576,7 +576,6 @@ qx.Class.define("qx.test.Promise", {
576
576
  alpha: {
577
577
  init: null,
578
578
  nullable: true,
579
- async: true,
580
579
  apply: "_applyAlpha",
581
580
 
582
581
  event: "changeAlpha"
@@ -632,9 +631,8 @@ qx.Class.define("qx.test.Promise", {
632
631
  alpha: {
633
632
  init: null,
634
633
  nullable: true,
635
- async: true,
636
634
  event: "changeAlpha",
637
- apply: () => { }
635
+ apply: () => {}
638
636
  }
639
637
  }
640
638
  });
@@ -685,8 +683,9 @@ qx.Class.define("qx.test.Promise", {
685
683
  });
686
684
 
687
685
  asyncObj.getAlphaAsync();
688
- asyncObj.bind("alpha", syncObj, "bravo");
689
- asyncObj.setAlphaAsync("zyx");
686
+ asyncObj.bindAsync("alpha", syncObj, "bravo").then(() => {
687
+ asyncObj.setAlphaAsync("zyx");
688
+ });
690
689
  qx.Promise.all([p1, p2]).then(function () {
691
690
  var p3 = new qx.Promise();
692
691
  syncObj.addListenerOnce("changeBravo", evt => {
@@ -707,10 +706,11 @@ qx.Class.define("qx.test.Promise", {
707
706
  /*
708
707
  * Test binding a "normal" sync property to an async property
709
708
  */
710
- if (!qx.core.Environment.get("qx.core.property.Property.applyDuringConstruct")) {
711
- console.warn("Not working with applyDuringConstruct = false, skipping testBinding async-to-sync part");
712
- return; // Test wird übersprungen, zählt als "ok"
713
- }
709
+ //2026-FEB-25 - Commented this out because it causes this unit test to fail.
710
+ // if (!qx.core.Environment.get("qx.core.property.Property.applyDuringConstruct")) {
711
+ // console.warn("Not working with applyDuringConstruct = false, skipping testBinding async-to-sync part");
712
+ // return;
713
+ // }
714
714
  asyncToSync.then(function () {
715
715
  var asyncObj = new AsyncClazz();
716
716
  var syncObj = new SyncClazz();
@@ -755,7 +755,6 @@ qx.Class.define("qx.test.Promise", {
755
755
  alpha: {
756
756
  init: null,
757
757
  nullable: true,
758
- async: true,
759
758
  apply: "_applyAlpha",
760
759
  event: "changeAlpha"
761
760
  }