Quellcode durchsuchen

Replace wait-until-promise with utility method

and bump default timeout

Also let `_converse.api.waitUntil` use it if a function is passed in.
JC Brand vor 6 Jahren
Ursprung
Commit
a1630b5c1f

+ 211 - 451
package-lock.json

@@ -873,25 +873,6 @@
           "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
           "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
           "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
           "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
           "dev": true
           "dev": true
-        },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
         }
         }
       }
       }
     },
     },
@@ -918,25 +899,6 @@
           "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
           "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
           "dev": true
           "dev": true
         },
         },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
         "semver": {
         "semver": {
           "version": "5.7.0",
           "version": "5.7.0",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
@@ -971,9 +933,9 @@
           }
           }
         },
         },
         "safe-buffer": {
         "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
           "dev": true
           "dev": true
         }
         }
       }
       }
@@ -1013,21 +975,6 @@
         "which": "^1.3.1"
         "which": "^1.3.1"
       },
       },
       "dependencies": {
       "dependencies": {
-        "bluebird": {
-          "version": "3.5.5",
-          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
-          "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
-          "dev": true
-        },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
         "glob": {
         "glob": {
           "version": "7.1.4",
           "version": "7.1.4",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
@@ -1051,20 +998,10 @@
             "yallist": "^3.0.2"
             "yallist": "^3.0.2"
           }
           }
         },
         },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
         "safe-buffer": {
         "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
           "dev": true
           "dev": true
         },
         },
         "semver": {
         "semver": {
@@ -1189,64 +1126,6 @@
         "chalk": "^2.3.1",
         "chalk": "^2.3.1",
         "execa": "^1.0.0",
         "execa": "^1.0.0",
         "strong-log-transformer": "^2.0.0"
         "strong-log-transformer": "^2.0.0"
-      },
-      "dependencies": {
-        "cross-spawn": {
-          "version": "6.0.5",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
-          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
-          "dev": true,
-          "requires": {
-            "nice-try": "^1.0.4",
-            "path-key": "^2.0.1",
-            "semver": "^5.5.0",
-            "shebang-command": "^1.2.0",
-            "which": "^1.2.9"
-          }
-        },
-        "execa": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
-          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
-          "dev": true,
-          "requires": {
-            "cross-spawn": "^6.0.0",
-            "get-stream": "^4.0.0",
-            "is-stream": "^1.1.0",
-            "npm-run-path": "^2.0.0",
-            "p-finally": "^1.0.0",
-            "signal-exit": "^3.0.0",
-            "strip-eof": "^1.0.0"
-          }
-        },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
-        "which": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-          "dev": true,
-          "requires": {
-            "isexe": "^2.0.0"
-          }
-        }
       }
       }
     },
     },
     "@lerna/clean": {
     "@lerna/clean": {
@@ -1318,64 +1197,6 @@
         "is-ci": "^1.0.10",
         "is-ci": "^1.0.10",
         "lodash": "^4.17.5",
         "lodash": "^4.17.5",
         "npmlog": "^4.1.2"
         "npmlog": "^4.1.2"
-      },
-      "dependencies": {
-        "cross-spawn": {
-          "version": "6.0.5",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
-          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
-          "dev": true,
-          "requires": {
-            "nice-try": "^1.0.4",
-            "path-key": "^2.0.1",
-            "semver": "^5.5.0",
-            "shebang-command": "^1.2.0",
-            "which": "^1.2.9"
-          }
-        },
-        "execa": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
-          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
-          "dev": true,
-          "requires": {
-            "cross-spawn": "^6.0.0",
-            "get-stream": "^4.0.0",
-            "is-stream": "^1.1.0",
-            "npm-run-path": "^2.0.0",
-            "p-finally": "^1.0.0",
-            "signal-exit": "^3.0.0",
-            "strip-eof": "^1.0.0"
-          }
-        },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
-        "which": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-          "dev": true,
-          "requires": {
-            "isexe": "^2.0.0"
-          }
-        }
       }
       }
     },
     },
     "@lerna/conventional-commits": {
     "@lerna/conventional-commits": {
@@ -1394,27 +1215,6 @@
         "npmlog": "^4.1.2",
         "npmlog": "^4.1.2",
         "pify": "^3.0.0",
         "pify": "^3.0.0",
         "semver": "^5.5.0"
         "semver": "^5.5.0"
-      },
-      "dependencies": {
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        }
       }
       }
     },
     },
     "@lerna/create": {
     "@lerna/create": {
@@ -2050,12 +1850,12 @@
       "dev": true
       "dev": true
     },
     },
     "@octokit/endpoint": {
     "@octokit/endpoint": {
-      "version": "5.1.7",
-      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.1.7.tgz",
-      "integrity": "sha512-MfsXHx9z9EPxLYSf7PYuzWvVZTotx+/QTFk7UMp4Fv83k3QrvmovEjP0pl141g+Uq/w9CcDuuXhsiq4X3oxVsA==",
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.2.1.tgz",
+      "integrity": "sha512-GoUsRSRhtbCQugRY8eDWg5BnsczUZNq00qArrP7tKPHFmvz2KzJ8DoEq6IAQhLGwAOBHbZQ/Zml3DiaEKAWwkA==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "deepmerge": "3.2.1",
+        "deepmerge": "4.0.0",
         "is-plain-object": "^3.0.0",
         "is-plain-object": "^3.0.0",
         "universal-user-agent": "^2.1.0",
         "universal-user-agent": "^2.1.0",
         "url-template": "^2.0.8"
         "url-template": "^2.0.8"
@@ -2085,9 +1885,9 @@
       "dev": true
       "dev": true
     },
     },
     "@octokit/request": {
     "@octokit/request": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-4.1.1.tgz",
-      "integrity": "sha512-LOyL0i3oxRo418EXRSJNk/3Q4I0/NKawTn6H/CQp+wnrG1UFLGu080gSsgnWobhPo5BpUNgSQ5BRk5FOOJhD1Q==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.0.0.tgz",
+      "integrity": "sha512-eAknm2Aq+/uQDLHUn7KHHpXB7A/NFfWgaVN+ZhC6mQlCNRzCv242eLYgt6cC4h2DZL7mM+QidS/UtZVwYvQXBw==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "@octokit/endpoint": "^5.1.0",
         "@octokit/endpoint": "^5.1.0",
@@ -2127,15 +1927,15 @@
       }
       }
     },
     },
     "@octokit/rest": {
     "@octokit/rest": {
-      "version": "16.28.2",
-      "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.28.2.tgz",
-      "integrity": "sha512-csuYiHvJ1P/GFDadVn0QhwO83R1+YREjcwCY7ZIezB6aJTRIEidJZj+R7gAkUhT687cqYb4cXTZsDVu9F+Fmug==",
+      "version": "16.28.3",
+      "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.28.3.tgz",
+      "integrity": "sha512-hzM2VvVn9o0+sS08y2pp33UF5tKy0XdR2z+/AFD583TjhHlX/9Lmdv3SmRiz0UC6rNqNXe1X7BiZ/QNUwVm27Q==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@octokit/request": "^4.0.1",
+        "@octokit/request": "^5.0.0",
         "@octokit/request-error": "^1.0.2",
         "@octokit/request-error": "^1.0.2",
         "atob-lite": "^2.0.0",
         "atob-lite": "^2.0.0",
-        "before-after-hook": "^1.4.0",
+        "before-after-hook": "^2.0.0",
         "btoa-lite": "^1.0.0",
         "btoa-lite": "^1.0.0",
         "deprecation": "^2.0.0",
         "deprecation": "^2.0.0",
         "lodash.get": "^4.4.2",
         "lodash.get": "^4.4.2",
@@ -2853,9 +2653,9 @@
       }
       }
     },
     },
     "before-after-hook": {
     "before-after-hook": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.4.0.tgz",
-      "integrity": "sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.0.1.tgz",
+      "integrity": "sha512-dpgMHA51KZyCu7uuxF6FCkN+scfGd/6aLxEr/14vKUo/1nPxcd2fhFv4BgYCbWxKt7JfgpbjJq9nc30Ip/p2uw==",
       "dev": true
       "dev": true
     },
     },
     "big.js": {
     "big.js": {
@@ -2880,9 +2680,9 @@
       }
       }
     },
     },
     "bluebird": {
     "bluebird": {
-      "version": "3.5.1",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
-      "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
+      "version": "3.5.5",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
+      "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
       "dev": true
       "dev": true
     },
     },
     "bn.js": {
     "bn.js": {
@@ -3701,9 +3501,9 @@
       },
       },
       "dependencies": {
       "dependencies": {
         "semver": {
         "semver": {
-          "version": "6.1.1",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz",
-          "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==",
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz",
+          "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==",
           "dev": true
           "dev": true
         },
         },
         "through2": {
         "through2": {
@@ -4136,9 +3936,9 @@
       "dev": true
       "dev": true
     },
     },
     "deepmerge": {
     "deepmerge": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.1.tgz",
-      "integrity": "sha512-+hbDSzTqEW0fWgnlKksg7XAOtT+ddZS5lHZJ6f6MdixRs9wQy+50fm1uUCVb1IkvjLUYX/SfFO021ZNwriURTw==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz",
+      "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==",
       "dev": true
       "dev": true
     },
     },
     "defaults": {
     "defaults": {
@@ -4439,6 +4239,31 @@
         "is-arrayish": "^0.2.1"
         "is-arrayish": "^0.2.1"
       }
       }
     },
     },
+    "es-abstract": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
+      "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.0",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "is-callable": "^1.1.4",
+        "is-regex": "^1.0.4",
+        "object-keys": "^1.0.12"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+      "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
     "es6-promise": {
     "es6-promise": {
       "version": "4.2.8",
       "version": "4.2.8",
       "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
       "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
@@ -6396,6 +6221,15 @@
         "har-schema": "^2.0.0"
         "har-schema": "^2.0.0"
       }
       }
     },
     },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
     "has-ansi": {
     "has-ansi": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
@@ -6574,12 +6408,12 @@
       "dev": true
       "dev": true
     },
     },
     "https-proxy-agent": {
     "https-proxy-agent": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
-      "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz",
+      "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "agent-base": "^4.1.0",
+        "agent-base": "^4.3.0",
         "debug": "^3.1.0"
         "debug": "^3.1.0"
       }
       }
     },
     },
@@ -6854,6 +6688,12 @@
       "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
       "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
       "dev": true
       "dev": true
     },
     },
+    "is-callable": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+      "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+      "dev": true
+    },
     "is-ci": {
     "is-ci": {
       "version": "1.2.1",
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
       "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
@@ -6872,6 +6712,12 @@
         "kind-of": "^3.0.2"
         "kind-of": "^3.0.2"
       }
       }
     },
     },
+    "is-date-object": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+      "dev": true
+    },
     "is-descriptor": {
     "is-descriptor": {
       "version": "0.1.6",
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
       "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@@ -6969,6 +6815,15 @@
       "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
       "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
       "dev": true
       "dev": true
     },
     },
+    "is-regex": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+      "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.1"
+      }
+    },
     "is-ssh": {
     "is-ssh": {
       "version": "1.3.1",
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
       "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
@@ -6984,6 +6839,15 @@
       "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
       "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
       "dev": true
       "dev": true
     },
     },
+    "is-symbol": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+      "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.0"
+      }
+    },
     "is-text-path": {
     "is-text-path": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
@@ -7407,22 +7271,22 @@
       "dev": true
       "dev": true
     },
     },
     "lodash.template": {
     "lodash.template": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
-      "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
+      "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "lodash._reinterpolate": "~3.0.0",
+        "lodash._reinterpolate": "^3.0.0",
         "lodash.templatesettings": "^4.0.0"
         "lodash.templatesettings": "^4.0.0"
       }
       }
     },
     },
     "lodash.templatesettings": {
     "lodash.templatesettings": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz",
-      "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
+      "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "lodash._reinterpolate": "~3.0.0"
+        "lodash._reinterpolate": "^3.0.0"
       }
       }
     },
     },
     "lodash.uniq": {
     "lodash.uniq": {
@@ -7479,28 +7343,20 @@
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "pify": "^3.0.0"
         "pify": "^3.0.0"
-      },
-      "dependencies": {
-        "pify": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
-          "dev": true
-        }
       }
       }
     },
     },
     "make-fetch-happen": {
     "make-fetch-happen": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz",
-      "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz",
+      "integrity": "sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "agentkeepalive": "^3.4.1",
         "agentkeepalive": "^3.4.1",
-        "cacache": "^11.0.1",
+        "cacache": "^11.3.3",
         "http-cache-semantics": "^3.8.1",
         "http-cache-semantics": "^3.8.1",
         "http-proxy-agent": "^2.1.0",
         "http-proxy-agent": "^2.1.0",
         "https-proxy-agent": "^2.2.1",
         "https-proxy-agent": "^2.2.1",
-        "lru-cache": "^4.1.2",
+        "lru-cache": "^5.1.1",
         "mississippi": "^3.0.0",
         "mississippi": "^3.0.0",
         "node-fetch-npm": "^2.0.2",
         "node-fetch-npm": "^2.0.2",
         "promise-retry": "^1.1.1",
         "promise-retry": "^1.1.1",
@@ -7508,15 +7364,71 @@
         "ssri": "^6.0.0"
         "ssri": "^6.0.0"
       },
       },
       "dependencies": {
       "dependencies": {
+        "cacache": {
+          "version": "11.3.3",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz",
+          "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==",
+          "dev": true,
+          "requires": {
+            "bluebird": "^3.5.5",
+            "chownr": "^1.1.1",
+            "figgy-pudding": "^3.5.1",
+            "glob": "^7.1.4",
+            "graceful-fs": "^4.1.15",
+            "lru-cache": "^5.1.1",
+            "mississippi": "^3.0.0",
+            "mkdirp": "^0.5.1",
+            "move-concurrently": "^1.0.1",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^2.6.3",
+            "ssri": "^6.0.1",
+            "unique-filename": "^1.1.1",
+            "y18n": "^4.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "graceful-fs": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
+          "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
+          "dev": true
+        },
         "lru-cache": {
         "lru-cache": {
-          "version": "4.1.5",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
-          "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
           "dev": true,
           "dev": true,
           "requires": {
           "requires": {
-            "pseudomap": "^1.0.2",
-            "yallist": "^2.1.2"
+            "yallist": "^3.0.2"
+          }
+        },
+        "rimraf": {
+          "version": "2.6.3",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+          "dev": true,
+          "requires": {
+            "glob": "^7.1.3"
           }
           }
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+          "dev": true
         }
         }
       }
       }
     },
     },
@@ -7807,9 +7719,9 @@
       },
       },
       "dependencies": {
       "dependencies": {
         "safe-buffer": {
         "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
           "dev": true
           "dev": true
         },
         },
         "yallist": {
         "yallist": {
@@ -11589,9 +11501,9 @@
       },
       },
       "dependencies": {
       "dependencies": {
         "graceful-fs": {
         "graceful-fs": {
-          "version": "4.1.15",
-          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
-          "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
+          "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
           "dev": true
           "dev": true
         },
         },
         "which": {
         "which": {
@@ -11618,9 +11530,9 @@
       }
       }
     },
     },
     "npm-packlist": {
     "npm-packlist": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz",
-      "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz",
+      "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "ignore-walk": "^3.0.1",
         "ignore-walk": "^3.0.1",
@@ -11726,6 +11638,16 @@
         "object-keys": "^1.0.11"
         "object-keys": "^1.0.11"
       }
       }
     },
     },
+    "object.getownpropertydescriptors": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+      "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.5.1"
+      }
+    },
     "object.pick": {
     "object.pick": {
       "version": "1.3.0",
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
       "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@@ -12568,16 +12490,14 @@
       }
       }
     },
     },
     "read-package-tree": {
     "read-package-tree": {
-      "version": "5.2.2",
-      "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.2.tgz",
-      "integrity": "sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
+      "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "debuglog": "^1.0.1",
-        "dezalgo": "^1.0.0",
-        "once": "^1.3.0",
         "read-package-json": "^2.0.0",
         "read-package-json": "^2.0.0",
-        "readdir-scoped-modules": "^1.0.0"
+        "readdir-scoped-modules": "^1.0.0",
+        "util-promisify": "^2.1.0"
       }
       }
     },
     },
     "read-pkg": {
     "read-pkg": {
@@ -12617,9 +12537,9 @@
       }
       }
     },
     },
     "readdir-scoped-modules": {
     "readdir-scoped-modules": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz",
-      "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
+      "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "debuglog": "^1.0.1",
         "debuglog": "^1.0.1",
@@ -14007,9 +13927,9 @@
       },
       },
       "dependencies": {
       "dependencies": {
         "safe-buffer": {
         "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
           "dev": true
           "dev": true
         },
         },
         "yallist": {
         "yallist": {
@@ -14673,6 +14593,15 @@
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
       "dev": true
       "dev": true
     },
     },
+    "util-promisify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
+      "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
+      "dev": true,
+      "requires": {
+        "object.getownpropertydescriptors": "^2.0.3"
+      }
+    },
     "uuid": {
     "uuid": {
       "version": "3.3.2",
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
@@ -14721,12 +14650,6 @@
       "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
       "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
       "dev": true
       "dev": true
     },
     },
-    "wait-until-promise": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/wait-until-promise/-/wait-until-promise-1.0.0.tgz",
-      "integrity": "sha1-03Uy1bfv9oJwIMtE2OyqIzyWeMU=",
-      "dev": true
-    },
     "watchpack": {
     "watchpack": {
       "version": "1.6.0",
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
@@ -15076,64 +14999,6 @@
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "execa": "^1.0.0"
         "execa": "^1.0.0"
-      },
-      "dependencies": {
-        "cross-spawn": {
-          "version": "6.0.5",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
-          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
-          "dev": true,
-          "requires": {
-            "nice-try": "^1.0.4",
-            "path-key": "^2.0.1",
-            "semver": "^5.5.0",
-            "shebang-command": "^1.2.0",
-            "which": "^1.2.9"
-          }
-        },
-        "execa": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
-          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
-          "dev": true,
-          "requires": {
-            "cross-spawn": "^6.0.0",
-            "get-stream": "^4.0.0",
-            "is-stream": "^1.1.0",
-            "npm-run-path": "^2.0.0",
-            "p-finally": "^1.0.0",
-            "signal-exit": "^3.0.0",
-            "strip-eof": "^1.0.0"
-          }
-        },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
-        "which": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-          "dev": true,
-          "requires": {
-            "isexe": "^2.0.0"
-          }
-        }
       }
       }
     },
     },
     "wordwrap": {
     "wordwrap": {
@@ -15302,34 +15167,6 @@
           "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
           "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
           "dev": true
           "dev": true
         },
         },
-        "cross-spawn": {
-          "version": "6.0.5",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
-          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
-          "dev": true,
-          "requires": {
-            "nice-try": "^1.0.4",
-            "path-key": "^2.0.1",
-            "semver": "^5.5.0",
-            "shebang-command": "^1.2.0",
-            "which": "^1.2.9"
-          }
-        },
-        "execa": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
-          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
-          "dev": true,
-          "requires": {
-            "cross-spawn": "^6.0.0",
-            "get-stream": "^4.0.0",
-            "is-stream": "^1.1.0",
-            "npm-run-path": "^2.0.0",
-            "p-finally": "^1.0.0",
-            "signal-exit": "^3.0.0",
-            "strip-eof": "^1.0.0"
-          }
-        },
         "find-up": {
         "find-up": {
           "version": "3.0.0",
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@@ -15339,30 +15176,6 @@
             "locate-path": "^3.0.0"
             "locate-path": "^3.0.0"
           }
           }
         },
         },
-        "get-stream": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "invert-kv": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
-          "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
-          "dev": true
-        },
-        "lcid": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
-          "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
-          "dev": true,
-          "requires": {
-            "invert-kv": "^2.0.0"
-          }
-        },
         "locate-path": {
         "locate-path": {
           "version": "3.0.0",
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@@ -15373,40 +15186,6 @@
             "path-exists": "^3.0.0"
             "path-exists": "^3.0.0"
           }
           }
         },
         },
-        "mem": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
-          "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
-          "dev": true,
-          "requires": {
-            "map-age-cleaner": "^0.1.1",
-            "mimic-fn": "^2.0.0",
-            "p-is-promise": "^2.0.0"
-          }
-        },
-        "mimic-fn": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-          "dev": true
-        },
-        "os-locale": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
-          "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
-          "dev": true,
-          "requires": {
-            "execa": "^1.0.0",
-            "lcid": "^2.0.0",
-            "mem": "^4.0.0"
-          }
-        },
-        "p-is-promise": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
-          "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
-          "dev": true
-        },
         "p-limit": {
         "p-limit": {
           "version": "2.2.0",
           "version": "2.2.0",
           "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
           "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
@@ -15431,25 +15210,6 @@
           "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
           "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
           "dev": true
           "dev": true
         },
         },
