@qooxdoo/framework 7.7.2 → 7.8.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 (59) hide show
  1. package/Manifest.json +1 -1
  2. package/lib/compiler/compile-info.json +63 -63
  3. package/lib/compiler/index.js +513 -353
  4. package/lib/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  5. package/package.json +2 -2
  6. package/source/class/qx/bom/Label.js +82 -2
  7. package/source/class/qx/bom/webfonts/WebFont.js +1 -0
  8. package/source/class/qx/core/Environment.js +82 -1
  9. package/source/class/qx/data/controller/List.js +50 -21
  10. package/source/class/qx/data/controller/MSelection.js +45 -12
  11. package/source/class/qx/dev/unit/AsyncWrapper.js +8 -0
  12. package/source/class/qx/event/Manager.js +163 -124
  13. package/source/class/qx/io/ImageLoader.js +6 -3
  14. package/source/class/qx/io/exception/Transport.js +1 -0
  15. package/source/class/qx/io/jsonrpc/Client.js +64 -8
  16. package/source/class/qx/io/jsonrpc/protocol/Request.js +10 -6
  17. package/source/class/qx/test/bom/client/Pdfjs.js +4 -0
  18. package/source/class/qx/test/bom/element/AnimationJs.js +3 -0
  19. package/source/class/qx/test/bom/element/Style.js +1 -0
  20. package/source/class/qx/test/bom/media/MediaTestCase.js +6 -0
  21. package/source/class/qx/test/core/Environment.js +44 -0
  22. package/source/class/qx/test/io/MAssert.js +94 -0
  23. package/source/class/qx/test/io/TestMAssert.js +47 -0
  24. package/source/class/qx/test/io/jsonrpc/Client.js +79 -19
  25. package/source/class/qx/test/io/jsonrpc/PostMessageClient.js +152 -0
  26. package/source/class/qx/test/io/jsonrpc/Protocol.js +1 -5
  27. package/source/class/qx/test/ui/embed/Iframe.js +1 -1
  28. package/source/class/qx/test/ui/form/TextArea.js +4 -0
  29. package/source/class/qx/test/util/DeferredCall.js +6 -0
  30. package/source/class/qx/test/util/Function.js +2 -2
  31. package/source/class/qx/tool/cli/api/Test.js +22 -0
  32. package/source/class/qx/tool/cli/commands/Compile.js +17 -4
  33. package/source/class/qx/tool/cli/commands/Test.js +7 -1
  34. package/source/class/qx/tool/compiler/Analyser.js +7 -0
  35. package/source/class/qx/tool/compiler/ClassFile.js +3 -2
  36. package/source/class/qx/tool/compiler/MetaExtraction.js +0 -5
  37. package/source/class/qx/tool/compiler/targets/meta/Browserify.js +8 -2
  38. package/source/class/qx/ui/basic/Image.js +72 -8
  39. package/source/class/qx/ui/basic/Label.js +4 -6
  40. package/source/class/qx/ui/control/DateChooser.js +4 -6
  41. package/source/class/qx/ui/core/Blocker.js +4 -6
  42. package/source/class/qx/ui/core/LayoutItem.js +4 -6
  43. package/source/class/qx/ui/core/MPlacement.js +11 -0
  44. package/source/class/qx/ui/core/Widget.js +7 -5
  45. package/source/class/qx/ui/form/AbstractField.js +4 -6
  46. package/source/class/qx/ui/form/MForm.js +4 -6
  47. package/source/class/qx/ui/form/Spinner.js +4 -6
  48. package/source/class/qx/ui/form/renderer/AbstractRenderer.js +4 -6
  49. package/source/class/qx/ui/menu/AbstractButton.js +7 -11
  50. package/source/class/qx/ui/mobile/basic/Label.js +4 -6
  51. package/source/class/qx/ui/mobile/form/Label.js +4 -6
  52. package/source/class/qx/ui/mobile/list/List.js +4 -6
  53. package/source/class/qx/ui/progressive/renderer/table/Row.js +35 -7
  54. package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +4 -6
  55. package/source/class/qx/ui/table/Table.js +4 -6
  56. package/source/class/qx/ui/table/cellrenderer/Abstract.js +4 -6
  57. package/source/class/qx/ui/table/cellrenderer/Boolean.js +4 -6
  58. package/source/class/qx/ui/table/rowrenderer/Default.js +4 -6
  59. package/source/resource/qx/tool/schema/compile-1-0-0.json +13 -0
