@qooxdoo/framework 7.7.2 → 7.9.0
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/Manifest.json +2 -2
- package/lib/compiler/compile-info.json +91 -89
- package/lib/compiler/index.js +2517 -1488
- package/lib/resource/qx/tool/schema/compile-1-0-0.json +13 -0
- package/lib/resource/qx/tool/website/build/404.html +3 -25
- package/lib/resource/qx/tool/website/build/about.html +3 -25
- package/lib/resource/qx/tool/website/build/assets/common.js +20 -0
- package/lib/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
- package/lib/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
- package/lib/resource/qx/tool/website/build/index.html +3 -25
- package/lib/resource/qx/tool/website/partials/footer.html +3 -21
- package/lib/resource/qx/tool/website/partials/head.html +0 -1
- package/package.json +2 -2
- package/source/class/qx/Bootstrap.js +6 -3
- package/source/class/qx/Promise.js +93 -6964
- package/source/class/qx/bom/Label.js +82 -2
- package/source/class/qx/bom/webfonts/WebFont.js +1 -0
- package/source/class/qx/core/Environment.js +83 -1
- package/source/class/qx/data/controller/List.js +50 -21
- package/source/class/qx/data/controller/MSelection.js +45 -12
- package/source/class/qx/data/marshal/Json.js +64 -11
- package/source/class/qx/dev/unit/AsyncWrapper.js +8 -0
- package/source/class/qx/event/Manager.js +163 -124
- package/source/class/qx/io/ImageLoader.js +6 -3
- package/source/class/qx/io/exception/Transport.js +1 -0
- package/source/class/qx/io/jsonrpc/Client.js +64 -8
- package/source/class/qx/io/jsonrpc/protocol/Request.js +10 -6
- package/source/class/qx/lang/Type.js +36 -3
- package/source/class/qx/promise/BluebirdImpl.js +6918 -0
- package/source/class/qx/promise/NativeWrapper.js +738 -0
- package/source/class/qx/test/Promise.js +1145 -22
- package/source/class/qx/test/bom/client/Pdfjs.js +4 -0
- package/source/class/qx/test/bom/element/AnimationJs.js +3 -0
- package/source/class/qx/test/bom/element/Style.js +1 -0
- package/source/class/qx/test/bom/media/MediaTestCase.js +6 -0
- package/source/class/qx/test/core/Environment.js +44 -0
- package/source/class/qx/test/data/controller/List.js +6 -0
- package/source/class/qx/test/data/marshal/Json.js +29 -0
- package/source/class/qx/test/io/MAssert.js +94 -0
- package/source/class/qx/test/io/TestMAssert.js +47 -0
- package/source/class/qx/test/io/jsonrpc/Client.js +79 -19
- package/source/class/qx/test/io/jsonrpc/PostMessageClient.js +152 -0
- package/source/class/qx/test/io/jsonrpc/Protocol.js +1 -5
- package/source/class/qx/test/io/request/Xhr.js +16 -0
- package/source/class/qx/test/lang/Type.js +151 -0
- package/source/class/qx/test/ui/embed/Iframe.js +1 -1
- package/source/class/qx/test/ui/form/TextArea.js +4 -0
- package/source/class/qx/test/util/DeferredCall.js +6 -0
- package/source/class/qx/test/util/Function.js +2 -2
- package/source/class/qx/theme/indigo/ColorDark.js +1 -1
- package/source/class/qx/tool/cli/api/Test.js +22 -0
- package/source/class/qx/tool/cli/commands/Compile.js +17 -4
- package/source/class/qx/tool/cli/commands/Test.js +7 -1
- package/source/class/qx/tool/compiler/Analyser.js +7 -0
- package/source/class/qx/tool/compiler/ClassFile.js +3 -2
- package/source/class/qx/tool/compiler/MetaExtraction.js +0 -5
- package/source/class/qx/tool/compiler/targets/meta/Browserify.js +8 -2
- package/source/class/qx/ui/basic/Image.js +72 -8
- package/source/class/qx/ui/basic/Label.js +4 -6
- package/source/class/qx/ui/control/DateChooser.js +4 -6
- package/source/class/qx/ui/core/Blocker.js +4 -6
- package/source/class/qx/ui/core/LayoutItem.js +4 -6
- package/source/class/qx/ui/core/MPlacement.js +23 -1
- package/source/class/qx/ui/core/Widget.js +7 -5
- package/source/class/qx/ui/form/AbstractField.js +4 -6
- package/source/class/qx/ui/form/MForm.js +4 -6
- package/source/class/qx/ui/form/Spinner.js +4 -6
- package/source/class/qx/ui/form/renderer/AbstractRenderer.js +4 -6
- package/source/class/qx/ui/menu/AbstractButton.js +7 -11
- package/source/class/qx/ui/mobile/basic/Label.js +4 -6
- package/source/class/qx/ui/mobile/form/Label.js +4 -6
- package/source/class/qx/ui/mobile/list/List.js +4 -6
- package/source/class/qx/ui/progressive/renderer/table/Row.js +35 -7
- package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +4 -6
- package/source/class/qx/ui/table/Table.js +4 -6
- package/source/class/qx/ui/table/cellrenderer/Abstract.js +4 -6
- package/source/class/qx/ui/table/cellrenderer/Boolean.js +4 -6
- package/source/class/qx/ui/table/pane/Scroller.js +1 -1
- package/source/class/qx/ui/table/rowrenderer/Default.js +4 -6
- package/source/class/qx/util/ConcurrencyLimiter.js +78 -0
- package/source/resource/qx/tool/schema/compile-1-0-0.json +13 -0
- package/source/resource/qx/tool/website/build/404.html +3 -25
- package/source/resource/qx/tool/website/build/about.html +3 -25
- package/source/resource/qx/tool/website/build/assets/common.js +20 -0
- package/source/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
- package/source/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
- package/source/resource/qx/tool/website/build/index.html +3 -25
- package/source/resource/qx/tool/website/partials/footer.html +3 -21
- package/source/resource/qx/tool/website/partials/head.html +0 -1
- package/lib/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
- package/lib/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
- package/source/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
- package/source/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
|
@@ -33,6 +33,10 @@ qx.Class.define("qx.test.bom.client.Pdfjs", {
|
|
|
33
33
|
"test: is PDF.js available"() {
|
|
34
34
|
this.require(["firefox"]);
|
|
35
35
|
|
|
36
|
+
if (navigator.plugins.length == 0) {
|
|
37
|
+
this.skip("test disabled on headless browsers");
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
qx.core.Environment.getAsync(
|
|
37
41
|
"plugin.pdfjs",
|
|
38
42
|
function (result) {
|
|
@@ -66,6 +66,9 @@ qx.Class.define("qx.test.bom.element.AnimationJs", {
|
|
|
66
66
|
if (qx.core.Environment.get("browser.name") == "chrome") {
|
|
67
67
|
throw new qx.dev.unit.RequirementError();
|
|
68
68
|
}
|
|
69
|
+
if (qx.core.Environment.get("browser.name") == "safari") {
|
|
70
|
+
throw new qx.dev.unit.RequirementError();
|
|
71
|
+
}
|
|
69
72
|
|
|
70
73
|
var handle = qx.bom.element.Animation.animate(this.__el, {
|
|
71
74
|
duration: 100,
|
|
@@ -202,6 +202,12 @@ qx.Class.define("qx.test.bom.media.MediaTestCase", {
|
|
|
202
202
|
"HTML5 audio/video playback must be triggered by user interaction in Chrome on Android."
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
|
+
if (qx.core.Environment.get("browser.name") == "safari") {
|
|
206
|
+
this.skip(
|
|
207
|
+
"we can not detect headless mode in safari"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
205
211
|
this.assertTrue(this._media.isPaused());
|
|
206
212
|
|
|
207
213
|
this._media.addListener("play", e => {
|
|
@@ -537,6 +537,50 @@ qx.Class.define("qx.test.core.Environment", {
|
|
|
537
537
|
|
|
538
538
|
// 3d transform support
|
|
539
539
|
this.assertBoolean(qx.core.Environment.get("css.transform.3d"));
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* This test is only run if "qx.environment.allowRuntimeMutations" is false which you need to
|
|
544
|
+
* manually set in the test runner configuration.
|
|
545
|
+
*/
|
|
546
|
+
testRuntimeMutationsIfNotAvailable() {
|
|
547
|
+
if (qx.core.Environment.get("qx.environment.allowRuntimeMutations") === false) {
|
|
548
|
+
// the mutation methods should not be available
|
|
549
|
+
for (let key in ['set', 'remove', 'reset']) {
|
|
550
|
+
this.assertUndefined(qx.core.Environment[key], `The method "qx.core.Environment.${key}()" should not be available.`);
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
this.skip("Runtime mutations are enabled.");
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
testRuntimeMutationsIfAvailable() {
|
|
558
|
+
if (qx.core.Environment.get("qx.environment.allowRuntimeMutations") === false) {
|
|
559
|
+
this.skip("Runtime mutations are disabled.");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
// compile-time environment
|
|
563
|
+
const qxVersion = qx.core.Environment.get("qx.version");
|
|
564
|
+
qx.core.Environment.set("qx.version", "1.0");
|
|
565
|
+
this.assertEquals("1.0", qx.core.Environment.get("qx.version"));
|
|
566
|
+
qx.core.Environment.reset("qx.version");
|
|
567
|
+
this.assertEquals(qxVersion, qx.core.Environment.get("qx.version"));
|
|
568
|
+
qx.core.Environment.remove("qx.version");
|
|
569
|
+
this.assertUndefined(qx.core.Environment.get("qx.version"));
|
|
570
|
+
|
|
571
|
+
// runtime environment
|
|
572
|
+
const browserName = qx.core.Environment.get("browser.name");
|
|
573
|
+
qx.core.Environment.set("browser.name", "lynx");
|
|
574
|
+
this.assertEquals("lynx", qx.core.Environment.get("browser.name"));
|
|
575
|
+
qx.core.Environment.reset("browser.name");
|
|
576
|
+
this.assertEquals(browserName, qx.core.Environment.get("browser.name"));
|
|
577
|
+
qx.core.Environment.remove("browser.name");
|
|
578
|
+
this.assertUndefined(qx.core.Environment.get("browser.name"));
|
|
579
|
+
|
|
580
|
+
// cleanup
|
|
581
|
+
qx.core.Environment.reset();
|
|
582
|
+
this.assertEquals(qxVersion, qx.core.Environment.get("qx.version"));
|
|
583
|
+
this.assertEquals(browserName, qx.core.Environment.get("browser.name"));
|
|
540
584
|
}
|
|
541
585
|
}
|
|
542
586
|
});
|
|
@@ -25,7 +25,13 @@ qx.Class.define("qx.test.data.controller.List", {
|
|
|
25
25
|
include: qx.dev.unit.MMock,
|
|
26
26
|
|
|
27
27
|
members: {
|
|
28
|
+
/**
|
|
29
|
+
* @type {qx.ui.form.List}
|
|
30
|
+
*/
|
|
28
31
|
__list: null,
|
|
32
|
+
/**
|
|
33
|
+
* @type {qx.data.controller.List}
|
|
34
|
+
*/
|
|
29
35
|
__controller: null,
|
|
30
36
|
__data: null,
|
|
31
37
|
__model: null,
|
|
@@ -1498,6 +1498,35 @@ qx.Class.define("qx.test.data.marshal.Json", {
|
|
|
1498
1498
|
|
|
1499
1499
|
model.dispose();
|
|
1500
1500
|
qx.Class.undefine("qx.test.Array");
|
|
1501
|
+
},
|
|
1502
|
+
|
|
1503
|
+
testNonPojoObjects() {
|
|
1504
|
+
function NativeClass() {
|
|
1505
|
+
this.foo = "bar";
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
NativeClass.prototype.myMethod = function () {
|
|
1509
|
+
return "method";
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
let data = {
|
|
1513
|
+
name: "Michael",
|
|
1514
|
+
nonPojo: new NativeClass()
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
this.__marshaler.dispose();
|
|
1518
|
+
this.__marshaler = new qx.data.marshal.Json();
|
|
1519
|
+
this.__marshaler.toClass(data);
|
|
1520
|
+
var model = this.__marshaler.toModel(data);
|
|
1521
|
+
|
|
1522
|
+
this.assertEquals("Michael", model.getName());
|
|
1523
|
+
|
|
1524
|
+
if (qx.core.Environment.get("qx.data.marshal.Json.breakOnNonPojos")) {
|
|
1525
|
+
this.assertEquals("method", model.getNonPojo().myMethod());
|
|
1526
|
+
this.assertEquals("bar", model.getNonPojo().foo);
|
|
1527
|
+
} else {
|
|
1528
|
+
this.assertEquals("bar", model.getNonPojo().getFoo());
|
|
1529
|
+
}
|
|
1501
1530
|
}
|
|
1502
1531
|
}
|
|
1503
1532
|
});
|
|
@@ -41,6 +41,100 @@ qx.Mixin.define("qx.test.io.MAssert", {
|
|
|
41
41
|
msg = `Failed to assert that '${actual}' contains '${expectedFragment}'.`;
|
|
42
42
|
}
|
|
43
43
|
this.assert(actual.includes(expectedFragment), msg);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
PROMISE: {
|
|
47
|
+
map: null,
|
|
48
|
+
PENDING: "pending",
|
|
49
|
+
FULFILLED: "fulfilled",
|
|
50
|
+
REJECTED: "rejected"
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Observes a promise so that its state can later be determined for the assertPromise*()
|
|
55
|
+
* methods.
|
|
56
|
+
* @param {Promise} promise
|
|
57
|
+
*/
|
|
58
|
+
observePromise(promise) {
|
|
59
|
+
if (!this.PROMISE.map) {
|
|
60
|
+
this.PROMISE.map = new WeakMap();
|
|
61
|
+
}
|
|
62
|
+
let state = this.PROMISE.PENDING;
|
|
63
|
+
promise.then(
|
|
64
|
+
() => (state = this.PROMISE.FULFILLED),
|
|
65
|
+
() => (state = this.PROMISE.REJECTED)
|
|
66
|
+
);
|
|
67
|
+
let stateFn = () => state;
|
|
68
|
+
this.PROMISE.map.set(promise, stateFn);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns the state of the given promise, which is either "pending", "fulfilled", or "rejected".
|
|
73
|
+
* Requires that the observePromise() method has previously been called with given promise.
|
|
74
|
+
* @param {Promise} promise
|
|
75
|
+
* @returns {String}
|
|
76
|
+
*/
|
|
77
|
+
getPromiseState(promise) {
|
|
78
|
+
let stateFn = this.PROMISE.map && this.PROMISE.map.get(promise);
|
|
79
|
+
if (!stateFn) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Promise is not being observed, call observePromise() first."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return stateFn();
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Asserts that the given promise object is still pending
|
|
89
|
+
* @param {Promise} promise
|
|
90
|
+
* @param {String?} msg Optional failure message
|
|
91
|
+
*/
|
|
92
|
+
assertPromisePending(promise, msg) {
|
|
93
|
+
let state = this.getPromiseState(promise);
|
|
94
|
+
this.assert(
|
|
95
|
+
state == this.PROMISE.PENDING,
|
|
96
|
+
msg || `Promise should be pending, but is ${state}.`
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Asserts that the given promise object is settled, i.e. has either
|
|
102
|
+
* been fulfilled or rejected
|
|
103
|
+
* @param {Promise} promise
|
|
104
|
+
* @param {String?} msg Optional failure message
|
|
105
|
+
*/
|
|
106
|
+
assertPromiseSettled(promise, msg) {
|
|
107
|
+
let state = this.getPromiseState(promise);
|
|
108
|
+
this.assert(
|
|
109
|
+
state != this.PROMISE.PENDING,
|
|
110
|
+
msg || `Promise should be settled, but is pending.`
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Asserts that the given promise object has been fulfilled
|
|
116
|
+
* @param {Promise} promise
|
|
117
|
+
* @param {String?} msg Optional failure message
|
|
118
|
+
*/
|
|
119
|
+
assertPromiseFulfilled(promise, msg) {
|
|
120
|
+
let state = this.getPromiseState(promise);
|
|
121
|
+
this.assert(
|
|
122
|
+
state == this.PROMISE.FULFILLED,
|
|
123
|
+
msg || `Promise should be fulfilled, but is ${state}.`
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Asserts that the given promise object has been rejected
|
|
129
|
+
* @param {Promise} promise
|
|
130
|
+
* @param {String?} msg Optional failure message
|
|
131
|
+
*/
|
|
132
|
+
assertPromiseRejected(promise, msg) {
|
|
133
|
+
let state = this.getPromiseState(promise);
|
|
134
|
+
this.assert(
|
|
135
|
+
state == this.PROMISE.REJECTED,
|
|
136
|
+
msg || `Promise should be rejected, but is ${state}.`
|
|
137
|
+
);
|
|
44
138
|
}
|
|
45
139
|
}
|
|
46
140
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* ************************************************************************
|
|
2
|
+
|
|
3
|
+
qooxdoo - the javascript framework for coders
|
|
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
|
+
Authors:
|
|
15
|
+
* Christian Boulanger (cboulanger)
|
|
16
|
+
|
|
17
|
+
************************************************************************ */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tests for qx.io.jsonrpc.Client with qx.test.io.request.PostMessage transport
|
|
21
|
+
* @ignore(Worker)
|
|
22
|
+
* @ignore(self)
|
|
23
|
+
*/
|
|
24
|
+
qx.Class.define("qx.test.io.TestMAssert", {
|
|
25
|
+
extend: qx.dev.unit.TestCase,
|
|
26
|
+
|
|
27
|
+
include: [qx.test.io.MAssert],
|
|
28
|
+
|
|
29
|
+
members: {
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Test the promise Assertion API
|
|
33
|
+
*/
|
|
34
|
+
testPromiseAssertions() {
|
|
35
|
+
let p1 = new Promise(resolve => setTimeout(resolve, 10));
|
|
36
|
+
this.observePromise(p1);
|
|
37
|
+
this.assertPromisePending(p1);
|
|
38
|
+
this.wait(100, () => this.assertPromiseFulfilled(p1));
|
|
39
|
+
let p2 = new Promise((_, reject) => setTimeout(reject, 10));
|
|
40
|
+
this.observePromise(p2);
|
|
41
|
+
this.wait(100, () => {
|
|
42
|
+
this.assertPromiseRejected(p2);
|
|
43
|
+
this.assertPromiseSettled(p2);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -60,21 +60,33 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
60
60
|
},
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Sets up the fake server and instructs it to send the given response
|
|
64
|
-
* @param {String} response
|
|
63
|
+
* Sets up the fake server and instructs it to send the given response
|
|
64
|
+
* @param {String} response Optional server response
|
|
65
65
|
*/
|
|
66
66
|
setUpFakeServer(response) {
|
|
67
67
|
// Not fake transport
|
|
68
68
|
this.getSandbox().restore();
|
|
69
69
|
this.useFakeServer();
|
|
70
70
|
this.setUpRequest();
|
|
71
|
+
if (response) {
|
|
72
|
+
this.setServerResponse(response);
|
|
73
|
+
}
|
|
74
|
+
this.getServer().autoRespond = true;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set the fake server's response
|
|
79
|
+
* @param {{String|Object} } response Server response. Will be stringified to a JSON string if not a string
|
|
80
|
+
*/
|
|
81
|
+
setServerResponse(response) {
|
|
82
|
+
if (typeof response != "string") {
|
|
83
|
+
response = JSON.stringify(response);
|
|
84
|
+
}
|
|
71
85
|
this.getServer().respondWith("POST", /.*/, [
|
|
72
86
|
200,
|
|
73
87
|
{ "Content-Type": "application/json; charset=utf-8" },
|
|
74
88
|
response
|
|
75
89
|
]);
|
|
76
|
-
|
|
77
|
-
this.getServer().autoRespond = true;
|
|
78
90
|
},
|
|
79
91
|
|
|
80
92
|
/**
|
|
@@ -103,7 +115,7 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
103
115
|
if (!(err instanceof qx.io.exception.Exception)) {
|
|
104
116
|
throw err;
|
|
105
117
|
}
|
|
106
|
-
this.assertEquals(exception, err.code, `Error code does not match
|
|
118
|
+
this.assertEquals(exception, err.code, `Error code does not match. Expected ${exception}, got ${err.code}.`);
|
|
107
119
|
} else {
|
|
108
120
|
this.assertInstance(
|
|
109
121
|
err,
|
|
@@ -119,16 +131,20 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
119
131
|
// check transport promise
|
|
120
132
|
client.send(message_out).catch(errorCallback);
|
|
121
133
|
this.wait(100, () => {
|
|
134
|
+
// in case of a transport error, ...
|
|
135
|
+
const n = qx.core.Environment.select("qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest", {
|
|
136
|
+
true: 1, // ... we are rejecting the request promise only, v8 default
|
|
137
|
+
false: 2 // ... both request promise and transport promise are rejected, v7 default
|
|
138
|
+
})
|
|
122
139
|
if (
|
|
123
|
-
// the request promise will not be
|
|
140
|
+
// the request promise will not be rejected because it already is rejected in this special case
|
|
124
141
|
exception === qx.io.exception.Transport.DUPLICATE_ID ||
|
|
125
|
-
// or the send promise will not be rejected because we have a server-side error
|
|
126
|
-
exception === qx.io.exception.Protocol
|
|
142
|
+
// or the send promise will not be rejected because we have a server-side error, v7 default only
|
|
143
|
+
(exception === qx.io.exception.Protocol && !qx.core.Environment.get("qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest"))
|
|
127
144
|
) {
|
|
128
|
-
this.
|
|
145
|
+
this.assertCallCount(errorCallback, n);
|
|
129
146
|
} else {
|
|
130
|
-
|
|
131
|
-
this.assertCalledThrice(errorCallback);
|
|
147
|
+
this.assertCallCount(errorCallback, n+1);
|
|
132
148
|
}
|
|
133
149
|
});
|
|
134
150
|
},
|
|
@@ -208,10 +224,9 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
208
224
|
|
|
209
225
|
this.setUpFakeServer(message_in.toString());
|
|
210
226
|
const client = new qx.io.jsonrpc.Client("http://jsonrpc");
|
|
211
|
-
let spy = this.spy(value => this.assertEquals(result, value));
|
|
212
|
-
message_out.getPromise().then(spy);
|
|
213
227
|
await client.send(message_out);
|
|
214
|
-
|
|
228
|
+
const value = await message_out.getPromise();
|
|
229
|
+
this.assertEquals(result, value);
|
|
215
230
|
},
|
|
216
231
|
|
|
217
232
|
async "test: call jsonrpc method and receive batched response"() {
|
|
@@ -225,10 +240,9 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
225
240
|
.toString();
|
|
226
241
|
this.setUpFakeServer(response);
|
|
227
242
|
const client = new qx.io.jsonrpc.Client("http://jsonrpc");
|
|
228
|
-
let spy = this.spy(value => this.assertEquals(result, value));
|
|
229
|
-
message_out.getPromise().then(spy);
|
|
230
243
|
await client.send(message_out);
|
|
231
|
-
|
|
244
|
+
const value = await message_out.getPromise();
|
|
245
|
+
this.assertEquals(result, value);
|
|
232
246
|
},
|
|
233
247
|
|
|
234
248
|
"test: call jsonrpc method and expect error on invalid reponse "() {
|
|
@@ -321,10 +335,56 @@ qx.Class.define("qx.test.io.jsonrpc.Client", {
|
|
|
321
335
|
spy(message);
|
|
322
336
|
});
|
|
323
337
|
client.sendNotification("ping");
|
|
338
|
+
this.wait(100, () => this.assertCalledTwice(spy) );
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Issue #10739
|
|
343
|
+
*/
|
|
344
|
+
testIssue10739() {
|
|
345
|
+
this.resetId();
|
|
346
|
+
this.setUpFakeServer();
|
|
347
|
+
const successCallback = this.spy(result =>
|
|
348
|
+
this.debug(`Server response is "${result}"`)
|
|
349
|
+
);
|
|
350
|
+
const errorCallback = this.spy(error => console.error(error.message));
|
|
351
|
+
const client = new qx.io.jsonrpc.Client("http://test.local");
|
|
352
|
+
const auth = new qx.io.request.authentication.Bearer("TOKEN");
|
|
353
|
+
client.getTransport().getTransportImpl().setAuthentication(auth);
|
|
354
|
+
this.setServerResponse({
|
|
355
|
+
jsonrpc: "2.0",
|
|
356
|
+
error: {
|
|
357
|
+
code: 8,
|
|
358
|
+
message: "stale or uninitialized auth token/rune"
|
|
359
|
+
},
|
|
360
|
+
id: 1
|
|
361
|
+
});
|
|
362
|
+
const sendRequest = this.spy(() => {
|
|
363
|
+
this.debug("sendRequest() was called");
|
|
364
|
+
client
|
|
365
|
+
.sendRequest("service.method", ["foo"])
|
|
366
|
+
.then(successCallback)
|
|
367
|
+
.catch(err => {
|
|
368
|
+
if (err.code == 8) {
|
|
369
|
+
this.debug(`Received expected error message.`);
|
|
370
|
+
this.setServerResponse({
|
|
371
|
+
jsonrpc: "2.0",
|
|
372
|
+
result: "OK",
|
|
373
|
+
id: 2
|
|
374
|
+
});
|
|
375
|
+
sendRequest(); // second request
|
|
376
|
+
} else {
|
|
377
|
+
errorCallback(err);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
sendRequest();
|
|
324
382
|
this.wait(
|
|
325
|
-
|
|
383
|
+
250,
|
|
326
384
|
function () {
|
|
327
|
-
this.assertCalledTwice(
|
|
385
|
+
this.assertCalledTwice(sendRequest);
|
|
386
|
+
this.assertCalledOnce(successCallback);
|
|
387
|
+
this.assertNotCalled(errorCallback);
|
|
328
388
|
},
|
|
329
389
|
this
|
|
330
390
|
);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* ************************************************************************
|
|
2
|
+
|
|
3
|
+
qooxdoo - the javascript framework for coders
|
|
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
|
+
Authors:
|
|
15
|
+
* Christian Boulanger (cboulanger)
|
|
16
|
+
|
|
17
|
+
************************************************************************ */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tests for qx.io.jsonrpc.Client with qx.test.io.request.PostMessage transport
|
|
21
|
+
* @ignore(Worker)
|
|
22
|
+
* @ignore(self)
|
|
23
|
+
*/
|
|
24
|
+
qx.Class.define("qx.test.io.jsonrpc.PostMessageClient", {
|
|
25
|
+
extend: qx.dev.unit.TestCase,
|
|
26
|
+
|
|
27
|
+
include: [qx.dev.unit.MMock, qx.test.io.MAssert],
|
|
28
|
+
|
|
29
|
+
members: {
|
|
30
|
+
setUp() {
|
|
31
|
+
qx.io.jsonrpc.protocol.Request.resetId();
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
tearDown() {
|
|
35
|
+
this.getSandbox().restore();
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Given a function, return a `Worker` object which calls the function
|
|
40
|
+
* with the function whenever the Worker receives a message from the main
|
|
41
|
+
* thread.
|
|
42
|
+
* @param {Function} fn
|
|
43
|
+
* @returns {Worker}
|
|
44
|
+
*/
|
|
45
|
+
createOnMessageWorker(fn) {
|
|
46
|
+
let blob = new Blob(["self.onmessage = ", fn.toString()], {
|
|
47
|
+
type: "text/javascript"
|
|
48
|
+
});
|
|
49
|
+
return new Worker(URL.createObjectURL(blob));
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async "test: receive 10 out-of-order jsonrpc responses from server, all successful"() {
|
|
53
|
+
qx.io.jsonrpc.protocol.Request.resetId();
|
|
54
|
+
|
|
55
|
+
// create server worker which sends a response with random delay
|
|
56
|
+
const server = this.createOnMessageWorker(evt => {
|
|
57
|
+
let request = JSON.parse(evt.data);
|
|
58
|
+
let id = request.id;
|
|
59
|
+
let response = JSON.stringify({
|
|
60
|
+
jsonrpc: "2.0",
|
|
61
|
+
result: `Result for #${id}`,
|
|
62
|
+
id
|
|
63
|
+
});
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
console.log(`Sending response for request #${id}`);
|
|
66
|
+
self.postMessage(response);
|
|
67
|
+
}, Math.random() * 1000);
|
|
68
|
+
});
|
|
69
|
+
const transport = new qx.io.transport.PostMessage(server);
|
|
70
|
+
const client = new qx.io.jsonrpc.Client(transport);
|
|
71
|
+
const promises = [];
|
|
72
|
+
// send 10 requests without waiting for the response
|
|
73
|
+
for (let i = 0; i < 10; i++) {
|
|
74
|
+
let request = new qx.io.jsonrpc.protocol.Request("someMethod", ["foo"]);
|
|
75
|
+
await client.send(request);
|
|
76
|
+
promises.push(request.getPromise());
|
|
77
|
+
}
|
|
78
|
+
// Make sure that alle requests have been responded to, i.e. that their promises have
|
|
79
|
+
// been settled.
|
|
80
|
+
const allSettledPromise = Promise.allSettled(promises);
|
|
81
|
+
this.observePromise(allSettledPromise);
|
|
82
|
+
this.assertPromisePending(allSettledPromise);
|
|
83
|
+
this.wait(2000, () => {
|
|
84
|
+
this.assertPromiseFulfilled(
|
|
85
|
+
allSettledPromise,
|
|
86
|
+
"Some request promises were not fulfilled"
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async "test: receive 100 out-of-order jsonrpc responses from server, with jsonrpc errors"() {
|
|
92
|
+
qx.io.jsonrpc.protocol.Request.resetId();
|
|
93
|
+
|
|
94
|
+
// create server worker which sends a response with random delay
|
|
95
|
+
// some requests fail
|
|
96
|
+
const server = this.createOnMessageWorker(evt => {
|
|
97
|
+
let request = JSON.parse(evt.data);
|
|
98
|
+
let id = request.id;
|
|
99
|
+
let response = JSON.stringify(
|
|
100
|
+
Math.random() > 0.5
|
|
101
|
+
? {
|
|
102
|
+
jsonrpc: "2.0",
|
|
103
|
+
result: "OK",
|
|
104
|
+
id
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
error: {
|
|
109
|
+
code: -1,
|
|
110
|
+
message: "error",
|
|
111
|
+
},
|
|
112
|
+
id
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
self.postMessage(response);
|
|
117
|
+
}, Math.random() * 1000);
|
|
118
|
+
});
|
|
119
|
+
const transport = new qx.io.transport.PostMessage(server);
|
|
120
|
+
const client = new qx.io.jsonrpc.Client(transport);
|
|
121
|
+
const promises = [];
|
|
122
|
+
const spies = [];
|
|
123
|
+
// send 100 requests without waiting for the response
|
|
124
|
+
for (let i = 0; i < 100; i++) {
|
|
125
|
+
let request = new qx.io.jsonrpc.protocol.Request("someMethod", ["foo"]);
|
|
126
|
+
await client.send(request);
|
|
127
|
+
// create a spy to receive error objects
|
|
128
|
+
let spy = this.spy()
|
|
129
|
+
spies.push(spy)
|
|
130
|
+
promises.push(request.getPromise().then(spy,spy));
|
|
131
|
+
}
|
|
132
|
+
// Make sure that alle requests have been responded to, i.e. that their promises have
|
|
133
|
+
// been settled.
|
|
134
|
+
const allSettledPromise = Promise.allSettled(promises);
|
|
135
|
+
this.observePromise(allSettledPromise);
|
|
136
|
+
this.assertPromisePending(allSettledPromise);
|
|
137
|
+
this.wait(2000, () => {
|
|
138
|
+
this.assertPromiseSettled(
|
|
139
|
+
allSettledPromise,
|
|
140
|
+
"Some request promises were not settled"
|
|
141
|
+
);
|
|
142
|
+
for(let spy of spies){
|
|
143
|
+
const firstCallArg = spy.getCall(0)?.args[0];
|
|
144
|
+
this.assert(
|
|
145
|
+
(firstCallArg instanceof qx.io.exception.Protocol || typeof firstCallArg == "string"),
|
|
146
|
+
`An unexpected error occurred: "${firstCallArg}"`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
@@ -25,11 +25,7 @@ qx.Class.define("qx.test.io.jsonrpc.Protocol", {
|
|
|
25
25
|
},
|
|
26
26
|
members: {
|
|
27
27
|
"test: JSON-RPC request message object"() {
|
|
28
|
-
let message = new qx.io.jsonrpc.protocol.Request("foo", [
|
|
29
|
-
"bar",
|
|
30
|
-
1,
|
|
31
|
-
false
|
|
32
|
-
]);
|
|
28
|
+
let message = new qx.io.jsonrpc.protocol.Request("foo", ["bar",1,false], 1);
|
|
33
29
|
|
|
34
30
|
let expected = {
|
|
35
31
|
id: 1,
|
|
@@ -760,6 +760,12 @@ qx.Class.define("qx.test.io.request.Xhr", {
|
|
|
760
760
|
this.skip("Skipping because qx.promise==false");
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
+
if (qx.core.Environment.get("qx.Promise.useNativePromise")) {
|
|
764
|
+
this.skip(
|
|
765
|
+
"Skipping because qx.Promise.useNativePromise===true, meaning we can't cancel promises."
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
763
769
|
this.setUpFakeTransport();
|
|
764
770
|
var req = this.req;
|
|
765
771
|
|
|
@@ -818,6 +824,11 @@ qx.Class.define("qx.test.io.request.Xhr", {
|
|
|
818
824
|
if (!qx.core.Environment.get("qx.promise")) {
|
|
819
825
|
this.skip("Skipping because qx.promise==false");
|
|
820
826
|
}
|
|
827
|
+
if (qx.core.Environment.get("qx.Promise.useNativePromise")) {
|
|
828
|
+
this.skip(
|
|
829
|
+
"Skipping because qx.Promise.useNativePromise===true, meaning we can't cancel promises."
|
|
830
|
+
);
|
|
831
|
+
}
|
|
821
832
|
|
|
822
833
|
this.setUpFakeTransport();
|
|
823
834
|
var req = this.req;
|
|
@@ -863,6 +874,11 @@ qx.Class.define("qx.test.io.request.Xhr", {
|
|
|
863
874
|
if (!qx.core.Environment.get("qx.promise")) {
|
|
864
875
|
this.skip("Skipping because qx.promise==false");
|
|
865
876
|
}
|
|
877
|
+
if (qx.core.Environment.get("qx.Promise.useNativePromise")) {
|
|
878
|
+
this.skip(
|
|
879
|
+
"Skipping because qx.Promise.useNativePromise===true, meaning we can't cancel promises."
|
|
880
|
+
);
|
|
881
|
+
}
|
|
866
882
|
|
|
867
883
|
this.setUpFakeTransport();
|
|
868
884
|
var req = this.req;
|