-        "pump": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-          "dev": true,
-          "requires": {
-            "end-of-stream": "^1.1.0",
-            "once": "^1.3.1"
-          }
-        },
-        "which": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-          "dev": true,
-          "requires": {
-            "isexe": "^2.0.0"
-          }
-        },
         "yargs-parser": {
         "yargs-parser": {
           "version": "11.1.1",
           "version": "11.1.1",
           "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
           "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",

+ 0 - 1
package.json

@@ -94,7 +94,6 @@
     "style-loader": "^0.23.1",
     "style-loader": "^0.23.1",
     "uglify-es": "^3.3.9",
     "uglify-es": "^3.3.9",
     "urijs": "^1.19.1",
     "urijs": "^1.19.1",
-    "wait-until-promise": "^1.0.0",
     "webpack": "^4.35.3",
     "webpack": "^4.35.3",
     "webpack-cli": "^3.3.5",
     "webpack-cli": "^3.3.5",
     "xss": "^1.0.6"
     "xss": "^1.0.6"

+ 25 - 25
spec/bookmarks.js

@@ -40,7 +40,7 @@
             const view = _converse.chatboxviews.get(jid);
             const view = _converse.chatboxviews.get(jid);
             spyOn(view, 'renderBookmarkForm').and.callThrough();
             spyOn(view, 'renderBookmarkForm').and.callThrough();
             spyOn(view, 'closeForm').and.callThrough();
             spyOn(view, 'closeForm').and.callThrough();
-            await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
+            await u.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
             let toggle = view.el.querySelector('.toggle-bookmark');
             let toggle = view.el.querySelector('.toggle-bookmark');
             expect(toggle.title).toBe('Bookmark this groupchat');
             expect(toggle.title).toBe('Bookmark this groupchat');
             toggle.click();
             toggle.click();
@@ -94,7 +94,7 @@
             _converse.connection.IQ_stanzas = [];
             _converse.connection.IQ_stanzas = [];
             view.el.querySelector('.btn-primary').click();
             view.el.querySelector('.btn-primary').click();
 
 
-            await test_utils.waitUntil(() => sent_stanza);
+            await u.waitUntil(() => sent_stanza);
             expect(sent_stanza.toLocaleString()).toBe(
             expect(sent_stanza.toLocaleString()).toBe(
                 `<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="set" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -133,8 +133,8 @@
                 'id':IQ_id
                 'id':IQ_id
             });
             });
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.get('bookmarked'));
-            toggle = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+            await u.waitUntil(() => view.model.get('bookmarked'));
+            toggle = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
             expect(view.model.get('bookmarked')).toBeTruthy();
             expect(view.model.get('bookmarked')).toBeTruthy();
             expect(toggle.title).toBe('Unbookmark this groupchat');
             expect(toggle.title).toBe('Unbookmark this groupchat');
             expect(u.hasClass('on-button', toggle), true);
             expect(u.hasClass('on-button', toggle), true);
@@ -153,7 +153,7 @@
                 [{'category': 'pubsub', 'type': 'pep'}],
                 [{'category': 'pubsub', 'type': 'pep'}],
                 ['http://jabber.org/protocol/pubsub#publish-options']
                 ['http://jabber.org/protocol/pubsub#publish-options']
             );
             );
-            await test_utils.waitUntil(() => _converse.bookmarks);
+            await u.waitUntil(() => _converse.bookmarks);
             let jid = 'lounge@montague.lit';
             let jid = 'lounge@montague.lit';
             _converse.bookmarks.create({
             _converse.bookmarks.create({
                 'jid': jid,
                 'jid': jid,
@@ -198,7 +198,7 @@
                     ['http://jabber.org/protocol/pubsub#publish-options']
                     ['http://jabber.org/protocol/pubsub#publish-options']
                 );
                 );
                 const room_jid = 'coven@chat.shakespeare.lit';
                 const room_jid = 'coven@chat.shakespeare.lit';
-                await test_utils.waitUntil(() => _converse.bookmarks);
+                await u.waitUntil(() => _converse.bookmarks);
                 _converse.bookmarks.create({
                 _converse.bookmarks.create({
                     'jid': room_jid,
                     'jid': room_jid,
                     'autojoin': false,
                     'autojoin': false,
@@ -208,7 +208,7 @@
                 const model = await _converse.api.rooms.open(room_jid);
                 const model = await _converse.api.rooms.open(room_jid);
                 spyOn(model, 'join').and.callThrough();
                 spyOn(model, 'join').and.callThrough();
                 await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
                 await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
-                await test_utils.waitUntil(() => model.join.calls.count());
+                await u.waitUntil(() => model.join.calls.count());
                 expect(model.get('nick')).toBe('Othello');
                 expect(model.get('nick')).toBe('Othello');
                 done();
                 done();
             }));
             }));
@@ -224,7 +224,7 @@
                 );
                 );
                 await _converse.api.rooms.open(`lounge@montague.lit`);
                 await _converse.api.rooms.open(`lounge@montague.lit`);
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
-                let bookmark_icon = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+                let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
                 _converse.bookmarks.create({
                 _converse.bookmarks.create({
                     'jid': view.model.get('jid'),
                     'jid': view.model.get('jid'),
@@ -233,10 +233,10 @@
                     'nick': ' some1'
                     'nick': ' some1'
                 });
                 });
                 view.model.set('bookmarked', true);
                 view.model.set('bookmarked', true);
-                bookmark_icon = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+                bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy();
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy();
                 view.model.set('bookmarked', false);
                 view.model.set('bookmarked', false);
-                bookmark_icon = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+                bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
                 expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
                 done();
                 done();
             }));
             }));
@@ -257,7 +257,7 @@
 
 
                 const jid = 'theplay@conference.shakespeare.lit';
                 const jid = 'theplay@conference.shakespeare.lit';
                 const view = _converse.chatboxviews.get(jid);
                 const view = _converse.chatboxviews.get(jid);
-                await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
+                await u.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
 
 
                 spyOn(view, 'toggleBookmark').and.callThrough();
                 spyOn(view, 'toggleBookmark').and.callThrough();
                 spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
                 spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
@@ -271,7 +271,7 @@
                 });
                 });
                 expect(_converse.bookmarks.length).toBe(1);
                 expect(_converse.bookmarks.length).toBe(1);
                 expect(view.model.get('bookmarked')).toBeTruthy();
                 expect(view.model.get('bookmarked')).toBeTruthy();
-                let bookmark_icon = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+                let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
                 expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy();
                 expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy();
 
 
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -280,7 +280,7 @@
                 });
                 });
                 spyOn(_converse.connection, 'getUniqueId').and.callThrough();
                 spyOn(_converse.connection, 'getUniqueId').and.callThrough();
                 bookmark_icon.click();
                 bookmark_icon.click();
-                bookmark_icon = await test_utils.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
+                bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
                 expect(view.toggleBookmark).toHaveBeenCalled();
                 expect(view.toggleBookmark).toHaveBeenCalled();
                 expect(u.hasClass('button-on', bookmark_icon)).toBeFalsy();
                 expect(u.hasClass('button-on', bookmark_icon)).toBeFalsy();
                 expect(_converse.bookmarks.length).toBe(0);
                 expect(_converse.bookmarks.length).toBe(0);
@@ -360,7 +360,7 @@
                 [{'category': 'pubsub', 'type': 'pep'}],
                 [{'category': 'pubsub', 'type': 'pep'}],
                 ['http://jabber.org/protocol/pubsub#publish-options']
                 ['http://jabber.org/protocol/pubsub#publish-options']
             );
             );
-            await test_utils.waitUntil(() => _converse.bookmarks);
+            await u.waitUntil(() => _converse.bookmarks);
             // Emit here instead of mocking fetching of bookmarks.
             // Emit here instead of mocking fetching of bookmarks.
             _converse.api.trigger('bookmarksInitialized');
             _converse.api.trigger('bookmarksInitialized');
 
 
@@ -402,7 +402,7 @@
                                             'jid':'theplay@conference.shakespeare.lit'})
                                             'jid':'theplay@conference.shakespeare.lit'})
                                 .c('nick').t('JC');
                                 .c('nick').t('JC');
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.bookmarks.length);
+            await u.waitUntil(() => _converse.bookmarks.length);
             expect(_converse.bookmarks.length).toBe(1);
             expect(_converse.bookmarks.length).toBe(1);
             expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
             expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
             done();
             done();
@@ -428,7 +428,7 @@
              *  </iq>
              *  </iq>
              */
              */
             let IQ_id;
             let IQ_id;
-            const call = await test_utils.waitUntil(() =>
+            const call = await u.waitUntil(() =>
                 _.filter(
                 _.filter(
                     _converse.connection.send.calls.all(),
                     _converse.connection.send.calls.all(),
                     call => {
                     call => {
@@ -491,7 +491,7 @@
                                     'jid': 'another@conference.shakespeare.lit'
                                     'jid': 'another@conference.shakespeare.lit'
                                 }); // Purposefully exclude the <nick> element to test #1043
                                 }); // Purposefully exclude the <nick> element to test #1043
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.bookmarks.onBookmarksReceived.calls.count());
+            await u.waitUntil(() => _converse.bookmarks.onBookmarksReceived.calls.count());
             expect(_converse.bookmarks.models.length).toBe(2);
             expect(_converse.bookmarks.models.length).toBe(2);
             expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
             expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
             expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
             expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
@@ -512,7 +512,7 @@
                 test_utils.openControlBox();
                 test_utils.openControlBox();
 
 
                 let IQ_id;
                 let IQ_id;
-                const call = await test_utils.waitUntil(() =>
+                const call = await u.waitUntil(() =>
                     _.filter(
                     _.filter(
                         _converse.connection.send.calls.all(),
                         _converse.connection.send.calls.all(),
                         call => {
                         call => {
@@ -566,7 +566,7 @@
                                     }).c('nick').t('JC').up().up();
                                     }).c('nick').t('JC').up().up();
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-                await test_utils.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length);
+                await u.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length);
                 expect(document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length).toBe(5);
                 expect(document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length).toBe(5);
                 let els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
                 let els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
                 expect(els[0].textContent).toBe("1st Bookmark");
                 expect(els[0].textContent).toBe("1st Bookmark");
@@ -578,7 +578,7 @@
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
                 document.querySelector('#chatrooms .bookmarks.rooms-list .room-item:nth-child(2) a:nth-child(2)').click();
                 document.querySelector('#chatrooms .bookmarks.rooms-list .room-item:nth-child(2) a:nth-child(2)').click();
                 expect(window.confirm).toHaveBeenCalled();
                 expect(window.confirm).toHaveBeenCalled();
-                await test_utils.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length === 4)
+                await u.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length === 4)
                 els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
                 els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
                 expect(els[0].textContent).toBe("1st Bookmark");
                 expect(els[0].textContent).toBe("1st Bookmark");
                 expect(els[1].textContent).toBe("Bookmark with a very very long name that will be shortened");
                 expect(els[1].textContent).toBe("Bookmark with a very very long name that will be shortened");
@@ -600,7 +600,7 @@
                 );
                 );
 
 
                 let IQ_id;
                 let IQ_id;
-                const call = await test_utils.waitUntil(() =>
+                const call = await u.waitUntil(() =>
                     _.filter(
                     _.filter(
                         _converse.connection.send.calls.all(),
                         _converse.connection.send.calls.all(),
                         call => {
                         call => {
@@ -638,7 +638,7 @@
                 });
                 });
                 const el = _converse.chatboxviews.el
                 const el = _converse.chatboxviews.el
                 const selector = '#chatrooms .bookmarks.rooms-list .room-item';
                 const selector = '#chatrooms .bookmarks.rooms-list .room-item';
-                await test_utils.waitUntil(() => sizzle(selector, el).filter(u.isVisible).length);
+                await u.waitUntil(() => sizzle(selector, el).filter(u.isVisible).length);
                 expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', el).pop())).toBeFalsy();
                 expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', el).pop())).toBeFalsy();
                 expect(sizzle(selector, el).filter(u.isVisible).length).toBe(1);
                 expect(sizzle(selector, el).filter(u.isVisible).length).toBe(1);
                 expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
                 expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
@@ -668,7 +668,7 @@
                 [{'category': 'pubsub', 'type': 'pep'}],
                 [{'category': 'pubsub', 'type': 'pep'}],
                 ['http://jabber.org/protocol/pubsub#publish-options']
                 ['http://jabber.org/protocol/pubsub#publish-options']
             );
             );
-            await test_utils.waitUntil(() => _converse.bookmarks);
+            await u.waitUntil(() => _converse.bookmarks);
             // XXX Create bookmarks view here, otherwise we need to mock stanza
             // XXX Create bookmarks view here, otherwise we need to mock stanza
             // traffic for it to get created.
             // traffic for it to get created.
             _converse.bookmarksview = new _converse.BookmarksView(
             _converse.bookmarksview = new _converse.BookmarksView(
@@ -686,13 +686,13 @@
             expect(_converse.bookmarks.length).toBe(1);
             expect(_converse.bookmarks.length).toBe(1);
 
 
             const bmarks_view = _converse.bookmarksview;
             const bmarks_view = _converse.bookmarksview;
-            await test_utils.waitUntil(() => bmarks_view.el.querySelectorAll(".open-room").length, 500);
+            await u.waitUntil(() => bmarks_view.el.querySelectorAll(".open-room").length, 500);
             const room_els = bmarks_view.el.querySelectorAll(".open-room");
             const room_els = bmarks_view.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
 
 
             const bookmark = _converse.bookmarksview.el.querySelector(".open-room");
             const bookmark = _converse.bookmarksview.el.querySelector(".open-room");
             bookmark.click();
             bookmark.click();
-            await test_utils.waitUntil(() => _converse.chatboxviews.get(jid));
+            await u.waitUntil(() => _converse.chatboxviews.get(jid));
 
 
             expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
             expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
             // Check that it reappears once the room is closed
             // Check that it reappears once the room is closed

+ 68 - 68
spec/chatbox.js

@@ -43,7 +43,7 @@
                         id: (new Date()).getTime()
                         id: (new Date()).getTime()
                     }).c('body').t('hello world').tree();
                     }).c('body').t('hello world').tree();
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
+                await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
                 expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
                 expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
                 done();
                 done();
             }));
             }));
@@ -56,7 +56,7 @@
 
 
                 await test_utils.waitForRoster(_converse, 'current');
                 await test_utils.waitForRoster(_converse, 'current');
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
-                await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
+                await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
                 await test_utils.openControlBox();
                 await test_utils.openControlBox();
                 expect(_converse.chatboxes.length).toEqual(1);
                 expect(_converse.chatboxes.length).toEqual(1);
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -78,7 +78,7 @@
                 message = '/me is as well';
                 message = '/me is as well';
                 await test_utils.sendMessage(view, message);
                 await test_utils.sendMessage(view, message);
                 expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
                 expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
-                await test_utils.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague');
+                await u.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague');
                 const last_el = sizzle('.chat-msg__text:last', view.el).pop();
                 const last_el = sizzle('.chat-msg__text:last', view.el).pop();
                 expect(last_el.textContent).toBe('is as well');
                 expect(last_el.textContent).toBe('is as well');
                 expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
                 expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
@@ -111,19 +111,19 @@
                 spyOn(_converse.chatboxviews, 'trimChats');
                 spyOn(_converse.chatboxviews, 'trimChats');
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
 
 
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
                 const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
                 const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
                 expect(online_contacts.length).toBe(15);
                 expect(online_contacts.length).toBe(15);
                 let el = online_contacts[0];
                 let el = online_contacts[0];
                 const jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 el.click();
                 el.click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 2);
+                await u.waitUntil(() => _converse.chatboxes.length == 2);
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 // Check that new chat boxes are created to the left of the
                 // Check that new chat boxes are created to the left of the
                 // controlbox (but to the right of all existing chat boxes)
                 // controlbox (but to the right of all existing chat boxes)
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(2);
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(2);
                 online_contacts[1].click();
                 online_contacts[1].click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 3);
+                await u.waitUntil(() => _converse.chatboxes.length == 3);
                 el = online_contacts[1];
                 el = online_contacts[1];
                 const new_jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const new_jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
@@ -148,8 +148,8 @@
 
 
                 const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve));
                 const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => _converse.api.chats.get().length === 2);
-                await test_utils.waitUntil(() => message_promise);
+                await u.waitUntil(() => _converse.api.chats.get().length === 2);
+                await u.waitUntil(() => message_promise);
                 expect(_converse.chatboxviews.keys().length).toBe(2);
                 expect(_converse.chatboxviews.keys().length).toBe(2);
                 done();
                 done();
             }));
             }));
@@ -168,7 +168,7 @@
                     </message>`);
                     </message>`);
                 const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve))
                 const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve))
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => message_promise);
+                await u.waitUntil(() => message_promise);
                 expect(_converse.chatboxviews.keys().length).toBe(1);
                 expect(_converse.chatboxviews.keys().length).toBe(1);
                 done();
                 done();
             }));
             }));
@@ -193,7 +193,7 @@
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
                 expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
 
 
                 _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached.
                 _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached.
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
                 // Test that they can be maximized again
                 // Test that they can be maximized again
                 const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
                 const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
                 expect(online_contacts.length).toBe(15);
                 expect(online_contacts.length).toBe(15);
@@ -202,7 +202,7 @@
                     const el = online_contacts[i];
                     const el = online_contacts[i];
                     el.click();
                     el.click();
                 }
                 }
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 16);
+                await u.waitUntil(() => _converse.chatboxes.length == 16);
                 expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16);
                 expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16);
 
 
                 for (i=0; i<online_contacts.length; i++) {
                 for (i=0; i<online_contacts.length; i++) {
@@ -214,7 +214,7 @@
                     expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
                     expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
                     expect(chatboxview.onMinimized).toHaveBeenCalled();
                     expect(chatboxview.onMinimized).toHaveBeenCalled();
                 }
                 }
-                await test_utils.waitUntil(() => _converse.chatboxviews.keys().length);
+                await u.waitUntil(() => _converse.chatboxviews.keys().length);
                 var key = _converse.chatboxviews.keys()[1];
                 var key = _converse.chatboxviews.keys()[1];
                 const trimmedview = trimmed_chatboxes.get(key);
                 const trimmedview = trimmed_chatboxes.get(key);
                 const chatbox = trimmedview.model;
                 const chatbox = trimmedview.model;
@@ -237,7 +237,7 @@
                 await test_utils.waitForRoster(_converse, 'current');
                 await test_utils.waitForRoster(_converse, 'current');
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const chat = await _converse.api.chats.create(sender_jid, {'minimized': true});
                 const chat = await _converse.api.chats.create(sender_jid, {'minimized': true});
-                await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
+                await u.waitUntil(() => _converse.chatboxes.length > 1);
                 const chatBoxView = _converse.chatboxviews.get(sender_jid);
                 const chatBoxView = _converse.chatboxviews.get(sender_jid);
                 expect(u.isVisible(chatBoxView.el)).toBeFalsy();
                 expect(u.isVisible(chatBoxView.el)).toBeFalsy();
 
 
@@ -265,7 +265,7 @@
                 const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 spyOn(_converse.api, "trigger");
                 spyOn(_converse.api, "trigger");
                 el.click();
                 el.click();
-                await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500);
+                await u.waitUntil(() => _converse.api.trigger.calls.count(), 500);
                 expect(_converse.chatboxes.length).toEqual(2);
                 expect(_converse.chatboxes.length).toEqual(2);
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
                 done();
                 done();
@@ -284,7 +284,7 @@
                 test_utils.openControlBox();
                 test_utils.openControlBox();
 
 
                 test_utils.openChatBoxes(_converse, 6);
                 test_utils.openChatBoxes(_converse, 6);
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 7);
+                await u.waitUntil(() => _converse.chatboxes.length == 7);
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 // We instantiate a new ChatBoxes collection, which by default
                 // We instantiate a new ChatBoxes collection, which by default
                 // will be empty.
                 // will be empty.
@@ -316,7 +316,7 @@
                 test_utils.openControlBox();
                 test_utils.openControlBox();
 
 
                 const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                 await test_utils.openChatBoxFor(_converse, contact_jid);
                 await test_utils.openChatBoxFor(_converse, contact_jid);
                 const controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open
                 const controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open
                       chatview = _converse.chatboxviews.get(contact_jid);
                       chatview = _converse.chatboxviews.get(contact_jid);
@@ -351,7 +351,7 @@
                 test_utils.openControlBox();
                 test_utils.openControlBox();
 
 
                 const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                 await test_utils.openChatBoxFor(_converse, contact_jid);
                 await test_utils.openChatBoxFor(_converse, contact_jid);
                 const trimmed_chatboxes = _converse.minimized_chats;
                 const trimmed_chatboxes = _converse.minimized_chats;
                 const chatview = _converse.chatboxviews.get(contact_jid);
                 const chatview = _converse.chatboxviews.get(contact_jid);
@@ -388,7 +388,7 @@
 
 
                 await test_utils.waitForRoster(_converse, 'current');
                 await test_utils.waitForRoster(_converse, 'current');
                 test_utils.openControlBox();
                 test_utils.openControlBox();
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                 spyOn(_converse.api, "trigger");
                 spyOn(_converse.api, "trigger");
                 spyOn(_converse.chatboxviews, 'trimChats');
                 spyOn(_converse.chatboxviews, 'trimChats');
                 _converse.chatboxes.browserStorage._clear();
                 _converse.chatboxes.browserStorage._clear();
@@ -399,7 +399,7 @@
                 expect(_converse.chatboxes.length).toEqual(1);
                 expect(_converse.chatboxes.length).toEqual(1);
                 expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
                 expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
                 test_utils.openChatBoxes(_converse, 6);
                 test_utils.openChatBoxes(_converse, 6);
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 7)
+                await u.waitUntil(() => _converse.chatboxes.length == 7)
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(_converse.chatboxes.length).toEqual(7);
                 expect(_converse.chatboxes.length).toEqual(7);
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxInitialized', jasmine.any(Object));
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxInitialized', jasmine.any(Object));
@@ -461,7 +461,7 @@
                     view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     toolbar.querySelector('li.toggle-smiley').click();
                     toolbar.querySelector('li.toggle-smiley').click();
 
 
-                    await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
+                    await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
                     var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
                     var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
                     var items = picker.querySelectorAll('.emoji-picker li');
                     var items = picker.querySelectorAll('.emoji-picker li');
                     items[0].click()
                     items[0].click()
@@ -488,7 +488,7 @@
                     expect(counter.textContent).toBe('188');
                     expect(counter.textContent).toBe('188');
 
 
                     toolbar.querySelector('li.toggle-smiley').click();
                     toolbar.querySelector('li.toggle-smiley').click();
-                    await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
+                    await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
                     var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
                     var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
                     var items = picker.querySelectorAll('.emoji-picker li');
                     var items = picker.querySelectorAll('.emoji-picker li');
                     items[0].click()
                     items[0].click()
@@ -628,7 +628,7 @@
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         test_utils.openControlBox();
                         test_utils.openControlBox();
-                        test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         spyOn(_converse.connection, 'send');
                         spyOn(_converse.connection, 'send');
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
@@ -651,14 +651,14 @@
                         test_utils.openControlBox();
                         test_utils.openControlBox();
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
                         view.model.minimize();
                         view.model.minimize();
                         expect(view.model.get('chat_state')).toBe('inactive');
                         expect(view.model.get('chat_state')).toBe('inactive');
                         spyOn(_converse.connection, 'send');
                         spyOn(_converse.connection, 'send');
                         view.model.maximize();
                         view.model.maximize();
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
                         expect(_converse.connection.send).toHaveBeenCalled();
                         expect(_converse.connection.send).toHaveBeenCalled();
                         const calls = _.filter(_converse.connection.send.calls.all(), function (call) {
                         const calls = _.filter(_converse.connection.send.calls.all(), function (call) {
                             return call.args[0] instanceof Strophe.Builder;
                             return call.args[0] instanceof Strophe.Builder;
@@ -685,7 +685,7 @@
                         test_utils.openControlBox();
                         test_utils.openControlBox();
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         var view = _converse.chatboxviews.get(contact_jid);
                         var view = _converse.chatboxviews.get(contact_jid);
                         expect(view.model.get('chat_state')).toBe('active');
                         expect(view.model.get('chat_state')).toBe('active');
@@ -726,7 +726,7 @@
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
                         spyOn(_converse.api, "trigger");
                         spyOn(_converse.api, "trigger");
                         const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         await test_utils.openChatBoxFor(_converse, sender_jid);
                         await test_utils.openChatBoxFor(_converse, sender_jid);
 
 
                         // <composing> state
                         // <composing> state
@@ -741,7 +741,7 @@
                         var view = _converse.chatboxviews.get(sender_jid);
                         var view = _converse.chatboxviews.get(sender_jid);
                         expect(view).toBeDefined();
                         expect(view).toBeDefined();
 
 
-                        await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
+                        await u.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
                         // Check that the notification appears inside the chatbox in the DOM
                         // Check that the notification appears inside the chatbox in the DOM
                         let events = view.el.querySelectorAll('.chat-state-notification');
                         let events = view.el.querySelectorAll('.chat-state-notification');
                         expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
                         expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
@@ -767,7 +767,7 @@
 
 
                         let contact, sent_stanza, IQ_id, stanza;
                         let contact, sent_stanza, IQ_id, stanza;
                         await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
                         await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
-                        await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
+                        await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         // Send a message from a different resource
                         // Send a message from a different resource
                         spyOn(_converse, 'log');
                         spyOn(_converse, 'log');
@@ -788,7 +788,7 @@
                                     'type': 'chat'
                                     'type': 'chat'
                             }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                             }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
-                        await test_utils.waitUntil(() => view.model.messages.length);
+                        await u.waitUntil(() => view.model.messages.length);
                         // Check that the chatbox and its view now exist
                         // Check that the chatbox and its view now exist
                         const chatbox = _converse.chatboxes.get(recipient_jid);
                         const chatbox = _converse.chatboxes.get(recipient_jid);
                         const chatboxview = _converse.chatboxviews.get(recipient_jid);
                         const chatboxview = _converse.chatboxviews.get(recipient_jid);
@@ -814,7 +814,7 @@
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         test_utils.openControlBox();
                         test_utils.openControlBox();
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
                         _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
                         _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
@@ -829,7 +829,7 @@
                         expect(_converse.connection.send).toHaveBeenCalled();
                         expect(_converse.connection.send).toHaveBeenCalled();
                         let stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
                         let stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
                         expect(stanza.childNodes[0].tagName).toBe('composing');
                         expect(stanza.childNodes[0].tagName).toBe('composing');
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'paused', 500);
                         expect(_converse.connection.send).toHaveBeenCalled();
                         expect(_converse.connection.send).toHaveBeenCalled();
                         var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
                         var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
                             return call.args[0] instanceof Strophe.Builder;
                             return call.args[0] instanceof Strophe.Builder;
@@ -867,7 +867,7 @@
 
 
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         test_utils.openControlBox();
                         test_utils.openControlBox();
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         // TODO: only show paused state if the previous state was composing
                         // TODO: only show paused state if the previous state was composing
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
                         spyOn(_converse.api, "trigger").and.callThrough();
                         spyOn(_converse.api, "trigger").and.callThrough();
@@ -882,7 +882,7 @@
                             }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                             }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
-                        await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
+                        await u.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
                         var event = view.el.querySelector('.chat-info.chat-state-notification');
                         var event = view.el.querySelector('.chat-info.chat-state-notification');
                         expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing');
                         expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing');
                         done();
                         done();
@@ -895,7 +895,7 @@
 
 
                         let contact, sent_stanza, IQ_id, stanza;
                         let contact, sent_stanza, IQ_id, stanza;
                         await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
                         await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
-                        await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
+                        await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         // Send a message from a different resource
                         // Send a message from a different resource
                         spyOn(_converse, 'log');
                         spyOn(_converse, 'log');
@@ -916,7 +916,7 @@
                                     'type': 'chat'
                                     'type': 'chat'
                             }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                             }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
-                        await test_utils.waitUntil(() => view.model.messages.length);
+                        await u.waitUntil(() => view.model.messages.length);
                         // Check that the chatbox and its view now exist
                         // Check that the chatbox and its view now exist
                         const chatbox = _converse.chatboxes.get(recipient_jid);
                         const chatbox = _converse.chatboxes.get(recipient_jid);
                         const chatboxview = _converse.chatboxviews.get(recipient_jid);
                         const chatboxview = _converse.chatboxviews.get(recipient_jid);
@@ -946,22 +946,22 @@
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         test_utils.openControlBox();
                         test_utils.openControlBox();
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         await test_utils.openChatBoxFor(_converse, contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
                         const view = _converse.chatboxviews.get(contact_jid);
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
                         console.log('chat_state set to active');
                         console.log('chat_state set to active');
                         expect(view.model.get('chat_state')).toBe('active');
                         expect(view.model.get('chat_state')).toBe('active');
                         view.onKeyDown({
                         view.onKeyDown({
                             target: view.el.querySelector('textarea.chat-textarea'),
                             target: view.el.querySelector('textarea.chat-textarea'),
                             keyCode: 1
                             keyCode: 1
                         });
                         });
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'composing', 500);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'composing', 500);
                         console.log('chat_state set to composing');
                         console.log('chat_state set to composing');
                         expect(view.model.get('chat_state')).toBe('composing');
                         expect(view.model.get('chat_state')).toBe('composing');
                         spyOn(_converse.connection, 'send');
                         spyOn(_converse.connection, 'send');
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 1000);
-                        await test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 1000);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'paused', 1000);
+                        await u.waitUntil(() => view.model.get('chat_state') === 'inactive', 1000);
                         console.log('chat_state set to inactive');
                         console.log('chat_state set to inactive');
                         expect(_converse.connection.send).toHaveBeenCalled();
                         expect(_converse.connection.send).toHaveBeenCalled();
                         var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
                         var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
@@ -1013,7 +1013,7 @@
                         await test_utils.waitForRoster(_converse, 'current');
                         await test_utils.waitForRoster(_converse, 'current');
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         test_utils.openControlBox();
                         test_utils.openControlBox();
-                        await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                        await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         const view = await test_utils.openChatBoxFor(_converse, contact_jid);
                         const view = await test_utils.openChatBoxFor(_converse, contact_jid);
                         expect(view.model.get('chat_state')).toBe('active');
                         expect(view.model.get('chat_state')).toBe('active');
                         spyOn(_converse.connection, 'send');
                         spyOn(_converse.connection, 'send');
@@ -1054,7 +1054,7 @@
                             .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
                             .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
                             .tree();
                             .tree();
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
-                        await test_utils.waitUntil(() => view.model.messages.length);
+                        await u.waitUntil(() => view.model.messages.length);
                         expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1);
                         expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1);
                         msg = $msg({
                         msg = $msg({
                                 from: sender_jid,
                                 from: sender_jid,
@@ -1063,7 +1063,7 @@
                                 id: (new Date()).getTime()
                                 id: (new Date()).getTime()
                             }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                             }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
-                        await test_utils.waitUntil(() => (view.model.messages.length > 1));
+                        await u.waitUntil(() => (view.model.messages.length > 1));
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                         expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(0);
                         expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(0);
                         done();
                         done();
@@ -1092,7 +1092,7 @@
                         await _converse.chatboxes.onMessage(msg);
                         await _converse.chatboxes.onMessage(msg);
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                         const view = _converse.chatboxviews.get(sender_jid);
                         const view = _converse.chatboxviews.get(sender_jid);
-                        await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]);
+                        await u.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]);
                         const event = view.el.querySelector('.chat-state-notification');
                         const event = view.el.querySelector('.chat-state-notification');
                         expect(event.textContent).toEqual(mock.cur_names[1] + ' has gone away');
                         expect(event.textContent).toEqual(mock.cur_names[1] + ' has gone away');
                         done();
                         done();
@@ -1120,7 +1120,7 @@
 
 
                 expect(view.model.messages.length > 0).toBeTruthy();
                 expect(view.model.messages.length > 0).toBeTruthy();
                 expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
                 expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
-                await test_utils.waitUntil(() => view.el.querySelector('.chat-msg'));
+                await u.waitUntil(() => view.el.querySelector('.chat-msg'));
 
 
                 message = '/clear';
                 message = '/clear';
                 spyOn(view, 'clearMessages').and.callThrough();
                 spyOn(view, 'clearMessages').and.callThrough();
@@ -1250,7 +1250,7 @@
                 // leave converse-chat page
                 // leave converse-chat page
                 _converse.windowState = 'hidden';
                 _converse.windowState = 'hidden';
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => _converse.api.chats.get().length === 2)
+                await u.waitUntil(() => _converse.api.chats.get().length === 2)
                 let view = _converse.chatboxviews.get(sender_jid);
                 let view = _converse.chatboxviews.get(sender_jid);
                 expect(_converse.msg_counter).toBe(1);
                 expect(_converse.msg_counter).toBe(1);
 
 
@@ -1265,7 +1265,7 @@
 
 
                 // check that msg_counter is incremented from zero again
                 // check that msg_counter is incremented from zero again
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => _converse.api.chats.get().length === 2)
+                await u.waitUntil(() => _converse.api.chats.get().length === 2)
                 view = _converse.chatboxviews.get(sender_jid);
                 view = _converse.chatboxviews.get(sender_jid);
                 expect(u.isVisible(view.el)).toBeTruthy();
                 expect(u.isVisible(view.el)).toBeTruthy();
                 expect(_converse.msg_counter).toBe(1);
                 expect(_converse.msg_counter).toBe(1);
@@ -1287,7 +1287,7 @@
                 const view = await test_utils.openChatBoxFor(_converse, sender_jid)
                 const view = await test_utils.openChatBoxFor(_converse, sender_jid)
                 view.model.save('scrolled', true);
                 view.model.save('scrolled', true);
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 expect(view.model.get('num_unread')).toBe(1);
                 expect(view.model.get('num_unread')).toBe(1);
                 done();
                 done();
             }));
             }));
@@ -1323,7 +1323,7 @@
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 _converse.windowState = 'hidden';
                 _converse.windowState = 'hidden';
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 expect(chatbox.get('num_unread')).toBe(1);
                 expect(chatbox.get('num_unread')).toBe(1);
                 done();
                 done();
             }));
             }));
@@ -1341,7 +1341,7 @@
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 _converse.windowState = 'hidden';
                 _converse.windowState = 'hidden';
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 expect(chatbox.get('num_unread')).toBe(1);
                 expect(chatbox.get('num_unread')).toBe(1);
                 done();
                 done();
             }));
             }));
@@ -1358,7 +1358,7 @@
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 _converse.windowState = 'hidden';
                 _converse.windowState = 'hidden';
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 expect(chatbox.get('num_unread')).toBe(1);
                 expect(chatbox.get('num_unread')).toBe(1);
                 _converse.saveWindowState(null, 'focus');
                 _converse.saveWindowState(null, 'focus');
                 expect(chatbox.get('num_unread')).toBe(0);
                 expect(chatbox.get('num_unread')).toBe(0);
@@ -1378,7 +1378,7 @@
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 _converse.windowState = 'hidden';
                 _converse.windowState = 'hidden';
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 expect(chatbox.get('num_unread')).toBe(1);
                 expect(chatbox.get('num_unread')).toBe(1);
                 _converse.saveWindowState(null, 'focus');
                 _converse.saveWindowState(null, 'focus');
                 expect(chatbox.get('num_unread')).toBe(1);
                 expect(chatbox.get('num_unread')).toBe(1);
@@ -1396,19 +1396,19 @@
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 let msg, indicator_el;
                 let msg, indicator_el;
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
                 const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 expect(indicator_el.textContent).toBe('1');
                 expect(indicator_el.textContent).toBe('1');
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => chatbox.messages.length > 1);
+                await u.waitUntil(() => chatbox.messages.length > 1);
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 expect(indicator_el.textContent).toBe('2');
                 expect(indicator_el.textContent).toBe('2');
                 done();
                 done();
@@ -1423,7 +1423,7 @@
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
                 let indicator_el, msg;
                 let indicator_el, msg;
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 var chatboxview = _converse.chatboxviews.get(sender_jid);
                 var chatboxview = _converse.chatboxviews.get(sender_jid);
@@ -1431,14 +1431,14 @@
 
 
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
                 const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 expect(indicator_el.textContent).toBe('1');
                 expect(indicator_el.textContent).toBe('1');
 
 
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
                 msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => chatbox.messages.length === 2);
+                await u.waitUntil(() => chatbox.messages.length === 2);
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 indicator_el = sizzle(selector, _converse.rosterview.el).pop();
                 expect(indicator_el.textContent).toBe('2');
                 expect(indicator_el.textContent).toBe('2');
                 done();
                 done();
@@ -1452,7 +1452,7 @@
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
                 const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
@@ -1460,10 +1460,10 @@
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 view.minimize();
                 view.minimize();
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 expect(select_msgs_indicator().textContent).toBe('1');
                 expect(select_msgs_indicator().textContent).toBe('1');
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length > 1);
+                await u.waitUntil(() => chatbox.messages.length > 1);
                 expect(select_msgs_indicator().textContent).toBe('2');
                 expect(select_msgs_indicator().textContent).toBe('2');
                 view.model.maximize();
                 view.model.maximize();
                 expect(select_msgs_indicator()).toBeUndefined();
                 expect(select_msgs_indicator()).toBeUndefined();
@@ -1477,7 +1477,7 @@
 
 
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
@@ -1486,7 +1486,7 @@
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 expect(select_msgs_indicator().textContent).toBe('1');
                 expect(select_msgs_indicator().textContent).toBe('1');
                 view.viewUnreadMessages();
                 view.viewUnreadMessages();
                 _converse.rosterview.render();
                 _converse.rosterview.render();
@@ -1501,7 +1501,7 @@
 
 
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 await test_utils.waitForRoster(_converse, 'current', 1);
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
@@ -1511,7 +1511,7 @@
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 expect(select_msgs_indicator().textContent).toBe('1');
                 expect(select_msgs_indicator().textContent).toBe('1');
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 expect(select_msgs_indicator().textContent).toBe('1');
                 expect(select_msgs_indicator().textContent).toBe('1');
@@ -1539,7 +1539,7 @@
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 chatbox.save('scrolled', true);
                 chatbox.save('scrolled', true);
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => chatbox.messages.length);
+                await u.waitUntil(() => chatbox.messages.length);
                 const chatboxview = _converse.chatboxviews.get(sender_jid);
                 const chatboxview = _converse.chatboxviews.get(sender_jid);
                 chatboxview.minimize();
                 chatboxview.minimize();
 
 
@@ -1567,7 +1567,7 @@
                 };
                 };
                 view.minimize();
                 view.minimize();
                 _converse.chatboxes.onMessage(msgFactory());
                 _converse.chatboxes.onMessage(msgFactory());
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 const unread_count = selectUnreadMsgCount();
                 const unread_count = selectUnreadMsgCount();
                 expect(u.isVisible(unread_count)).toBeTruthy();
                 expect(u.isVisible(unread_count)).toBeTruthy();
                 expect(unread_count.innerHTML).toBe('1');
                 expect(unread_count.innerHTML).toBe('1');
@@ -1589,7 +1589,7 @@
                 const view = _converse.chatboxviews.get(contact_jid);
                 const view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view.model, 'sendMessage').and.callThrough();
                 spyOn(view.model, 'sendMessage').and.callThrough();
                 test_utils.sendMessage(view, message);
                 test_utils.sendMessage(view, message);
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length, 1000);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length, 1000);
                 expect(view.model.sendMessage).toHaveBeenCalled();
                 expect(view.model.sendMessage).toHaveBeenCalled();
                 const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
                 const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
                 expect(msg.innerHTML).toEqual(
                 expect(msg.innerHTML).toEqual(

+ 12 - 12
spec/controlbox.js

@@ -59,7 +59,7 @@
                     ask: 'subscribe',
                     ask: 'subscribe',
                     fullname: mock.pend_names[0]
                     fullname: mock.pend_names[0]
                 });
                 });
-                await test_utils.waitUntil(() => _.filter(_converse.rosterview.el.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
+                await u.waitUntil(() => _.filter(_converse.rosterview.el.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
                 // Checking that only one entry is created because both JID is same (Case sensitive check)
                 // Checking that only one entry is created because both JID is same (Case sensitive check)
                 expect(_.filter(_converse.rosterview.el.querySelectorAll('li'), u.isVisible).length).toBe(1);
                 expect(_.filter(_converse.rosterview.el.querySelectorAll('li'), u.isVisible).length).toBe(1);
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.update).toHaveBeenCalled();
@@ -76,7 +76,7 @@
 
 
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 await test_utils.openChatBoxFor(_converse, sender_jid);
                 await test_utils.openChatBoxFor(_converse, sender_jid);
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
+                await u.waitUntil(() => _converse.chatboxes.length);
                 const chatview = _converse.chatboxviews.get(sender_jid);
                 const chatview = _converse.chatboxviews.get(sender_jid);
                 chatview.model.set({'minimized': true});
                 chatview.model.set({'minimized': true});
 
 
@@ -91,7 +91,7 @@
                     }).c('body').t('hello').up()
                     }).c('body').t('hello').up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                 _converse.chatboxes.onMessage(msg);
                 _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator").length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator").length);
                 spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
                 spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
                 expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
                 expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
                 expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
                 expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
@@ -104,7 +104,7 @@
                     }).c('body').t('hello again').up()
                     }).c('body').t('hello again').up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                 _converse.chatboxes.onMessage(msg);
                 _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
+                await u.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
                 expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2');
                 expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2');
                 expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
                 expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
                 chatview.model.set({'minimized': false});
                 chatview.model.set({'minimized': false});
@@ -139,7 +139,7 @@
                 cbview.el.querySelector('.change-status').click()
                 cbview.el.querySelector('.change-status').click()
                 var modal = _converse.xmppstatusview.status_modal;
                 var modal = _converse.xmppstatusview.status_modal;
 
 
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 const view = _converse.xmppstatusview;
                 const view = _converse.xmppstatusview;
                 modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
                 modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
                 modal.el.querySelector('[type="submit"]').click();
                 modal.el.querySelector('[type="submit"]').click();
@@ -168,7 +168,7 @@
                 cbview.el.querySelector('.change-status').click()
                 cbview.el.querySelector('.change-status').click()
                 const modal = _converse.xmppstatusview.status_modal;
                 const modal = _converse.xmppstatusview.status_modal;
 
 
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 const view = _converse.xmppstatusview;
                 const view = _converse.xmppstatusview;
                 const msg = 'I am happy';
                 const msg = 'I am happy';
                 modal.el.querySelector('input[name="status_message"]').value = msg;
                 modal.el.querySelector('input[name="status_message"]').value = msg;
@@ -202,7 +202,7 @@
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.add-contact').click()
             cbview.el.querySelector('.add-contact').click()
             const modal = _converse.rosterview.add_contact_modal;
             const modal = _converse.rosterview.add_contact_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             const sendIQ = _converse.connection.sendIQ;
             const sendIQ = _converse.connection.sendIQ;
             let sent_stanza, IQ_id;
             let sent_stanza, IQ_id;
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -240,7 +240,7 @@
             expect(modal.jid_auto_complete).toBe(undefined);
             expect(modal.jid_auto_complete).toBe(undefined);
             expect(modal.name_auto_complete).toBe(undefined);
             expect(modal.name_auto_complete).toBe(undefined);
 
 
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             expect(!_.isNull(modal.el.querySelector('form.add-xmpp-contact'))).toBeTruthy();
             expect(!_.isNull(modal.el.querySelector('form.add-xmpp-contact'))).toBeTruthy();
             const input_jid = modal.el.querySelector('input[name="jid"]');
             const input_jid = modal.el.querySelector('input[name="jid"]');
             const input_name = modal.el.querySelector('input[name="name"]');
             const input_name = modal.el.querySelector('input[name="name"]');
@@ -248,7 +248,7 @@
             modal.el.querySelector('button[type="submit"]').click();
             modal.el.querySelector('button[type="submit"]').click();
 
 
             const IQ_stanzas = _converse.connection.IQ_stanzas;
             const IQ_stanzas = _converse.connection.IQ_stanzas;
-            const sent_stanza = await test_utils.waitUntil(
+            const sent_stanza = await u.waitUntil(
                 () => IQ_stanzas.filter(s => sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
                 () => IQ_stanzas.filter(s => sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
             );
             );
             expect(Strophe.serialize(sent_stanza)).toEqual(
             expect(Strophe.serialize(sent_stanza)).toEqual(
@@ -284,7 +284,7 @@
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.add-contact').click()
             cbview.el.querySelector('.add-contact').click()
             const modal = _converse.rosterview.add_contact_modal;
             const modal = _converse.rosterview.add_contact_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
 
 
             // We only have autocomplete for the name input
             // We only have autocomplete for the name input
             expect(modal.jid_auto_complete).toBe(undefined);
             expect(modal.jid_auto_complete).toBe(undefined);
@@ -293,7 +293,7 @@
             const input_el = modal.el.querySelector('input[name="name"]');
             const input_el = modal.el.querySelector('input[name="name"]');
             input_el.value = 'marty';
             input_el.value = 'marty';
             input_el.dispatchEvent(new Event('input'));
             input_el.dispatchEvent(new Event('input'));
-            await test_utils.waitUntil(() => modal.el.querySelector('.suggestion-box li'), 1000);
+            await u.waitUntil(() => modal.el.querySelector('.suggestion-box li'), 1000);
             const sendIQ = _converse.connection.sendIQ;
             const sendIQ = _converse.connection.sendIQ;
             let sent_stanza, IQ_id;
             let sent_stanza, IQ_id;
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -357,7 +357,7 @@
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.add-contact').click()
             cbview.el.querySelector('.add-contact').click()
             modal = _converse.rosterview.add_contact_modal;
             modal = _converse.rosterview.add_contact_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
 
 
             expect(modal.jid_auto_complete).toBe(undefined);
             expect(modal.jid_auto_complete).toBe(undefined);
             expect(modal.name_auto_complete).toBe(undefined);
             expect(modal.name_auto_complete).toBe(undefined);

+ 3 - 3
spec/converse.js

@@ -271,10 +271,10 @@
                 expect(box.get('box_id')).toBe(`box-${btoa(jid)}`);
                 expect(box.get('box_id')).toBe(`box-${btoa(jid)}`);
 
 
                 const view = _converse.chatboxviews.get(jid);
                 const view = _converse.chatboxviews.get(jid);
-                await test_utils.waitUntil(() => u.isVisible(view.el));
+                await u.waitUntil(() => u.isVisible(view.el));
                 // Test for multiple JIDs
                 // Test for multiple JIDs
                 test_utils.openChatBoxFor(_converse, jid2);
                 test_utils.openChatBoxFor(_converse, jid2);
-                await test_utils.waitUntil(() => _converse.chatboxes.length == 2);
+                await u.waitUntil(() => _converse.chatboxes.length == 2);
                 const list = _converse.api.chats.get([jid, jid2]);
                 const list = _converse.api.chats.get([jid, jid2]);
                 expect(Array.isArray(list)).toBeTruthy();
                 expect(Array.isArray(list)).toBeTruthy();
                 expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
                 expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
@@ -303,7 +303,7 @@
                     ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
                     ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
                 );
                 );
                 const view = _converse.chatboxviews.get(jid);
                 const view = _converse.chatboxviews.get(jid);
-                await test_utils.waitUntil(() => u.isVisible(view.el));
+                await u.waitUntil(() => u.isVisible(view.el));
                 // Test for multiple JIDs
                 // Test for multiple JIDs
                 const list = await _converse.api.chats.open([jid, jid2]);
                 const list = await _converse.api.chats.open([jid, jid2]);
                 expect(Array.isArray(list)).toBeTruthy();
                 expect(Array.isArray(list)).toBeTruthy();

+ 147 - 151
spec/disco.js

@@ -8,6 +8,7 @@
     const Strophe = converse.env.Strophe;
     const Strophe = converse.env.Strophe;
     const $iq = converse.env.$iq;
     const $iq = converse.env.$iq;
     const _ = converse.env._;
     const _ = converse.env._;
+    const u = converse.env.utils;
 
 
     describe("Service Discovery", function () {
     describe("Service Discovery", function () {
 
 
@@ -16,167 +17,162 @@
             it("stores the features it receives",
             it("stores the features it receives",
                 mock.initConverse(
                 mock.initConverse(
                     null, ['discoInitialized'], {},
                     null, ['discoInitialized'], {},
-                    function (done, _converse) {
+                    async function (done, _converse) {
 
 
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const IQ_ids =  _converse.connection.IQ_ids;
                 const IQ_ids =  _converse.connection.IQ_ids;
-                test_utils.waitUntil(function () {
+                await u.waitUntil(function () {
                     return _.filter(IQ_stanzas, function (iq) {
                     return _.filter(IQ_stanzas, function (iq) {
                         return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
                         return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
                     }).length > 0;
                     }).length > 0;
-                }, 300).then(function () {
-                    /* <iq type='result'
-                     *      from='plays.shakespeare.lit'
-                     *      to='romeo@montague.net/orchard'
-                     *      id='info1'>
-                     *  <query xmlns='http://jabber.org/protocol/disco#info'>
-                     *      <identity
-                     *          category='server'
-                     *          type='im'/>
-                     *      <identity
-                     *          category='conference'
-                     *          type='text'
-                     *          name='Play-Specific Chatrooms'/>
-                     *      <identity
-                     *          category='directory'
-                     *          type='chatroom'
-                     *          name='Play-Specific Chatrooms'/>
-                     *      <feature var='http://jabber.org/protocol/disco#info'/>
-                     *      <feature var='http://jabber.org/protocol/disco#items'/>
-                     *      <feature var='http://jabber.org/protocol/muc'/>
-                     *      <feature var='jabber:iq:register'/>
-                     *      <feature var='jabber:iq:search'/>
-                     *      <feature var='jabber:iq:time'/>
-                     *      <feature var='jabber:iq:version'/>
-                     *  </query>
-                     *  </iq>
-                     */
-                    var stanza = _.find(IQ_stanzas, function (iq) {
-                        return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
-                    });
-                    var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
-                    stanza = $iq({
-                        'type': 'result',
-                        'from': 'montague.lit',
-                        'to': 'romeo@montague.lit/orchard',
-                        'id': info_IQ_id
-                    }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
-                        .c('identity', {
-                            'category': 'server',
-                            'type': 'im'}).up()
-                        .c('identity', {
-                            'category': 'conference',
-                            'type': 'text',
-                            'name': 'Play-Specific Chatrooms'}).up()
-                        .c('identity', {
-                            'category': 'directory',
-                            'type': 'chatroom',
-                            'name': 'Play-Specific Chatrooms'}).up()
-                        .c('feature', {
-                            'var': 'http://jabber.org/protocol/disco#info'}).up()
-                        .c('feature', {
-                            'var': 'http://jabber.org/protocol/disco#items'}).up()
-                        .c('feature', {
-                            'var': 'jabber:iq:register'}).up()
-                        .c('feature', {
-                            'var': 'jabber:iq:time'}).up()
-                        .c('feature', {
-                            'var': 'jabber:iq:version'});
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-
-                    _converse.api.disco.entities.get().then(function (entities) {
-                        expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
-                        expect(entities.get(_converse.domain).features.length).toBe(5);
-                        expect(entities.get(_converse.domain).identities.length).toBe(3);
-                        expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
-                        expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
-                        expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
-                        expect(entities.get('montague.lit').features.where(
-                            {'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
-                        expect(entities.get('montague.lit').features.where(
-                            {'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
+                });
+                /* <iq type='result'
+                 *      from='plays.shakespeare.lit'
+                 *      to='romeo@montague.net/orchard'
+                 *      id='info1'>
+                 *  <query xmlns='http://jabber.org/protocol/disco#info'>
+                 *      <identity
+                 *          category='server'
+                 *          type='im'/>
+                 *      <identity
+                 *          category='conference'
+                 *          type='text'
+                 *          name='Play-Specific Chatrooms'/>
+                 *      <identity
+                 *          category='directory'
+                 *          type='chatroom'
+                 *          name='Play-Specific Chatrooms'/>
+                 *      <feature var='http://jabber.org/protocol/disco#info'/>
+                 *      <feature var='http://jabber.org/protocol/disco#items'/>
+                 *      <feature var='http://jabber.org/protocol/muc'/>
+                 *      <feature var='jabber:iq:register'/>
+                 *      <feature var='jabber:iq:search'/>
+                 *      <feature var='jabber:iq:time'/>
+                 *      <feature var='jabber:iq:version'/>
+                 *  </query>
+                 *  </iq>
+                 */
+                let stanza = _.find(IQ_stanzas, function (iq) {
+                    return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                });
+                const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
+                stanza = $iq({
+                    'type': 'result',
+                    'from': 'montague.lit',
+                    'to': 'romeo@montague.lit/orchard',
+                    'id': info_IQ_id
+                }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
+                    .c('identity', {
+                        'category': 'server',
+                        'type': 'im'}).up()
+                    .c('identity', {
+                        'category': 'conference',
+                        'type': 'text',
+                        'name': 'Play-Specific Chatrooms'}).up()
+                    .c('identity', {
+                        'category': 'directory',
+                        'type': 'chatroom',
+                        'name': 'Play-Specific Chatrooms'}).up()
+                    .c('feature', {
+                        'var': 'http://jabber.org/protocol/disco#info'}).up()
+                    .c('feature', {
+                        'var': 'http://jabber.org/protocol/disco#items'}).up()
+                    .c('feature', {
+                        'var': 'jabber:iq:register'}).up()
+                    .c('feature', {
+                        'var': 'jabber:iq:time'}).up()
+                    .c('feature', {
+                        'var': 'jabber:iq:version'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
+                let entities = await _converse.api.disco.entities.get()
+                expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
+                expect(entities.get(_converse.domain).features.length).toBe(5);
+                expect(entities.get(_converse.domain).identities.length).toBe(3);
+                expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
+                expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
+                expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
+                expect(entities.get('montague.lit').features.where(
+                    {'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
+                expect(entities.get('montague.lit').features.where(
+                    {'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
 
 
-                        test_utils.waitUntil(function () {
-                            // Converse.js sees that the entity has a disco#items feature,
-                            // so it will make a query for it.
-                            return _.filter(IQ_stanzas, function (iq) {
-                                return iq.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]');
-                            }).length > 0;
-                        }, 300).then(function () {
-                            /* <iq type='result'
-                             *     from='catalog.shakespeare.lit'
-                             *     to='romeo@montague.net/orchard'
-                             *     id='items2'>
-                             * <query xmlns='http://jabber.org/protocol/disco#items'>
-                             *     <item jid='people.shakespeare.lit'
-                             *         name='Directory of Characters'/>
-                             *     <item jid='plays.shakespeare.lit'
-                             *         name='Play-Specific Chatrooms'/>
-                             *     <item jid='mim.shakespeare.lit'
-                             *         name='Gateway to Marlowe IM'/>
-                             *     <item jid='words.shakespeare.lit'
-                             *         name='Shakespearean Lexicon'/>
-                             *
-                             *     <item jid='catalog.shakespeare.lit'
-                             *         node='books'
-                             *         name='Books by and about Shakespeare'/>
-                             *     <item jid='catalog.shakespeare.lit'
-                             *         node='clothing'
-                             *         name='Wear your literary taste with pride'/>
-                             *     <item jid='catalog.shakespeare.lit'
-                             *         node='music'
-                             *         name='Music from the time of Shakespeare'/>
-                             * </query>
-                             * </iq>
-                             */
-                            var stanza = _.find(IQ_stanzas, function (iq) {
-                                return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]');
-                            });
-                            var items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
-                            stanza = $iq({
-                                'type': 'result',
-                                'from': 'montague.lit',
-                                'to': 'romeo@montague.lit/orchard',
-                                'id': items_IQ_id
-                            }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
-                                .c('item', {
-                                    'jid': 'people.shakespeare.lit',
-                                    'name': 'Directory of Characters'}).up()
-                                .c('item', {
-                                    'jid': 'plays.shakespeare.lit',
-                                    'name': 'Play-Specific Chatrooms'}).up()
-                                .c('item', {
-                                    'jid': 'words.shakespeare.lit',
-                                    'name': 'Gateway to Marlowe IM'}).up()
+                await u.waitUntil(function () {
+                    // Converse.js sees that the entity has a disco#items feature,
+                    // so it will make a query for it.
+                    return _.filter(IQ_stanzas, function (iq) {
+                        return iq.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]');
+                    }).length > 0;
+                });
+                /* <iq type='result'
+                 *     from='catalog.shakespeare.lit'
+                 *     to='romeo@montague.net/orchard'
+                 *     id='items2'>
+                 * <query xmlns='http://jabber.org/protocol/disco#items'>
+                 *     <item jid='people.shakespeare.lit'
+                 *         name='Directory of Characters'/>
+                 *     <item jid='plays.shakespeare.lit'
+                 *         name='Play-Specific Chatrooms'/>
+                 *     <item jid='mim.shakespeare.lit'
+                 *         name='Gateway to Marlowe IM'/>
+                 *     <item jid='words.shakespeare.lit'
+                 *         name='Shakespearean Lexicon'/>
+                 *
+                 *     <item jid='catalog.shakespeare.lit'
+                 *         node='books'
+                 *         name='Books by and about Shakespeare'/>
+                 *     <item jid='catalog.shakespeare.lit'
+                 *         node='clothing'
+                 *         name='Wear your literary taste with pride'/>
+                 *     <item jid='catalog.shakespeare.lit'
+                 *         node='music'
+                 *         name='Music from the time of Shakespeare'/>
+                 * </query>
+                 * </iq>
+                 */
+                stanza = _.find(IQ_stanzas, function (iq) {
+                    return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]');
+                });
+                var items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
+                stanza = $iq({
+                    'type': 'result',
+                    'from': 'montague.lit',
+                    'to': 'romeo@montague.lit/orchard',
+                    'id': items_IQ_id
+                }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
+                    .c('item', {
+                        'jid': 'people.shakespeare.lit',
+                        'name': 'Directory of Characters'}).up()
+                    .c('item', {
+                        'jid': 'plays.shakespeare.lit',
+                        'name': 'Play-Specific Chatrooms'}).up()
+                    .c('item', {
+                        'jid': 'words.shakespeare.lit',
+                        'name': 'Gateway to Marlowe IM'}).up()
 
 
-                                .c('item', {
-                                    'jid': 'montague.lit',
-                                    'node': 'books',
-                                    'name': 'Books by and about Shakespeare'}).up()
-                                .c('item', {
-                                    'node': 'montague.lit',
-                                    'name': 'Wear your literary taste with pride'}).up()
-                                .c('item', {
-                                    'jid': 'montague.lit',
-                                    'node': 'music',
-                                    'name': 'Music from the time of Shakespeare'
-                                });
-                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                            return test_utils.waitUntil(() => _converse.disco_entities);
-                        }).then(() => {
-                            const entities = _converse.disco_entities;
-                            expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
-                            expect(entities.get(_converse.domain).items.length).toBe(3);
-                            expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'people.shakespeare.lit')).toBeTruthy();
-                            expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'plays.shakespeare.lit')).toBeTruthy();
-                            expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'words.shakespeare.lit')).toBeTruthy();
-                            expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
-                            expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
-                            done();
-                        });
+                    .c('item', {
+                        'jid': 'montague.lit',
+                        'node': 'books',
+                        'name': 'Books by and about Shakespeare'}).up()
+                    .c('item', {
+                        'node': 'montague.lit',
+                        'name': 'Wear your literary taste with pride'}).up()
+                    .c('item', {
+                        'jid': 'montague.lit',
+                        'node': 'music',
+                        'name': 'Music from the time of Shakespeare'
                     });
                     });
-                });
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                await u.waitUntil(() => _converse.disco_entities);
+                entities = _converse.disco_entities;
+                expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
+                expect(entities.get(_converse.domain).items.length).toBe(3);
+                expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'people.shakespeare.lit')).toBeTruthy();
+                expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'plays.shakespeare.lit')).toBeTruthy();
+                expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'words.shakespeare.lit')).toBeTruthy();
+                expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
+                expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
+                done();
             }));
             }));
         });
         });
 
 

+ 24 - 24
spec/http-file-upload.js

@@ -19,7 +19,7 @@
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const IQ_ids =  _converse.connection.IQ_ids;
                 const IQ_ids =  _converse.connection.IQ_ids;
                 await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []);
                 await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []);
-                await test_utils.waitUntil(() => _.filter(
+                await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
                     iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
                 );
                 );
@@ -67,7 +67,7 @@
 
 
                 // Converse.js sees that the entity has a disco#items feature,
                 // Converse.js sees that the entity has a disco#items feature,
                 // so it will make a query for it.
                 // so it will make a query for it.
-                await test_utils.waitUntil(() => _.filter(
+                await u.waitUntil(() => _.filter(
                         IQ_stanzas,
                         IQ_stanzas,
                         iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]')
                         iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]')
                     ).length
                     ).length
@@ -101,7 +101,7 @@
                 _converse.api.disco.entities.get().then(function (entities) {
                 _converse.api.disco.entities.get().then(function (entities) {
                     expect(entities.length).toBe(2);
                     expect(entities.length).toBe(2);
                     expect(entities.get('montague.lit').items.length).toBe(1);
                     expect(entities.get('montague.lit').items.length).toBe(1);
-                    return test_utils.waitUntil(function () {
+                    return u.waitUntil(function () {
                         // Converse.js sees that the entity has a disco#info feature,
                         // Converse.js sees that the entity has a disco#info feature,
                         // so it will make a query for it.
                         // so it will make a query for it.
                         return _.filter(IQ_stanzas, function (iq) {
                         return _.filter(IQ_stanzas, function (iq) {
@@ -110,7 +110,7 @@
                     }, 300);
                     }, 300);
                 });
                 });
 
 
-                stanza = await test_utils.waitUntil(() =>
+                stanza = await u.waitUntil(() =>
                     _.filter(
                     _.filter(
                         IQ_stanzas,
                         IQ_stanzas,
                         iq => iq.querySelector('iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')
                         iq => iq.querySelector('iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')
@@ -223,7 +223,7 @@
                     const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     await test_utils.openChatBoxFor(_converse, contact_jid);
                     await test_utils.openChatBoxFor(_converse, contact_jid);
                     const view = _converse.chatboxviews.get(contact_jid);
                     const view = _converse.chatboxviews.get(contact_jid);
-                    test_utils.waitUntil(() => view.el.querySelector('.upload-file'));
+                    u.waitUntil(() => view.el.querySelector('.upload-file'));
                     expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
                     expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
                     done();
                     done();
                 }));
                 }));
@@ -240,7 +240,7 @@
                     await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items');
                     await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items');
                     await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
                     await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
                     await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                     await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
-                    await test_utils.waitUntil(() => _converse.chatboxviews.get('lounge@montague.lit').el.querySelector('.upload-file'));
+                    await u.waitUntil(() => _converse.chatboxviews.get('lounge@montague.lit').el.querySelector('.upload-file'));
                     const view = _converse.chatboxviews.get('lounge@montague.lit');
                     const view = _converse.chatboxviews.get('lounge@montague.lit');
                     expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
                     expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
                     done();
                     done();
@@ -274,7 +274,7 @@
                         view.model.sendFiles([file]);
                         view.model.sendFiles([file]);
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
 
 
-                        await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
+                        await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
                         const iq = IQ_stanzas.pop();
                         const iq = IQ_stanzas.pop();
                         expect(Strophe.serialize(iq)).toBe(
                         expect(Strophe.serialize(iq)).toBe(
                             `<iq from="romeo@montague.lit/orchard" `+
                             `<iq from="romeo@montague.lit/orchard" `+
@@ -309,10 +309,10 @@
                             const message = view.model.messages.at(0);
                             const message = view.model.messages.at(0);
                             expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                             expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                             message.set('progress', 0.5);
                             message.set('progress', 0.5);
-                            test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
+                            u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
                             .then(() => {
                             .then(() => {
                                 message.set('progress', 1);
                                 message.set('progress', 1);
-                                test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
+                                u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
                             }).then(() => {
                             }).then(() => {
                                 message.save({
                                 message.save({
                                     'upload': _converse.SUCCESS,
                                     'upload': _converse.SUCCESS,
@@ -326,7 +326,7 @@
                         spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
                         spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-                        await test_utils.waitUntil(() => sent_stanza, 1000);
+                        await u.waitUntil(() => sent_stanza, 1000);
                         expect(sent_stanza.toLocaleString()).toBe(
                         expect(sent_stanza.toLocaleString()).toBe(
                             `<message from="romeo@montague.lit/orchard" `+
                             `<message from="romeo@montague.lit/orchard" `+
                                 `id="${sent_stanza.nodeTree.getAttribute("id")}" `+
                                 `id="${sent_stanza.nodeTree.getAttribute("id")}" `+
@@ -341,7 +341,7 @@
                                     `</x>`+
                                     `</x>`+
                                     `<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                                     `<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                             `</message>`);
                             `</message>`);
-                        await test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
+                        await u.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
                         // Check that the image renders
                         // Check that the image renders
                         expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                         expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                             `<!-- src/templates/image.html -->\n`+
                             `<!-- src/templates/image.html -->\n`+
@@ -369,7 +369,7 @@
 
 
                         // Wait until MAM query has been sent out
                         // Wait until MAM query has been sent out
                         const sent_stanzas = _converse.connection.sent_stanzas;
                         const sent_stanzas = _converse.connection.sent_stanzas;
-                        await test_utils.waitUntil(() => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
+                        await u.waitUntil(() => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
 
 
                         const view = _converse.chatboxviews.get('lounge@montague.lit');
                         const view = _converse.chatboxviews.get('lounge@montague.lit');
                         const file = {
                         const file = {
@@ -381,7 +381,7 @@
                         view.model.sendFiles([file]);
                         view.model.sendFiles([file]);
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
 
 
-                        await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
+                        await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
                         const iq = IQ_stanzas.pop();
                         const iq = IQ_stanzas.pop();
                         expect(Strophe.serialize(iq)).toBe(
                         expect(Strophe.serialize(iq)).toBe(
                             `<iq from="romeo@montague.lit/orchard" `+
                             `<iq from="romeo@montague.lit/orchard" `+
@@ -415,10 +415,10 @@
                             const message = view.model.messages.at(0);
                             const message = view.model.messages.at(0);
                             expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                             expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                             message.set('progress', 0.5);
                             message.set('progress', 0.5);
-                            test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
+                            u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
                             .then(() => {
                             .then(() => {
                                 message.set('progress', 1);
                                 message.set('progress', 1);
-                                test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
+                                u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
                             }).then(() => {
                             }).then(() => {
                                 message.save({
                                 message.save({
                                     'upload': _converse.SUCCESS,
                                     'upload': _converse.SUCCESS,
@@ -432,7 +432,7 @@
                         spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
                         spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-                        await test_utils.waitUntil(() => sent_stanza, 1000);
+                        await u.waitUntil(() => sent_stanza, 1000);
                         expect(sent_stanza.toLocaleString()).toBe(
                         expect(sent_stanza.toLocaleString()).toBe(
                             `<message `+
                             `<message `+
                                 `from="romeo@montague.lit/orchard" `+
                                 `from="romeo@montague.lit/orchard" `+
@@ -447,7 +447,7 @@
                                     `</x>`+
                                     `</x>`+
                                     `<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                                     `<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                             `</message>`);
                             `</message>`);
-                        await test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
+                        await u.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
                         // Check that the image renders
                         // Check that the image renders
                         expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                         expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
                             `<!-- src/templates/image.html -->\n`+
                             `<!-- src/templates/image.html -->\n`+
@@ -464,7 +464,7 @@
                         const send_backup = XMLHttpRequest.prototype.send;
                         const send_backup = XMLHttpRequest.prototype.send;
 
 
                         await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []);
                         await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []);
-                        await test_utils.waitUntil(() => _.filter(
+                        await u.waitUntil(() => _.filter(
                             IQ_stanzas,
                             IQ_stanzas,
                             iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
                             iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
                         );
                         );
@@ -497,7 +497,7 @@
                         expect(entities.get(_converse.domain).features.length).toBe(2);
                         expect(entities.get(_converse.domain).features.length).toBe(2);
                         expect(entities.get(_converse.domain).identities.length).toBe(1);
                         expect(entities.get(_converse.domain).identities.length).toBe(1);
 
 
-                        await test_utils.waitUntil(function () {
+                        await u.waitUntil(function () {
                             // Converse.js sees that the entity has a disco#items feature,
                             // Converse.js sees that the entity has a disco#items feature,
                             // so it will make a query for it.
                             // so it will make a query for it.
                             return _.filter(IQ_stanzas, function (iq) {
                             return _.filter(IQ_stanzas, function (iq) {
@@ -525,7 +525,7 @@
 
 
                         expect(entities.length).toBe(2);
                         expect(entities.length).toBe(2);
                         expect(entities.get('montague.lit').items.length).toBe(1);
                         expect(entities.get('montague.lit').items.length).toBe(1);
-                        await test_utils.waitUntil(function () {
+                        await u.waitUntil(function () {
                             // Converse.js sees that the entity has a disco#info feature,
                             // Converse.js sees that the entity has a disco#info feature,
                             // so it will make a query for it.
                             // so it will make a query for it.
                             return _.filter(IQ_stanzas, function (iq) {
                             return _.filter(IQ_stanzas, function (iq) {
@@ -567,7 +567,7 @@
                             'name': "my-juliet.jpg"
                             'name': "my-juliet.jpg"
                         };
                         };
                         view.model.sendFiles([file]);
                         view.model.sendFiles([file]);
-                        await test_utils.waitUntil(() => view.el.querySelectorAll('.message').length)
+                        await u.waitUntil(() => view.el.querySelectorAll('.message').length)
                         const messages = view.el.querySelectorAll('.message.chat-error');
                         const messages = view.el.querySelectorAll('.message.chat-error');
                         expect(messages.length).toBe(1);
                         expect(messages.length).toBe(1);
                         expect(messages[0].textContent).toBe(
                         expect(messages[0].textContent).toBe(
@@ -606,7 +606,7 @@
                     };
                     };
                     view.model.sendFiles([file]);
                     view.model.sendFiles([file]);
                     await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                     await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-                    await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length)
+                    await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length)
                     const iq = IQ_stanzas.pop();
                     const iq = IQ_stanzas.pop();
                     expect(Strophe.serialize(iq)).toBe(
                     expect(Strophe.serialize(iq)).toBe(
                         `<iq from="romeo@montague.lit/orchard" `+
                         `<iq from="romeo@montague.lit/orchard" `+
@@ -640,10 +640,10 @@
                         const message = view.model.messages.at(0);
                         const message = view.model.messages.at(0);
                         expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                         expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
                         message.set('progress', 0.5);
                         message.set('progress', 0.5);
-                        test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
+                        u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
                         .then(() => {
                         .then(() => {
                             message.set('progress', 1);
                             message.set('progress', 1);
-                            test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
+                            u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
                         }).then(() => {
                         }).then(() => {
                             expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
                             expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
                             done();
                             done();

+ 4 - 2
spec/login.js

@@ -2,6 +2,8 @@
     define(["jasmine", "mock", "test-utils"], factory);
     define(["jasmine", "mock", "test-utils"], factory);
 } (this, function (jasmine, mock, test_utils) {
 } (this, function (jasmine, mock, test_utils) {
 
 
+    const u = converse.env.utils;
+
     describe("The Login Form", function () {
     describe("The Login Form", function () {
 
 
         it("contains a checkbox to indicate whether the computer is trusted or not",
         it("contains a checkbox to indicate whether the computer is trusted or not",
@@ -12,7 +14,7 @@
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
             test_utils.openControlBox();
             test_utils.openControlBox();
-            const cbview = await test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'));
+            const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
             const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
             const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
             expect(checkboxes.length).toBe(1);
             expect(checkboxes.length).toBe(1);
 
 
@@ -46,7 +48,7 @@
                   allow_registration: false },
                   allow_registration: false },
                 function (done, _converse) {
                 function (done, _converse) {
 
 
-            test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'))
+            u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
             .then(() => {
             .then(() => {
                 var cbview = _converse.chatboxviews.get('controlbox');
                 var cbview = _converse.chatboxviews.get('controlbox');
                 test_utils.openControlBox();
                 test_utils.openControlBox();

+ 21 - 21
spec/mam.js

@@ -33,7 +33,7 @@
                             <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
                             <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
                         </message>`);
                         </message>`);
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
+                    await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
                     expect(view.model.messages.length).toBe(1);
                     expect(view.model.messages.length).toBe(1);
                     expect(view.model.messages.at(0).get('is_archived')).toBe(false);
                     expect(view.model.messages.at(0).get('is_archived')).toBe(false);
                     expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
                     expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
@@ -54,13 +54,13 @@
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     spyOn(view.model, 'updateMessage').and.callThrough();
                     spyOn(view.model, 'updateMessage').and.callThrough();
                     view.model.onMessage(stanza);
                     view.model.onMessage(stanza);
-                    await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
+                    await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     expect(result instanceof _converse.Message).toBe(true);
                     expect(result instanceof _converse.Message).toBe(true);
                     expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
                     expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
 
 
-                    await test_utils.waitUntil(() => view.model.updateMessage.calls.count());
+                    await u.waitUntil(() => view.model.updateMessage.calls.count());
                     expect(view.model.messages.length).toBe(1);
                     expect(view.model.messages.length).toBe(1);
                     expect(view.model.messages.at(0).get('is_archived')).toBe(true);
                     expect(view.model.messages.at(0).get('is_archived')).toBe(true);
                     expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
                     expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
@@ -80,7 +80,7 @@
                             <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
                             <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
                         </message>`);
                         </message>`);
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
+                    await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
                     // Not sure whether such a race-condition might pose a problem
                     // Not sure whether such a race-condition might pose a problem
                     // in "real-world" situations.
                     // in "real-world" situations.
                     stanza = u.toStanza(
                     stanza = u.toStanza(
@@ -98,7 +98,7 @@
                         </message>`);
                         </message>`);
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     view.model.onMessage(stanza);
                     view.model.onMessage(stanza);
-                    await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
+                    await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     expect(result instanceof _converse.Message).toBe(true);
                     expect(result instanceof _converse.Message).toBe(true);
@@ -128,7 +128,7 @@
                             </result>
                             </result>
                         </message>`);
                         </message>`);
                     view.model.onMessage(stanza);
                     view.model.onMessage(stanza);
-                    await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
+                    await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
                     expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
                     expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
 
 
                     stanza = u.toStanza(
                     stanza = u.toStanza(
@@ -148,7 +148,7 @@
 
 
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
                     view.model.onMessage(stanza);
                     view.model.onMessage(stanza);
-                    await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
+                    await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
                     expect(result instanceof _converse.Message).toBe(true);
                     expect(result instanceof _converse.Message).toBe(true);
@@ -173,7 +173,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 _converse.api.archive.query();
                 _converse.api.archive.query();
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
@@ -193,7 +193,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 _converse.api.archive.query({'with':'juliet@capulet.lit'});
                 _converse.api.archive.query({'with':'juliet@capulet.lit'});
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -221,7 +221,7 @@
                 await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
                 await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
 
 
                 const sent_stanzas = _converse.connection.sent_stanzas;
                 const sent_stanzas = _converse.connection.sent_stanzas;
-                const stanza = await test_utils.waitUntil(
+                const stanza = await u.waitUntil(
                     () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
                     () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
 
 
                 const queryid = stanza.querySelector('query').getAttribute('queryid');
                 const queryid = stanza.querySelector('query').getAttribute('queryid');
@@ -249,7 +249,7 @@
                 await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
                 await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
 
 
                 const sent_stanzas = _converse.connection.sent_stanzas;
                 const sent_stanzas = _converse.connection.sent_stanzas;
-                const sent_stanza = await test_utils.waitUntil(
+                const sent_stanza = await u.waitUntil(
                     () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
                     () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
                 const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
 
 
@@ -327,7 +327,7 @@
                     'start': start,
                     'start': start,
                     'end': end
                     'end': end
                 });
                 });
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -380,7 +380,7 @@
                 }
                 }
                 const start = '2010-06-07T00:00:00Z';
                 const start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query({'start': start});
                 _converse.api.archive.query({'start': start});
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -413,7 +413,7 @@
                 });
                 });
                 const start = '2010-06-07T00:00:00Z';
                 const start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query({'start': start, 'max':10});
                 _converse.api.archive.query({'start': start, 'max':10});
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -453,7 +453,7 @@
                     'after': '09af3-cc343-b409f',
                     'after': '09af3-cc343-b409f',
                     'max':10
                     'max':10
                 });
                 });
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -488,7 +488,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 _converse.api.archive.query({'before': '', 'max':10});
                 _converse.api.archive.query({'before': '', 'max':10});
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -527,7 +527,7 @@
                 rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
                 rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
                 rsm.start = '2010-06-07T00:00:00Z';
                 rsm.start = '2010-06-07T00:00:00Z';
                 _converse.api.archive.query(rsm);
                 _converse.api.archive.query(rsm);
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@@ -564,7 +564,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
                 const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
                 const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
 
 
                 /*  <message id='aeb213' to='juliet@capulet.lit/chamber'>
                 /*  <message id='aeb213' to='juliet@capulet.lit/chamber'>
@@ -682,7 +682,7 @@
                     .c('never').c('jid').t('montague@montague.lit');
                     .c('never').c('jid').t('montague@montague.lit');
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-                await test_utils.waitUntil(() => _converse.onMAMPreferences.calls.count());
+                await u.waitUntil(() => _converse.onMAMPreferences.calls.count());
                 expect(_converse.onMAMPreferences).toHaveBeenCalled();
                 expect(_converse.onMAMPreferences).toHaveBeenCalled();
 
 
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(
@@ -711,7 +711,7 @@
                         .c('always').up()
                         .c('always').up()
                         .c('never');
                         .c('never');
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => feature.save.calls.count());
+                await u.waitUntil(() => feature.save.calls.count());
                 expect(feature.save).toHaveBeenCalled();
                 expect(feature.save).toHaveBeenCalled();
                 expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
                 expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
                 done();
                 done();
@@ -738,7 +738,7 @@
                     sent_stanza = iq;
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
-                await test_utils.waitUntil(() => sent_stanza);
+                await u.waitUntil(() => sent_stanza);
                 const stanza_el = sent_stanza.root().nodeTree;
                 const stanza_el = sent_stanza.root().nodeTree;
                 const queryid = stanza_el.querySelector('query').getAttribute('queryid');
                 const queryid = stanza_el.querySelector('query').getAttribute('queryid');
                 expect(sent_stanza.toString()).toBe(
                 expect(sent_stanza.toString()).toBe(

+ 75 - 78
spec/messages.js

@@ -94,7 +94,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === true);
+            await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === true);
 
 
             action = view.el.querySelector('.chat-msg .chat-msg__action');
             action = view.el.querySelector('.chat-msg .chat-msg__action');
             action.style.opacity = 1;
             action.style.opacity = 1;
@@ -102,7 +102,7 @@
             expect(textarea.value).toBe('');
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
+            await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
 
             // Test that messages from other users don't have the pencil icon
             // Test that messages from other users don't have the pencil icon
             _converse.chatboxes.onMessage(
             _converse.chatboxes.onMessage(
@@ -178,7 +178,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
+            await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
 
 
             spyOn(_converse.connection, 'send');
             spyOn(_converse.connection, 'send');
             textarea.value = 'But soft, what light through yonder window breaks?';
             textarea.value = 'But soft, what light through yonder window breaks?';
@@ -212,7 +212,7 @@
             expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
             expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
 
 
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
+            await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
 
             // Test that pressing the down arrow cancels message correction
             // Test that pressing the down arrow cancels message correction
             expect(textarea.value).toBe('');
             expect(textarea.value).toBe('');
@@ -223,7 +223,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
+            await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             view.onKeyDown({
             view.onKeyDown({
                 target: textarea,
                 target: textarea,
@@ -232,7 +232,7 @@
             expect(textarea.value).toBe('');
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
+            await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
 
             textarea.value = 'It is the east, and Juliet is the one.';
             textarea.value = 'It is the east, and Juliet is the one.';
             view.onKeyDown({
             view.onKeyDown({
@@ -260,7 +260,7 @@
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(2).get('correcting')).toBe(true);
             expect(view.model.messages.at(2).get('correcting')).toBe(true);
-            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
+            await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
 
 
             textarea.selectionEnd = 0; // Happens by pressing up,
             textarea.selectionEnd = 0; // Happens by pressing up,
                                     // but for some reason not in tests, so we set it manually.
                                     // but for some reason not in tests, so we set it manually.
@@ -272,7 +272,7 @@
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(1).get('correcting')).toBe(true);
             expect(view.model.messages.at(1).get('correcting')).toBe(true);
             expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
-            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view.el)[1]), 500);
+            await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view.el)[1]), 500);
 
 
             textarea.value = 'It is the east, and Juliet is the sun.';
             textarea.value = 'It is the east, and Juliet is the sun.';
             view.onKeyDown({
             view.onKeyDown({
@@ -309,7 +309,7 @@
 
 
             let message, msg;
             let message, msg;
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
+            await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
             spyOn(_converse, 'log');
             spyOn(_converse, 'log');
             spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
             spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
             _converse.filter_by_resource = true;
             _converse.filter_by_resource = true;
@@ -337,7 +337,7 @@
                     .c('body').t("message")
                     .c('body').t("message")
                     .tree();
                     .tree();
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
+            await u.waitUntil(() => _converse.api.chats.get().length);
             const view = _converse.api.chatviews.get(sender_jid);
             const view = _converse.api.chatviews.get(sender_jid);
 
 
             msg = $msg({'id': 'aeb214', 'to': _converse.bare_jid})
             msg = $msg({'id': 'aeb214', 'to': _converse.bare_jid})
@@ -550,7 +550,7 @@
                 }).c('body').t(msgtext).tree();
                 }).c('body').t(msgtext).tree();
 
 
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => (_converse.api.chats.get().length > 1))
+            await u.waitUntil(() => (_converse.api.chats.get().length > 1))
             const chatbox = _converse.chatboxes.get(sender_jid);
             const chatbox = _converse.chatboxes.get(sender_jid);
             const view = _converse.chatboxviews.get(sender_jid);
             const view = _converse.chatboxviews.get(sender_jid);
 
 
@@ -561,15 +561,14 @@
             const msg_obj = chatbox.messages.models[0];
             const msg_obj = chatbox.messages.models[0];
             expect(msg_obj.get('message')).toEqual(msgtext);
             expect(msg_obj.get('message')).toEqual(msgtext);
             expect(msg_obj.get('fullname')).toBeUndefined();
             expect(msg_obj.get('fullname')).toBeUndefined();
-            expect(msg_obj.get('nickname')).toBeUndefined();
+            expect(msg_obj.get('nickname')).toBe(null);
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('is_delayed')).toEqual(false);
             expect(msg_obj.get('is_delayed')).toEqual(false);
             // Now check that the message appears inside the chatbox in the DOM
             // Now check that the message appears inside the chatbox in the DOM
             const chat_content = view.el.querySelector('.chat-content');
             const chat_content = view.el.querySelector('.chat-content');
-            await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
-            await test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
+            await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
             expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
             expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
             done();
             done();
         }));
         }));
@@ -602,7 +601,6 @@
                         'type': 'chat'
                         'type': 'chat'
                 }).c('body').t(msgtext).tree();
                 }).c('body').t(msgtext).tree();
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
             // Check that the chatbox and its view now exist
             // Check that the chatbox and its view now exist
             const chatbox = _converse.chatboxes.get(recipient_jid);
             const chatbox = _converse.chatboxes.get(recipient_jid);
             const view = _converse.chatboxviews.get(recipient_jid);
             const view = _converse.chatboxviews.get(recipient_jid);
@@ -683,7 +681,7 @@
             test_utils.openControlBox();
             test_utils.openControlBox();
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
 
 
-            await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+            await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
             await test_utils.openChatBoxFor(_converse, contact_jid);
             await test_utils.openChatBoxFor(_converse, contact_jid);
             const chatview = _converse.api.chatviews.get(contact_jid);
             const chatview = _converse.api.chatviews.get(contact_jid);
             expect(u.isVisible(chatview.el)).toBeTruthy();
             expect(u.isVisible(chatview.el)).toBeTruthy();
@@ -701,7 +699,7 @@
             .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
             .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
 
 
-            await test_utils.waitUntil(() => chatview.model.messages.length);
+            await u.waitUntil(() => chatview.model.messages.length);
             expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
             expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
             const trimmed_chatboxes = _converse.minimized_chats;
             const trimmed_chatboxes = _converse.minimized_chats;
             const trimmedview = trimmed_chatboxes.get(contact_jid);
             const trimmedview = trimmed_chatboxes.get(contact_jid);
@@ -720,7 +718,7 @@
                 .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                 .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
             );
             );
 
 
-            await test_utils.waitUntil(() => (chatview.model.messages.length > 1));
+            await u.waitUntil(() => (chatview.model.messages.length > 1));
             expect(u.isVisible(chatview.el)).toBeFalsy();
             expect(u.isVisible(chatview.el)).toBeFalsy();
             expect(trimmedview.model.get('minimized')).toBeTruthy();
             expect(trimmedview.model.get('minimized')).toBeTruthy();
             count = trimmedview.el.querySelector('.message-count');
             count = trimmedview.el.querySelector('.message-count');
@@ -743,7 +741,7 @@
             const contact_name = mock.cur_names[1];
             const contact_name = mock.cur_names[1];
             const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-            await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+            await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
             await test_utils.openChatBoxFor(_converse, contact_jid);
             await test_utils.openChatBoxFor(_converse, contact_jid);
             test_utils.clearChatBoxMessages(_converse, contact_jid);
             test_utils.clearChatBoxMessages(_converse, contact_jid);
             const one_day_ago = dayjs().subtract(1, 'day');
             const one_day_ago = dayjs().subtract(1, 'day');
@@ -770,7 +768,7 @@
             expect(msg_obj.get('nickname')).toBe(null);
             expect(msg_obj.get('nickname')).toBe(null);
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('is_delayed')).toEqual(true);
             expect(msg_obj.get('is_delayed')).toEqual(true);
-            await test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
+            await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
             const chat_content = view.el.querySelector('.chat-content');
             const chat_content = view.el.querySelector('.chat-content');
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
@@ -1002,7 +1000,7 @@
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
             spyOn(view.model, 'sendMessage').and.callThrough();
             spyOn(view.model, 'sendMessage').and.callThrough();
             test_utils.sendMessage(view, message);
             test_utils.sendMessage(view, message);
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
             expect(view.model.sendMessage).toHaveBeenCalled();
             expect(view.model.sendMessage).toHaveBeenCalled();
             let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             expect(msg.innerHTML.trim()).toEqual(
             expect(msg.innerHTML.trim()).toEqual(
@@ -1011,7 +1009,7 @@
                 ' src="' + message + '"></a>');
                 ' src="' + message + '"></a>');
             message += "?param1=val1&param2=val2";
             message += "?param1=val1&param2=val2";
             test_utils.sendMessage(view, message);
             test_utils.sendMessage(view, message);
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
             expect(view.model.sendMessage).toHaveBeenCalled();
             expect(view.model.sendMessage).toHaveBeenCalled();
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             expect(msg.innerHTML.trim()).toEqual(
             expect(msg.innerHTML.trim()).toEqual(
@@ -1022,7 +1020,7 @@
             // Test now with two images in one message
             // Test now with two images in one message
             message += ' hello world '+base_url+"/logo/conversejs-filled.svg";
             message += ' hello world '+base_url+"/logo/conversejs-filled.svg";
             test_utils.sendMessage(view, message);
             test_utils.sendMessage(view, message);
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
             expect(view.model.sendMessage).toHaveBeenCalled();
             expect(view.model.sendMessage).toHaveBeenCalled();
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
             expect(msg.textContent.trim()).toEqual('hello world');
             expect(msg.textContent.trim()).toEqual('hello world');
@@ -1075,7 +1073,7 @@
             const base_time = new Date();
             const base_time = new Date();
             const ONE_MINUTE_LATER = 60000;
             const ONE_MINUTE_LATER = 60000;
 
 
-            await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
+            await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             _converse.filter_by_resource = true;
             _converse.filter_by_resource = true;
 
 
@@ -1283,7 +1281,7 @@
                 }).c('body').t('Message!').up()
                 }).c('body').t('Message!').up()
                 .c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
                 .c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
+            await u.waitUntil(() => _converse.api.chats.get().length);
             expect(view.model.sendReceiptStanza).not.toHaveBeenCalled();
             expect(view.model.sendReceiptStanza).not.toHaveBeenCalled();
             done();
             done();
         }));
         }));
@@ -1313,7 +1311,7 @@
                 }).c('body').t('Message!').up()
                 }).c('body').t('Message!').up()
                 .c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
                 .c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
+            await u.waitUntil(() => _converse.api.chats.get().length);
             expect(view.model.sendReceiptStanza).not.toHaveBeenCalled();
             expect(view.model.sendReceiptStanza).not.toHaveBeenCalled();
             done();
             done();
         }));
         }));
@@ -1336,7 +1334,7 @@
                     preventDefault: _.noop,
                     preventDefault: _.noop,
                     keyCode: 13 // Enter
                     keyCode: 13 // Enter
                 });
                 });
-                await test_utils.waitUntil(() => _converse.api.chats.get().length);
+                await u.waitUntil(() => _converse.api.chats.get().length);
                 const chatbox = _converse.chatboxes.get(contact_jid);
                 const chatbox = _converse.chatboxes.get(contact_jid);
                 expect(chatbox).toBeDefined();
                 expect(chatbox).toBeDefined();
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
@@ -1429,7 +1427,7 @@
                 const include_nick = false;
                 const include_nick = false;
                 await test_utils.waitForRoster(_converse, 'current', 1, include_nick);
                 await test_utils.waitForRoster(_converse, 'current', 1, include_nick);
                 test_utils.openControlBox();
                 test_utils.openControlBox();
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
                 spyOn(_converse.api, "trigger").and.callThrough();
                 spyOn(_converse.api, "trigger").and.callThrough();
                 const message = 'This is a received message';
                 const message = 'This is a received message';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -1444,7 +1442,7 @@
                     }).c('body').t(message).up()
                     }).c('body').t(message).up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                 );
                 );
-                await test_utils.waitUntil(() => (_converse.api.chats.get().length === 2));
+                await u.waitUntil(() => (_converse.api.chats.get().length === 2));
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 const chatbox = _converse.chatboxes.get(sender_jid);
                 expect(chatbox).toBeDefined();
                 expect(chatbox).toBeDefined();
                 const view = _converse.api.chatviews.get(sender_jid);
                 const view = _converse.api.chatviews.get(sender_jid);
@@ -1462,7 +1460,7 @@
                 const chat_content = view.el.querySelector('.chat-content');
                 const chat_content = view.el.querySelector('.chat-content');
                 expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
                 expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
                 expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
                 expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
-                await test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0]);
+                await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0]);
                 expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Mercutio');
                 expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Mercutio');
                 done();
                 done();
             }));
             }));
@@ -1473,7 +1471,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 await test_utils.waitForRoster(_converse, 'current', 1, false);
                 await test_utils.waitForRoster(_converse, 'current', 1, false);
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
                 const message = '\n\n        This is a received message         \n\n';
                 const message = '\n\n        This is a received message         \n\n';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 _converse.chatboxes.onMessage(
                 _converse.chatboxes.onMessage(
@@ -1485,7 +1483,7 @@
                     }).c('body').t(message).up()
                     }).c('body').t(message).up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                 );
                 );
-                await test_utils.waitUntil(() => (_converse.api.chats.get().length === 2));
+                await u.waitUntil(() => (_converse.api.chats.get().length === 2));
                 const view = _converse.api.chatviews.get(sender_jid);
                 const view = _converse.api.chatviews.get(sender_jid);
                 expect(view.model.messages.length).toEqual(1);
                 expect(view.model.messages.length).toEqual(1);
                 const msg_obj = view.model.messages.at(0);
                 const msg_obj = view.model.messages.at(0);
@@ -1549,7 +1547,7 @@
                 expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
                 expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
                 view.el.querySelector('.chat-msg__content .fa-edit').click();
                 view.el.querySelector('.chat-msg__content .fa-edit').click();
                 const modal = view.model.messages.at(0).message_versions_modal;
                 const modal = view.model.messages.at(0).message_versions_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 const older_msgs = modal.el.querySelectorAll('.older-msg');
                 const older_msgs = modal.el.querySelectorAll('.older-msg');
                 expect(older_msgs.length).toBe(2);
                 expect(older_msgs.length).toBe(2);
                 expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
                 expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
@@ -1594,7 +1592,7 @@
                     expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
                     expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
 
 
                     await _converse.chatboxes.onMessage(msg);
                     await _converse.chatboxes.onMessage(msg);
-                    await test_utils.waitUntil(() => _converse.api.chats.get().length);
+                    await u.waitUntil(() => _converse.api.chats.get().length);
                     expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
                     expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
 
 
                     // Check that the chatbox and its view now exist
                     // Check that the chatbox and its view now exist
@@ -1604,14 +1602,13 @@
                     expect(chatbox).toBeDefined();
                     expect(chatbox).toBeDefined();
                     expect(view).toBeDefined();
                     expect(view).toBeDefined();
                     expect(chatbox.get('fullname') === sender_jid);
                     expect(chatbox.get('fullname') === sender_jid);
-                    await new Promise(resolve => view.once('messageInserted', resolve));
 
 
-                    await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
+                    await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
                     let author_el = view.el.querySelector('.chat-msg__author');
                     let author_el = view.el.querySelector('.chat-msg__author');
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
-                    await test_utils.waitUntil(() => vcard_fetched, 100);
+                    await u.waitUntil(() => vcard_fetched, 100);
                     expect(_converse.api.vcard.get).toHaveBeenCalled();
                     expect(_converse.api.vcard.get).toHaveBeenCalled();
-                    await test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
+                    await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
                     author_el = view.el.querySelector('.chat-msg__author');
                     author_el = view.el.querySelector('.chat-msg__author');
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
                     done();
                     done();
@@ -1669,7 +1666,7 @@
                     expect(msg_obj.get('sender')).toEqual('them');
                     expect(msg_obj.get('sender')).toEqual('them');
                     expect(msg_obj.get('is_delayed')).toEqual(false);
                     expect(msg_obj.get('is_delayed')).toEqual(false);
 
 
-                    await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
+                    await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
                     // Now check that the message appears inside the chatbox in the DOM
                     // Now check that the message appears inside the chatbox in the DOM
                     const chat_content = view.el.querySelector('.chat-content');
                     const chat_content = view.el.querySelector('.chat-content');
                     expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
                     expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
@@ -1888,11 +1885,11 @@
                 }
                 }
                 await Promise.all(promises);
                 await Promise.all(promises);
                 // XXX Fails on Travis
                 // XXX Fails on Travis
-                // await test_utils.waitUntil(() => view.content.scrollTop, 1000)
-                await test_utils.waitUntil(() => !view.model.get('auto_scrolled'), 500);
+                // await u.waitUntil(() => view.content.scrollTop, 1000)
+                await u.waitUntil(() => !view.model.get('auto_scrolled'), 500);
                 view.content.scrollTop = 0;
                 view.content.scrollTop = 0;
                 // XXX Fails on Travis
                 // XXX Fails on Travis
-                // await test_utils.waitUntil(() => view.model.get('scrolled'), 900);
+                // await u.waitUntil(() => view.model.get('scrolled'), 900);
                 view.model.set('scrolled', true);
                 view.model.set('scrolled', true);
 
 
                 const message = 'This message is received while the chat area is scrolled up';
                 const message = 'This message is received while the chat area is scrolled up';
@@ -1904,19 +1901,19 @@
                     }).c('body').t(message).up()
                     }).c('body').t(message).up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-                await test_utils.waitUntil(() => view.model.messages.length > 20, 1000);
+                await u.waitUntil(() => view.model.messages.length > 20, 1000);
                 // Now check that the message appears inside the chatbox in the DOM
                 // Now check that the message appears inside the chatbox in the DOM
                 const chat_content = view.el.querySelector('.chat-content');
                 const chat_content = view.el.querySelector('.chat-content');
                 const  msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent;
                 const  msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent;
                 expect(msg_txt).toEqual(message);
                 expect(msg_txt).toEqual(message);
-                await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
+                await u.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
                 expect(view.model.get('scrolled')).toBe(true);
                 expect(view.model.get('scrolled')).toBe(true);
                 expect(view.content.scrollTop).toBe(0);
                 expect(view.content.scrollTop).toBe(0);
                 expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy();
                 expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy();
                 // Scroll down again
                 // Scroll down again
                 view.content.scrollTop = view.content.scrollHeight;
                 view.content.scrollTop = view.content.scrollHeight;
                 // XXX Fails on Travis
                 // XXX Fails on Travis
-                // await test_utils.waitUntil(() => !u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
+                // await u.waitUntil(() => !u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
                 done();
                 done();
             }));
             }));
 
 
@@ -1926,7 +1923,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 await test_utils.waitForRoster(_converse, 'current');
                 await test_utils.waitForRoster(_converse, 'current');
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
                 // Send a message from a different resource
                 // Send a message from a different resource
                 spyOn(_converse, 'log');
                 spyOn(_converse, 'log');
                 spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
                 spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
@@ -1940,7 +1937,7 @@
                     }).c('body').t("This message will not be shown").up()
                     }).c('body').t("This message will not be shown").up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => _converse.api.chats.get().length);
+                await u.waitUntil(() => _converse.api.chats.get().length);
                 expect(_converse.log).toHaveBeenCalledWith(
                 expect(_converse.log).toHaveBeenCalledWith(
                         "onMessage: Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
                         "onMessage: Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
                         Strophe.LogLevel.INFO);
                         Strophe.LogLevel.INFO);
@@ -1956,9 +1953,9 @@
                     }).c('body').t(message).up()
                     }).c('body').t(message).up()
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                 await _converse.chatboxes.onMessage(msg);
                 await _converse.chatboxes.onMessage(msg);
-                await test_utils.waitUntil(() => _converse.chatboxviews.keys().length > 1, 1000);
+                await u.waitUntil(() => _converse.chatboxviews.keys().length > 1, 1000);
                 const view = _converse.chatboxviews.get(sender_jid);
                 const view = _converse.chatboxviews.get(sender_jid);
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
                 expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
                 const chat_content = sizzle('.chat-content:last', view.el).pop();
                 const chat_content = sizzle('.chat-content:last', view.el).pop();
                 const msg_txt = chat_content.querySelector('.chat-msg .chat-msg__text').textContent;
                 const msg_txt = chat_content.querySelector('.chat-msg .chat-msg__text').textContent;
@@ -1990,7 +1987,7 @@
                     </message>`)
                     </message>`)
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
                 let msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 let msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 expect(msg.classList.length).toEqual(1);
                 expect(msg.classList.length).toEqual(1);
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
@@ -2041,7 +2038,7 @@
                         <x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
                         <x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
                     </message>`);
                     </message>`);
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000)
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000)
                 let msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 let msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 expect(msg.classList.length).toBe(1);
                 expect(msg.classList.length).toBe(1);
                 expect(msg.textContent).toEqual('Have you seen this funny video?');
                 expect(msg.textContent).toEqual('Have you seen this funny video?');
@@ -2090,7 +2087,7 @@
                     </message>`);
                     </message>`);
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000);
                 const msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 const msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
                 expect(msg.textContent).toEqual('Have you downloaded this funny file?');
                 expect(msg.textContent).toEqual('Have you downloaded this funny file?');
@@ -2122,7 +2119,7 @@
                         <x xmlns="jabber:x:oob"><url>${url}</url></x>
                         <x xmlns="jabber:x:oob"><url>${url}</url></x>
                     </message>`);
                     </message>`);
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg img').length, 2000);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg img').length, 2000);
 
 
                 const msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 const msg = view.el.querySelector('.chat-msg .chat-msg__text');
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
                 expect(u.hasClass('chat-msg__text', msg)).toBe(true);
@@ -2163,7 +2160,7 @@
             spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s));
             spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s));
             spyOn(view.model, 'sendMarker').and.callThrough();
             spyOn(view.model, 'sendMarker').and.callThrough();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.sendMarker.calls.count() === 1);
+            await u.waitUntil(() => view.model.sendMarker.calls.count() === 1);
             expect(Strophe.serialize(sent_stanzas[0])).toBe(
             expect(Strophe.serialize(sent_stanzas[0])).toBe(
                 `<message from="romeo@montague.lit/orchard" `+
                 `<message from="romeo@montague.lit/orchard" `+
                         `id="${sent_stanzas[0].nodeTree.getAttribute('id')}" `+
                         `id="${sent_stanzas[0].nodeTree.getAttribute('id')}" `+
@@ -2193,7 +2190,7 @@
             const sent_stanzas = [];
             const sent_stanzas = [];
             spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s));
             spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.api.chats.get().length == 2);
+            await u.waitUntil(() => _converse.api.chats.get().length == 2);
             const sent_messages = sent_stanzas
             const sent_messages = sent_stanzas
                 .map(s => _.isElement(s) ? s : s.nodeTree)
                 .map(s => _.isElement(s) ? s : s.nodeTree)
                 .filter(e => e.nodeName === 'message');
                 .filter(e => e.nodeName === 'message');
@@ -2251,7 +2248,7 @@
                 </message>`);
                 </message>`);
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500);
+            await u.waitUntil(() => _converse.api.trigger.calls.count(), 500);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.model.messages.length).toBe(1);
             expect(view.model.messages.length).toBe(1);
             done();
             done();
@@ -2336,9 +2333,9 @@
                                by="room@muc.example.com"/>
                                by="room@muc.example.com"/>
                 </message>`);
                 </message>`);
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
-            await test_utils.waitUntil(() => view.model.messages.length === 1);
-            await test_utils.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 1);
+            await u.waitUntil(() => _converse.api.chats.get().length);
+            await u.waitUntil(() => view.model.messages.length === 1);
+            await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 1);
             let result = await view.model.findDuplicateFromStanzaID.calls.all()[0].returnValue;
             let result = await view.model.findDuplicateFromStanzaID.calls.all()[0].returnValue;
             expect(result).toBe(undefined);
             expect(result).toBe(undefined);
 
 
@@ -2354,11 +2351,11 @@
                 </message>`);
                 </message>`);
             spyOn(view.model, 'updateMessage');
             spyOn(view.model, 'updateMessage');
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 2);
+            await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 2);
             result = await view.model.findDuplicateFromStanzaID.calls.all()[1].returnValue;
             result = await view.model.findDuplicateFromStanzaID.calls.all()[1].returnValue;
             expect(result instanceof _converse.Message).toBe(true);
             expect(result instanceof _converse.Message).toBe(true);
             expect(view.model.messages.length).toBe(1);
             expect(view.model.messages.length).toBe(1);
-            await test_utils.waitUntil(() => view.model.updateMessage.calls.count());
+            await u.waitUntil(() => view.model.updateMessage.calls.count());
             done();
             done();
         }));
         }));
 
 
@@ -2424,7 +2421,7 @@
                 .c('status').attrs({code:'210'}).nodeTree;
                 .c('status').attrs({code:'210'}).nodeTree;
             _converse.connection._dataRecv(test_utils.createRequest(presence));
             _converse.connection._dataRecv(test_utils.createRequest(presence));
             view.model.sendMessage('hello world');
             view.model.sendMessage('hello world');
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
 
 
             expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
             expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
             expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
             expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
@@ -2492,7 +2489,7 @@
                     'id': u.getUniqueId(),
                     'id': u.getUniqueId(),
                 }).c('body').t('But soft, what light through yonder chimney breaks?').up()
                 }).c('body').t('But soft, what light through yonder chimney breaks?').up()
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
-            await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
+            await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                 'But soft, what light through yonder chimney breaks?', 500);
                 'But soft, what light through yonder chimney breaks?', 500);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
@@ -2505,13 +2502,13 @@
                 }).c('body').t('But soft, what light through yonder window breaks?').up()
                 }).c('body').t('But soft, what light through yonder window breaks?').up()
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
 
-            await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
+            await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                 'But soft, what light through yonder window breaks?', 500);
                 'But soft, what light through yonder window breaks?', 500);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
             view.el.querySelector('.chat-msg__content .fa-edit').click();
             view.el.querySelector('.chat-msg__content .fa-edit').click();
             const modal = view.model.messages.at(0).message_versions_modal;
             const modal = view.model.messages.at(0).message_versions_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             const older_msgs = modal.el.querySelectorAll('.older-msg');
             const older_msgs = modal.el.querySelectorAll('.older-msg');
             expect(older_msgs.length).toBe(2);
             expect(older_msgs.length).toBe(2);
             expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?');
             expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?');
@@ -2613,7 +2610,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
+            await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             view.onKeyDown({
             view.onKeyDown({
                 target: textarea,
                 target: textarea,
@@ -2622,7 +2619,7 @@
             expect(textarea.value).toBe('');
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
-            await test_utils.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
+            await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             done();
             done();
         }));
         }));
 
 
@@ -2658,7 +2655,7 @@
                     <origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
                     <origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
                 </message>`);
                 </message>`);
             await view.model.onMessage(stanza);
             await view.model.onMessage(stanza);
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
             expect(view.model.messages.length).toBe(1);
             expect(view.model.messages.length).toBe(1);
@@ -2679,8 +2676,8 @@
             const view = _converse.api.chatviews.get(muc_jid);
             const view = _converse.api.chatviews.get(muc_jid);
 
 
             view.model.sendMessage('hello world');
             view.model.sendMessage('hello world');
-            await test_utils.waitUntil(() => _converse.api.chats.get().length);
-            await test_utils.waitUntil(() => view.model.messages.length === 1);
+            await u.waitUntil(() => _converse.api.chats.get().length);
+            await u.waitUntil(() => view.model.messages.length === 1);
             const msg = view.model.messages.at(0);
             const msg = view.model.messages.at(0);
             expect(msg.get('stanza_id')).toBeUndefined();
             expect(msg.get('stanza_id')).toBeUndefined();
             expect(msg.get('origin_id')).toBe(msg.get('origin_id'));
             expect(msg.get('origin_id')).toBe(msg.get('origin_id'));
@@ -2698,7 +2695,7 @@
                 </message>`);
                 </message>`);
             spyOn(view.model, 'updateMessage').and.callThrough();
             spyOn(view.model, 'updateMessage').and.callThrough();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.updateMessage.calls.count() === 1);
+            await u.waitUntil(() => view.model.updateMessage.calls.count() === 1);
             expect(view.model.messages.length).toBe(1);
             expect(view.model.messages.length).toBe(1);
             expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad");
             expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad");
             expect(view.model.messages.at(0).get('origin_id')).toBe(msg.get('origin_id'));
             expect(view.model.messages.at(0).get('origin_id')).toBe(msg.get('origin_id'));
@@ -2734,7 +2731,7 @@
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(view.model, "isReceipt").and.callThrough();
             spyOn(view.model, "isReceipt").and.callThrough();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.isReceipt.calls.count() === 1);
+            await u.waitUntil(() => view.model.isReceipt.calls.count() === 1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
             expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
@@ -2770,7 +2767,7 @@
                 </message>`);
                 </message>`);
             spyOn(view.model, "isChatMarker").and.callThrough();
             spyOn(view.model, "isChatMarker").and.callThrough();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.isChatMarker.calls.count() === 1);
+            await u.waitUntil(() => view.model.isChatMarker.calls.count() === 1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
 
 
@@ -2780,7 +2777,7 @@
                     <displayed xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
                     <displayed xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
                 </message>`);
                 </message>`);
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.isChatMarker.calls.count() === 2);
+            await u.waitUntil(() => view.model.isChatMarker.calls.count() === 2);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
 
 
@@ -2791,7 +2788,7 @@
                 </message>`);
                 </message>`);
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            await test_utils.waitUntil(() => view.model.isChatMarker.calls.count() === 3);
+            await u.waitUntil(() => view.model.isChatMarker.calls.count() === 3);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
 
 
@@ -2802,7 +2799,7 @@
                     <markable xmlns="urn:xmpp:chat-markers:0"/>
                     <markable xmlns="urn:xmpp:chat-markers:0"/>
                 </message>`);
                 </message>`);
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => view.model.isChatMarker.calls.count() === 4);
+            await u.waitUntil(() => view.model.isChatMarker.calls.count() === 4);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
             done();
             done();
@@ -3027,11 +3024,11 @@
                 expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
                 expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
                 expect(view.model.messages.at(0).get('correcting')).toBe(true);
                 expect(view.model.messages.at(0).get('correcting')).toBe(true);
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
+                await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
 
 
                 textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
                 textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
                 view.onKeyDown(enter_event);
                 view.onKeyDown(enter_event);
-                await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
+                await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                     'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
                     'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
 
 
                 const correction = _converse.connection.send.calls.all()[2].args[0];
                 const correction = _converse.connection.send.calls.all()[2].args[0];

+ 4 - 4
spec/minchats.js

@@ -66,7 +66,7 @@
             expect(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))).toBeTruthy();
             expect(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))).toBeTruthy();
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
             _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
             _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
-            await test_utils.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
+            await u.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
             expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
             done();
             done();
         }));
         }));
@@ -93,7 +93,7 @@
                 contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 test_utils.openChatBoxFor(_converse, contact_jid);
                 test_utils.openChatBoxFor(_converse, contact_jid);
             }
             }
-            test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
+            u.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
                 for (i=0; i<3; i++) {
                 for (i=0; i<3; i++) {
                     chatview = _converse.chatboxviews.get(contact_jid);
                     chatview = _converse.chatboxviews.get(contact_jid);
                     chatview.model.set({'minimized': true});
                     chatview.model.set({'minimized': true});
@@ -106,7 +106,7 @@
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                     _converse.chatboxes.onMessage(msg);
                     _converse.chatboxes.onMessage(msg);
                 }
                 }
-                return test_utils.waitUntil(() => chatview.model.messages.length);
+                return u.waitUntil(() => chatview.model.messages.length);
             }).then(() => {
             }).then(() => {
                 expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
                 expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
                 expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
                 expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
@@ -169,7 +169,7 @@
                     type: 'groupchat'
                     type: 'groupchat'
                 }).c('body').t(message).tree();
                 }).c('body').t(message).tree();
             view.model.onMessage(msg);
             view.model.onMessage(msg);
-            await test_utils.waitUntil(() => view.model.messages.length);
+            await u.waitUntil(() => view.model.messages.length);
             expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
             expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
             expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
             expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
             done();
             done();

+ 108 - 108
spec/muc.js

@@ -53,7 +53,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300);
                 let muc_jid = 'chillout@montague.lit';
                 let muc_jid = 'chillout@montague.lit';
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 let room = _converse.api.rooms.get(muc_jid);
                 let room = _converse.api.rooms.get(muc_jid);
@@ -107,13 +107,13 @@
                 let chatroomview, IQ_id;
                 let chatroomview, IQ_id;
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length);
                 let room = await _converse.api.rooms.open(jid);
                 let room = await _converse.api.rooms.open(jid);
                 // Test on groupchat that's not yet open
                 // Test on groupchat that's not yet open
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 chatroomview = _converse.chatboxviews.get(jid);
                 chatroomview = _converse.chatboxviews.get(jid);
                 expect(chatroomview.is_chatroom).toBeTruthy();
                 expect(chatroomview.is_chatroom).toBeTruthy();
-                await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
+                await u.waitUntil(() => u.isVisible(chatroomview.el));
 
 
                 // Test again, now that the room exists.
                 // Test again, now that the room exists.
                 room = await _converse.api.rooms.open(jid);
                 room = await _converse.api.rooms.open(jid);
@@ -128,19 +128,19 @@
                 room = await _converse.api.rooms.open(jid);
                 room = await _converse.api.rooms.open(jid);
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
-                await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
+                await u.waitUntil(() => u.isVisible(chatroomview.el));
 
 
                 jid = 'leisure@montague.lit';
                 jid = 'leisure@montague.lit';
                 room = await _converse.api.rooms.open(jid);
                 room = await _converse.api.rooms.open(jid);
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
-                await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
+                await u.waitUntil(() => u.isVisible(chatroomview.el));
 
 
                 jid = 'leiSure@montague.lit';
                 jid = 'leiSure@montague.lit';
                 room = await _converse.api.rooms.open(jid);
                 room = await _converse.api.rooms.open(jid);
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 expect(room instanceof Backbone.Model).toBeTruthy();
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
                 chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
-                await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
+                await u.waitUntil(() => u.isVisible(chatroomview.el));
                 chatroomview.close();
                 chatroomview.close();
 
 
                 _converse.muc_instant_rooms = false;
                 _converse.muc_instant_rooms = false;
@@ -244,7 +244,7 @@
 
 
                 spyOn(chatroomview.model, 'sendConfiguration').and.callThrough();
                 spyOn(chatroomview.model, 'sendConfiguration').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(node));
                 _converse.connection._dataRecv(test_utils.createRequest(node));
-                await test_utils.waitUntil(() => chatroomview.model.sendConfiguration.calls.count() === 1);
+                await u.waitUntil(() => chatroomview.model.sendConfiguration.calls.count() === 1);
 
 
                 const sent_stanza = IQ_stanzas.filter(s => s.getAttribute('type') === 'set').pop();
                 const sent_stanza = IQ_stanzas.filter(s => s.getAttribute('type') === 'set').pop();
                 expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent).toBe('Room');
                 expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent).toBe('Room');
@@ -280,7 +280,7 @@
                     }
                     }
                 });
                 });
                 await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
                 await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
-                let stanza = await test_utils.waitUntil(() => _.filter(
+                let stanza = await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -315,7 +315,7 @@
                  *          node="x-roomuser-item"/>
                  *          node="x-roomuser-item"/>
                  * </iq>
                  * </iq>
                  */
                  */
-                stanza = await test_utils.waitUntil(() => _.filter(
+                stanza = await u.waitUntil(() => _.filter(
                         IQ_stanzas,
                         IQ_stanzas,
                         s => sizzle(`iq[to="${muc_jid}"] query[node="x-roomuser-item"]`, s).length
                         s => sizzle(`iq[to="${muc_jid}"] query[node="x-roomuser-item"]`, s).length
                     ).pop()
                     ).pop()
@@ -340,12 +340,12 @@
                     .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
                     .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
                 _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
 
 
-                const input = await test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
+                const input = await u.waitUntil(() => view.el.querySelector('input[name="nick"]'));
                 expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED);
                 expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED);
                 input.value = 'nicky';
                 input.value = 'nicky';
                 view.el.querySelector('input[type=submit]').click();
                 view.el.querySelector('input[type=submit]').click();
                 expect(view.model.join).toHaveBeenCalled();
                 expect(view.model.join).toHaveBeenCalled();
-                await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
+                await u.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
 
 
                 // The user has just entered the room (because join was called)
                 // The user has just entered the room (because join was called)
                 // and receives their own presence from the server.
                 // and receives their own presence from the server.
@@ -374,9 +374,9 @@
                 .c('status').attrs({code:'201'}).nodeTree;
                 .c('status').attrs({code:'201'}).nodeTree;
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
-                await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED);
+                await u.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED);
                 await test_utils.returnMemberLists(_converse, muc_jid);
                 await test_utils.returnMemberLists(_converse, muc_jid);
-                // await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
+                // await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
 
 
                 const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
                 const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
                 expect(info_texts[0]).toBe('A new groupchat has been created');
                 expect(info_texts[0]).toBe('A new groupchat has been created');
@@ -421,7 +421,7 @@
 
 
                 spyOn(view.model, 'clearMessages').and.callThrough();
                 spyOn(view.model, 'clearMessages').and.callThrough();
                 view.model.close();
                 view.model.close();
-                await test_utils.waitUntil(() => view.model.clearMessages.calls.count());
+                await u.waitUntil(() => view.model.clearMessages.calls.count());
                 expect(view.model.messages.length).toBe(0);
                 expect(view.model.messages.length).toBe(0);
                 expect(view.content.innerHTML).toBe('');
                 expect(view.content.innerHTML).toBe('');
                 done()
                 done()
@@ -451,7 +451,7 @@
                 await view.model.onMessage(msg);
                 await view.model.onMessage(msg);
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                 view.el.querySelector('.chat-msg__text a').click();
                 view.el.querySelector('.chat-msg__text a').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length === 3)
+                await u.waitUntil(() => _converse.chatboxes.length === 3)
                 expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true);
                 expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true);
                 done()
                 done()
             }));
             }));
@@ -486,7 +486,7 @@
                     .c('status', {code: '100'});
                     .c('status', {code: '100'});
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
-                await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2);
+                await u.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2);
                 expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
                 expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
                     .toBe("This groupchat is not anonymous");
                     .toBe("This groupchat is not anonymous");
                 expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
                 expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
@@ -1227,7 +1227,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
-                await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
+                await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
@@ -1275,7 +1275,7 @@
 
 
                 await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
                 await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
                 const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
                 const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
-                await test_utils.waitUntil(() => u.isVisible(view.el));
+                await u.waitUntil(() => u.isVisible(view.el));
                 spyOn(view.model, 'saveAffiliationAndRole').and.callThrough();
                 spyOn(view.model, 'saveAffiliationAndRole').and.callThrough();
                 // We pretend this is a new room, so no disco info is returned.
                 // We pretend this is a new room, so no disco info is returned.
                 const features_stanza = $iq({
                 const features_stanza = $iq({
@@ -1308,7 +1308,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.model.saveAffiliationAndRole).toHaveBeenCalled();
                 expect(view.model.saveAffiliationAndRole).toHaveBeenCalled();
                 expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy();
                 expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy();
-                await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button')))
+                await u.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button')))
                 expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy();
                 expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy();
                 view.el.querySelector('.configure-chatroom-button').click();
                 view.el.querySelector('.configure-chatroom-button').click();
 
 
@@ -1446,7 +1446,7 @@
                             .c('value').t('cauldronburn');
                             .c('value').t('cauldronburn');
                 _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
 
 
-                await test_utils.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length)
+                await u.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length)
                 expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1);
                 expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1);
                 expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
                 expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
                 const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
                 const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
@@ -1489,7 +1489,7 @@
                 const muc_jid = 'lounge@montague.lit'
                 const muc_jid = 'lounge@montague.lit'
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', [], ['juliet']);
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', [], ['juliet']);
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => view.model.occupants.length === 2);
+                await u.waitUntil(() => view.model.occupants.length === 2);
 
 
                 const occupants = view.el.querySelector('.occupant-list');
                 const occupants = view.el.querySelector('.occupant-list');
                 for (let i=0; i<mock.chatroom_names.length; i++) {
                 for (let i=0; i<mock.chatroom_names.length; i++) {
@@ -1508,7 +1508,7 @@
                     _converse.connection._dataRecv(test_utils.createRequest(presence));
                     _converse.connection._dataRecv(test_utils.createRequest(presence));
                 }
                 }
 
 
-                await test_utils.waitUntil(() => occupants.querySelectorAll('li').length > 2, 500);
+                await u.waitUntil(() => occupants.querySelectorAll('li').length > 2, 500);
                 expect(occupants.querySelectorAll('li').length).toBe(2+mock.chatroom_names.length);
                 expect(occupants.querySelectorAll('li').length).toBe(2+mock.chatroom_names.length);
                 expect(view.model.occupants.length).toBe(2+mock.chatroom_names.length);
                 expect(view.model.occupants.length).toBe(2+mock.chatroom_names.length);
 
 
@@ -1546,7 +1546,7 @@
                     role: 'visitor'
                     role: 'visitor'
                 });
                 });
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
-                await test_utils.waitUntil(() => occupants.querySelectorAll('li').length > 8, 500);
+                await u.waitUntil(() => occupants.querySelectorAll('li').length > 8, 500);
                 expect(occupants.querySelectorAll('li').length).toBe(9);
                 expect(occupants.querySelectorAll('li').length).toBe(9);
                 expect(view.model.occupants.length).toBe(9);
                 expect(view.model.occupants.length).toBe(9);
                 expect(view.model.occupants.filter(o => o.isMember()).length).toBe(8);
                 expect(view.model.occupants.filter(o => o.isMember()).length).toBe(8);
@@ -1589,7 +1589,7 @@
                     _converse.connection._dataRecv(test_utils.createRequest(presence));
                     _converse.connection._dataRecv(test_utils.createRequest(presence));
                 }
                 }
 
 
-                await test_utils.waitUntil(() => occupants.querySelectorAll('li').length > 1, 500);
+                await u.waitUntil(() => occupants.querySelectorAll('li').length > 1, 500);
                 expect(occupants.querySelectorAll('li').length).toBe(1+mock.chatroom_names.length);
                 expect(occupants.querySelectorAll('li').length).toBe(1+mock.chatroom_names.length);
 
 
                 mock.chatroom_names.forEach(name => {
                 mock.chatroom_names.forEach(name => {
@@ -1644,7 +1644,7 @@
 
 
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
-                await test_utils.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
                 const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
                 const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
                 expect(occupants.length).toBe(2);
                 expect(occupants.length).toBe(2);
                 expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
                 expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
@@ -1660,7 +1660,7 @@
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 let contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 let contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length, 500);
                 let occupants = view.el.querySelectorAll('.occupant-list li');
                 let occupants = view.el.querySelectorAll('.occupant-list li');
                 expect(occupants.length).toBe(1);
                 expect(occupants.length).toBe(1);
                 expect(occupants[0].querySelector('.occupant-nick').textContent.trim()).toBe("romeo");
                 expect(occupants[0].querySelector('.occupant-nick').textContent.trim()).toBe("romeo");
@@ -1680,7 +1680,7 @@
                 .c('status').attrs({code:'110'}).nodeTree;
                 .c('status').attrs({code:'110'}).nodeTree;
 
 
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length > 1, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length > 1, 500);
                 occupants = view.el.querySelectorAll('.occupant-list li');
                 occupants = view.el.querySelectorAll('.occupant-list li');
                 expect(occupants.length).toBe(2);
                 expect(occupants.length).toBe(2);
                 expect(occupants[0].querySelector('.occupant-nick').textContent.trim()).toBe("moderatorman");
                 expect(occupants[0].querySelector('.occupant-nick').textContent.trim()).toBe("moderatorman");
@@ -1705,7 +1705,7 @@
                 .c('status').attrs({code:'110'}).nodeTree;
                 .c('status').attrs({code:'110'}).nodeTree;
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length > 2, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('.occupant-list li').length > 2, 500);
                 occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
                 occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
                 expect(occupants.length).toBe(3);
                 expect(occupants.length).toBe(3);
                 expect(occupants[2].querySelector('.occupant-nick').textContent.trim()).toBe("visitorwoman");
                 expect(occupants[2].querySelector('.occupant-nick').textContent.trim()).toBe("visitorwoman");
@@ -1759,7 +1759,7 @@
 
 
                 await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
                 await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
 
 
-                let stanza = await test_utils.waitUntil(() => _.filter(
+                let stanza = await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -1786,7 +1786,7 @@
                  *         node='x-roomuser-item'/>
                  *         node='x-roomuser-item'/>
                  * </iq>
                  * </iq>
                  */
                  */
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                         IQ_stanzas,
                         IQ_stanzas,
                         s => sizzle(`iq[to="${muc_jid}"] query[node="x-roomuser-item"]`, s).length
                         s => sizzle(`iq[to="${muc_jid}"] query[node="x-roomuser-item"]`, s).length
                     ).pop()
                     ).pop()
@@ -1838,7 +1838,7 @@
                     .c('status').attrs({code:'210'}).nodeTree;
                     .c('status').attrs({code:'210'}).nodeTree;
 
 
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
                 const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent;
                 const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent;
                 expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
                 expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
                 done();
                 done();
@@ -1875,11 +1875,11 @@
                 expect(view.el.querySelectorAll('input.invited-contact').length).toBe(1);
                 expect(view.el.querySelectorAll('input.invited-contact').length).toBe(1);
 
 
                 view.model.getOwnOccupant().set('affiliation', 'member');
                 view.model.getOwnOccupant().set('affiliation', 'member');
-                await test_utils.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length === 0);
+                await u.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length === 0);
 
 
                 view.model.features.set('open', 'true');
                 view.model.features.set('open', 'true');
                 spyOn(view.model, 'directInvite').and.callThrough();
                 spyOn(view.model, 'directInvite').and.callThrough();
-                await test_utils.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length);
+                await u.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length);
                 const input = view.el.querySelector('input.invited-contact');
                 const input = view.el.querySelector('input.invited-contact');
                 expect(input.getAttribute('placeholder')).toBe('Invite');
                 expect(input.getAttribute('placeholder')).toBe('Invite');
                 input.value = "Balt";
                 input.value = "Balt";
@@ -1888,7 +1888,7 @@
 
 
                 let sent_stanza;
                 let sent_stanza;
                 spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
                 spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
-                const hint = await test_utils.waitUntil(() => view.el.querySelector('.suggestion-box__results li'));
+                const hint = await u.waitUntil(() => view.el.querySelector('.suggestion-box__results li'));
                 expect(input.value).toBe('Balt');
                 expect(input.value).toBe('Balt');
                 expect(hint.textContent).toBe('Balthasar');
                 expect(hint.textContent).toBe('Balthasar');
 
 
@@ -2143,7 +2143,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
                 let sent_stanzas = _converse.connection.sent_stanzas;
                 let sent_stanzas = _converse.connection.sent_stanzas;
-                const iq = await test_utils.waitUntil(() => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.PING}"]`, s).length).pop());
+                const iq = await u.waitUntil(() => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.PING}"]`, s).length).pop());
                 expect(Strophe.serialize(iq)).toBe(
                 expect(Strophe.serialize(iq)).toBe(
                     `<iq id="${iq.getAttribute('id')}" to="coven@chat.shakespeare.lit/romeo" type="get" xmlns="jabber:client">`+
                     `<iq id="${iq.getAttribute('id')}" to="coven@chat.shakespeare.lit/romeo" type="get" xmlns="jabber:client">`+
                         `<ping xmlns="urn:xmpp:ping"/>`+
                         `<ping xmlns="urn:xmpp:ping"/>`+
@@ -2164,7 +2164,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
                 await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
 
 
-                const pres = await test_utils.waitUntil(
+                const pres = await u.waitUntil(
                     () => sent_stanzas.slice(index).filter(s => s.nodeName === 'presence').pop());
                     () => sent_stanzas.slice(index).filter(s => s.nodeName === 'presence').pop());
                 expect(Strophe.serialize(pres)).toBe(
                 expect(Strophe.serialize(pres)).toBe(
                     `<presence from="${_converse.jid}" to="coven@chat.shakespeare.lit/romeo" xmlns="jabber:client">`+
                     `<presence from="${_converse.jid}" to="coven@chat.shakespeare.lit/romeo" xmlns="jabber:client">`+
@@ -2194,7 +2194,7 @@
                     </x>
                     </x>
                     </message>`);
                     </message>`);
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
                 const info_messages = view.el.querySelectorAll('.chat-content .chat-info');
                 const info_messages = view.el.querySelectorAll('.chat-content .chat-info');
                 expect(info_messages[0].textContent).toBe('romeo has entered the groupchat');
                 expect(info_messages[0].textContent).toBe('romeo has entered the groupchat');
                 expect(info_messages[1].textContent).toBe('groupchat logging is now enabled');
                 expect(info_messages[1].textContent).toBe('groupchat logging is now enabled');
@@ -2248,7 +2248,7 @@
                 expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
                 expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
                 const chat_content = view.el.querySelector('.chat-content');
                 const chat_content = view.el.querySelector('.chat-content');
 
 
-                await test_utils.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
                 let occupants = view.el.querySelector('.occupant-list');
                 let occupants = view.el.querySelector('.occupant-list');
                 expect(occupants.childNodes.length).toBe(1);
                 expect(occupants.childNodes.length).toBe(1);
                 expect(occupants.firstElementChild.querySelector('.occupant-nick').textContent.trim()).toBe("oldnick");
                 expect(occupants.firstElementChild.querySelector('.occupant-nick').textContent.trim()).toBe("oldnick");
@@ -2274,7 +2274,7 @@
                     .c('status').attrs({code:'110'}).nodeTree;
                     .c('status').attrs({code:'110'}).nodeTree;
 
 
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
 
 
                 expect(sizzle('div.chat-info:last').pop().textContent).toBe(
                 expect(sizzle('div.chat-info:last').pop().textContent).toBe(
                     __(_converse.muc.new_nickname_messages["303"], "newnick")
                     __(_converse.muc.new_nickname_messages["303"], "newnick")
@@ -2322,7 +2322,7 @@
                 const muc_jid = 'coven@chat.shakespeare.lit';
                 const muc_jid = 'coven@chat.shakespeare.lit';
 
 
                 await _converse.api.rooms.open(muc_jid, {'nick': 'some1'});
                 await _converse.api.rooms.open(muc_jid, {'nick': 'some1'});
-                const stanza = await test_utils.waitUntil(() => _.filter(
+                const stanza = await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -2374,7 +2374,7 @@
                         .c('feature', {'var': 'muc_nonanonymous'});
                         .c('feature', {'var': 'muc_nonanonymous'});
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
                 let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
                 view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
                 view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
                 expect(view.model.features.get('fetched')).toBeTruthy();
                 expect(view.model.features.get('fetched')).toBeTruthy();
                 expect(view.model.features.get('passwordprotected')).toBe(true);
                 expect(view.model.features.get('passwordprotected')).toBe(true);
@@ -2425,7 +2425,7 @@
                 chatroomview.el.querySelector('.configure-chatroom-button').click();
                 chatroomview.el.querySelector('.configure-chatroom-button').click();
 
 
                 const IQs = _converse.connection.IQ_stanzas;
                 const IQs = _converse.connection.IQ_stanzas;
-                let iq = await test_utils.waitUntil(() => _.filter(
+                let iq = await u.waitUntil(() => _.filter(
                     IQs,
                     IQs,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${jid}"] query[xmlns="${Strophe.NS.MUC_OWNER}"]`
                         `iq[to="${jid}"] query[xmlns="${Strophe.NS.MUC_OWNER}"]`
@@ -2497,13 +2497,13 @@
                      </query>
                      </query>
                      </iq>`);
                      </iq>`);
                 _converse.connection._dataRecv(test_utils.createRequest(response_el));
                 _converse.connection._dataRecv(test_utils.createRequest(response_el));
-                const el = await test_utils.waitUntil(() => document.querySelector('.chatroom-form legend'));
+                const el = await u.waitUntil(() => document.querySelector('.chatroom-form legend'));
                 expect(el.textContent).toBe("Configuration for room@conference.example.org");
                 expect(el.textContent).toBe("Configuration for room@conference.example.org");
                 sizzle('[name="muc#roomconfig_membersonly"]', chatroomview.el).pop().click();
                 sizzle('[name="muc#roomconfig_membersonly"]', chatroomview.el).pop().click();
                 sizzle('[name="muc#roomconfig_roomname"]', chatroomview.el).pop().value = "New room name"
                 sizzle('[name="muc#roomconfig_roomname"]', chatroomview.el).pop().value = "New room name"
                 chatroomview.el.querySelector('.btn-primary').click();
                 chatroomview.el.querySelector('.btn-primary').click();
 
 
-                iq = await test_utils.waitUntil(() => _.filter(IQs, iq => u.matchesSelector(iq, `iq[to="${jid}"][type="set"]`)).pop());
+                iq = await u.waitUntil(() => _.filter(IQs, iq => u.matchesSelector(iq, `iq[to="${jid}"][type="set"]`)).pop());
                 const result = $iq({
                 const result = $iq({
                     "xmlns": "jabber:client",
                     "xmlns": "jabber:client",
                     "type": "result",
                     "type": "result",
@@ -2515,7 +2515,7 @@
                 IQs.length = 0; // Empty the array
                 IQs.length = 0; // Empty the array
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
 
 
-                iq = await test_utils.waitUntil(() => _.filter(
+                iq = await u.waitUntil(() => _.filter(
                     IQs,
                     IQs,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -2553,7 +2553,7 @@
 
 
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
-                await test_utils.waitUntil(() => new Promise(success => chatroomview.model.features.on('change', success)));
+                await u.waitUntil(() => new Promise(success => chatroomview.model.features.on('change', success)));
                 features_list = chatroomview.el.querySelector('.features-list');
                 features_list = chatroomview.el.querySelector('.features-list');
                 features_shown = features_list.textContent.split('\n').map(s => s.trim()).filter(s => s);
                 features_shown = features_list.textContent.split('\n').map(s => s.trim()).filter(s => s);
                 expect(_.difference(["Password protected", "Hidden", "Members only", "Temporary", "Not anonymous", "Not moderated"], features_shown).length).toBe(0);
                 expect(_.difference(["Password protected", "Hidden", "Members only", "Temporary", "Not anonymous", "Not moderated"], features_shown).length).toBe(0);
@@ -2617,7 +2617,7 @@
                     .c('status', {code: '104'}).up()
                     .c('status', {code: '104'}).up()
                     .c('status', {code: '172'});
                     .c('status', {code: '172'});
                 _converse.connection._dataRecv(test_utils.createRequest(message));
                 _converse.connection._dataRecv(test_utils.createRequest(message));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3);
                 const chat_body = view.el.querySelector('.chatroom-body');
                 const chat_body = view.el.querySelector('.chatroom-body');
                 expect(sizzle('.message:last', chat_body).pop().textContent)
                 expect(sizzle('.message:last', chat_body).pop().textContent)
                     .toBe('This groupchat is now no longer anonymous');
                     .toBe('This groupchat is now no longer anonymous');
@@ -2730,7 +2730,7 @@
                 expect(u.isVisible(view.el)).toBeFalsy();
                 expect(u.isVisible(view.el)).toBeFalsy();
                 expect(view.model.get('minimized')).toBeTruthy();
                 expect(view.model.get('minimized')).toBeTruthy();
                 expect(view.onMinimized).toHaveBeenCalled();
                 expect(view.onMinimized).toHaveBeenCalled();
-                await test_utils.waitUntil(() => trimmed_chatboxes.get(view.model.get('id')));
+                await u.waitUntil(() => trimmed_chatboxes.get(view.model.get('id')));
                 const trimmedview = trimmed_chatboxes.get(view.model.get('id'));
                 const trimmedview = trimmed_chatboxes.get(view.model.get('id'));
                 trimmedview.el.querySelector("a.restore-chat").click();
                 trimmedview.el.querySelector("a.restore-chat").click();
                 expect(view.onMaximized).toHaveBeenCalled();
                 expect(view.onMaximized).toHaveBeenCalled();
@@ -2943,7 +2943,7 @@
                 });
                 });
                 _converse.connection.IQ_stanzas = [];
                 _converse.connection.IQ_stanzas = [];
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
-                iq_stanza = await test_utils.waitUntil(() => _.filter(
+                iq_stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="member"]')).pop()
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="member"]')).pop()
                 );
                 );
@@ -2967,7 +2967,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
 
 
                 expect(view.model.occupants.length).toBe(2);
                 expect(view.model.occupants.length).toBe(2);
-                iq_stanza = await test_utils.waitUntil(() => _.filter(
+                iq_stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="owner"]')).pop()
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="owner"]')).pop()
                 );
                 );
@@ -2991,7 +2991,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
 
 
                 expect(view.model.occupants.length).toBe(2);
                 expect(view.model.occupants.length).toBe(2);
-                iq_stanza = await test_utils.waitUntil(() => _.filter(
+                iq_stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="admin"]')).pop()
                     iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="admin"]')).pop()
                 );
                 );
@@ -3012,8 +3012,8 @@
                     "id": iq_stanza.getAttribute("id")
                     "id": iq_stanza.getAttribute("id")
                 }).c("query", {"xmlns": "http://jabber.org/protocol/muc#admin"})
                 }).c("query", {"xmlns": "http://jabber.org/protocol/muc#admin"})
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.occupant').length, 500);
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.badge').length > 1);
+                await u.waitUntil(() => view.el.querySelectorAll('.occupant').length, 500);
+                await u.waitUntil(() => view.el.querySelectorAll('.badge').length > 1);
                 expect(view.model.occupants.length).toBe(2);
                 expect(view.model.occupants.length).toBe(2);
                 expect(view.el.querySelectorAll('.occupant').length).toBe(2);
                 expect(view.el.querySelectorAll('.occupant').length).toBe(2);
                 done();
                 done();
@@ -3363,7 +3363,7 @@
                         .c('status', {'code': '307'});
                         .c('status', {'code': '307'});
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
-                await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4);
                 expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out");
                 expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out");
                 expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
                 expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
                 done();
                 done();
@@ -3700,7 +3700,7 @@
                 spyOn(_converse.api, "trigger");
                 spyOn(_converse.api, "trigger");
                 expect(_converse.chatboxes.length).toBe(2);
                 expect(_converse.chatboxes.length).toBe(2);
                 _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED));
                 expect(_converse.chatboxes.length).toBe(1);
                 expect(_converse.chatboxes.length).toBe(1);
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
                 done();
                 done();