@@ -29,10 +29,17 @@ qx.Class.define("qx.io.jsonrpc.protocol.Request", {
29
29
  */
30
30
  __current_request_id: 0,
31
31
 
32
+ /**
33
+ * Returns the current request id
34
+ * @returns {Number}
35
+ */
32
36
  getCurrentId() {
33
37
  return qx.io.jsonrpc.protocol.Request.__current_request_id;
34
38
  },
35
39
 
40
+ /**
41
+ * Resets the request id to zero
42
+ */
36
43
  resetId() {
37
44
  qx.io.jsonrpc.protocol.Request.__current_request_id = 0;
38
45
  }
@@ -69,13 +76,14 @@ qx.Class.define("qx.io.jsonrpc.protocol.Request", {
69
76
  __promise: null,
70
77
 
71
78
  /**
72
- * Getter for promise which resolves with the result to the request
79
+ * Getter for promise which resolves with the result to the request, if successful
73
80
  * @return {qx.Promise}
74
81
  */
75
82
  getPromise() {
76
83
  return this.__promise;
77
84
  },
78
85
 
86
+
79
87
  /**
80
88
  * Determines how an exception during transport is handled. Standard
81
89
  * behavior is to reject the request's promise with that exception.
@@ -84,11 +92,7 @@ qx.Class.define("qx.io.jsonrpc.protocol.Request", {
84
92
  * @param {qx.io.exception.Transport} exception
85
93
  */
86
94
  handleTransportException(exception) {
87
- try {
88
- this.getPromise().reject(exception);
89
- } catch (e) {
90
- this.warn("Promise has already been rejected");
91
- }
95
+ this.__promise.reject(exception);
92
96
  }
93
97
  }
94
98
  });
@@ -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
  });
@@ -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,
@@ -145,7 +145,7 @@ qx.Class.define("qx.test.ui.embed.Iframe", {
145
145
  this.assertEquals("Hello World!", innerText);
146
146
  });
147
147
  }.bind(this),
148
- 4000
148
+ 5000
149
149
  );
150
150
 
151
151
  this.wait(10000);
@@ -294,6 +294,10 @@ qx.Class.define("qx.test.ui.form.TextArea", {
294
294
  this.skip();
295
295
  }
296
296
 
297
+ if ((qx.core.Environment.get("engine.name") === "gecko") && (navigator.plugins.length == 0)) {
298
+ this.skip("test disabled on headless firefox browser");
299
+ }
300
+
297
301
  var textArea = this.__textArea;
298
302
  textArea.set({
299
303
  autoSize: true,
@@ -24,6 +24,12 @@ qx.Class.define("qx.test.util.DeferredCall", {
24
24
  if (navigator.plugins.length == 0) {
25
25
  this.skip("test disabled on headless browsers");
26
26
  }
27
+
28
+ if (qx.core.Environment.get("browser.name") == "safari") {
29
+ this.skip(
30
+ "we can not detect headless mode in safari"
31
+ );
32
+ }
27
33
 
28
34
  var fail = function () {
29
35
  throw new Error("fail");
@@ -30,7 +30,7 @@ qx.Class.define("qx.test.util.Function", {
30
30
  this.assertNotCalled(test);
31
31
  debouncedTest(false);
32
32
  this.wait(
33
- 100,
33
+ 250,
34
34
  function () {
35
35
  this.assertCalledOnce(test);
36
36
  this.assertCalledWith(test, false);
@@ -52,7 +52,7 @@ qx.Class.define("qx.test.util.Function", {
52
52
  debouncedTest(true);
53
53
  debouncedTest(false);
54
54
  this.wait(
55
- 100,
55
+ 250,
56
56
  function () {
57
57
  this.assertCalledTwice(test);
58
58
  this.assertCalledWith(test, false);