@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.
- package/CHANGELOG.md +9 -0
- package/Manifest.json +1 -1
- package/lib/compiler/compile-info.json +42 -43
- package/lib/compiler/index.js +555 -645
- package/package.json +1 -1
- package/source/class/qx/Class.js +23 -4
- package/source/class/qx/Mixin.js +15 -6
- package/source/class/qx/core/check/AbstractCheck.js +5 -1
- package/source/class/qx/core/check/CheckFactory.js +6 -0
- package/source/class/qx/core/check/DynamicTypeCheck.js +5 -0
- package/source/class/qx/core/property/ExplicitPropertyStorage.js +7 -19
- package/source/class/qx/core/property/IPropertyStorage.js +2 -21
- package/source/class/qx/core/property/Property.js +96 -72
- package/source/class/qx/core/property/SimplePropertyStorage.js +2 -18
- package/source/class/qx/data/MBinding.js +1 -1
- package/source/class/qx/data/SingleValueBinding.js +58 -100
- package/source/class/qx/data/binding/AbstractSegment.js +15 -10
- package/source/class/qx/data/binding/PropNameSegment.js +34 -11
- package/source/class/qx/test/Mixin.js +219 -0
- package/source/class/qx/test/Promise.js +10 -11
- package/source/class/qx/test/core/Property.js +22 -16
- package/source/class/qx/test/data/singlevalue/Async.js +17 -4
- package/source/class/qx/test/performance/Property.js +0 -1
- package/source/class/qx/test/ui/core/SingleSelectionManager.js +150 -0
- package/source/class/qx/tool/compiler/cli/api/CompilerApi.js +1 -2
- package/source/class/qx/ui/core/SingleSelectionManager.js +4 -4
- package/source/class/qx/ui/form/validation/Manager.js +1 -1
- package/source/resource/qx/decoration/Modern/table/boolean-false.png +0 -0
- 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).
|
|
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 =
|
|
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
|
-
|
|
725
|
+
let previous;
|
|
726
|
+
for (let segStr of segsStrings) {
|
|
727
|
+
let seg;
|
|
790
728
|
//if it's an array index:
|
|
791
|
-
if (
|
|
792
|
-
|
|
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
|
-
|
|
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.
|
|
133
|
+
return out.then(() => this.__changeInputCallback(value, old));
|
|
129
134
|
} else {
|
|
130
|
-
return this.
|
|
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
|
-
|
|
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.
|
|
99
|
-
let nextInput = property.get(input
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
if (async) {
|
|
132
|
+
method = `set${upname}Async`;
|
|
133
|
+
} else {
|
|
134
|
+
method = `set${upname}`;
|
|
135
|
+
}
|
|
136
|
+
return input[method](targetValue);
|
|
117
137
|
} else {
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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.
|
|
689
|
-
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
}
|