@@ -3764,7 +3764,7 @@
                 const muc_jid = 'members-only@muc.montague.lit'
                 const muc_jid = 'members-only@muc.montague.lit'
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -3788,7 +3788,7 @@
                         .c('feature', {'var': 'muc_temporary'}).up()
                         .c('feature', {'var': 'muc_temporary'}).up()
                         .c('feature', {'var': 'muc_membersonly'}).up();
                         .c('feature', {'var': 'muc_membersonly'}).up();
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
-                await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
+                await u.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -3813,7 +3813,7 @@
                 const muc_jid = 'off-limits@muc.montague.lit'
                 const muc_jid = 'off-limits@muc.montague.lit'
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
 
 
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -3833,7 +3833,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
+                await u.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -3882,7 +3882,7 @@
 
 
             it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true",
             it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true",
                 mock.initConverse(
                 mock.initConverse(
-                    null, ['rosterGroupsFetched'], {},
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 const muc_jid = 'conflicting@muc.montague.lit'
                 const muc_jid = 'conflicting@muc.montague.lit'
@@ -3950,7 +3950,7 @@
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo')
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo')
 
 
                 // We pretend this is a new room, so no disco info is returned.
                 // We pretend this is a new room, so no disco info is returned.
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -3965,7 +3965,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -3990,7 +3990,7 @@
                 const muc_jid = 'conformist@muc.montague.lit'
                 const muc_jid = 'conformist@muc.montague.lit'
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
 
 
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -4006,7 +4006,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -4032,7 +4032,7 @@
                 const muc_jid = 'nonexistent@muc.montague.lit'
                 const muc_jid = 'nonexistent@muc.montague.lit'
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo');
 
 
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -4048,7 +4048,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -4074,7 +4074,7 @@
                 const muc_jid = 'maxed-out@muc.montague.lit'
                 const muc_jid = 'maxed-out@muc.montague.lit'
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo')
                 await test_utils.openChatRoomViaModal(_converse, muc_jid, 'romeo')
 
 
-                const iq = await test_utils.waitUntil(() => _.filter(
+                const iq = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -4090,7 +4090,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
 
 
                 const presence = $pres().attrs({
                 const presence = $pres().attrs({
                         from: `${muc_jid}/romeo`,
                         from: `${muc_jid}/romeo`,
@@ -4126,7 +4126,7 @@
                 });
                 });
 
 
                 await _converse.api.rooms.open(muc_jid, {'nick': 'romeo'});
                 await _converse.api.rooms.open(muc_jid, {'nick': 'romeo'});
-                let stanza = await test_utils.waitUntil(() => _.filter(
+                let stanza = await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -4156,7 +4156,7 @@
                         .c('feature', {'var': 'muc_temporary'}).up()
                         .c('feature', {'var': 'muc_temporary'}).up()
                         .c('feature', {'var': 'muc_membersonly'}).up();
                         .c('feature', {'var': 'muc_membersonly'}).up();
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
-                await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
+                await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
                 expect(view.model.features.get('membersonly')).toBeTruthy();
                 expect(view.model.features.get('membersonly')).toBeTruthy();
 
 
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
@@ -4250,8 +4250,8 @@
                             'jid': 'crone1@shakespeare.lit',
                             'jid': 'crone1@shakespeare.lit',
                         });
                         });
                 _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
-                await test_utils.waitUntil(() => IQ_ids.length, 300);
-                stanza = await test_utils.waitUntil(() => _.filter(
+                await u.waitUntil(() => IQ_ids.length, 300);
+                stanza = await u.waitUntil(() => _.filter(
                     IQ_stanzas,
                     IQ_stanzas,
                     iq => iq.querySelector(
                     iq => iq.querySelector(
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/muc#admin"]`
                         `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/muc#admin"]`
@@ -4353,7 +4353,7 @@
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
 
 
                 let label_name = modal.el.querySelector('label[for="chatroom"]');
                 let label_name = modal.el.querySelector('label[for="chatroom"]');
                 expect(label_name.textContent).toBe('Groupchat address:');
                 expect(label_name.textContent).toBe('Groupchat address:');
@@ -4371,8 +4371,8 @@
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 modal.el.querySelector('input[name="chatroom"]').value = 'lounce@muc.montague.lit';
                 modal.el.querySelector('input[name="chatroom"]').value = 'lounce@muc.montague.lit';
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
-                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+                await u.waitUntil(() => _converse.chatboxes.length);
+                await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
 
 
                 roomspanel.model.set('muc_domain', 'muc.example.org');
                 roomspanel.model.set('muc_domain', 'muc.example.org');
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
@@ -4394,13 +4394,13 @@
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
                 const name_input = modal.el.querySelector('input[name="chatroom"]');
                 const name_input = modal.el.querySelector('input[name="chatroom"]');
                 name_input.value = 'lounge@montague.lit';
                 name_input.value = 'lounge@montague.lit';
                 expect(modal.el.querySelector('label[for="nickname"]')).toBe(null);
                 expect(modal.el.querySelector('label[for="nickname"]')).toBe(null);
                 expect(modal.el.querySelector('input[name="nickname"]')).toBe(null);
                 expect(modal.el.querySelector('input[name="nickname"]')).toBe(null);
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
+                await u.waitUntil(() => _converse.chatboxes.length > 1);
                 const chatroom = _converse.chatboxes.get('lounge@montague.lit');
                 const chatroom = _converse.chatboxes.get('lounge@montague.lit');
                 expect(chatroom.get('nick')).toBe('romeo');
                 expect(chatroom.get('nick')).toBe('romeo');
                 done();
                 done();
@@ -4417,7 +4417,7 @@
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
                 const label_nick = modal.el.querySelector('label[for="nickname"]');
                 const label_nick = modal.el.querySelector('label[for="nickname"]');
                 expect(label_nick.textContent).toBe('Nickname:');
                 expect(label_nick.textContent).toBe('Nickname:');
                 const nick_input = modal.el.querySelector('input[name="nickname"]');
                 const nick_input = modal.el.querySelector('input[name="nickname"]');
@@ -4436,7 +4436,7 @@
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
                 const label_nick = modal.el.querySelector('label[for="nickname"]');
                 const label_nick = modal.el.querySelector('label[for="nickname"]');
                 expect(label_nick.textContent).toBe('Nickname:');
                 expect(label_nick.textContent).toBe('Nickname:');
                 const nick_input = modal.el.querySelector('input[name="nickname"]');
                 const nick_input = modal.el.querySelector('input[name="nickname"]');
@@ -4453,7 +4453,7 @@
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@@ -4466,20 +4466,20 @@
                 nick_input.value = 'max';
                 nick_input.value = 'max';
 
 
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
-                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+                await u.waitUntil(() => _converse.chatboxes.length);
+                await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
 
 
                 // However, you can still open MUCs with different domains
                 // However, you can still open MUCs with different domains
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 name_input = modal.el.querySelector('input[name="chatroom"]');
                 name_input = modal.el.querySelector('input[name="chatroom"]');
                 name_input.value = 'lounge@conference.example.org';
                 name_input.value = 'lounge@conference.example.org';
                 nick_input = modal.el.querySelector('input[name="nickname"]');
                 nick_input = modal.el.querySelector('input[name="nickname"]');
                 nick_input.value = 'max';
                 nick_input.value = 'max';
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
-                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
+                await u.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
+                await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@conference.example.org')).toBe(true);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@conference.example.org')).toBe(true);
                 done();
                 done();
             }));
             }));
