@qooxdoo/framework 8.0.0-beta.1 → 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 +42 -0
- package/Manifest.json +1 -1
- package/lib/compiler/compile-info.json +54 -55
- package/lib/compiler/index.js +19039 -23607
- package/lib/resource/qx/tool/compiler/cli/templates/class/default.tmpl.js +6 -7
- package/lib/resource/qx/tool/compiler/cli/templates/class/singleton.tmpl.js +5 -6
- package/lib/resource/qx/tool/compiler/schema/compile-1-0-0.json +6 -2
- package/package.json +8 -10
- package/source/class/qx/Class.js +26 -7
- package/source/class/qx/Mixin.js +15 -6
- package/source/class/qx/core/BaseInit.js +14 -13
- package/source/class/qx/core/MObjectId.js +16 -0
- package/source/class/qx/core/MProperty.js +147 -175
- 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 +9 -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 +115 -90
- 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 +63 -107
- package/source/class/qx/data/binding/AbstractSegment.js +16 -11
- package/source/class/qx/data/binding/ArrayIndexSegment.js +17 -10
- package/source/class/qx/data/binding/IInputReceiver.js +1 -1
- package/source/class/qx/data/binding/PropNameSegment.js +35 -12
- package/source/class/qx/dev/unit/TestCase.js +4 -1
- package/source/class/qx/event/handler/Focus.js +2 -1
- package/source/class/qx/html/Jsx.js +2 -3
- package/source/class/qx/html/Node.js +3 -3
- package/source/class/qx/io/jsonrpc/Client.js +1 -1
- package/source/class/qx/promise/NativeWrapper.js +1 -1
- 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 +50 -16
- package/source/class/qx/test/data/singlevalue/Async.js +17 -4
- package/source/class/qx/test/data/singlevalue/Simple.js +6 -0
- package/source/class/qx/test/locale/Date.js +2 -2
- 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/theme/classic/Appearance.js +21 -0
- package/source/class/qx/theme/modern/Appearance.js +21 -0
- package/source/class/qx/theme/simple/Appearance.js +21 -0
- package/source/class/qx/theme/tangible/Appearance.js +2 -0
- package/source/class/qx/tool/cli/AbstractCliApp.js +18 -2
- package/source/class/qx/tool/compiler/ClassFile.js +0 -4
- package/source/class/qx/tool/compiler/MetaDatabase.js +47 -0
- package/source/class/qx/tool/compiler/cli/api/CompilerApi.js +1 -2
- package/source/class/qx/tool/compiler/cli/commands/Compile.js +139 -8
- package/source/class/qx/tool/compiler/cli/commands/Create.js +1 -1
- package/source/class/qx/tool/compiler/cli/commands/Serve.js +1 -1
- package/source/class/qx/tool/compiler/cli/commands/Typescript.js +26 -39
- package/source/class/qx/tool/compiler/cli/commands/add/Script.js +1 -1
- package/source/class/qx/tool/compiler/cli/commands/package/Publish.js +3 -2
- package/source/class/qx/tool/compiler/cli/commands/package/Update.js +2 -2
- package/source/class/qx/tool/compiler/targets/TypeScriptWriter.js +3 -0
- package/source/class/qx/tool/compiler/targets/meta/Browserify.js +142 -80
- package/source/class/qx/tool/migration/M8_0_0.js +4 -4
- package/source/class/qx/ui/core/SingleSelectionManager.js +4 -4
- package/source/class/qx/ui/form/validation/Manager.js +1 -1
- package/source/class/qx/ui/toolbar/ToolBar.js +4 -4
- package/source/resource/qx/decoration/Modern/table/boolean-false.png +0 -0
- package/source/resource/qx/decoration/Modern/table/boolean-true.png +0 -0
- package/source/resource/qx/tool/compiler/cli/templates/class/default.tmpl.js +6 -7
- package/source/resource/qx/tool/compiler/cli/templates/class/singleton.tmpl.js +5 -6
- package/source/resource/qx/tool/compiler/schema/compile-1-0-0.json +6 -2
|
@@ -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
|
}
|
|
@@ -107,7 +107,18 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
107
107
|
|
|
108
108
|
readOnly: {
|
|
109
109
|
initFunction: () => 42,
|
|
110
|
-
immutable: "readonly"
|
|
110
|
+
immutable: "readonly",
|
|
111
|
+
apply() {
|
|
112
|
+
this.appliedReadOnly = true;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
noAutoApply: {
|
|
117
|
+
initFunction:() => 24,
|
|
118
|
+
autoApply: false,
|
|
119
|
+
apply() {
|
|
120
|
+
throw new Error("Should not call here!")
|
|
121
|
+
}
|
|
111
122
|
}
|
|
112
123
|
},
|
|
113
124
|
|
|
@@ -159,11 +170,6 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
159
170
|
qx.test.cpnfv8.ExternalStorage._subclassStorage[property.getPropertyName()] = value;
|
|
160
171
|
},
|
|
161
172
|
|
|
162
|
-
/**@override */
|
|
163
|
-
isAsyncStorage() {
|
|
164
|
-
return false;
|
|
165
|
-
},
|
|
166
|
-
|
|
167
173
|
/**@override */
|
|
168
174
|
get(thisObj, prop) {
|
|
169
175
|
console.log("in externallyStored getter");
|
|
@@ -176,6 +182,11 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
176
182
|
qx.test.cpnfv8.ExternalStorage._subclassStorage[property.getPropertyName()] = value;
|
|
177
183
|
},
|
|
178
184
|
|
|
185
|
+
/**@override */
|
|
186
|
+
supportsGetAsync() {
|
|
187
|
+
return false;
|
|
188
|
+
},
|
|
189
|
+
|
|
179
190
|
/**@override */
|
|
180
191
|
dereference(prop, property) {}
|
|
181
192
|
},
|
|
@@ -224,7 +235,6 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
224
235
|
},
|
|
225
236
|
delay: {
|
|
226
237
|
init: 0,
|
|
227
|
-
async: true,
|
|
228
238
|
getAsync: async (prop, self) => {
|
|
229
239
|
let p;
|
|
230
240
|
p = new qx.Promise((resolve, reject) => {
|
|
@@ -540,6 +550,7 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
540
550
|
|
|
541
551
|
testApply() {
|
|
542
552
|
let obj = new qx.test.cpnfv8.Superclass();
|
|
553
|
+
this.assertTrue(obj.appliedReadOnly, "Expected apply method for property 'readOnly' to be called, but wasn't");
|
|
543
554
|
this.assertEquals(3, obj.applyCount, "initial apply count should be 3");
|
|
544
555
|
this.assertArrayEquals([null, undefined], obj.lastApply, "last apply call during construction");
|
|
545
556
|
obj.setRunning(false);
|
|
@@ -574,6 +585,7 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
574
585
|
//old, deprecated behaviour
|
|
575
586
|
qx.core.Environment.set("qx.core.property.Property.applyDuringConstruct", false);
|
|
576
587
|
obj = new qx.test.cpnfv8.Superclass();
|
|
588
|
+
this.assertTrue(obj.appliedReadOnly, "Expected apply method for property 'readOnly' to be called, but wasn't");
|
|
577
589
|
this.assertEquals(1, obj.applyCount, "old behaviour: initial apply count should be 1");
|
|
578
590
|
obj.resetAnyProp();
|
|
579
591
|
this.assertEquals(1, obj.applyCount, "old behaviour: apply count after reset should still be 1");
|
|
@@ -821,6 +833,21 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
821
833
|
let second = inst.getJsdocProp();
|
|
822
834
|
this.assertArrayEquals([10, 20, 30], initial, "properties created with initFunction returned by getter should be the same");
|
|
823
835
|
this.assertIdentical(initial, second, "properties created with initFunction returned by getter should be the same");
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
qx.Class.define(null, {
|
|
839
|
+
extend: qx.core.Object,
|
|
840
|
+
properties: {
|
|
841
|
+
badProp: {
|
|
842
|
+
init: 5,
|
|
843
|
+
initFunction: () => 10
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
this.fail("Defining a property with both init and initFunction should throw an error");
|
|
848
|
+
} catch (e) {
|
|
849
|
+
// expected
|
|
850
|
+
}
|
|
824
851
|
},
|
|
825
852
|
|
|
826
853
|
testDefinesThanSubClassWithInterface() {
|
|
@@ -1672,7 +1699,6 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
1672
1699
|
propTwo: {
|
|
1673
1700
|
init: null,
|
|
1674
1701
|
nullable: true,
|
|
1675
|
-
async: true,
|
|
1676
1702
|
apply: "_applyPropTwo",
|
|
1677
1703
|
event: "changePropTwo"
|
|
1678
1704
|
}
|
|
@@ -2295,7 +2321,6 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
2295
2321
|
extend: qx.core.Object,
|
|
2296
2322
|
properties: {
|
|
2297
2323
|
foo: {
|
|
2298
|
-
async: true,
|
|
2299
2324
|
init: 7,
|
|
2300
2325
|
apply: function (value, old) {
|
|
2301
2326
|
return new qx.Promise((resolve, reject) => {
|
|
@@ -2325,27 +2350,36 @@ qx.Class.define("qx.test.core.Property", {
|
|
|
2325
2350
|
|
|
2326
2351
|
/**
|
|
2327
2352
|
* Ensures that an error is thrown when trying to get an async property synchronously
|
|
2328
|
-
* when the property is not ready yet
|
|
2353
|
+
* when the property is not ready yet.
|
|
2329
2354
|
*/
|
|
2330
2355
|
testGetSyncWhenNotReady() {
|
|
2331
2356
|
qx.Class.undefine("qx.test.cpnfv8.GetSyncTest");
|
|
2357
|
+
|
|
2358
|
+
let fooCached = undefined;
|
|
2332
2359
|
var Clazz = qx.Class.define("qx.test.cpnfv8.GetSyncTest", {
|
|
2333
2360
|
extend: qx.core.Object,
|
|
2334
2361
|
properties: {
|
|
2335
2362
|
foo: {
|
|
2336
|
-
|
|
2337
|
-
|
|
2363
|
+
get() {
|
|
2364
|
+
return fooCached;
|
|
2365
|
+
},
|
|
2366
|
+
getAsync() {
|
|
2367
|
+
if (fooCached === undefined) {
|
|
2368
|
+
fooCached = 7;
|
|
2369
|
+
}
|
|
2370
|
+
return fooCached;
|
|
2371
|
+
},
|
|
2372
|
+
set(value) {
|
|
2373
|
+
fooCached = value;
|
|
2374
|
+
}
|
|
2338
2375
|
}
|
|
2339
2376
|
}
|
|
2340
2377
|
});
|
|
2341
2378
|
|
|
2342
2379
|
const doit = async () => {
|
|
2343
2380
|
let instance = new Clazz();
|
|
2344
|
-
//The property storage will be set to the init value during construction,
|
|
2345
|
-
//so we must reset it manually.
|
|
2346
|
-
delete instance.$$propertyValues.foo;
|
|
2347
2381
|
let prop = qx.Class.getByProperty(Clazz, "foo");
|
|
2348
|
-
this.assertFalse(prop.
|
|
2382
|
+
this.assertFalse(prop.hasLocalValue(instance), "Property foo should not be locally defined");
|
|
2349
2383
|
this.assertUndefined(instance.getSafe("foo"));
|
|
2350
2384
|
try {
|
|
2351
2385
|
instance.getFoo();
|
|
@@ -30,6 +30,19 @@ qx.Class.define("qx.test.data.singlevalue.Async", {
|
|
|
30
30
|
__b2: null,
|
|
31
31
|
|
|
32
32
|
setUp() {
|
|
33
|
+
qx.Bootstrap.undefine("qx.test.data.singlevalue.async.AsyncPropertyStorage");
|
|
34
|
+
qx.Bootstrap.define("qx.test.data.singlevalue.async.AsyncPropertyStorage", {
|
|
35
|
+
extend: qx.core.property.SimplePropertyStorage,
|
|
36
|
+
members: {
|
|
37
|
+
/**
|
|
38
|
+
* @Override
|
|
39
|
+
*/
|
|
40
|
+
supportsGetAsync() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
33
46
|
qx.Class.undefine("qx.test.data.singlevalue.async.Test");
|
|
34
47
|
const Clazz = qx.Class.define("qx.test.data.singlevalue.async.Test", {
|
|
35
48
|
extend: qx.core.Object,
|
|
@@ -38,17 +51,17 @@ qx.Class.define("qx.test.data.singlevalue.Async", {
|
|
|
38
51
|
* The async input
|
|
39
52
|
*/
|
|
40
53
|
ai: {
|
|
41
|
-
async: true,
|
|
42
54
|
nullable: true,
|
|
43
|
-
init: null
|
|
55
|
+
init: null,
|
|
56
|
+
storage: qx.test.data.singlevalue.async.AsyncPropertyStorage
|
|
44
57
|
},
|
|
45
58
|
/**
|
|
46
59
|
* The async output
|
|
47
60
|
*/
|
|
48
61
|
ao: {
|
|
49
|
-
async: true,
|
|
50
62
|
nullable: true,
|
|
51
|
-
init: null
|
|
63
|
+
init: null,
|
|
64
|
+
storage: qx.test.data.singlevalue.async.AsyncPropertyStorage
|
|
52
65
|
},
|
|
53
66
|
/**
|
|
54
67
|
* Syncronous input
|
|
@@ -791,6 +791,12 @@ qx.Class.define("qx.test.data.singlevalue.Simple", {
|
|
|
791
791
|
person2.setFriend(null);
|
|
792
792
|
this.assertEquals(3, timesCalled, "Converter should not be called when chain is broken and ignoreConverter is set and matches.");
|
|
793
793
|
this.assertEquals("Patryk", person3.getName(), "Target should be reset when chain is broken");
|
|
794
|
+
|
|
795
|
+
//ensure that during the initial set, if the chain is incomplete (i.e. has a null value),
|
|
796
|
+
//the target property is reset (not set to null)
|
|
797
|
+
let person4 = new Person("Harry");
|
|
798
|
+
person3.bind("friend.friend.name", person4, "name");
|
|
799
|
+
this.assertEquals("Patryk", person4.getName(), "Target should be reset when chain has null element during initial set");
|
|
794
800
|
}
|
|
795
801
|
}
|
|
796
802
|
});
|
|
@@ -32,7 +32,7 @@ qx.Class.define("qx.test.locale.Date", {
|
|
|
32
32
|
testGetAmMarker() {
|
|
33
33
|
var Date = qx.locale.Date;
|
|
34
34
|
var items = [
|
|
35
|
-
{ locale: "
|
|
35
|
+
{ locale: "zh", expected: "上午" },
|
|
36
36
|
{ locale: "fr", expected: "AM" },
|
|
37
37
|
{ locale: "af", expected: "vm." }
|
|
38
38
|
];
|
|
@@ -46,7 +46,7 @@ qx.Class.define("qx.test.locale.Date", {
|
|
|
46
46
|
testGetPMMarker() {
|
|
47
47
|
var Date = qx.locale.Date;
|
|
48
48
|
var items = [
|
|
49
|
-
{ locale: "
|
|
49
|
+
{ locale: "zh", expected: "下午" },
|
|
50
50
|
{ locale: "fr", expected: "PM" },
|
|
51
51
|
{ locale: "af", expected: "nm." }
|
|
52
52
|
];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* ************************************************************************
|
|
2
|
+
|
|
3
|
+
qooxdoo - the new era of web development
|
|
4
|
+
|
|
5
|
+
http://qooxdoo.org
|
|
6
|
+
|
|
7
|
+
Copyright:
|
|
8
|
+
2025 Qooxdoo contributors
|
|
9
|
+
|
|
10
|
+
License:
|
|
11
|
+
MIT: https://opensource.org/licenses/MIT
|
|
12
|
+
See the LICENSE file in the project's top-level directory for details.
|
|
13
|
+
|
|
14
|
+
************************************************************************ */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Minimal ISingleSelectionProvider implementation for unit testing.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
* @ignore(qx.test.ui.core.MockSingleSelectionProvider)
|
|
21
|
+
*/
|
|
22
|
+
qx.Class.define("qx.test.ui.core.MockSingleSelectionProvider", {
|
|
23
|
+
extend: qx.core.Object,
|
|
24
|
+
implement: [qx.ui.core.ISingleSelectionProvider],
|
|
25
|
+
|
|
26
|
+
construct(items) {
|
|
27
|
+
super();
|
|
28
|
+
this._items = items;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
members: {
|
|
32
|
+
_items: null,
|
|
33
|
+
|
|
34
|
+
getItems() {
|
|
35
|
+
return this._items || [];
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
isItemSelectable(item) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tests for qx.ui.core.SingleSelectionManager
|
|
46
|
+
*/
|
|
47
|
+
qx.Class.define("qx.test.ui.core.SingleSelectionManager", {
|
|
48
|
+
extend: qx.dev.unit.TestCase,
|
|
49
|
+
|
|
50
|
+
members: {
|
|
51
|
+
__manager: null,
|
|
52
|
+
__provider: null,
|
|
53
|
+
__items: null,
|
|
54
|
+
|
|
55
|
+
setUp() {
|
|
56
|
+
this.__items = [new qx.core.Object(), new qx.core.Object(), new qx.core.Object()];
|
|
57
|
+
this.__provider = new qx.test.ui.core.MockSingleSelectionProvider(this.__items);
|
|
58
|
+
this.__manager = new qx.ui.core.SingleSelectionManager(this.__provider);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
tearDown() {
|
|
62
|
+
this.__manager.dispose();
|
|
63
|
+
this.__provider.dispose();
|
|
64
|
+
for (var item of this.__items) {
|
|
65
|
+
item.dispose();
|
|
66
|
+
}
|
|
67
|
+
this.__items = null;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Regression test for the fix: resetSelected() with allowEmptySelection=false
|
|
72
|
+
* must NOT fire changeSelected when the first item is already selected.
|
|
73
|
+
*/
|
|
74
|
+
testNoSpuriousEventWhenResetSelectedWithFirstItemAlreadySelected() {
|
|
75
|
+
this.__manager.setAllowEmptySelection(false);
|
|
76
|
+
this.__manager.resetSelected();
|
|
77
|
+
this.assertIdentical(this.__items[0], this.__manager.getSelected(), "item[0] should be auto-selected");
|
|
78
|
+
|
|
79
|
+
var eventCount = 0;
|
|
80
|
+
this.__manager.addListener("changeSelected", function () {
|
|
81
|
+
eventCount++;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.__manager.resetSelected();
|
|
85
|
+
|
|
86
|
+
this.assertEquals(0, eventCount, "changeSelected must not fire when selection is unchanged");
|
|
87
|
+
this.assertIdentical(this.__items[0], this.__manager.getSelected(), "Selection must still be item[0]");
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* resetSelected() when another item is selected must fire changeSelected
|
|
92
|
+
* and switch back to item[0].
|
|
93
|
+
*/
|
|
94
|
+
testEventFiredWhenResetSelectedChangesSelection() {
|
|
95
|
+
this.__manager.setAllowEmptySelection(false);
|
|
96
|
+
this.__manager.setSelected(this.__items[1]);
|
|
97
|
+
|
|
98
|
+
var eventCount = 0;
|
|
99
|
+
var eventData = null;
|
|
100
|
+
this.__manager.addListener("changeSelected", function (e) {
|
|
101
|
+
eventCount++;
|
|
102
|
+
eventData = e.getData();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this.__manager.resetSelected();
|
|
106
|
+
|
|
107
|
+
this.assertEquals(1, eventCount, "changeSelected must fire when selection changes");
|
|
108
|
+
this.assertIdentical(this.__items[0], eventData, "Event data should be item[0]");
|
|
109
|
+
this.assertIdentical(this.__items[0], this.__manager.getSelected(), "item[0] should be selected after reset");
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* setSelected() with the already-selected item must not fire changeSelected
|
|
114
|
+
* (pre-existing early-exit guard).
|
|
115
|
+
*/
|
|
116
|
+
testNoEventWhenSetSelectedWithSameItem() {
|
|
117
|
+
this.__manager.setSelected(this.__items[0]);
|
|
118
|
+
|
|
119
|
+
var eventCount = 0;
|
|
120
|
+
this.__manager.addListener("changeSelected", function () {
|
|
121
|
+
eventCount++;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.__manager.setSelected(this.__items[0]);
|
|
125
|
+
|
|
126
|
+
this.assertEquals(0, eventCount, "changeSelected must not fire when setting the same item");
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Switching allowEmptySelection from true to false on an empty manager
|
|
131
|
+
* must auto-select item[0] and fire changeSelected exactly once.
|
|
132
|
+
*/
|
|
133
|
+
testAllowEmptySelectionFalseTriggersAutoSelect() {
|
|
134
|
+
this.assertTrue(this.__manager.isSelectionEmpty(), "Selection should be empty initially");
|
|
135
|
+
|
|
136
|
+
var eventCount = 0;
|
|
137
|
+
var eventData = null;
|
|
138
|
+
this.__manager.addListener("changeSelected", function (e) {
|
|
139
|
+
eventCount++;
|
|
140
|
+
eventData = e.getData();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.__manager.setAllowEmptySelection(false);
|
|
144
|
+
|
|
145
|
+
this.assertEquals(1, eventCount, "changeSelected must fire when auto-selecting the first item");
|
|
146
|
+
this.assertIdentical(this.__items[0], eventData, "Event data should be item[0]");
|
|
147
|
+
this.assertIdentical(this.__items[0], this.__manager.getSelected(), "item[0] should be auto-selected");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|