@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.
Files changed (93) hide show
  1. package/Manifest.json +2 -2
  2. package/lib/compiler/compile-info.json +91 -89
  3. package/lib/compiler/index.js +2517 -1488
  4. package/lib/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  5. package/lib/resource/qx/tool/website/build/404.html +3 -25
  6. package/lib/resource/qx/tool/website/build/about.html +3 -25
  7. package/lib/resource/qx/tool/website/build/assets/common.js +20 -0
  8. package/lib/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  9. package/lib/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  10. package/lib/resource/qx/tool/website/build/index.html +3 -25
  11. package/lib/resource/qx/tool/website/partials/footer.html +3 -21
  12. package/lib/resource/qx/tool/website/partials/head.html +0 -1
  13. package/package.json +2 -2
  14. package/source/class/qx/Bootstrap.js +6 -3
  15. package/source/class/qx/Promise.js +93 -6964
  16. package/source/class/qx/bom/Label.js +82 -2
  17. package/source/class/qx/bom/webfonts/WebFont.js +1 -0
  18. package/source/class/qx/core/Environment.js +83 -1
  19. package/source/class/qx/data/controller/List.js +50 -21
  20. package/source/class/qx/data/controller/MSelection.js +45 -12
  21. package/source/class/qx/data/marshal/Json.js +64 -11
  22. package/source/class/qx/dev/unit/AsyncWrapper.js +8 -0
  23. package/source/class/qx/event/Manager.js +163 -124
  24. package/source/class/qx/io/ImageLoader.js +6 -3
  25. package/source/class/qx/io/exception/Transport.js +1 -0
  26. package/source/class/qx/io/jsonrpc/Client.js +64 -8
  27. package/source/class/qx/io/jsonrpc/protocol/Request.js +10 -6
  28. package/source/class/qx/lang/Type.js +36 -3
  29. package/source/class/qx/promise/BluebirdImpl.js +6918 -0
  30. package/source/class/qx/promise/NativeWrapper.js +738 -0
  31. package/source/class/qx/test/Promise.js +1145 -22
  32. package/source/class/qx/test/bom/client/Pdfjs.js +4 -0
  33. package/source/class/qx/test/bom/element/AnimationJs.js +3 -0
  34. package/source/class/qx/test/bom/element/Style.js +1 -0
  35. package/source/class/qx/test/bom/media/MediaTestCase.js +6 -0
  36. package/source/class/qx/test/core/Environment.js +44 -0
  37. package/source/class/qx/test/data/controller/List.js +6 -0
  38. package/source/class/qx/test/data/marshal/Json.js +29 -0
  39. package/source/class/qx/test/io/MAssert.js +94 -0
  40. package/source/class/qx/test/io/TestMAssert.js +47 -0
  41. package/source/class/qx/test/io/jsonrpc/Client.js +79 -19
  42. package/source/class/qx/test/io/jsonrpc/PostMessageClient.js +152 -0
  43. package/source/class/qx/test/io/jsonrpc/Protocol.js +1 -5
  44. package/source/class/qx/test/io/request/Xhr.js +16 -0
  45. package/source/class/qx/test/lang/Type.js +151 -0
  46. package/source/class/qx/test/ui/embed/Iframe.js +1 -1
  47. package/source/class/qx/test/ui/form/TextArea.js +4 -0
  48. package/source/class/qx/test/util/DeferredCall.js +6 -0
  49. package/source/class/qx/test/util/Function.js +2 -2
  50. package/source/class/qx/theme/indigo/ColorDark.js +1 -1
  51. package/source/class/qx/tool/cli/api/Test.js +22 -0
  52. package/source/class/qx/tool/cli/commands/Compile.js +17 -4
  53. package/source/class/qx/tool/cli/commands/Test.js +7 -1
  54. package/source/class/qx/tool/compiler/Analyser.js +7 -0
  55. package/source/class/qx/tool/compiler/ClassFile.js +3 -2
  56. package/source/class/qx/tool/compiler/MetaExtraction.js +0 -5
  57. package/source/class/qx/tool/compiler/targets/meta/Browserify.js +8 -2
  58. package/source/class/qx/ui/basic/Image.js +72 -8
  59. package/source/class/qx/ui/basic/Label.js +4 -6
  60. package/source/class/qx/ui/control/DateChooser.js +4 -6
  61. package/source/class/qx/ui/core/Blocker.js +4 -6
  62. package/source/class/qx/ui/core/LayoutItem.js +4 -6
  63. package/source/class/qx/ui/core/MPlacement.js +23 -1
  64. package/source/class/qx/ui/core/Widget.js +7 -5
  65. package/source/class/qx/ui/form/AbstractField.js +4 -6
  66. package/source/class/qx/ui/form/MForm.js +4 -6
  67. package/source/class/qx/ui/form/Spinner.js +4 -6
  68. package/source/class/qx/ui/form/renderer/AbstractRenderer.js +4 -6
  69. package/source/class/qx/ui/menu/AbstractButton.js +7 -11
  70. package/source/class/qx/ui/mobile/basic/Label.js +4 -6
  71. package/source/class/qx/ui/mobile/form/Label.js +4 -6
  72. package/source/class/qx/ui/mobile/list/List.js +4 -6
  73. package/source/class/qx/ui/progressive/renderer/table/Row.js +35 -7
  74. package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +4 -6
  75. package/source/class/qx/ui/table/Table.js +4 -6
  76. package/source/class/qx/ui/table/cellrenderer/Abstract.js +4 -6
  77. package/source/class/qx/ui/table/cellrenderer/Boolean.js +4 -6
  78. package/source/class/qx/ui/table/pane/Scroller.js +1 -1
  79. package/source/class/qx/ui/table/rowrenderer/Default.js +4 -6
  80. package/source/class/qx/util/ConcurrencyLimiter.js +78 -0
  81. package/source/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  82. package/source/resource/qx/tool/website/build/404.html +3 -25
  83. package/source/resource/qx/tool/website/build/about.html +3 -25
  84. package/source/resource/qx/tool/website/build/assets/common.js +20 -0
  85. package/source/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  86. package/source/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  87. package/source/resource/qx/tool/website/build/index.html +3 -25
  88. package/source/resource/qx/tool/website/partials/footer.html +3 -21
  89. package/source/resource/qx/tool/website/partials/head.html +0 -1
  90. package/lib/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  91. package/lib/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
  92. package/source/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  93. 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,
@@ -98,6 +98,7 @@ qx.Class.define("qx.test.bom.element.Style", {
98
98
  qx.core.Environment.get("browser.version") < 6;
99
99
 
100
100
  if (
101
+ engine == "gecko" ||
101
102
  engine == "opera" ||
102
103
  (engine == "webkit" &&
103
104
  !isOldSafari &&
@@ -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(s)
64
- * @param {String} response The server response to the first request
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 called since the promise is already rejected
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.assertCalledTwice(errorCallback);
145
+ this.assertCallCount(errorCallback, n);
129
146
  } else {
130
- // the error handler will be called three times
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
- this.assertCalled(spy);
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
- this.assertCalled(spy);
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
- 100,
383
+ 250,
326
384
  function () {
327
- this.assertCalledTwice(spy);
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;