@@ -4493,7 +4493,7 @@
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                await u.waitUntil(() => u.isVisible(modal.el), 1000)
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@@ -4505,20 +4505,20 @@
                 let nick_input = modal.el.querySelector('input[name="nickname"]');
                 let nick_input = modal.el.querySelector('input[name="nickname"]');
                 nick_input.value = 'max';
                 nick_input.value = 'max';
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
-                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+                await u.waitUntil(() => _converse.chatboxes.length);
+                await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
 
 
                 // However, you can still open MUCs with different domains
                 // However, you can still open MUCs with different domains
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 name_input = modal.el.querySelector('input[name="chatroom"]');
                 name_input = modal.el.querySelector('input[name="chatroom"]');
                 name_input.value = 'lounge@conference';
                 name_input.value = 'lounge@conference';
                 nick_input = modal.el.querySelector('input[name="nickname"]');
                 nick_input = modal.el.querySelector('input[name="nickname"]');
                 nick_input.value = 'max';
                 nick_input.value = 'max';
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
-                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
+                await u.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
+                await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge\\40conference@muc.example.org')).toBe(true);
                 expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge\\40conference@muc.example.org')).toBe(true);
                 done();
                 done();
             }));
             }));
@@ -4536,7 +4536,7 @@
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.list_rooms_modal;
                 const modal = roomspanel.list_rooms_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
 
 
@@ -4547,10 +4547,10 @@
                 expect(server_input.placeholder).toBe('conference.example.org');
                 expect(server_input.placeholder).toBe('conference.example.org');
                 const input = server_input.value = 'chat.shakespeare.lit';
                 const input = server_input.value = 'chat.shakespeare.lit';
                 modal.el.querySelector('input[type="submit"]').click();
                 modal.el.querySelector('input[type="submit"]').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
+                await u.waitUntil(() => _converse.chatboxes.length);
 
 
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
-                const sent_stanza = await test_utils.waitUntil(
+                const sent_stanza = await u.waitUntil(
                     () => IQ_stanzas.filter(s => sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"]`, s).length).pop()
                     () => IQ_stanzas.filter(s => sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"]`, s).length).pop()
                 );
                 );
                 const id = sent_stanza.getAttribute('id');
                 const id = sent_stanza.getAttribute('id');
@@ -4580,7 +4580,7 @@
                 .c('item', { jid:'street@chat.shakespeare.lit', name:'A street'}).nodeTree;
                 .c('item', { jid:'street@chat.shakespeare.lit', name:'A street'}).nodeTree;
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
 
 
-                await test_utils.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 11);
+                await u.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 11);
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
@@ -4595,7 +4595,7 @@
                 expect(rooms[10].textContent.trim()).toBe('A street');
                 expect(rooms[10].textContent.trim()).toBe('A street');
 
 
                 rooms[4].querySelector('.open-room').click();
                 rooms[4].querySelector('.open-room').click();
-                await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
+                await u.waitUntil(() => _converse.chatboxes.length > 1);
                 expect(sizzle('.chatroom', _converse.el).filter(u.isVisible).length).toBe(1); // There should now be an open chatroom
                 expect(sizzle('.chatroom', _converse.el).filter(u.isVisible).length).toBe(1); // There should now be an open chatroom
                 var view = _converse.chatboxviews.get('inverness@chat.shakespeare.lit');
                 var view = _converse.chatboxviews.get('inverness@chat.shakespeare.lit');
                 expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
                 expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
@@ -4613,7 +4613,7 @@
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.list_rooms_modal;
                 const modal = roomspanel.list_rooms_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 const server_input = modal.el.querySelector('input[name="server"]');
                 const server_input = modal.el.querySelector('input[name="server"]');
                 expect(server_input.value).toBe('muc.example.org');
                 expect(server_input.value).toBe('muc.example.org');
                 done();
                 done();
@@ -4630,14 +4630,14 @@
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 roomspanel.el.querySelector('.show-list-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.list_rooms_modal;
                 const modal = roomspanel.list_rooms_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
 
 
                 expect(modal.el.querySelector('input[name="server"]')).toBe(null);
                 expect(modal.el.querySelector('input[name="server"]')).toBe(null);
                 expect(modal.el.querySelector('input[type="submit"]')).toBe(null);
                 expect(modal.el.querySelector('input[type="submit"]')).toBe(null);
-                await test_utils.waitUntil(() => _converse.chatboxes.length);
-                const sent_stanza = await test_utils.waitUntil(() =>
+                await u.waitUntil(() => _converse.chatboxes.length);
+                const sent_stanza = await u.waitUntil(() =>
                     _converse.connection.sent_stanzas.filter(
                     _converse.connection.sent_stanzas.filter(
                         s => sizzle(`query[xmlns="http://jabber.org/protocol/disco#items"]`, s).length).pop()
                         s => sizzle(`query[xmlns="http://jabber.org/protocol/disco#items"]`, s).length).pop()
                 );
                 );
@@ -4658,7 +4658,7 @@
                 .c('item', { jid:'forres@chat.shakespeare.lit', name:'The Palace'}).up()
                 .c('item', { jid:'forres@chat.shakespeare.lit', name:'The Palace'}).up()
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
 
 
-                await test_utils.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 4);
+                await u.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 4);
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
@@ -4697,7 +4697,7 @@
                         to: 'romeo@montague.lit',
                         to: 'romeo@montague.lit',
                         type: 'groupchat'
                         type: 'groupchat'
                     }).c('body').t(message).tree());
                     }).c('body').t(message).tree());
-                await test_utils.waitUntil(() => view.model.messages.length);
+                await u.waitUntil(() => view.model.messages.length);
                 expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
                 expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('1');
                 expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('1');
@@ -4708,7 +4708,7 @@
                     'to': 'romeo@montague.lit',
                     'to': 'romeo@montague.lit',
                     'type': 'groupchat'
                     'type': 'groupchat'
                 }).c('body').t(message).tree());
                 }).c('body').t(message).tree());
-                await test_utils.waitUntil(() => view.model.messages.length > 1);
+                await u.waitUntil(() => view.model.messages.length > 1);
                 expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
                 expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
                 expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('2');
                 expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('2');
@@ -4829,7 +4829,7 @@
                         }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
                         }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
 
 
                     await view.model.onMessage(msg);
                     await view.model.onMessage(msg);
-                    await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
+                    await u.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
 
 
                     // Check that the notification appears inside the chatbox in the DOM
                     // Check that the notification appears inside the chatbox in the DOM
                     let events = view.el.querySelectorAll('.chat-event');
                     let events = view.el.querySelectorAll('.chat-event');
@@ -4879,7 +4879,7 @@
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
 
 
-                    await test_utils.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2));
+                    await u.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2));
                     notifications = view.el.querySelectorAll('.chat-state-notification');
                     notifications = view.el.querySelectorAll('.chat-state-notification');
                     expect(notifications.length).toBe(2);
                     expect(notifications.length).toBe(2);
                     expect(notifications[0].textContent).toEqual('nomorenicks is typing');
                     expect(notifications[0].textContent).toEqual('nomorenicks is typing');
@@ -5006,7 +5006,7 @@
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
 
 
-                    await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
+                    await u.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
                     let notifications = view.el.querySelectorAll('.chat-state-notification');
                     let notifications = view.el.querySelectorAll('.chat-state-notification');
                     expect(notifications.length).toBe(1);
                     expect(notifications.length).toBe(1);
                     expect(notifications[0].textContent).toEqual('newguy is typing');
                     expect(notifications[0].textContent).toEqual('newguy is typing');
@@ -5044,7 +5044,7 @@
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
 
 
-                    await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length ===  2);
+                    await u.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length ===  2);
                     notifications = view.el.querySelectorAll('.chat-state-notification');
                     notifications = view.el.querySelectorAll('.chat-state-notification');
                     // We check for the messages' text without assuming order,
                     // We check for the messages' text without assuming order,
                     // because it can be variable since getLastMessageDate
                     // because it can be variable since getLastMessageDate
@@ -5067,7 +5067,7 @@
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[1].textContent).toEqual('newguy has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
                     expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
 
 
-                    await test_utils.waitUntil(() => {
+                    await u.waitUntil(() => {
                         return _.map(
                         return _.map(
                             view.el.querySelectorAll('.chat-state-notification'), 'textContent')
                             view.el.querySelectorAll('.chat-state-notification'), 'textContent')
                                 .join(' ').includes('stopped typing')
                                 .join(' ').includes('stopped typing')

+ 7 - 6
spec/notification.js

@@ -2,9 +2,10 @@
     define(["jasmine", "mock", "test-utils"], factory);
     define(["jasmine", "mock", "test-utils"], factory);
 } (this, function (jasmine, mock, test_utils) {
 } (this, function (jasmine, mock, test_utils) {
     "use strict";
     "use strict";
-    const Strophe = converse.env.Strophe,
-          _ = converse.env._,
-          $msg = converse.env.$msg;
+    const Strophe = converse.env.Strophe;
+    const _ = converse.env._;
+    const $msg = converse.env.$msg;
+    const u = converse.env.utils;
 
 
     describe("Notifications", function () {
     describe("Notifications", function () {
         // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
         // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
@@ -35,7 +36,7 @@
                             }).c('body').t(message).up()
                             }).c('body').t(message).up()
                             .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                             .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                         await _converse.chatboxes.onMessage(msg); // This will emit 'message'
                         await _converse.chatboxes.onMessage(msg); // This will emit 'message'
-                        await test_utils.waitUntil(() => _converse.api.chatviews.get(sender_jid));
+                        await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
                         expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                         expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
                         expect(_converse.showMessageNotification).toHaveBeenCalled();
                         done();
                         done();
@@ -102,7 +103,7 @@
                             .c('x', {'xmlns': 'jabber:x:oob'})
                             .c('x', {'xmlns': 'jabber:x:oob'})
                             .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
                             .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
                         _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                        await test_utils.waitUntil(() => _converse.chatboxviews.keys().length);
+                        await u.waitUntil(() => _converse.chatboxviews.keys().length);
                         const view = _converse.chatboxviews.get('notify.example.com');
                         const view = _converse.chatboxviews.get('notify.example.com');
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                         await new Promise((resolve, reject) => view.once('messageInserted', resolve));
                         expect(
                         expect(
@@ -188,7 +189,7 @@
                         type: 'groupchat'
                         type: 'groupchat'
                     }).c('body').t(text);
                     }).c('body').t(text);
                     await view.model.onMessage(message.nodeTree);
                     await view.model.onMessage(message.nodeTree);
-                    await test_utils.waitUntil(() => _converse.playSoundNotification.calls.count());
+                    await u.waitUntil(() => _converse.playSoundNotification.calls.count());
                     expect(_converse.playSoundNotification).toHaveBeenCalled();
                     expect(_converse.playSoundNotification).toHaveBeenCalled();
 
 
                     text = "This message won't play a sound";
                     text = "This message won't play a sound";

+ 70 - 70
spec/omemo.js

@@ -6,11 +6,11 @@
 
 
 
 
     async function deviceListFetched (_converse, jid) {
     async function deviceListFetched (_converse, jid) {
-        const stanza = await test_utils.waitUntil(() => _.filter(
+        const stanza = await u.waitUntil(() => _.filter(
             _converse.connection.IQ_stanzas,
             _converse.connection.IQ_stanzas,
             iq => iq.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
             iq => iq.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
         ).pop());
         ).pop());
-        await test_utils.waitUntil(() => _converse.devicelists.get(jid));
+        await u.waitUntil(() => _converse.devicelists.get(jid));
         return stanza;
         return stanza;
     }
     }
 
 
@@ -41,7 +41,7 @@
             [{'category': 'pubsub', 'type': 'pep'}],
             [{'category': 'pubsub', 'type': 'pep'}],
             ['http://jabber.org/protocol/pubsub#publish-options']
             ['http://jabber.org/protocol/pubsub#publish-options']
         );
         );
-        let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
+        let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
         let stanza = $iq({
         let stanza = $iq({
             'from': _converse.bare_jid,
             'from': _converse.bare_jid,
             'id': iq_stanza.getAttribute('id'),
             'id': iq_stanza.getAttribute('id'),
@@ -53,7 +53,7 @@
                     .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                     .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('device', {'id': '482886413b977930064a5888b92134fe'});
                         .c('device', {'id': '482886413b977930064a5888b92134fe'});
         _converse.connection._dataRecv(test_utils.createRequest(stanza));
         _converse.connection._dataRecv(test_utils.createRequest(stanza));
-        iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
+        iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse))
 
 
         stanza = $iq({
         stanza = $iq({
             'from': _converse.bare_jid,
             'from': _converse.bare_jid,
@@ -61,7 +61,7 @@
             'to': _converse.bare_jid,
             'to': _converse.bare_jid,
             'type': 'result'});
             'type': 'result'});
         _converse.connection._dataRecv(test_utils.createRequest(stanza));
         _converse.connection._dataRecv(test_utils.createRequest(stanza));
-        iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
+        iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse))
 
 
         stanza = $iq({
         stanza = $iq({
             'from': _converse.bare_jid,
             'from': _converse.bare_jid,
@@ -101,9 +101,9 @@
             test_utils.createContacts(_converse, 'current', 1);
             test_utils.createContacts(_converse, 'current', 1);
             _converse.api.trigger('rosterContactsFetched');
             _converse.api.trigger('rosterContactsFetched');
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
             await test_utils.openChatBoxFor(_converse, contact_jid);
             await test_utils.openChatBoxFor(_converse, contact_jid);
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             let stanza = $iq({
             let stanza = $iq({
                     'from': contact_jid,
                     'from': contact_jid,
                     'id': iq_stanza.getAttribute('id'),
                     'id': iq_stanza.getAttribute('id'),
@@ -115,9 +115,9 @@
                             .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                                 .c('device', {'id': '555'});
                                 .c('device', {'id': '555'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             const devicelist = _converse.devicelists.get({'jid': contact_jid});
             const devicelist = _converse.devicelists.get({'jid': contact_jid});
-            await test_utils.waitUntil(() => devicelist.devices.length === 1);
+            await u.waitUntil(() => devicelist.devices.length === 1);
 
 
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
             view.model.set('omemo_active', true);
             view.model.set('omemo_active', true);
@@ -129,7 +129,7 @@
                 preventDefault: _.noop,
                 preventDefault: _.noop,
                 keyCode: 13 // Enter
                 keyCode: 13 // Enter
             });
             });
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
             stanza = $iq({
             stanza = $iq({
                 'from': contact_jid,
                 'from': contact_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
@@ -148,7 +148,7 @@
                                 .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
                                 .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
@@ -169,7 +169,7 @@
 
 
             spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
             spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => sent_stanza);
+            await u.waitUntil(() => sent_stanza);
             expect(sent_stanza.toLocaleString()).toBe(
             expect(sent_stanza.toLocaleString()).toBe(
                 `<message from="romeo@montague.lit/orchard" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
                 `<message from="romeo@montague.lit/orchard" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
                             `to="mercutio@montague.lit" `+
                             `to="mercutio@montague.lit" `+
@@ -225,7 +225,7 @@
                     .c('payload').t(obj.payload);
                     .c('payload').t(obj.payload);
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-            await test_utils.waitUntil(() => view.model.messages.length > 1);
+            await u.waitUntil(() => view.model.messages.length > 1);
             expect(view.model.messages.length).toBe(3);
             expect(view.model.messages.length).toBe(3);
             expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim())
             expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim())
                 .toBe('Another received encrypted message without fallback');
                 .toBe('Another received encrypted message without fallback');
@@ -251,7 +251,7 @@
             ];
             ];
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             const view = _converse.chatboxviews.get('lounge@montague.lit');
             const view = _converse.chatboxviews.get('lounge@montague.lit');
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
 
 
             const toolbar = view.el.querySelector('.chat-toolbar');
             const toolbar = view.el.querySelector('.chat-toolbar');
             let toggle = toolbar.querySelector('.toggle-omemo');
             let toggle = toolbar.querySelector('.toggle-omemo');
@@ -273,7 +273,7 @@
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
             // Wait for Converse to fetch newguy's device list
             // Wait for Converse to fetch newguy's device list
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -293,10 +293,10 @@
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.devicelists.length).toBe(2);
             expect(_converse.devicelists.length).toBe(2);
 
 
-            await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             const devicelist = _converse.devicelists.get(contact_jid);
             const devicelist = _converse.devicelists.get(contact_jid);
             expect(devicelist.devices.length).toBe(1);
             expect(devicelist.devices.length).toBe(1);
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
@@ -313,7 +313,7 @@
                 preventDefault: _.noop,
                 preventDefault: _.noop,
                 keyCode: 13 // Enter
                 keyCode: 13 // Enter
             });
             });
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'), 1000);
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'), 1000);
             console.log("Bundle fetched 4e30f35051b7b8b42abe083742187228");
             console.log("Bundle fetched 4e30f35051b7b8b42abe083742187228");
             stanza = $iq({
             stanza = $iq({
                 'from': contact_jid,
                 'from': contact_jid,
@@ -334,7 +334,7 @@
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'), 1000);
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'), 1000);
             console.log("Bundle fetched 482886413b977930064a5888b92134fe");
             console.log("Bundle fetched 482886413b977930064a5888b92134fe");
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
@@ -356,7 +356,7 @@
 
 
             spyOn(_converse.connection, 'send');
             spyOn(_converse.connection, 'send');
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.connection.send.calls.count(), 1000);
+            await u.waitUntil(() => _converse.connection.send.calls.count(), 1000);
             const sent_stanza = _converse.connection.send.calls.all()[0].args[0];
             const sent_stanza = _converse.connection.send.calls.all()[0].args[0];
 
 
             expect(Strophe.serialize(sent_stanza)).toBe(
             expect(Strophe.serialize(sent_stanza)).toBe(
@@ -390,9 +390,9 @@
             test_utils.createContacts(_converse, 'current', 1);
             test_utils.createContacts(_converse, 'current', 1);
             _converse.api.trigger('rosterContactsFetched');
             _converse.api.trigger('rosterContactsFetched');
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
             await test_utils.openChatBoxFor(_converse, contact_jid);
             await test_utils.openChatBoxFor(_converse, contact_jid);
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             const stanza = $iq({
             const stanza = $iq({
                     'from': contact_jid,
                     'from': contact_jid,
                     'id': iq_stanza.getAttribute('id'),
                     'id': iq_stanza.getAttribute('id'),
@@ -404,9 +404,9 @@
                             .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                                 .c('device', {'id': '555'});
                                 .c('device', {'id': '555'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             const devicelist = _converse.devicelists.get({'jid': contact_jid});
             const devicelist = _converse.devicelists.get({'jid': contact_jid});
-            await test_utils.waitUntil(() => devicelist.devices.length === 1);
+            await u.waitUntil(() => devicelist.devices.length === 1);
 
 
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
             view.model.set('omemo_active', true);
             view.model.set('omemo_active', true);
@@ -461,7 +461,7 @@
                 preventDefault: _.noop,
                 preventDefault: _.noop,
                 keyCode: 13 // Enter
                 keyCode: 13 // Enter
             });
             });
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '988349631'));
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '988349631'));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${_converse.bare_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${_converse.bare_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -490,7 +490,7 @@
             ];
             ];
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             const view = _converse.chatboxviews.get('lounge@montague.lit');
             const view = _converse.chatboxviews.get('lounge@montague.lit');
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
 
 
             const contact_jid = 'newguy@montague.lit';
             const contact_jid = 'newguy@montague.lit';
             let stanza = $pres({
             let stanza = $pres({
@@ -518,7 +518,7 @@
                 preventDefault: _.noop,
                 preventDefault: _.noop,
                 keyCode: 13 // Enter
                 keyCode: 13 // Enter
             });
             });
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -538,15 +538,15 @@
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
 
 
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.devicelists.length).toBe(2);
             expect(_converse.devicelists.length).toBe(2);
 
 
             const devicelist = _converse.devicelists.get(contact_jid);
             const devicelist = _converse.devicelists.get(contact_jid);
-            await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(devicelist.devices.length).toBe(1);
             expect(devicelist.devices.length).toBe(1);
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
 
 
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
@@ -564,7 +564,7 @@
                                 .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
                                 .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
                                 .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
                                 .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
 
 
             /* <iq xmlns="jabber:client" to="jc@opkode.com/converse.js-34183907" type="error" id="945c8ab3-b561-4d8a-92da-77c226bb1689:sendIQ" from="joris@konuro.net">
             /* <iq xmlns="jabber:client" to="jc@opkode.com/converse.js-34183907" type="error" id="945c8ab3-b561-4d8a-92da-77c226bb1689:sendIQ" from="joris@konuro.net">
              *     <pubsub xmlns="http://jabber.org/protocol/pubsub">
              *     <pubsub xmlns="http://jabber.org/protocol/pubsub">
@@ -588,7 +588,7 @@
                 .c('not-authorized', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
                 .c('not-authorized', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            await test_utils.waitUntil(() => document.querySelectorAll('.alert-danger').length, 2000);
+            await u.waitUntil(() => document.querySelectorAll('.alert-danger').length, 2000);
             const header = document.querySelector('.alert-danger .modal-title');
             const header = document.querySelector('.alert-danger .modal-title');
             expect(header.textContent).toBe("Error");
             expect(header.textContent).toBe("Error");
             expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
             expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
@@ -611,7 +611,7 @@
             _converse.api.trigger('rosterContactsFetched');
             _converse.api.trigger('rosterContactsFetched');
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
             const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact');
             const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact');
             // XXX: Normally the key will be encrypted via libsignal.
             // XXX: Normally the key will be encrypted via libsignal.
             // However, we're mocking libsignal in the tests, so we include
             // However, we're mocking libsignal in the tests, so we include
@@ -642,7 +642,7 @@
                 return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
                 return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
             });
             });
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            let iq_stanza = await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid));
+            let iq_stanza = await u.waitUntil(() => _converse.chatboxviews.get(contact_jid));
             iq_stanza = await deviceListFetched(_converse, contact_jid);
             iq_stanza = await deviceListFetched(_converse, contact_jid);
             stanza = $iq({
             stanza = $iq({
                 'from': contact_jid,
                 'from': contact_jid,
@@ -660,9 +660,9 @@
             // stanzas.
             // stanzas.
             _converse.connection.IQ_stanzas = [];
             _converse.connection.IQ_stanzas = [];
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
 
 
-            iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" type="set" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" type="set" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -716,7 +716,7 @@
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
             // Wait until own devices are fetched
             // Wait until own devices are fetched
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -735,21 +735,21 @@
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('device', {'id': '555'});
                             .c('device', {'id': '555'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.chatboxes.length).toBe(1);
             expect(_converse.chatboxes.length).toBe(1);
             expect(_converse.devicelists.length).toBe(1);
             expect(_converse.devicelists.length).toBe(1);
             const devicelist = _converse.devicelists.get(_converse.bare_jid);
             const devicelist = _converse.devicelists.get(_converse.bare_jid);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.at(0).get('id')).toBe('555');
             expect(devicelist.devices.at(0).get('id')).toBe('555');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
-            iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse));
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
                 'to': _converse.bare_jid,
                 'to': _converse.bare_jid,
                 'type': 'result'});
                 'type': 'result'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse));
 
 
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
@@ -835,7 +835,7 @@
                             .c('device', {'id': '444'})
                             .c('device', {'id': '444'})
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse));
             // Check that our own device is added again, but that removed
             // Check that our own device is added again, but that removed
             // devices are not added.
             // devices are not added.
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
@@ -888,7 +888,7 @@
 
 
             test_utils.createContacts(_converse, 'current');
             test_utils.createContacts(_converse, 'current');
             const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -907,20 +907,20 @@
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('device', {'id': '555'});
                             .c('device', {'id': '555'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await await test_utils.waitUntil(() => _converse.omemo_store);
+            await await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.devicelists.length).toBe(1);
             expect(_converse.devicelists.length).toBe(1);
             let devicelist = _converse.devicelists.get(_converse.bare_jid);
             let devicelist = _converse.devicelists.get(_converse.bare_jid);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.at(0).get('id')).toBe('555');
             expect(devicelist.devices.at(0).get('id')).toBe('555');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
-            iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse));
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
                 'to': _converse.bare_jid,
                 'to': _converse.bare_jid,
                 'type': 'result'});
                 'type': 'result'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse));
             stanza = $iq({
             stanza = $iq({
                 'from': _converse.bare_jid,
                 'from': _converse.bare_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
@@ -1041,7 +1041,7 @@
             test_utils.createContacts(_converse, 'current', 1);
             test_utils.createContacts(_converse, 'current', 1);
             _converse.api.trigger('rosterContactsFetched');
             _converse.api.trigger('rosterContactsFetched');
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
             let stanza = $iq({
             let stanza = $iq({
                 'from': contact_jid,
                 'from': contact_jid,
                 'id': iq_stanza.getAttribute('id'),
                 'id': iq_stanza.getAttribute('id'),
@@ -1063,7 +1063,7 @@
                 'type': 'result'});
                 'type': 'result'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" type="set" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" type="set" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1119,7 +1119,7 @@
             _converse.api.trigger('rosterContactsFetched');
             _converse.api.trigger('rosterContactsFetched');
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
 
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1138,14 +1138,14 @@
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('device', {'id': '482886413b977930064a5888b92134fe'});
                             .c('device', {'id': '482886413b977930064a5888b92134fe'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.devicelists.length).toBe(1);
             expect(_converse.devicelists.length).toBe(1);
             let devicelist = _converse.devicelists.get(_converse.bare_jid);
             let devicelist = _converse.devicelists.get(_converse.bare_jid);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
             expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
             expect(devicelist.devices.at(1).get('id')).toBe('123456789');
             // Check that own device was published
             // Check that own device was published
-            iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
+            iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1177,7 +1177,7 @@
                 'type': 'result'});
                 'type': 'result'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            const iq_el = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
+            const iq_el = await u.waitUntil(() => bundleHasBeenPublished(_converse));
             expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
             expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
             expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100);
             expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100);
 
 
@@ -1196,7 +1196,7 @@
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             await _converse.api.waitUntil('OMEMOInitialized', 1000);
             await _converse.api.waitUntil('OMEMOInitialized', 1000);
             await test_utils.openChatBoxFor(_converse, contact_jid);
             await test_utils.openChatBoxFor(_converse, contact_jid);
-            iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1219,7 +1219,7 @@
                             .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
                             .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             devicelist = _converse.devicelists.get(contact_jid);
             devicelist = _converse.devicelists.get(contact_jid);
-            await test_utils.waitUntil(() => devicelist.devices.length);
+            await u.waitUntil(() => devicelist.devices.length);
             expect(_converse.devicelists.length).toBe(2);
             expect(_converse.devicelists.length).toBe(2);
             devicelist = _converse.devicelists.get(contact_jid);
             devicelist = _converse.devicelists.get(contact_jid);
             expect(devicelist.devices.length).toBe(4);
             expect(devicelist.devices.length).toBe(4);
@@ -1227,7 +1227,7 @@
             expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
             expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
             expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
             expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
             expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
             expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
-            await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
+            await u.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
             const toolbar = view.el.querySelector('.chat-toolbar');
             const toolbar = view.el.querySelector('.chat-toolbar');
             expect(view.model.get('omemo_active')).toBe(undefined);
             expect(view.model.get('omemo_active')).toBe(undefined);
@@ -1242,7 +1242,7 @@
             expect(view.toggleOMEMO).toHaveBeenCalled();
             expect(view.toggleOMEMO).toHaveBeenCalled();
             expect(view.model.get('omemo_active')).toBe(true);
             expect(view.model.get('omemo_active')).toBe(true);
 
 
-            await test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
+            await u.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
             toggle = toolbar.querySelector('.toggle-omemo');
             toggle = toolbar.querySelector('.toggle-omemo');
             expect(u.hasClass('fa-unlock', toggle)).toBe(false);
             expect(u.hasClass('fa-unlock', toggle)).toBe(false);
             expect(u.hasClass('fa-lock', toggle)).toBe(true);
             expect(u.hasClass('fa-lock', toggle)).toBe(true);
@@ -1293,7 +1293,7 @@
             ];
             ];
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
             const view = _converse.chatboxviews.get('lounge@montague.lit');
             const view = _converse.chatboxviews.get('lounge@montague.lit');
-            await test_utils.waitUntil(() => initializedOMEMO(_converse));
+            await u.waitUntil(() => initializedOMEMO(_converse));
 
 
             const toolbar = view.el.querySelector('.chat-toolbar');
             const toolbar = view.el.querySelector('.chat-toolbar');
             let toggle = toolbar.querySelector('.toggle-omemo');
             let toggle = toolbar.querySelector('.toggle-omemo');
@@ -1325,7 +1325,7 @@
                 }).tree();
                 }).tree();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1345,10 +1345,10 @@
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
                             .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
                             .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
                             .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => _converse.omemo_store);
+            await u.waitUntil(() => _converse.omemo_store);
             expect(_converse.devicelists.length).toBe(2);
             expect(_converse.devicelists.length).toBe(2);
 
 
-            await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             const devicelist = _converse.devicelists.get(contact_jid);
             const devicelist = _converse.devicelists.get(contact_jid);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.length).toBe(2);
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
             expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
@@ -1365,13 +1365,13 @@
             // Test that the button gets disabled when the room becomes
             // Test that the button gets disabled when the room becomes
             // anonymous or semi-anonymous
             // anonymous or semi-anonymous
             view.model.features.save({'nonanonymous': false, 'semianonymous': true});
             view.model.features.save({'nonanonymous': false, 'semianonymous': true});
-            await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
+            await u.waitUntil(() => !view.model.get('omemo_supported'));
             toggle = toolbar.querySelector('.toggle-omemo');
             toggle = toolbar.querySelector('.toggle-omemo');
             expect(_.isNull(toggle)).toBe(true);
             expect(_.isNull(toggle)).toBe(true);
             expect(view.model.get('omemo_supported')).toBe(false);
             expect(view.model.get('omemo_supported')).toBe(false);
 
 
             view.model.features.save({'nonanonymous': true, 'semianonymous': false});
             view.model.features.save({'nonanonymous': true, 'semianonymous': false});
-            await test_utils.waitUntil(() => view.model.get('omemo_supported'));
+            await u.waitUntil(() => view.model.get('omemo_supported'));
             toggle = toolbar.querySelector('.toggle-omemo');
             toggle = toolbar.querySelector('.toggle-omemo');
             expect(_.isNull(toggle)).toBe(false);
             expect(_.isNull(toggle)).toBe(false);
             expect(u.hasClass('fa-unlock', toggle)).toBe(true);
             expect(u.hasClass('fa-unlock', toggle)).toBe(true);
@@ -1380,12 +1380,12 @@
 
 
             // Test that the button gets disabled when the room becomes open
             // Test that the button gets disabled when the room becomes open
             view.model.features.save({'membersonly': false, 'open': true});
             view.model.features.save({'membersonly': false, 'open': true});
-            await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
+            await u.waitUntil(() => !view.model.get('omemo_supported'));
             toggle = toolbar.querySelector('.toggle-omemo');
             toggle = toolbar.querySelector('.toggle-omemo');
             expect(_.isNull(toggle)).toBe(true);
             expect(_.isNull(toggle)).toBe(true);
 
 
             view.model.features.save({'membersonly': true, 'open': false});
             view.model.features.save({'membersonly': true, 'open': false});
-            await test_utils.waitUntil(() => view.model.get('omemo_supported'));
+            await u.waitUntil(() => view.model.get('omemo_supported'));
             toggle = toolbar.querySelector('.toggle-omemo');
             toggle = toolbar.querySelector('.toggle-omemo');
             expect(_.isNull(toggle)).toBe(false);
             expect(_.isNull(toggle)).toBe(false);
             expect(u.hasClass('fa-unlock', toggle)).toBe(true);
             expect(u.hasClass('fa-unlock', toggle)).toBe(true);
@@ -1411,7 +1411,7 @@
                     'role': 'participant'
                     'role': 'participant'
                 }).tree();
                 }).tree();
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1428,7 +1428,7 @@
                 .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
                 .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
+            await u.waitUntil(() => !view.model.get('omemo_supported'));
 
 
             expect(view.el.querySelector('.chat-error').textContent).toBe(
             expect(view.el.querySelector('.chat-error').textContent).toBe(
                 "oldguy doesn't appear to have a client that supports OMEMO. "+
                 "oldguy doesn't appear to have a client that supports OMEMO. "+
@@ -1473,8 +1473,8 @@
             const show_modal_button = view.el.querySelector('.show-user-details-modal');
             const show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button.click();
             show_modal_button.click();
             const modal = view.user_details_modal;
             const modal = view.user_details_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
-            let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
+            let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="mercutio@montague.lit" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="mercutio@montague.lit" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
@@ -1490,8 +1490,8 @@
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                         .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
                             .c('device', {'id': '555'});
                             .c('device', {'id': '555'});
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
-            iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
+            iq_stanza = await u.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
             expect(Strophe.serialize(iq_stanza)).toBe(
             expect(Strophe.serialize(iq_stanza)).toBe(
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="mercutio@montague.lit" type="get" xmlns="jabber:client">`+
                 `<iq from="romeo@montague.lit" id="${iq_stanza.getAttribute("id")}" to="mercutio@montague.lit" type="get" xmlns="jabber:client">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
                     `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
@@ -1517,7 +1517,7 @@
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
                                 .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
             _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-            await test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
+            await u.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
             expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
             expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
             const el = modal.el.querySelector('.fingerprints .fingerprint');
             const el = modal.el.querySelector('.fingerprints .fingerprint');
             expect(el.textContent.trim()).toBe(
             expect(el.textContent.trim()).toBe(

+ 3 - 3
spec/presence.js

@@ -86,7 +86,7 @@
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.change-status').click()
             cbview.el.querySelector('.change-status').click()
             const modal = _converse.xmppstatusview.status_modal;
             const modal = _converse.xmppstatusview.status_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             const msg = 'My custom status';
             const msg = 'My custom status';
             modal.el.querySelector('input[name="status_message"]').value = msg;
             modal.el.querySelector('input[name="status_message"]').value = msg;
             modal.el.querySelector('[type="submit"]').click();
             modal.el.querySelector('[type="submit"]').click();
@@ -98,9 +98,9 @@
                         `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
                         `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
                         `</presence>`)
                         `</presence>`)
 
 
-            await test_utils.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
+            await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
             cbview.el.querySelector('.change-status').click()
             cbview.el.querySelector('.change-status').click()
-            await test_utils.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
+            await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
             modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
             modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
             modal.el.querySelector('[type="submit"]').click();
             modal.el.querySelector('[type="submit"]').click();
             expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString())
             expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString())

+ 4 - 4
spec/profiling.js

@@ -64,7 +64,7 @@
             });
             });
             _converse.roster.onReceivedFromServer(stanza.tree());
             _converse.roster.onReceivedFromServer(stanza.tree());
 
 
-            return test_utils.waitUntil(function () {
+            return u.waitUntil(function () {
                 var $group = _converse.rosterview.$el.find('.roster-group')
                 var $group = _converse.rosterview.$el.find('.roster-group')
                 return $group.length && u.isVisible($group[0]);
                 return $group.length && u.isVisible($group[0]);
             }).then(function () {
             }).then(function () {
@@ -75,7 +75,7 @@
                         count += 1;
                         count += 1;
                     }
                     }
                 });
                 });
-                return test_utils.waitUntil(function () {
+                return u.waitUntil(function () {
                     return _converse.rosterview.$el.find('li.online').length
                     return _converse.rosterview.$el.find('li.online').length
                 })
                 })
             }).then(done);
             }).then(done);
@@ -109,7 +109,7 @@
             });
             });
             _converse.roster.onReceivedFromServer(stanza.tree());
             _converse.roster.onReceivedFromServer(stanza.tree());
 
 
-            return test_utils.waitUntil(function () {
+            return u.waitUntil(function () {
                 var $group = _converse.rosterview.$el.find('.roster-group')
                 var $group = _converse.rosterview.$el.find('.roster-group')
                 return $group.length && u.isVisible($group[0]);
                 return $group.length && u.isVisible($group[0]);
             }).then(function () {
             }).then(function () {
@@ -124,7 +124,7 @@
                         }
                         }
                     });
                     });
                 });
                 });
-                return test_utils.waitUntil(function () {
+                return u.waitUntil(function () {
                     return _converse.rosterview.$el.find('li.online').length
                     return _converse.rosterview.$el.find('li.online').length
                 })
                 })
             }).then(done);
             }).then(done);

+ 9 - 9
spec/protocol.js

@@ -56,7 +56,7 @@
 
 
                 let contact, sent_stanza, IQ_id, stanza;
                 let contact, sent_stanza, IQ_id, stanza;
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
                 await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
-                await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'), 300);
+                await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'), 300);
                 /* The process by which a user subscribes to a contact, including
                 /* The process by which a user subscribes to a contact, including
                  * the interaction between roster items and subscription states.
                  * the interaction between roster items and subscription states.
                  */
                  */
@@ -76,7 +76,7 @@
 
 
                 cbview.el.querySelector('.add-contact').click()
                 cbview.el.querySelector('.add-contact').click()
                 const modal = _converse.rosterview.add_contact_modal;
                 const modal = _converse.rosterview.add_contact_modal;
-                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                await u.waitUntil(() => u.isVisible(modal.el), 1000);
                 spyOn(modal, "addContactFromForm").and.callThrough();
                 spyOn(modal, "addContactFromForm").and.callThrough();
                 modal.delegateEvents();
                 modal.delegateEvents();
 
 
@@ -160,7 +160,7 @@
                 stanza = $iq({'type': 'result', 'id':IQ_id});
                 stanza = $iq({'type': 'result', 'id':IQ_id});
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
 
-                await test_utils.waitUntil(() => _converse.roster.create.calls.count());
+                await u.waitUntil(() => _converse.roster.create.calls.count());
 
 
                 // A contact should now have been created
                 // A contact should now have been created
                 expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
                 expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
@@ -173,7 +173,7 @@
                  *
                  *
                  *  <presence to='contact@example.org' type='subscribe'/>
                  *  <presence to='contact@example.org' type='subscribe'/>
                  */
                  */
-                const sent_presence = await test_utils.waitUntil(() => sent_stanzas.filter(s => s.match('presence')).pop());
+                const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => s.match('presence')).pop());
                 expect(contact.subscribe).toHaveBeenCalled();
                 expect(contact.subscribe).toHaveBeenCalled();
                 expect(sent_presence).toBe( // Strophe adds the xmlns attr (although not in spec)
                 expect(sent_presence).toBe( // Strophe adds the xmlns attr (although not in spec)
                     `<presence to="contact@example.org" type="subscribe" xmlns="jabber:client">`+
                     `<presence to="contact@example.org" type="subscribe" xmlns="jabber:client">`+
@@ -211,7 +211,7 @@
                 expect(_converse.roster.updateContact).toHaveBeenCalled();
                 expect(_converse.roster.updateContact).toHaveBeenCalled();
                 // Check that the user is now properly shown as a pending
                 // Check that the user is now properly shown as a pending
                 // contact in the roster.
                 // contact in the roster.
-                await test_utils.waitUntil(() => {
+                await u.waitUntil(() => {
                     const header = sizzle('a:contains("Pending contacts")', _converse.rosterview.el).pop();
                     const header = sizzle('a:contains("Pending contacts")', _converse.rosterview.el).pop();
                     const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
                     const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
                     return contacts.length;
                     return contacts.length;
@@ -280,7 +280,7 @@
 
 
                 // The contact should now be visible as an existing
                 // The contact should now be visible as an existing
                 // contact (but still offline).
                 // contact (but still offline).
-                await test_utils.waitUntil(() => {
+                await u.waitUntil(() => {
                     const header = sizzle('a:contains("My contacts")', _converse.rosterview.el);
                     const header = sizzle('a:contains("My contacts")', _converse.rosterview.el);
                     return sizzle('li', header[0].parentNode).filter(l => u.isVisible(l)).length;
                     return sizzle('li', header[0].parentNode).filter(l => u.isVisible(l)).length;
                 }, 600);
                 }, 600);
@@ -471,7 +471,7 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 const header = sizzle('a:contains("My contacts")', _converse.rosterview.el).pop();
                 const header = sizzle('a:contains("My contacts")', _converse.rosterview.el).pop();
-                await test_utils.waitUntil(() => header.parentElement.querySelectorAll('li').length);
+                await u.waitUntil(() => header.parentElement.querySelectorAll('li').length);
 
 
                 // remove the first user
                 // remove the first user
                 header.parentElement.querySelector('li .remove-xmpp-contact').click();
                 header.parentElement.querySelector('li .remove-xmpp-contact').click();
@@ -508,7 +508,7 @@
                 const stanza = $iq({'type': 'result', 'id':IQ_id});
                 const stanza = $iq({'type': 'result', 'id':IQ_id});
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 // Our contact has now been removed
                 // Our contact has now been removed
-                await test_utils.waitUntil(() => typeof _converse.roster.get(jid) === "undefined");
+                await u.waitUntil(() => typeof _converse.roster.get(jid) === "undefined");
                 done();
                 done();
             }));
             }));
 
 
@@ -533,7 +533,7 @@
                     'xmlns': Strophe.NS.NICK,
                     'xmlns': Strophe.NS.NICK,
                 }).t('Clint Contact');
                 }).t('Clint Contact');
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => {
+                await u.waitUntil(() => {
                     const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
                     const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
                     const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
                     const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
                     return contacts.length;
                     return contacts.length;

+ 10 - 10
spec/push.js

@@ -31,7 +31,7 @@
                     _converse.bare_jid,
                     _converse.bare_jid,
                     [{'category': 'account', 'type':'registered'}],
                     [{'category': 'account', 'type':'registered'}],
                     ['urn:xmpp:push:0'], [], 'info');
                     ['urn:xmpp:push:0'], [], 'info');
-            const stanza = await test_utils.waitUntil(() =>
+            const stanza = await u.waitUntil(() =>
                 _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
                 _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
             );
             );
             expect(Strophe.serialize(stanza)).toEqual(
             expect(Strophe.serialize(stanza)).toEqual(
@@ -44,7 +44,7 @@
                 'type': 'result',
                 'type': 'result',
                 'id': stanza.getAttribute('id')
                 'id': stanza.getAttribute('id')
             })));
             })));
-            await test_utils.waitUntil(() => _converse.session.get('push_enabled'));
+            await u.waitUntil(() => _converse.session.get('push_enabled'));
             done();
             done();
         }));
         }));
 
 
@@ -68,7 +68,7 @@
                 _converse, _converse.bare_jid, [],
                 _converse, _converse.bare_jid, [],
                 ['urn:xmpp:push:0']);
                 ['urn:xmpp:push:0']);
 
 
-            let iq = await test_utils.waitUntil(() => _.filter(
+            let iq = await u.waitUntil(() => _.filter(
                 IQ_stanzas,
                 IQ_stanzas,
                 iq => sizzle(`iq[type="set"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
                 iq => sizzle(`iq[type="set"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
             ).pop());
             ).pop());
@@ -81,7 +81,7 @@
             const result = u.toStanza(`<iq type="result" id="${iq.getAttribute('id')}" to="romeo@montague.lit" />`);
             const result = u.toStanza(`<iq type="result" id="${iq.getAttribute('id')}" to="romeo@montague.lit" />`);
             _converse.connection._dataRecv(test_utils.createRequest(result));
             _converse.connection._dataRecv(test_utils.createRequest(result));
 
 
-            await test_utils.waitUntil(() => _converse.session.get('push_enabled'));
+            await u.waitUntil(() => _converse.session.get('push_enabled'));
             expect(_converse.session.get('push_enabled').length).toBe(1);
             expect(_converse.session.get('push_enabled').length).toBe(1);
             expect(_.includes(_converse.session.get('push_enabled'), 'romeo@montague.lit')).toBe(true);
             expect(_.includes(_converse.session.get('push_enabled'), 'romeo@montague.lit')).toBe(true);
 
 
@@ -90,7 +90,7 @@
                 _converse, 'chat.shakespeare.lit',
                 _converse, 'chat.shakespeare.lit',
                 [{'category': 'account', 'type':'registered'}],
                 [{'category': 'account', 'type':'registered'}],
                 ['urn:xmpp:push:0'], [], 'info');
                 ['urn:xmpp:push:0'], [], 'info');
-            iq = await test_utils.waitUntil(() => _.filter(
+            iq = await u.waitUntil(() => _.filter(
                 IQ_stanzas,
                 IQ_stanzas,
                 iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
                 iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
             ).pop());
             ).pop());
@@ -105,7 +105,7 @@
                 'type': 'result',
                 'type': 'result',
                 'id': iq.getAttribute('id')
                 'id': iq.getAttribute('id')
             })));
             })));
-            await test_utils.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
+            await u.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
             done();
             done();
         }));
         }));
 
 
@@ -127,7 +127,7 @@
                 _converse.bare_jid,
                 _converse.bare_jid,
                 [{'category': 'account', 'type':'registered'}],
                 [{'category': 'account', 'type':'registered'}],
                 ['urn:xmpp:push:0'], [], 'info');
                 ['urn:xmpp:push:0'], [], 'info');
-            const stanza = await test_utils.waitUntil(
+            const stanza = await u.waitUntil(
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] disable[xmlns="urn:xmpp:push:0"]')).pop()
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] disable[xmlns="urn:xmpp:push:0"]')).pop()
             );
             );
             expect(Strophe.serialize(stanza)).toEqual(
             expect(Strophe.serialize(stanza)).toEqual(
@@ -140,7 +140,7 @@
                 'type': 'result',
                 'type': 'result',
                 'id': stanza.getAttribute('id')
                 'id': stanza.getAttribute('id')
             })));
             })));
-            await test_utils.waitUntil(() => _converse.session.get('push_enabled'))
+            await u.waitUntil(() => _converse.session.get('push_enabled'))
             done();
             done();
         }));
         }));
 
 
@@ -168,7 +168,7 @@
                     [{'category': 'account', 'type':'registered'}],
                     [{'category': 'account', 'type':'registered'}],
                     ['urn:xmpp:push:0'], [], 'info');
                     ['urn:xmpp:push:0'], [], 'info');
 
 
-            const stanza = await test_utils.waitUntil(
+            const stanza = await u.waitUntil(
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
             );
             );
             expect(Strophe.serialize(stanza)).toEqual(
             expect(Strophe.serialize(stanza)).toEqual(
@@ -186,7 +186,7 @@
                 'type': 'result',
                 'type': 'result',
                 'id': stanza.getAttribute('id')
                 'id': stanza.getAttribute('id')
             })));
             })));
-            await test_utils.waitUntil(() => _converse.session.get('push_enabled'))
+            await u.waitUntil(() => _converse.session.get('push_enabled'))
             done();
             done();
         }));
         }));
     });
     });

+ 8 - 8
spec/register.js

@@ -15,7 +15,7 @@
                   allow_registration: false },
                   allow_registration: false },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'));
+            await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
             expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
@@ -29,7 +29,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             test_utils.openControlBox();
             test_utils.openControlBox();
             const panels = cbview.el.querySelector('.controlbox-panes');
             const panels = cbview.el.querySelector('.controlbox-panes');
@@ -38,7 +38,7 @@
             const register_link = cbview.el.querySelector('a.register-account');
             const register_link = cbview.el.querySelector('a.register-account');
             expect(register_link.textContent).toBe("Create an account");
             expect(register_link.textContent).toBe("Create an account");
             register_link.click();
             register_link.click();
-            await test_utils.waitUntil(() => u.isVisible(registration));
+            await u.waitUntil(() => u.isVisible(registration));
             expect(u.isVisible(login)).toBe(false);
             expect(u.isVisible(login)).toBe(false);
             done();
             done();
         }));
         }));
@@ -50,7 +50,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             const registerview = cbview.registerpanel;
             const registerview = cbview.registerpanel;
@@ -86,7 +86,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.toggle-register-login').click();
             cbview.el.querySelector('.toggle-register-login').click();
@@ -142,7 +142,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.toggle-register-login').click();
             cbview.el.querySelector('.toggle-register-login').click();
@@ -199,7 +199,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.toggle-register-login').click();
             cbview.el.querySelector('.toggle-register-login').click();
@@ -273,7 +273,7 @@
                   allow_registration: true },
                   allow_registration: true },
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
-            await test_utils.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
+            await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
             test_utils.openControlBox();
             test_utils.openControlBox();
             const cbview = _converse.chatboxviews.get('controlbox');
             const cbview = _converse.chatboxviews.get('controlbox');
             cbview.el.querySelector('.toggle-register-login').click();
             cbview.el.querySelector('.toggle-register-login').click();

+ 4 - 4
spec/room_registration.js

@@ -27,7 +27,7 @@
                     preventDefault: _.noop,
                     preventDefault: _.noop,
                     keyCode: 13
                     keyCode: 13
                 });
                 });
-                let stanza = await test_utils.waitUntil(() => _.filter(
+                let stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
                     iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
                 ).pop());
                 ).pop());
@@ -48,7 +48,7 @@
                             'var': 'muc#register_roomnick'
                             'var': 'muc#register_roomnick'
                         }).c('required');
                         }).c('required');
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
-                stanza = await test_utils.waitUntil(() => _.filter(
+                stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
                     iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
                 ).pop());
                 ).pop());
@@ -79,7 +79,7 @@
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
 
 
-                let stanza = await test_utils.waitUntil(() => _.filter(
+                let stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
                     iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
                 ).pop());
                 ).pop());
@@ -101,7 +101,7 @@
                             'var': 'muc#register_roomnick'
                             'var': 'muc#register_roomnick'
                         }).c('required');
                         }).c('required');
                 _converse.connection._dataRecv(test_utils.createRequest(result));
                 _converse.connection._dataRecv(test_utils.createRequest(result));
-                stanza = await test_utils.waitUntil(() => _.filter(
+                stanza = await u.waitUntil(() => _.filter(
                     _converse.connection.IQ_stanzas,
                     _converse.connection.IQ_stanzas,
                     iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
                     iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
                 ).pop());
                 ).pop());

+ 18 - 20
spec/roomslist.js

@@ -10,7 +10,7 @@
         it("is shown in controlbox", mock.initConverse(
         it("is shown in controlbox", mock.initConverse(
                 null, ['rosterGroupsFetched', 'chatBoxesFetched'],
                 null, ['rosterGroupsFetched', 'chatBoxesFetched'],
                 { allow_bookmarks: false // Makes testing easier, otherwise we
                 { allow_bookmarks: false // Makes testing easier, otherwise we
-                                        // have to mock stanza traffic.
+                                         // have to mock stanza traffic.
                 }, async function (done, _converse) {
                 }, async function (done, _converse) {
 
 
             test_utils.openControlBox();
             test_utils.openControlBox();
@@ -22,13 +22,13 @@
             expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy();
             expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy();
 
 
             const lview = _converse.rooms_list_view
             const lview = _converse.rooms_list_view
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
+            await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
             let room_els = lview.el.querySelectorAll(".open-room");
             let room_els = lview.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
             expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
             expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
 
 
             await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
             await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
+            await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
             room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(2);
             expect(room_els.length).toBe(2);
 
 
@@ -38,7 +38,7 @@
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
             expect(room_els[0].innerText).toBe('lounge@montague.lit');
             expect(room_els[0].innerText).toBe('lounge@montague.lit');
             list = controlbox.el.querySelector('div.rooms-list-container');
             list = controlbox.el.querySelector('div.rooms-list-container');
-            test_utils.waitUntil(() => _.includes(list.classList, 'hidden'));
+            u.waitUntil(() => _.includes(list.classList, 'hidden'));
 
 
             view = _converse.chatboxviews.get('lounge@montague.lit');
             view = _converse.chatboxviews.get('lounge@montague.lit');
             view.close();
             view.close();
@@ -80,7 +80,7 @@
                 [`${Strophe.NS.PUBSUB}#publish-options`]
                 [`${Strophe.NS.PUBSUB}#publish-options`]
             );
             );
 
 
-            const call = await test_utils.waitUntil(() =>
+            const call = await u.waitUntil(() =>
                 _.filter(
                 _.filter(
                     _converse.connection.send.calls.all(),
                     _converse.connection.send.calls.all(),
                     c => sizzle('items[node="storage:bookmarks"]', c.args[0]).length
                     c => sizzle('items[node="storage:bookmarks"]', c.args[0]).length
@@ -119,24 +119,22 @@
     describe("A groupchat shown in the groupchats list", function () {
     describe("A groupchat shown in the groupchats list", function () {
 
 
         it("is highlighted if its currently open", mock.initConverse(
         it("is highlighted if its currently open", mock.initConverse(
-            null, ['rosterGroupsFetched', 'chatBoxesFetched'],
+            null, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
             { view_mode: 'fullscreen',
             { view_mode: 'fullscreen',
               allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
               allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
             }, async function (done, _converse) {
             }, async function (done, _converse) {
 
 
-            let item;
             await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
             await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
-
             const lview = _converse.rooms_list_view
             const lview = _converse.rooms_list_view
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
+            await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
             let room_els = lview.el.querySelectorAll(".available-chatroom");
             let room_els = lview.el.querySelectorAll(".available-chatroom");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
 
 
-            item = room_els[0];
+            let item = room_els[0];
             expect(u.hasClass('open', item)).toBe(true);
             expect(u.hasClass('open', item)).toBe(true);
             expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
             expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
             await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
             await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
+            await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
             room_els = lview.el.querySelectorAll(".open-room");
             room_els = lview.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(2);
             expect(room_els.length).toBe(2);
 
 
@@ -160,7 +158,7 @@
             const room_jid = 'coven@chat.shakespeare.lit';
             const room_jid = 'coven@chat.shakespeare.lit';
             test_utils.openControlBox();
             test_utils.openControlBox();
             await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
             await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
-            const last_stanza = await test_utils.waitUntil(() => _.filter(
+            const last_stanza = await u.waitUntil(() => _.filter(
                 IQ_stanzas,
                 IQ_stanzas,
                 iq => iq.querySelector(
                 iq => iq.querySelector(
                     `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                     `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -195,7 +193,7 @@
                         .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
                         .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
                             .c('value').t(0);
                             .c('value').t(0);
             _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
             _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
-            await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
+            await u.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
             let presence = $pres({
             let presence = $pres({
                     to: _converse.connection.jid,
                     to: _converse.connection.jid,
                     from: 'coven@chat.shakespeare.lit/some1',
                     from: 'coven@chat.shakespeare.lit/some1',
@@ -209,14 +207,14 @@
                 .c('status').attrs({code:'110'});
                 .c('status').attrs({code:'110'});
             _converse.connection._dataRecv(test_utils.createRequest(presence));
             _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
-            await test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".open-room").length, 500);
+            await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".open-room").length, 500);
             const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
             const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
             const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
             info_el.click();
             info_el.click();
 
 
             const  modal = view.model.room_details_modal;
             const  modal = view.model.room_details_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             let els = modal.el.querySelectorAll('p.room-info');
             let els = modal.el.querySelectorAll('p.room-info');
             expect(els[0].textContent).toBe("Name: A Dark Cave")
             expect(els[0].textContent).toBe("Name: A Dark Cave")
             expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
             expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
@@ -269,7 +267,7 @@
             await test_utils.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
             await test_utils.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
             expect(_converse.chatboxes.length).toBe(2);
             expect(_converse.chatboxes.length).toBe(2);
             const lview = _converse.rooms_list_view
             const lview = _converse.rooms_list_view
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
+            await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
             let room_els = lview.el.querySelectorAll(".open-room");
             let room_els = lview.el.querySelectorAll(".open-room");
             expect(room_els.length).toBe(1);
             expect(room_els.length).toBe(1);
             const close_el = _converse.rooms_list_view.el.querySelector(".close-room");
             const close_el = _converse.rooms_list_view.el.querySelector(".close-room");
@@ -290,7 +288,7 @@
 
 
             test_utils.openControlBox();
             test_utils.openControlBox();
             const room_jid = 'kitchen@conference.shakespeare.lit';
             const room_jid = 'kitchen@conference.shakespeare.lit';
-            await test_utils.waitUntil(() => !_.isUndefined(_converse.rooms_list_view), 500);
+            await u.waitUntil(() => !_.isUndefined(_converse.rooms_list_view), 500);
             await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'romeo');
             await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'romeo');
             const view = _converse.chatboxviews.get(room_jid);
             const view = _converse.chatboxviews.get(room_jid);
             view.model.set({'minimized': true});
             view.model.set({'minimized': true});
@@ -305,7 +303,7 @@
                 }).c('body').t('foo').tree());
                 }).c('body').t('foo').tree());
 
 
             const lview = _converse.rooms_list_view
             const lview = _converse.rooms_list_view
-            await test_utils.waitUntil(() => lview.el.querySelectorAll(".available-chatroom").length, 500);
+            await u.waitUntil(() => lview.el.querySelectorAll(".available-chatroom").length, 500);
 
 
             // If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
             // If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
             let room_el = lview.el.querySelector(".available-chatroom");
             let room_el = lview.el.querySelector(".available-chatroom");
@@ -320,7 +318,7 @@
                     type: 'groupchat'
                     type: 'groupchat'
                 }).c('body').t('romeo: Your attention is required').tree()
                 }).c('body').t('romeo: Your attention is required').tree()
             );
             );
-            await test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator").length);
+            await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator").length);
             spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
             spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
             let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
             let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
             expect(indicator_el.textContent).toBe('1');
             expect(indicator_el.textContent).toBe('1');
@@ -332,7 +330,7 @@
                     type: 'groupchat'
                     type: 'groupchat'
                 }).c('body').t('romeo: and another thing...').tree()
                 }).c('body').t('romeo: and another thing...').tree()
             );
             );
-            await test_utils.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
+            await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
             indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
             indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
             expect(indicator_el.textContent).toBe('2');
             expect(indicator_el.textContent).toBe('2');
 
 

+ 62 - 63
spec/roster.js

@@ -9,7 +9,6 @@
     const sizzle = converse.env.sizzle;
     const sizzle = converse.env.sizzle;
     const u = converse.env.utils;
     const u = converse.env.utils;
 
 
-
     const checkHeaderToggling = async function (group) {
     const checkHeaderToggling = async function (group) {
         const toggle = group.querySelector('a.group-toggle');
         const toggle = group.querySelector('a.group-toggle');
         expect(u.isVisible(group)).toBeTruthy();
         expect(u.isVisible(group)).toBeTruthy();
@@ -18,11 +17,11 @@
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeTruthy();
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeTruthy();
         toggle.click();
         toggle.click();
 
 
-        await test_utils.waitUntil(() => group.querySelectorAll('ul.collapsed').length === 1);
+        await u.waitUntil(() => group.querySelectorAll('ul.collapsed').length === 1);
         expect(u.hasClass('fa-caret-right', toggle.firstElementChild)).toBeTruthy();
         expect(u.hasClass('fa-caret-right', toggle.firstElementChild)).toBeTruthy();
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeFalsy();
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeFalsy();
         toggle.click();
         toggle.click();
-        await test_utils.waitUntil(() => group.querySelectorAll('li').length === _.filter(group.querySelectorAll('li'), u.isVisible).length);
+        await u.waitUntil(() => group.querySelectorAll('li').length === _.filter(group.querySelectorAll('li'), u.isVisible).length);
         expect(u.hasClass('fa-caret-right', toggle.firstElementChild)).toBeFalsy();
         expect(u.hasClass('fa-caret-right', toggle.firstElementChild)).toBeFalsy();
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeTruthy();
         expect(u.hasClass('fa-caret-down', toggle.firstElementChild)).toBeTruthy();
     };
     };
@@ -37,7 +36,7 @@
 
 
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
             const IQs = _converse.connection.IQ_stanzas;
             const IQs = _converse.connection.IQ_stanzas;
-            const stanza = await test_utils.waitUntil(
+            const stanza = await u.waitUntil(
                 () => _.filter(IQs, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop());
                 () => _.filter(IQs, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop());
             expect(_converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched')).toBeFalsy();
             expect(_converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched')).toBeFalsy();
 
 
@@ -54,7 +53,7 @@
             }).c('item', {'jid': 'nurse@example.com'}).up()
             }).c('item', {'jid': 'nurse@example.com'}).up()
               .c('item', {'jid': 'romeo@example.com'})
               .c('item', {'jid': 'romeo@example.com'})
             _converse.connection._dataRecv(test_utils.createRequest(result));
             _converse.connection._dataRecv(test_utils.createRequest(result));
-            await test_utils.waitUntil(() => _converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched'));
+            await u.waitUntil(() => _converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched'));
             done();
             done();
         }));
         }));
 
 
@@ -64,7 +63,7 @@
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
             const IQ_stanzas = _converse.connection.IQ_stanzas;
             const IQ_stanzas = _converse.connection.IQ_stanzas;
-            let stanza = await test_utils.waitUntil(
+            let stanza = await u.waitUntil(
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop()
                 () => _.filter(IQ_stanzas, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop()
             );
             );
             expect(_converse.roster.data.get('version')).toBeUndefined();
             expect(_converse.roster.data.get('version')).toBeUndefined();
@@ -83,7 +82,7 @@
               .c('item', {'jid': 'romeo@example.com'})
               .c('item', {'jid': 'romeo@example.com'})
             _converse.connection._dataRecv(test_utils.createRequest(result));
             _converse.connection._dataRecv(test_utils.createRequest(result));
 
 
-            await test_utils.waitUntil(() => _converse.roster.models.length > 1);
+            await u.waitUntil(() => _converse.roster.models.length > 1);
             expect(_converse.roster.data.get('version')).toBe('ver7');
             expect(_converse.roster.data.get('version')).toBe('ver7');
             expect(_converse.roster.models.length).toBe(2);
             expect(_converse.roster.models.length).toBe(2);
 
 
@@ -132,7 +131,7 @@
                     return el.isConnected && flyout.offsetHeight < panel.scrollHeight;
                     return el.isConnected && flyout.offsetHeight < panel.scrollHeight;
                 }
                 }
                 const el = _converse.rosterview.roster_el;
                 const el = _converse.rosterview.roster_el;
-                await test_utils.waitUntil(() => hasScrollBar(el) ? u.isVisible(filter) : !u.isVisible(filter), 900);
+                await u.waitUntil(() => hasScrollBar(el) ? u.isVisible(filter) : !u.isVisible(filter), 900);
                 done();
                 done();
             }));
             }));
 
 
@@ -147,11 +146,11 @@
                 const roster = _converse.rosterview.roster_el;
                 const roster = _converse.rosterview.roster_el;
                 _converse.rosterview.filter_view.delegateEvents();
                 _converse.rosterview.filter_view.delegateEvents();
 
 
-                const contacts = await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
+                const contacts = await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 filter.value = "juliet";
                 filter.value = "juliet";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 1), 600);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 1), 600);
                 // Only one roster contact is now visible
                 // Only one roster contact is now visible
                 let visible_contacts = sizzle('li', roster).filter(u.isVisible);
                 let visible_contacts = sizzle('li', roster).filter(u.isVisible);
                 expect(visible_contacts.length).toBe(1);
                 expect(visible_contacts.length).toBe(1);
@@ -164,7 +163,7 @@
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter.value = "j";
                 filter.value = "j";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 2), 700);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 2), 700);
 
 
                 visible_contacts = sizzle('li', roster).filter(u.isVisible);
                 visible_contacts = sizzle('li', roster).filter(u.isVisible);
                 expect(visible_contacts.length).toBe(2);
                 expect(visible_contacts.length).toBe(2);
@@ -177,14 +176,14 @@
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter.value = "xxx";
                 filter.value = "xxx";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 0), 600);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 0), 600);
                 visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
                 visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
                 expect(visible_groups.length).toBe(0);
                 expect(visible_groups.length).toBe(0);
 
 
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter.value = "";
                 filter.value = "";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 done();
                 done();
             }));
             }));
@@ -201,10 +200,10 @@
                 const roster = _converse.rosterview.roster_el;
                 const roster = _converse.rosterview.roster_el;
                 _converse.rosterview.filter_view.delegateEvents();
                 _converse.rosterview.filter_view.delegateEvents();
 
 
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
                 filter.value = "la";
                 filter.value = "la";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 3), 600);
+                await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 3), 600);
 
 
                 // Five roster contact is now visible
                 // Five roster contact is now visible
                 const visible_contacts = sizzle('li', roster).filter(u.isVisible);
                 const visible_contacts = sizzle('li', roster).filter(u.isVisible);
@@ -222,7 +221,7 @@
                     groups: ['newgroup'],
                     groups: ['newgroup'],
                     fullname: 'Valentine'
                     fullname: 'Valentine'
                 });
                 });
-                await test_utils.waitUntil(() => sizzle('.roster-group[data-group="newgroup"] li', roster).length, 300);
+                await u.waitUntil(() => sizzle('.roster-group[data-group="newgroup"] li', roster).length, 300);
                 visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
                 visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
                 // The "newgroup" group doesn't appear
                 // The "newgroup" group doesn't appear
                 expect(visible_groups.length).toBe(3);
                 expect(visible_groups.length).toBe(3);
@@ -246,14 +245,14 @@
                 var button = _converse.rosterview.el.querySelector('span[data-type="groups"]');
                 var button = _converse.rosterview.el.querySelector('span[data-type="groups"]');
                 button.click();
                 button.click();
 
 
-                const contacts = await test_utils.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
+                const contacts = await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600);
                 expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(5);
                 expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(5);
 
 
                 var filter = _converse.rosterview.el.querySelector('.roster-filter');
                 var filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter.value = "colleagues";
                 filter.value = "colleagues";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
 
 
-                await test_utils.waitUntil(() => (sizzle('div.roster-group:not(.collapsed)', roster).length === 1), 600);
+                await u.waitUntil(() => (sizzle('div.roster-group:not(.collapsed)', roster).length === 1), 600);
                 expect(sizzle('div.roster-group:not(.collapsed)', roster).pop().firstElementChild.textContent.trim()).toBe('colleagues');
                 expect(sizzle('div.roster-group:not(.collapsed)', roster).pop().firstElementChild.textContent.trim()).toBe('colleagues');
                 expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(u.isVisible).length).toBe(3);
                 expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(u.isVisible).length).toBe(3);
                 // Check that all contacts under the group are shown
                 // Check that all contacts under the group are shown
@@ -263,13 +262,13 @@
                 filter.value = "xxx";
                 filter.value = "xxx";
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
 
 
-                await test_utils.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 5), 700);
+                await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 5), 700);
                 expect(roster.querySelectorAll('div.roster-group:not(.collapsed) a').length).toBe(0);
                 expect(roster.querySelectorAll('div.roster-group:not(.collapsed) a').length).toBe(0);
 
 
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter = _converse.rosterview.el.querySelector('.roster-filter');
                 filter.value = ""; // Check that groups are shown again, when the filter string is cleared.
                 filter.value = ""; // Check that groups are shown again, when the filter string is cleared.
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
                 u.triggerEvent(filter, "keydown", "KeyboardEvent");
-                await test_utils.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 0), 700);
+                await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 0), 700);
                 expect(sizzle('div.roster-group:not(collapsed)', roster).length).toBe(5);
                 expect(sizzle('div.roster-group:not(collapsed)', roster).length).toBe(5);
                 expect(sizzle('div.roster-group:not(collapsed) li', roster).length).toBe(15);
                 expect(sizzle('div.roster-group:not(collapsed) li', roster).length).toBe(15);
                 done();
                 done();
@@ -291,7 +290,7 @@
                 expect(u.hasClass('hidden', _converse.rosterview.el.querySelector('.roster-filter-form .clear-input'))).toBeTruthy();
                 expect(u.hasClass('hidden', _converse.rosterview.el.querySelector('.roster-filter-form .clear-input'))).toBeTruthy();
 
 
                 const isHidden = _.partial(u.hasClass, 'hidden');
                 const isHidden = _.partial(u.hasClass, 'hidden');
-                await test_utils.waitUntil(() => !isHidden(_converse.rosterview.el.querySelector('.roster-filter-form .clear-input')), 900);
+                await u.waitUntil(() => !isHidden(_converse.rosterview.el.querySelector('.roster-filter-form .clear-input')), 900);
                 _converse.rosterview.el.querySelector('.clear-input').click();
                 _converse.rosterview.el.querySelector('.clear-input').click();
                 expect(document.querySelector('.roster-filter').value).toBe("");
                 expect(document.querySelector('.roster-filter').value).toBe("");
                 done();
                 done();
@@ -313,20 +312,20 @@
                 const button = _converse.rosterview.el.querySelector('span[data-type="state"]');
                 const button = _converse.rosterview.el.querySelector('span[data-type="state"]');
                 button.click();
                 button.click();
                 const roster = _converse.rosterview.roster_el;
                 const roster = _converse.rosterview.roster_el;
-                await test_utils.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 15, 900);
+                await u.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 15, 900);
                 const filter = _converse.rosterview.el.querySelector('.state-type');
                 const filter = _converse.rosterview.el.querySelector('.state-type');
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
                 filter.value = "online";
                 filter.value = "online";
                 u.triggerEvent(filter, 'change');
                 u.triggerEvent(filter, 'change');
 
 
-                await test_utils.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 1, 900);
+                await u.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 1, 900);
                 expect(sizzle('li', roster).filter(u.isVisible).pop().textContent.trim()).toBe('Lord Montague');
                 expect(sizzle('li', roster).filter(u.isVisible).pop().textContent.trim()).toBe('Lord Montague');
-                await test_utils.waitUntil(() => sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length === 1, 900);
+                await u.waitUntil(() => sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length === 1, 900);
                 const ul = sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).pop();
                 const ul = sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).pop();
                 expect(ul.parentElement.firstElementChild.textContent.trim()).toBe('friends & acquaintences');
                 expect(ul.parentElement.firstElementChild.textContent.trim()).toBe('friends & acquaintences');
                 filter.value = "dnd";
                 filter.value = "dnd";
                 u.triggerEvent(filter, 'change');
                 u.triggerEvent(filter, 'change');
-                await test_utils.waitUntil(() => sizzle('li', roster).filter(u.isVisible).pop().textContent.trim() === 'Friar Laurence', 900);
+                await u.waitUntil(() => sizzle('li', roster).filter(u.isVisible).pop().textContent.trim() === 'Friar Laurence', 900);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(1);
                 expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(1);
                 done();
                 done();
             }));
             }));
@@ -348,7 +347,7 @@
                 test_utils.createGroupedContacts(_converse);
                 test_utils.createGroupedContacts(_converse);
                 // Check that the groups appear alphabetically and that
                 // Check that the groups appear alphabetically and that
                 // requesting and pending contacts are last.
                 // requesting and pending contacts are last.
-                await test_utils.waitUntil(() => sizzle('.roster-group a.group-toggle', _converse.rosterview.el).length);
+                await u.waitUntil(() => sizzle('.roster-group a.group-toggle', _converse.rosterview.el).length);
                 const group_titles = _.map(
                 const group_titles = _.map(
                     sizzle('.roster-group a.group-toggle', _converse.rosterview.el),
                     sizzle('.roster-group a.group-toggle', _converse.rosterview.el),
                     o => o.textContent.trim()
                     o => o.textContent.trim()
@@ -393,7 +392,7 @@
 
 
                 // Check that the groups appear alphabetically and that
                 // Check that the groups appear alphabetically and that
                 // requesting and pending contacts are last.
                 // requesting and pending contacts are last.
-                let group_titles = await test_utils.waitUntil(() => {
+                let group_titles = await u.waitUntil(() => {
                     const toggles = sizzle('.roster-group a.group-toggle', _converse.rosterview.el);
                     const toggles = sizzle('.roster-group a.group-toggle', _converse.rosterview.el);
                     if (_.reduce(toggles, (result, t) => result && u.isVisible(t), true)) {
                     if (_.reduce(toggles, (result, t) => result && u.isVisible(t), true)) {
                         return _.map(toggles, o => o.textContent.trim());
                         return _.map(toggles, o => o.textContent.trim());
@@ -405,7 +404,7 @@
 
 
                 const contact = _converse.roster.get('groupchanger@montague.lit');
                 const contact = _converse.roster.get('groupchanger@montague.lit');
                 contact.set({'groups': ['secondgroup']});
                 contact.set({'groups': ['secondgroup']});
-                group_titles = await test_utils.waitUntil(() => {
+                group_titles = await u.waitUntil(() => {
                     const toggles = sizzle('.roster-group[data-group="secondgroup"] a.group-toggle', _converse.rosterview.el);
                     const toggles = sizzle('.roster-group[data-group="secondgroup"] a.group-toggle', _converse.rosterview.el);
                     if (_.reduce(toggles, (result, t) => result && u.isVisible(t), true)) {
                     if (_.reduce(toggles, (result, t) => result && u.isVisible(t), true)) {
                         return _.map(toggles, o => o.textContent.trim());
                         return _.map(toggles, o => o.textContent.trim());
@@ -435,7 +434,7 @@
                         fullname: mock.cur_names[i]
                         fullname: mock.cur_names[i]
                     });
                     });
                 }
                 }
-                await test_utils.waitUntil(() => (sizzle('li', _converse.rosterview.el).filter(u.isVisible).length === 30), 600);
+                await u.waitUntil(() => (sizzle('li', _converse.rosterview.el).filter(u.isVisible).length === 30), 600);
                 // Check that usernames appear alphabetically per group
                 // Check that usernames appear alphabetically per group
                 _.each(groups, function (name) {
                 _.each(groups, function (name) {
                     const contacts = sizzle('.roster-group[data-group="'+name+'"] ul li', _converse.rosterview.el);
                     const contacts = sizzle('.roster-group[data-group="'+name+'"] ul li', _converse.rosterview.el);
@@ -476,9 +475,9 @@
                 const toggle = view.el.querySelector('a.group-toggle');
                 const toggle = view.el.querySelector('a.group-toggle');
                 expect(view.model.get('state')).toBe('opened');
                 expect(view.model.get('state')).toBe('opened');
                 toggle.click();
                 toggle.click();
-                await test_utils.waitUntil(() => view.model.get('state') === 'closed');
+                await u.waitUntil(() => view.model.get('state') === 'closed');
                 toggle.click();
                 toggle.click();
-                await test_utils.waitUntil(() => view.model.get('state') === 'opened');
+                await u.waitUntil(() => view.model.get('state') === 'opened');
                 done();
                 done();
             }));
             }));
         });
         });
@@ -497,7 +496,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
+                await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
                 await checkHeaderToggling.apply(
                 await checkHeaderToggling.apply(
                     _converse,
                     _converse,
                     [_converse.rosterview.get('Pending contacts').el]
                     [_converse.rosterview.get('Pending contacts').el]
@@ -531,7 +530,7 @@
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
                 expect(u.isVisible(_converse.rosterview.el)).toEqual(true);
                 expect(u.isVisible(_converse.rosterview.el)).toEqual(true);
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.el.querySelectorAll('li').length).toBe(3);
                 expect(_converse.rosterview.el.querySelectorAll('li').length).toBe(3);
@@ -546,7 +545,7 @@
 
 
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(u.isVisible(_converse.rosterview.el)).toBe(true);
                 expect(u.isVisible(_converse.rosterview.el)).toBe(true);
                 expect(sizzle('li', _converse.rosterview.el).filter(u.isVisible).length).toBe(3);
                 expect(sizzle('li', _converse.rosterview.el).filter(u.isVisible).length).toBe(3);
@@ -567,14 +566,14 @@
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
                 spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
                 spyOn(contact, 'removeFromRoster').and.callThrough();
                 spyOn(contact, 'removeFromRoster').and.callThrough();
-                await test_utils.waitUntil(() => sizzle(".pending-contact-name:contains('"+name+"')", _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle(".pending-contact-name:contains('"+name+"')", _converse.rosterview.el).length, 700);
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_IQ = iq;
                     sent_IQ = iq;
                     callback();
                     callback();
                 });
                 });
                 sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, _converse.rosterview.el).pop().click();
                 sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, _converse.rosterview.el).pop().click();
-                await test_utils.waitUntil(() => (sizzle(".pending-contact-name:contains('"+name+"')", _converse.rosterview.el).length === 0), 1000);
+                await u.waitUntil(() => (sizzle(".pending-contact-name:contains('"+name+"')", _converse.rosterview.el).length === 0), 1000);
                 expect(window.confirm).toHaveBeenCalled();
                 expect(window.confirm).toHaveBeenCalled();
                 expect(contact.removeFromRoster).toHaveBeenCalled();
                 expect(contact.removeFromRoster).toHaveBeenCalled();
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
@@ -600,7 +599,7 @@
                     fullname: name
                     fullname: name
                 });
                 });
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
-                await test_utils.waitUntil(() => {
+                await u.waitUntil(() => {
                     const el = _converse.rosterview.get('Pending contacts').el;
                     const el = _converse.rosterview.get('Pending contacts').el;
                     return u.isVisible(el) && _.filter(el.querySelectorAll('li'), li => u.isVisible(li)).length;
                     return u.isVisible(el) && _.filter(el.querySelectorAll('li'), li => u.isVisible(li)).length;
                 }, 700)
                 }, 700)
@@ -620,7 +619,7 @@
 
 
                 const stanza = u.toStanza(`<iq id="${iq.getAttribute('id')}" to="romeo@montague.lit/orchard" type="result"/>`);
                 const stanza = u.toStanza(`<iq id="${iq.getAttribute('id')}" to="romeo@montague.lit/orchard" type="result"/>`);
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => !u.isVisible(_converse.rosterview.get('Pending contacts').el));
+                await u.waitUntil(() => !u.isVisible(_converse.rosterview.get('Pending contacts').el));
                 done();
                 done();
             }));
             }));
 
 
@@ -630,7 +629,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => _converse.roster.at(0).vcard.get('fullname'))
+                await u.waitUntil(() => _converse.roster.at(0).vcard.get('fullname'))
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
                 for (var i=0; i<mock.pend_names.length; i++) {
                 for (var i=0; i<mock.pend_names.length; i++) {
                     const name = mock.pend_names[i];
                     const name = mock.pend_names[i];
@@ -657,7 +656,7 @@
                     });
                     });
                     expect(_converse.rosterview.update).toHaveBeenCalled();
                     expect(_converse.rosterview.update).toHaveBeenCalled();
                 }
                 }
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.get('Pending contacts').el).filter(u.isVisible).length, 700);
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.get('Pending contacts').el).filter(u.isVisible).length, 700);
                 // Check that they are sorted alphabetically
                 // Check that they are sorted alphabetically
                 const view = _converse.rosterview.get('Pending contacts');
                 const view = _converse.rosterview.get('Pending contacts');
                 const spans = view.el.querySelectorAll('.pending-xmpp-contact span');
                 const spans = view.el.querySelectorAll('.pending-xmpp-contact span');
@@ -678,7 +677,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
                 await checkHeaderToggling.apply(_converse, [_converse.rosterview.el.querySelector('.roster-group')]);
                 await checkHeaderToggling.apply(_converse, [_converse.rosterview.el.querySelector('.roster-group')]);
                 done();
                 done();
             }));
             }));
@@ -690,7 +689,7 @@
 
 
                 _converse.roster_groups = false;
                 _converse.roster_groups = false;
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
                 _converse.rosterview.el.querySelector('.roster-group a.group-toggle').click();
                 _converse.rosterview.el.querySelector('.roster-group a.group-toggle').click();
                 const name = "Romeo Montague";
                 const name = "Romeo Montague";
                 const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -723,7 +722,7 @@
                     });
                     });
                     expect(_converse.rosterview.update).toHaveBeenCalled();
                     expect(_converse.rosterview.update).toHaveBeenCalled();
                 }
                 }
-                await test_utils.waitUntil(() => sizzle('li', _converse.rosterview.el).length, 600);
+                await u.waitUntil(() => sizzle('li', _converse.rosterview.el).length, 600);
                 // Check that they are sorted alphabetically
                 // Check that they are sorted alphabetically
                 const t = _.reduce(
                 const t = _.reduce(
                     _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact.offline a.open-chat'),
                     _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact.offline a.open-chat'),
@@ -739,7 +738,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('li').length);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('li').length);
                 const name = mock.cur_names[0];
                 const name = mock.cur_names[0];
                 const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const contact = _converse.roster.get(jid);
                 const contact = _converse.roster.get(jid);
@@ -759,7 +758,7 @@
                         `<query xmlns="jabber:iq:roster"><item jid="mercutio@montague.lit" subscription="remove"/></query>`+
                         `<query xmlns="jabber:iq:roster"><item jid="mercutio@montague.lit" subscription="remove"/></query>`+
                     `</iq>`);
                     `</iq>`);
                 expect(contact.removeFromRoster).toHaveBeenCalled();
                 expect(contact.removeFromRoster).toHaveBeenCalled();
-                await test_utils.waitUntil(() => sizzle(".open-chat:contains('"+name+"')", _converse.rosterview.el).length === 0);
+                await u.waitUntil(() => sizzle(".open-chat:contains('"+name+"')", _converse.rosterview.el).length === 0);
                 done();
                 done();
             }));
             }));
 
 
@@ -777,7 +776,7 @@
                     ask: null,
                     ask: null,
                     fullname: name
                     fullname: name
                 });
                 });
-                await test_utils.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
+                await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(contact, 'removeFromRoster').and.callThrough();
                 spyOn(contact, 'removeFromRoster').and.callThrough();
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
@@ -788,7 +787,7 @@
                 expect(window.confirm).toHaveBeenCalled();
                 expect(window.confirm).toHaveBeenCalled();
                 expect(_converse.connection.sendIQ).toHaveBeenCalled();
                 expect(_converse.connection.sendIQ).toHaveBeenCalled();
                 expect(contact.removeFromRoster).toHaveBeenCalled();
                 expect(contact.removeFromRoster).toHaveBeenCalled();
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length === 0);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length === 0);
                 done();
                 done();
             }));
             }));
 
 
@@ -798,7 +797,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
                 let jid, t;
                 let jid, t;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 const roster = _converse.rosterview.el;
                 const roster = _converse.rosterview.el;
@@ -820,7 +819,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 let jid, t;
                 let jid, t;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 const roster = _converse.rosterview.el;
                 const roster = _converse.rosterview.el;
@@ -842,7 +841,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 let jid, t;
                 let jid, t;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 const roster = _converse.rosterview.el;
                 const roster = _converse.rosterview.el;
@@ -864,7 +863,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 var jid, t;
                 var jid, t;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 const roster = _converse.rosterview.el;
                 const roster = _converse.rosterview.el;
@@ -888,7 +887,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 500)
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 500)
                 var jid, t;
                 var jid, t;
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 var roster = _converse.rosterview.el;
                 var roster = _converse.rosterview.el;
@@ -912,7 +911,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 _addContacts(_converse);
                 _addContacts(_converse);
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 let i, jid;
                 let i, jid;
                 for (i=0; i<3; i++) {
                 for (i=0; i<3; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -934,8 +933,8 @@
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     _converse.roster.get(jid).presence.set('show', 'unavailable');
                     _converse.roster.get(jid).presence.set('show', 'unavailable');
                 }
                 }
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('li.online').length)
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelector('li:first-child').textContent.trim() === 'Juliet Capulet', 900);
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('li.online').length)
+                await u.waitUntil(() => _converse.rosterview.el.querySelector('li:first-child').textContent.trim() === 'Juliet Capulet', 900);
                 const contacts = _converse.rosterview.el.querySelectorAll('.current-xmpp-contact');
                 const contacts = _converse.rosterview.el.querySelectorAll('.current-xmpp-contact');
                 for (i=0; i<3; i++) {
                 for (i=0; i<3; i++) {
                     expect(u.hasClass('online', contacts[i])).toBe(true);
                     expect(u.hasClass('online', contacts[i])).toBe(true);
@@ -1019,7 +1018,7 @@
                         nickname: mock.req_names[i]
                         nickname: mock.req_names[i]
                     });
                     });
                 }
                 }
-                await test_utils.waitUntil(() => _converse.rosterview.get('Contact requests').el.querySelectorAll('li').length, 700);
+                await u.waitUntil(() => _converse.rosterview.get('Contact requests').el.querySelectorAll('li').length, 700);
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 expect(_converse.rosterview.update).toHaveBeenCalled();
                 // Check that they are sorted alphabetically
                 // Check that they are sorted alphabetically
                 const children = _converse.rosterview.get('Contact requests').el.querySelectorAll('.requesting-xmpp-contact span');
                 const children = _converse.rosterview.get('Contact requests').el.querySelectorAll('.requesting-xmpp-contact span');
@@ -1044,7 +1043,7 @@
                     'requesting': true,
                     'requesting': true,
                     'nickname': name
                     'nickname': name
                 });
                 });
-                await test_utils.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 900);
+                await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 900);
                 expect(u.isVisible(_converse.rosterview.get('Contact requests').el)).toEqual(true);
                 expect(u.isVisible(_converse.rosterview.get('Contact requests').el)).toEqual(true);
                 expect(sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length).toBe(1);
                 expect(sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length).toBe(1);
                 sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li .decline-xmpp-request'))[0].click();
                 sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li .decline-xmpp-request'))[0].click();
@@ -1059,7 +1058,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
-                await test_utils.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 700);
                 await checkHeaderToggling.apply(
                 await checkHeaderToggling.apply(
                     _converse,
                     _converse,
                     [_converse.rosterview.get('Contact requests').el]
                     [_converse.rosterview.get('Contact requests').el]
@@ -1078,7 +1077,7 @@
                 const jid =  name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid =  name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const contact = _converse.roster.get(jid);
                 const contact = _converse.roster.get(jid);
                 spyOn(contact, 'authorize').and.callFake(() => contact);
                 spyOn(contact, 'authorize').and.callFake(() => contact);
-                await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length)
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length)
                 // TODO: Testing can be more thorough here, the user is
                 // TODO: Testing can be more thorough here, the user is
                 // actually not accepted/authorized because of
                 // actually not accepted/authorized because of
                 // mock_connection.
                 // mock_connection.
@@ -1086,7 +1085,7 @@
                 const req_contact = sizzle(`.req-contact-name:contains("${contact.getDisplayName()}")`, _converse.rosterview.el).pop();
                 const req_contact = sizzle(`.req-contact-name:contains("${contact.getDisplayName()}")`, _converse.rosterview.el).pop();
                 req_contact.parentElement.parentElement.querySelector('.accept-xmpp-request').click();
                 req_contact.parentElement.parentElement.querySelector('.accept-xmpp-request').click();
                 expect(_converse.roster.sendContactAddIQ).toHaveBeenCalled();
                 expect(_converse.roster.sendContactAddIQ).toHaveBeenCalled();
-                await test_utils.waitUntil(() => contact.authorize.calls.count());
+                await u.waitUntil(() => contact.authorize.calls.count());
                 expect(contact.authorize).toHaveBeenCalled();
                 expect(contact.authorize).toHaveBeenCalled();
                 done();
                 done();
             }));
             }));
@@ -1097,7 +1096,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
                 test_utils.createContacts(_converse, 'requesting').openControlBox();
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
                 _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
                 const name = mock.req_names.sort()[1];
                 const name = mock.req_names.sort()[1];
                 const jid =  name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 const jid =  name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -1123,7 +1122,7 @@
 
 
                 let stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
                 let stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                await test_utils.waitUntil(() => sizzle('a:contains("Contact requests")', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('a:contains("Contact requests")', _converse.rosterview.el).length, 700);
                 expect(_converse.roster.pluck('jid').length).toBe(1);
                 expect(_converse.roster.pluck('jid').length).toBe(1);
                 expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
                 expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
                 // Taken from the spec
                 // Taken from the spec
@@ -1191,7 +1190,7 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 test_utils.createContacts(_converse, 'all').openControlBox();
                 test_utils.createContacts(_converse, 'all').openControlBox();
-                await test_utils.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
+                await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
                 for (let i=0; i<mock.cur_names.length; i++) {
                 for (let i=0; i<mock.cur_names.length; i++) {
                     const name = mock.cur_names[i];
                     const name = mock.cur_names[i];
                     const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';

+ 7 - 7
spec/smacks.js

@@ -24,7 +24,7 @@
 
 
             _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
             _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
             const sent_stanzas = _converse.connection.sent_stanzas;
             const sent_stanzas = _converse.connection.sent_stanzas;
-            let stanza = await test_utils.waitUntil(() =>
+            let stanza = await u.waitUntil(() =>
                 sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
                 sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
 
 
             expect(_converse.session.get('smacks_enabled')).toBe(false);
             expect(_converse.session.get('smacks_enabled')).toBe(false);
@@ -34,10 +34,10 @@
             _converse.connection._dataRecv(test_utils.createRequest(result));
             _converse.connection._dataRecv(test_utils.createRequest(result));
             expect(_converse.session.get('smacks_enabled')).toBe(true);
             expect(_converse.session.get('smacks_enabled')).toBe(true);
 
 
-            await test_utils.waitUntil(() => view.renderControlBoxPane.calls.count());
+            await u.waitUntil(() => view.renderControlBoxPane.calls.count());
 
 
             let IQ_stanzas = _converse.connection.IQ_stanzas;
             let IQ_stanzas = _converse.connection.IQ_stanzas;
-            await test_utils.waitUntil(() => IQ_stanzas.length === 4);
+            await u.waitUntil(() => IQ_stanzas.length === 4);
 
 
             let iq = IQ_stanzas.pop();
             let iq = IQ_stanzas.pop();
             expect(Strophe.serialize(iq)).toBe(
             expect(Strophe.serialize(iq)).toBe(
@@ -69,7 +69,7 @@
             // test handling of ack requests
             // test handling of ack requests
             let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
             let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
             _converse.connection._dataRecv(test_utils.createRequest(r));
             _converse.connection._dataRecv(test_utils.createRequest(r));
-            ack = await test_utils.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
+            ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
             expect(Strophe.serialize(ack)).toBe('<a h="0" xmlns="urn:xmpp:sm:3"/>');
             expect(Strophe.serialize(ack)).toBe('<a h="0" xmlns="urn:xmpp:sm:3"/>');
 
 
             const disco_result = $iq({
             const disco_result = $iq({
@@ -92,14 +92,14 @@
 
 
             r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
             r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
             _converse.connection._dataRecv(test_utils.createRequest(r));
             _converse.connection._dataRecv(test_utils.createRequest(r));
-            ack = await test_utils.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
+            ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
             expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
             expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
 
 
             // test session resumption
             // test session resumption
             _converse.connection.IQ_stanzas = [];
             _converse.connection.IQ_stanzas = [];
             IQ_stanzas = _converse.connection.IQ_stanzas;
             IQ_stanzas = _converse.connection.IQ_stanzas;
             _converse.api.connection.reconnect();
             _converse.api.connection.reconnect();
-            stanza = await test_utils.waitUntil(() =>
+            stanza = await u.waitUntil(() =>
                 sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
                 sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
             expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
             expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
 
 
@@ -110,7 +110,7 @@
             expect(sizzle('enable', sent_stanzas).length).toBe(0);
             expect(sizzle('enable', sent_stanzas).length).toBe(0);
             expect(_converse.session.get('smacks_enabled')).toBe(true);
             expect(_converse.session.get('smacks_enabled')).toBe(true);
 
 
-            await test_utils.waitUntil(() => IQ_stanzas.length === 2);
+            await u.waitUntil(() => IQ_stanzas.length === 2);
 
 
             // Test that unacked stanzas get resent out
             // Test that unacked stanzas get resent out
             iq = IQ_stanzas.pop();
             iq = IQ_stanzas.pop();

+ 6 - 6
spec/spoilers.js

@@ -11,7 +11,7 @@
 
 
         it("can be received with a hint",
         it("can be received with a hint",
             mock.initConverse(
             mock.initConverse(
-                null, ['rosterGroupsFetched'], {},
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                 async (done, _converse) => {
                 async (done, _converse) => {
 
 
             await test_utils.waitForRoster(_converse, 'current');
             await test_utils.waitForRoster(_converse, 'current');
@@ -37,7 +37,7 @@
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
             const view = _converse.chatboxviews.get(sender_jid);
             const view = _converse.chatboxviews.get(sender_jid);
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-            await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
+            await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
             expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
             expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
             const message_content = view.el.querySelector('.chat-msg__text');
             const message_content = view.el.querySelector('.chat-msg__text');
             expect(message_content.textContent).toBe(spoiler);
             expect(message_content.textContent).toBe(spoiler);
@@ -48,7 +48,7 @@
 
 
         it("can be received without a hint",
         it("can be received without a hint",
             mock.initConverse(
             mock.initConverse(
-                null, ['rosterGroupsFetched'], {},
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                 async (done, _converse) => {
                 async (done, _converse) => {
 
 
             await test_utils.waitForRoster(_converse, 'current');
             await test_utils.waitForRoster(_converse, 'current');
@@ -71,7 +71,7 @@
             await _converse.chatboxes.onMessage(msg);
             await _converse.chatboxes.onMessage(msg);
             const view = _converse.chatboxviews.get(sender_jid);
             const view = _converse.chatboxviews.get(sender_jid);
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             await new Promise((resolve, reject) => view.once('messageInserted', resolve));
-            await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
+            await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
             expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Mercutio')).toBeTruthy();
             expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Mercutio')).toBeTruthy();
             const message_content = view.el.querySelector('.chat-msg__text');
             const message_content = view.el.querySelector('.chat-msg__text');
             expect(message_content.textContent).toBe(spoiler);
             expect(message_content.textContent).toBe(spoiler);
@@ -103,7 +103,7 @@
             const view = _converse.api.chatviews.get(contact_jid);
             const view = _converse.api.chatviews.get(contact_jid);
             spyOn(_converse.connection, 'send');
             spyOn(_converse.connection, 'send');
 
 
-            await test_utils.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
+            await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
             let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
             let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
             spoiler_toggle.click();
             spoiler_toggle.click();
 
 
@@ -175,7 +175,7 @@
             await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
             await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
 
 
-            await test_utils.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
+            await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
             let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
             let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
             spoiler_toggle.click();
             spoiler_toggle.click();
 
 

+ 7 - 7
spec/user-details-modal.js

@@ -24,13 +24,13 @@
 
 
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             test_utils.openChatBoxFor(_converse, contact_jid);
             test_utils.openChatBoxFor(_converse, contact_jid);
-            await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
+            await u.waitUntil(() => _converse.chatboxes.length > 1);
             const view = _converse.chatboxviews.get(contact_jid);
             const view = _converse.chatboxviews.get(contact_jid);
             let show_modal_button = view.el.querySelector('.show-user-details-modal');
             let show_modal_button = view.el.querySelector('.show-user-details-modal');
             expect(u.isVisible(show_modal_button)).toBeTruthy();
             expect(u.isVisible(show_modal_button)).toBeTruthy();
             show_modal_button.click();
             show_modal_button.click();
             const modal = view.user_details_modal;
             const modal = view.user_details_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+            await u.waitUntil(() => u.isVisible(modal.el), 1000);
             spyOn(window, 'confirm').and.returnValue(true);
             spyOn(window, 'confirm').and.returnValue(true);
             spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback) {
             spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback) {
                 callback();
                 callback();
@@ -38,7 +38,7 @@
             let remove_contact_button = modal.el.querySelector('button.remove-contact');
             let remove_contact_button = modal.el.querySelector('button.remove-contact');
             expect(u.isVisible(remove_contact_button)).toBeTruthy();
             expect(u.isVisible(remove_contact_button)).toBeTruthy();
             remove_contact_button.click();
             remove_contact_button.click();
-            await test_utils.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
+            await u.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
 
 
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button.click();
             show_modal_button.click();
@@ -62,7 +62,7 @@
             expect(u.isVisible(show_modal_button)).toBeTruthy();
             expect(u.isVisible(show_modal_button)).toBeTruthy();
             show_modal_button.click();
             show_modal_button.click();
             const modal = view.user_details_modal;
             const modal = view.user_details_modal;
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 2000);
+            await u.waitUntil(() => u.isVisible(modal.el), 2000);
             spyOn(window, 'confirm').and.returnValue(true);
             spyOn(window, 'confirm').and.returnValue(true);
             spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) {
             spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) {
                 errback();
                 errback();
@@ -70,7 +70,7 @@
             let remove_contact_button = modal.el.querySelector('button.remove-contact');
             let remove_contact_button = modal.el.querySelector('button.remove-contact');
             expect(u.isVisible(remove_contact_button)).toBeTruthy();
             expect(u.isVisible(remove_contact_button)).toBeTruthy();
             remove_contact_button.click();
             remove_contact_button.click();
-            await test_utils.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
+            await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
 
 
             const header = document.querySelector('.alert-danger .modal-title');
             const header = document.querySelector('.alert-danger .modal-title');
             expect(header.textContent).toBe("Error");
             expect(header.textContent).toBe("Error");
@@ -79,11 +79,11 @@
             document.querySelector('.alert-danger  button.close').click();
             document.querySelector('.alert-danger  button.close').click();
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button.click();
             show_modal_button.click();
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 2000)
+            await u.waitUntil(() => u.isVisible(modal.el), 2000)
 
 
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button = view.el.querySelector('.show-user-details-modal');
             show_modal_button.click();
             show_modal_button.click();
-            await test_utils.waitUntil(() => u.isVisible(modal.el), 2000)
+            await u.waitUntil(() => u.isVisible(modal.el), 2000)
 
 
             remove_contact_button = modal.el.querySelector('button.remove-contact');
             remove_contact_button = modal.el.querySelector('button.remove-contact');
             expect(u.isVisible(remove_contact_button)).toBeTruthy();
             expect(u.isVisible(remove_contact_button)).toBeTruthy();

+ 13 - 7
src/headless/converse-core.js

@@ -1769,17 +1769,23 @@ _converse.api = {
     },
     },
 
 
     /**
     /**
-     * Wait until a promise is resolved
+     * Wait until a promise is resolved or until the passed in function returns
+     * a truthy value.
      * @method _converse.api.waitUntil
      * @method _converse.api.waitUntil
-     * @param {string} name The name of the promise
+     * @param {string|function} condition - The name of the promise to wait for,
+     * or a function which should eventually return a truthy value.
      * @returns {Promise}
      * @returns {Promise}
      */
      */
-    waitUntil (name) {
-        const promise = _converse.promises[name];
-        if (_.isUndefined(promise)) {
-            return null;
+    waitUntil (condition) {
+        if (_.isFunction(condition)) {
+            return u.waitUntil(condition);
+        } else {
+            const promise = _converse.promises[condition];
+            if (_.isUndefined(promise)) {
+                return null;
+            }
+            return promise;
         }
         }
-        return promise;
     },
     },
 
 
     /**
     /**

+ 62 - 0
src/headless/utils/core.js

@@ -498,4 +498,66 @@ u.getUniqueId = function () {
     });
     });
 };
 };
 
 
+
+/**
+ * Clears the specified timeout and interval.
+ * @param {number} timeout - Id if the timeout to clear.
+ * @param {number} interval - Id of the interval to clear.
+ * @private
+ * @copyright Simen Bekkhus 2016
+ * @license MIT
+ */
+function clearTimers(timeout, interval) {
+    clearTimeout(timeout);
+    clearInterval(interval);
+}
+
+
+/**
+ * Creates a {@link Promise} that resolves if the passed in function returns a truthy value.
+ * Rejects if it throws or does not return truthy within the given max_wait.
+ * @param {Function} func - The function called every check_delay,
+ * and the result of which is the resolved value of the promise.
+ * @param {number} [max_wait=300] - The time to wait before rejecting the promise.
+ * @param {number} [check_delay=3] - The time to wait before each invocation of {func}.
+ * @returns {Promise} A promise resolved with the value of func,
+ * or rejected with the exception thrown by it or it times out.
+ * @copyright Simen Bekkhus 2016
+ * @license MIT
+ */
+u.waitUntil = function (func, max_wait=300, check_delay=3) {
+    // Run the function once without setting up any listeners in case it's already true
+    try {
+        const result = func();
+        if (result) {
+            return Promise.resolve(result);
+        }
+    } catch (e) {
+        return Promise.reject(e);
+    }
+
+    const promise = u.getResolveablePromise();
+
+    function checker () {
+        try {
+            const result = func();
+            if (result) {
+                clearTimers(max_wait_timeout, interval);
+                promise.resolve(result);
+            }
+        } catch (e) {
+            clearTimers(max_wait_timeout, interval);
+            promise.reject(e);
+        }
+    }
+
+    const interval = setInterval(checker, check_delay);
+    const max_wait_timeout = setTimeout(() => {
+        clearTimers(max_wait_timeout, interval);
+        promise.reject(new Error('Wait until promise timed out'));
+    }, max_wait);
+
+    return promise;
+};
+
 export default u;
 export default u;

+ 2 - 4
tests/runner.js

@@ -10,8 +10,7 @@ var config = {
         'mock': 'tests/mock',
         'mock': 'tests/mock',
         'sinon': 'node_modules/sinon/pkg/sinon',
         'sinon': 'node_modules/sinon/pkg/sinon',
         'test-utils': 'tests/utils',
         'test-utils': 'tests/utils',
-        'transcripts': 'converse-logs/converse-logs',
-        'wait-until-promise': 'node_modules/wait-until-promise/index'
+        'transcripts': 'converse-logs/converse-logs'
     },
     },
     shim: {
     shim: {
         'jasmine-html': {
         'jasmine-html': {
@@ -65,12 +64,11 @@ var specs = [
     "spec/http-file-upload"
     "spec/http-file-upload"
 ];
 ];
 
 
-require(['console-reporter', 'mock', 'sinon', 'wait-until-promise'], (ConsoleReporter, mock, sinon, waitUntilPromise) => {
+require(['console-reporter', 'mock', 'sinon'], (ConsoleReporter, mock, sinon) => {
     if (window.view_mode) {
     if (window.view_mode) {
         mock.view_mode = window.view_mode;
         mock.view_mode = window.view_mode;
     }
     }
     window.sinon = sinon;
     window.sinon = sinon;
-    window.waitUntilPromise = waitUntilPromise.default;
     // Load the specs
     // Load the specs
     require(specs, jasmine => {
     require(specs, jasmine => {
         jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
         jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;

+ 22 - 27
tests/utils.js

@@ -1,22 +1,17 @@
 (function (root, factory) {
 (function (root, factory) {
-    define(['es6-promise',  'mock', 'wait-until-promise'], factory);
-}(this, function (Promise, mock, waitUntilPromise) {
-    var _ = converse.env._;
-    var $msg = converse.env.$msg;
-    var $pres = converse.env.$pres;
-    var $iq = converse.env.$iq;
-    var Strophe = converse.env.Strophe;
-    var sizzle = converse.env.sizzle;
-    var u = converse.env.utils;
-    var utils = {};
-
-    if (typeof window.Promise === 'undefined') {
-        waitUntilPromise.setPromiseImplementation(Promise);
-    }
-    utils.waitUntil = waitUntilPromise.default;
+    define(['es6-promise',  'mock'], factory);
+}(this, function (Promise, mock) {
+    const _ = converse.env._;
+    const $msg = converse.env.$msg;
+    const $pres = converse.env.$pres;
+    const $iq = converse.env.$iq;
+    const Strophe = converse.env.Strophe;
+    const sizzle = converse.env.sizzle;
+    const u = converse.env.utils;
+    const utils = {};
 
 
     utils.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
     utils.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
-        const iq = await utils.waitUntil(() => {
+        const iq = await u.waitUntil(() => {
             return _.filter(
             return _.filter(
                 _converse.connection.IQ_stanzas,
                 _converse.connection.IQ_stanzas,
                 (iq) => sizzle(`iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`, iq).length
                 (iq) => sizzle(`iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`, iq).length
@@ -94,7 +89,7 @@
 
 
     utils.openChatBoxFor = function (_converse, jid) {
     utils.openChatBoxFor = function (_converse, jid) {
         _converse.roster.get(jid).trigger("open");
         _converse.roster.get(jid).trigger("open");
-        return utils.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
+        return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
     };
     };
 
 
     utils.openChatRoomViaModal = async function (_converse, jid, nick='') {
     utils.openChatRoomViaModal = async function (_converse, jid, nick='') {
@@ -104,13 +99,13 @@
         roomspanel.el.querySelector('.show-add-muc-modal').click();
         roomspanel.el.querySelector('.show-add-muc-modal').click();
         utils.closeControlBox(_converse);
         utils.closeControlBox(_converse);
         const modal = roomspanel.add_room_modal;
         const modal = roomspanel.add_room_modal;
-        await utils.waitUntil(() => u.isVisible(modal.el), 1500)
+        await u.waitUntil(() => u.isVisible(modal.el), 1500)
         modal.el.querySelector('input[name="chatroom"]').value = jid;
         modal.el.querySelector('input[name="chatroom"]').value = jid;
         if (nick) {
         if (nick) {
             modal.el.querySelector('input[name="nickname"]').value = nick;
             modal.el.querySelector('input[name="nickname"]').value = nick;
         }
         }
         modal.el.querySelector('form input[type="submit"]').click();
         modal.el.querySelector('form input[type="submit"]').click();
-        await utils.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
+        await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
         return _converse.chatboxviews.get(jid);
         return _converse.chatboxviews.get(jid);
     };
     };
 
 
@@ -124,7 +119,7 @@
         const muc_jid = `${room}@${server}`.toLowerCase();
         const muc_jid = `${room}@${server}`.toLowerCase();
         const stanzas = _converse.connection.IQ_stanzas;
         const stanzas = _converse.connection.IQ_stanzas;
         const index = stanzas.length-1;
         const index = stanzas.length-1;
-        const stanza = await utils.waitUntil(() => _.filter(
+        const stanza = await u.waitUntil(() => _.filter(
             stanzas.slice(index),
             stanzas.slice(index),
             iq => iq.querySelector(
             iq => iq.querySelector(
                 `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
                 `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
@@ -168,7 +163,7 @@
     utils.waitForReservedNick = async function (_converse, muc_jid, nick) {
     utils.waitForReservedNick = async function (_converse, muc_jid, nick) {
         const view = _converse.chatboxviews.get(muc_jid);
         const view = _converse.chatboxviews.get(muc_jid);
         const stanzas = _converse.connection.IQ_stanzas;
         const stanzas = _converse.connection.IQ_stanzas;
-        const iq = await utils.waitUntil(() => _.filter(
+        const iq = await u.waitUntil(() => _.filter(
             stanzas,
             stanzas,
             s => sizzle(`iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`, s).length
             s => sizzle(`iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`, s).length
         ).pop());
         ).pop());
@@ -185,13 +180,13 @@
         }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
         }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
             .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
             .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
         _converse.connection._dataRecv(utils.createRequest(stanza));
         _converse.connection._dataRecv(utils.createRequest(stanza));
-        return utils.waitUntil(() => view.model.get('nick'));
+        return u.waitUntil(() => view.model.get('nick'));
     };
     };
 
 
 
 
     utils.returnMemberLists = async function (_converse, muc_jid, members=[]) {
     utils.returnMemberLists = async function (_converse, muc_jid, members=[]) {
         const stanzas = _converse.connection.IQ_stanzas;
         const stanzas = _converse.connection.IQ_stanzas;
-        const member_IQ = await utils.waitUntil(() => _.filter(
+        const member_IQ = await u.waitUntil(() => _.filter(
             stanzas,
             stanzas,
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
         ).pop());
         ).pop());
@@ -211,7 +206,7 @@
         });
         });
         _converse.connection._dataRecv(utils.createRequest(member_list_stanza));
         _converse.connection._dataRecv(utils.createRequest(member_list_stanza));
 
 
-        const admin_IQ = await utils.waitUntil(() => _.filter(
+        const admin_IQ = await u.waitUntil(() => _.filter(
             stanzas,
             stanzas,
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
         ).pop());
         ).pop());
@@ -223,7 +218,7 @@
             }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
             }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
         _converse.connection._dataRecv(utils.createRequest(admin_list_stanza));
         _converse.connection._dataRecv(utils.createRequest(admin_list_stanza));
 
 
-        const owner_IQ = await utils.waitUntil(() => _.filter(
+        const owner_IQ = await u.waitUntil(() => _.filter(
             stanzas,
             stanzas,
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
             s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
         ).pop());
         ).pop());
@@ -262,7 +257,7 @@
         _converse.connection._dataRecv(utils.createRequest(presence));
         _converse.connection._dataRecv(utils.createRequest(presence));
 
 
         const view = _converse.chatboxviews.get(muc_jid);
         const view = _converse.chatboxviews.get(muc_jid);
-        await utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
+        await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
         await utils.returnMemberLists(_converse, muc_jid, members);
         await utils.returnMemberLists(_converse, muc_jid, members);
     };
     };
 
 
@@ -329,7 +324,7 @@
     };
     };
 
 
     utils.waitForRoster = async function (_converse, type='current', length, include_nick=true) {
     utils.waitForRoster = async function (_converse, type='current', length, include_nick=true) {
-        const iq = await utils.waitUntil(() =>
+        const iq = await u.waitUntil(() =>
             _.filter(
             _.filter(
                 _converse.connection.IQ_stanzas,
                 _converse.connection.IQ_stanzas,
                 iq => sizzle(`iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`, iq).length
                 iq => sizzle(`iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`, iq).length