From 866cbf98a2b3ace2d5a76528267dc6ec6ec4a467 Mon Sep 17 00:00:00 2001 From: Rob Brackett Date: Fri, 4 Oct 2024 11:24:12 -0700 Subject: [PATCH] Port relevant tests to Jest --- .npmrc | 3 + jest.config.js | 24 ++ package-lock.json | 392 ++++++++++------------ package.json | 8 +- spec.html | 4 - src/spec/tests/options/loadUtilsOnInit.js | 105 ------ src/spec/tests/static/loadUtils.js | 217 ------------ tests/helpers/helpers.js | 60 +++- tests/helpers/matchers.js | 24 ++ tests/options/loadUtilsOnInit.test.js | 127 +++++++ tests/static/loadUtils.test.js | 198 +++++++++++ 11 files changed, 606 insertions(+), 556 deletions(-) create mode 100644 .npmrc create mode 100644 jest.config.js delete mode 100644 src/spec/tests/options/loadUtilsOnInit.js delete mode 100644 src/spec/tests/static/loadUtils.js create mode 100644 tests/helpers/matchers.js create mode 100644 tests/options/loadUtilsOnInit.test.js create mode 100644 tests/static/loadUtils.test.js diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..05d694f2 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +# Jest uses the `vm` module, which does not have production ready module support +# yet. This option lets us use dynamic imports. +node-options='--experimental-vm-modules' diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..4bd58beb --- /dev/null +++ b/jest.config.js @@ -0,0 +1,24 @@ +/** @type {import('jest').Config} */ +module.exports = { + moduleDirectories: [ + "node_modules", + "build/js", + ], + transform: { + // Most of the build outputs in this project use UMD syntax, but the + // utilities script is a proper ES Module. Unfortunately, Jest cannot treat + // a file with a `.js` extension as ESM unless the whole project is ESM + // (Jest does not use Node.js built-in resolution and loading logic), so + // we have to set up special parsing for that file. + // See also: https://jestjs.io/docs/next/ecmascript-modules + "utils.js$": [ + "babel-jest", + { + plugins: [ + "@babel/plugin-transform-modules-commonjs", + "babel-plugin-add-module-exports", + ], + }, + ], + }, +}; diff --git a/package-lock.json b/package-lock.json index 2d5805d6..b1be2d01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "24.5.2", "license": "MIT", "devDependencies": { + "@babel/plugin-transform-modules-commonjs": "^7.25.7", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", "@types/react": "^18.2.74", @@ -16,6 +17,7 @@ "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", "@vitejs/plugin-vue": "^5.1.2", + "babel-plugin-add-module-exports": "^1.0.4", "cspell": "^8.6.1", "esbuild": "^0.23.0", "eslint": "^8.57.0", @@ -83,12 +85,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -173,15 +175,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -227,63 +229,29 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -293,51 +261,40 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -367,12 +324,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -420,12 +377,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.25.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -611,6 +568,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", @@ -625,33 +599,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -686,13 +657,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -5189,6 +5160,12 @@ "node": ">=8" } }, + "node_modules/babel-plugin-add-module-exports": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.4.tgz", + "integrity": "sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==", + "dev": true + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -9939,7 +9916,7 @@ "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -13320,15 +13297,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -19233,12 +19210,12 @@ } }, "@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, "requires": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" } }, @@ -19295,15 +19272,15 @@ } }, "@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dev": true, "requires": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" } }, "@babel/helper-compilation-targets": { @@ -19342,87 +19319,54 @@ } } }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, "@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dev": true, "requires": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" } }, "@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true }, "@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", - "dev": true, - "requires": { - "@babel/types": "^7.24.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dev": true, "requires": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true }, "@babel/helper-validator-option": { @@ -19443,12 +19387,12 @@ } }, "@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -19486,12 +19430,12 @@ } }, "@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", "dev": true, "requires": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.25.7" } }, "@babel/plugin-syntax-async-generators": { @@ -19620,6 +19564,17 @@ "@babel/helper-plugin-utils": "^7.24.0" } }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" + } + }, "@babel/runtime": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", @@ -19631,30 +19586,27 @@ } }, "@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dev": true, "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dev": true, "requires": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -19677,13 +19629,13 @@ } }, "@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" } }, @@ -22888,6 +22840,12 @@ } } }, + "babel-plugin-add-module-exports": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.4.tgz", + "integrity": "sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==", + "dev": true + }, "babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -26405,7 +26363,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "has-own-prop": { @@ -28867,9 +28825,9 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true }, "json-parse-even-better-errors": { diff --git a/package.json b/package.json index a4b18447..d6810d92 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "license": "MIT", "author": "Jack O'Connor (http://jackocnr.com)", "devDependencies": { + "@babel/plugin-transform-modules-commonjs": "^7.25.7", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", "@types/react": "^18.2.74", @@ -31,6 +32,7 @@ "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", "@vitejs/plugin-vue": "^5.1.2", + "babel-plugin-add-module-exports": "^1.0.4", "cspell": "^8.6.1", "esbuild": "^0.23.0", "eslint": "^8.57.0", @@ -152,11 +154,5 @@ "react/build/IntlTelInput.d.ts" ] } - }, - "jest": { - "moduleDirectories": [ - "node_modules", - "build/js" - ] } } diff --git a/spec.html b/spec.html index adfb2c2d..90995331 100644 --- a/spec.html +++ b/spec.html @@ -72,8 +72,6 @@ - - @@ -88,8 +86,6 @@ - - diff --git a/src/spec/tests/options/loadUtilsOnInit.js b/src/spec/tests/options/loadUtilsOnInit.js deleted file mode 100644 index 3b58da94..00000000 --- a/src/spec/tests/options/loadUtilsOnInit.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict"; - -describe("loadUtilsOnInit:", function() { - useIntlTelInputBuild("build/js/intlTelInput.js"); - - const loadUtilsOnInit = "/build/js/utils.js"; - - beforeEach(function() { - intlSetup(); - input = $("").wrap("div"); - }); - - afterEach(function() { - intlTeardown(); - }); - - it("does not load the utils script if `loadUtilsOnInit` option is not set", async () => { - iti = window.intlTelInput(input[0]); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - - await iti.promise; - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - }); - - it("waits until the page is loaded before loading utils", async () => { - spyOn(window.intlTelInput, "documentReady").and.returnValue(false); - iti = window.intlTelInput(input[0], { - loadUtilsOnInit, - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - - // Ideally, this would dispatch a load event and test that the utils script started loading after that. - // Unfortunately, doing so triggers Jasmine's internals to partially reboot and all the other tests blow up. - iti._handlePageLoad(); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - await expectAsync(iti.promise).toBeResolved(); - }); - - it("loads utils immediately if page is already finished loading", async function() { - spyOn(window.intlTelInput, "documentReady").and.returnValue(true); - iti = window.intlTelInput(input[0], { - loadUtilsOnInit, - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - await expectAsync(iti.promise).toBeResolved(); - }); - - it("rejects with an error if the utils script cannot load", async function() { - spyOn(window.intlTelInput, "documentReady").and.returnValue(true); - iti = window.intlTelInput(input[0], { - loadUtilsOnInit: "/some/incorrect/url", - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - await expectAsync(iti.promise).toBeRejectedWithError(); - }); - - it("works if loadUtilsOnInit is a function", async function() { - const mockUtils = { default: { mockUtils: true } }; - spyOn(window.intlTelInput, "documentReady").and.returnValue(true); - - iti = window.intlTelInput(input[0], { - async loadUtilsOnInit () { - return mockUtils; - }, - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - await expectAsync(iti.promise).toBeResolved(); - - expect(window.intlTelInput.utils).toBe(mockUtils.default); - }); - - describe("in 'withUtils' builds", () => { - useIntlTelInputBuild("build/js/intlTelInputWithUtils.js"); - - it("ignores the `loadUtilsOnInit` option and does not load", async () => { - iti = window.intlTelInput(input[0], { - loadUtilsOnInit, - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - - await iti.promise; - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - }); - - it("Raises an informative error when `utils` is missing", async () => { - delete window.intlTelInput.utils; - iti = window.intlTelInput(input[0], { - loadUtilsOnInit, - }); - - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - await expectAsync(iti.promise).toBeRejectedWithError(/INTENTIONALLY BROKEN/); - }); - }); - -}); diff --git a/src/spec/tests/static/loadUtils.js b/src/spec/tests/static/loadUtils.js deleted file mode 100644 index f2c34c53..00000000 --- a/src/spec/tests/static/loadUtils.js +++ /dev/null @@ -1,217 +0,0 @@ -"use strict"; - -describe("loadUtils:", function() { - useIntlTelInputBuild("build/js/intlTelInput.js"); - - function setTimeoutAsync (milliseconds) { - return new Promise((resolve) => setTimeout(resolve, milliseconds)); - } - - beforeEach(function() { - intlSetup(); - //* Must be in markup for utils loaded handler to work. - input = $("").appendTo("body"); - }); - - afterEach(function() { - intlTeardown(); - }); - - describe("calling loadUtils before init plugin", function() { - - let url = "/build/js/utils.js?v=1"; - let loadResult; - - beforeEach(function() { - loadResult = window.intlTelInput.loadUtils(url); - }); - - it("starts loading the utils", function() { - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - }); - - it("resolves the promise", async function() { - expect(loadResult).toBeInstanceOf(Promise); - await expectAsync(loadResult).toBeResolvedTo(true); - }); - - describe("then init plugin with loadUtilsOnInit option", function() { - - beforeEach(async function() { - iti = window.intlTelInput(input[0], { - loadUtilsOnInit: "some/other/url/ok", - }); - }); - - it("resolves the instance's promise", async function() { - await expectAsync(iti.promise).toBeResolved(); - }); - - }); - - }); - - - - describe("init plugin with loadUtilsOnInit option, but force documentReady=false so it wont fire", function() { - - let url2 = "/build/js/utils.js?v=2"; - let loadUtilsSpy; - - beforeEach(function() { - loadUtilsSpy = spyOn(window.intlTelInput, "loadUtils").and.callThrough(); - - window.intlTelInput.documentReady = () => false; - - iti = window.intlTelInput(input[0], { - loadUtilsOnInit: "some/other/url/ok", - }); - }); - - it("does not start loading the utils", function() { - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(false); - }); - - it("does not resolve the promise", async function() { - await expectAsync(iti.promise).toBePending(); - }); - - - - describe("calling loadUtils", function() { - - let loadUtilsPromise; - - beforeEach(async function() { - loadUtilsPromise = window.intlTelInput.loadUtils(url2); - }); - - it("starts loading the utils", function() { - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - }); - - it("resolves the promise", async function() { - await expectAsync(loadUtilsPromise).toBeResolvedTo(true); - }); - - - - describe("then init another plugin instance with loadUtilsOnInit option", function() { - - var iti2, - input2; - - beforeEach(async function() { - // Wait for previous load to finish. - await loadUtilsPromise; - - input2 = $("").appendTo("body"); - iti2 = window.intlTelInput(input2[0], { - loadUtilsOnInit: "test/url/three/utils.js", - }); - }); - - afterEach(function() { - iti2.destroy(); - input2.remove(); - iti2 = input2 = null; - }); - - it("does resolve the promise immediately", async function() { - await expectAsync(iti2.promise).already.toBeResolved(); - }); - - it("only loads once", function() { - // `loadUtils()` returns undefined if it doesn't do anything. - const loads = loadUtilsSpy.calls.all().filter(call => call.returnValue); - - expect(loads).toHaveLength(1); - }); - - }); - - }); - - }); - - - - describe("force documentReady=true then init plugin with loadUtilsOnInit", function() { - - var url3 = "/build/js/utils.js?v=3"; - - beforeEach(function() { - window.intlTelInput.documentReady = () => true; - iti = window.intlTelInput(input[0], { - loadUtilsOnInit: url3, - }); - }); - - afterEach(async function() { - await iti.promise.catch(() => {}); - }); - - it("resolves the promise immediately", async function() { - await expectAsync(iti.promise).toBeResolved(); - }); - - it("starts loading the utils", function() { - expect(window.intlTelInput.startedLoadingUtilsScript).toEqual(true); - }); - - }); - - - - describe("calling with a function", function() { - const mockUtils = { default: { mymodule: "fakeutils" } }; - - it("uses the object the function resolves with", async () => { - const result = await window.intlTelInput.loadUtils(async () => mockUtils); - - expect(result).toEqual(true); - expect(window.intlTelInput.utils).toBe(mockUtils.default); - }); - - it("rejects if the function rejects", async () => { - const loadPromise = window.intlTelInput.loadUtils(async () => { - throw new Error("Uhoh!"); - }); - - await expectAsync(loadPromise).toBeRejectedWithError("Uhoh!"); - }); - - it("rejects if the function throws", async () => { - const loadPromise = window.intlTelInput.loadUtils(() => { - throw new Error("Uhoh!"); - }); - - await expectAsync(loadPromise).toBeRejectedWithError("Uhoh!"); - }); - - it("rejects if the function returns a non-promise", async () => { - const loadPromise = window.intlTelInput.loadUtils(() => ({ - anObject: "That is not a promise", - })); - - await expectAsync(loadPromise).toBeRejectedWithError(); - }); - - it("rejects if the function resolves to a non-object", async () => { - const loadPromise = window.intlTelInput.loadUtils(async () => "Hello!"); - - await expectAsync(loadPromise).toBeRejectedWithError(); - }); - - it("does not call the function a second time", async () => { - const loader = jasmine.createSpy("mockLoader").and.resolveTo(mockUtils); - - await window.intlTelInput.loadUtils(loader); - await window.intlTelInput.loadUtils(loader); - - expect(loader).toHaveBeenCalledTimes(1); - }); - - }); - -}); diff --git a/tests/helpers/helpers.js b/tests/helpers/helpers.js index a40036f9..98df8154 100644 --- a/tests/helpers/helpers.js +++ b/tests/helpers/helpers.js @@ -1,5 +1,9 @@ require("@testing-library/jest-dom"); -const intlTelInput = require("intlTelInputWithUtils.js"); +const intlTelInputWithUtils = require("intlTelInputWithUtils.js"); + +/** @typedef {typeof import("intl-tel-input").default} IntlTelInputInterface */ +/** @typedef {import("intl-tel-input").Iti} Iti */ +/** @typedef {import("intl-tel-input").SomeOptions} SomeOptions */ exports.totalCountries = 244; @@ -17,19 +21,61 @@ exports.injectInput = ({ inputValue = "", disabled = false } = injectInputDefaul return input; }; -const initPluginDefaults = { input: null, options: {}, inputValue: "" }; - -exports.initPlugin = ({ input = null, options = {}, inputValue = "" } = initPluginDefaults) => { +/** + * Create an intl-tel-input instance to test. + * @param {{intlTelInput?: IntlTelInputInterface, input?: any, inputValue?: string, options?: SomeOptions}} options + * @returns {{input: HTMLInputElement, iti: Iti, container: HTMLElement}} + */ +exports.initPlugin = ({ intlTelInput = intlTelInputWithUtils, input = null, inputValue = "", options = {} } = {}) => { const inputToUse = input || exports.injectInput({ inputValue }); const iti = intlTelInput(inputToUse, options); const container = inputToUse.parentElement; return { input: inputToUse, iti, container }; }; +/** + * Tear down a standard test environment. Optionally takes an intl-tel-input + * instance to tear down, or a copy of the whole library in which to tear down + * all instances. + * @param {Iti|IntlTelInputInterface} iti + */ exports.teardown = (iti) => { - iti.destroy(); + let toDestroy = []; + if (iti?.instances) { + toDestroy = Object.values(iti.instances); + } else if (iti) { + toDestroy = [iti]; + } + + for (const instance of toDestroy) { + // Tests might intentionally set up an instance wrong, so ignore any + // rejections of the instance's promise (otherwise the unhandled + // rejection causes the test to fail). But don't *wait*, since the test + // may also create a state where the promise will never fulfill. + instance.promise.catch(() => {}); + + instance.destroy(); + } + document.body.innerHTML = ""; - jest.clearAllMocks(); + jest.restoreAllMocks(); +}; + +/** + * @param {IntlTelInputInterface} intlTelInput + */ +exports.resetPackageAfterEach = (intlTelInput = intlTelInputWithUtils) => { + const originalUtils = intlTelInput.utils; + + afterEach(function() { + try { + exports.teardown(intlTelInput); + } finally { + // Reset package-wide state. + intlTelInput.utils = originalUtils; + intlTelInput.startedLoadingUtilsScript = false; + } + }); }; exports.getCountryListLength = (container) => { @@ -87,4 +133,4 @@ exports.selectCountryAndTypePlaceholderNumberAsync = async (container, iso2, use }; // strip formatting chars like space, dash, brackets (leaving just numerics and optional plus) -exports.stripFormattingChars = (str) => str.replace(/[^0-9+]/g, ""); \ No newline at end of file +exports.stripFormattingChars = (str) => str.replace(/[^0-9+]/g, ""); diff --git a/tests/helpers/matchers.js b/tests/helpers/matchers.js new file mode 100644 index 00000000..f0f280c0 --- /dev/null +++ b/tests/helpers/matchers.js @@ -0,0 +1,24 @@ +/** Test whether something is a promise. Works across realms. */ +function toBeAPromise(actual) { + const pass = Object.prototype.toString.call(actual) === "[object Promise]"; + return { + pass, + message: () => `expected ${this.utils.printReceived(actual)}${pass ? " not" : ""} to be a promise`, + }; +} + +async function toBePending(actual) { + const pending = Symbol(); + const resolution = await Promise.race([actual, Promise.resolve(pending)]); + const pass = resolution === pending; + + return { + pass, + message: () => `expected ${this.utils.printReceived(actual)}${pass ? " not" : ""} to be a pending`, + }; +} + +expect.extend({ + toBeAPromise, + toBePending, +}); diff --git a/tests/options/loadUtilsOnInit.test.js b/tests/options/loadUtilsOnInit.test.js new file mode 100644 index 00000000..9cc449a8 --- /dev/null +++ b/tests/options/loadUtilsOnInit.test.js @@ -0,0 +1,127 @@ +/** + * @jest-environment jsdom + */ + +const intlTelInput = require("intl-tel-input"); +const { initPlugin, resetPackageAfterEach } = require("../helpers/helpers"); + +describe("loadUtilsOnInit", () => { + resetPackageAfterEach(intlTelInput); + + const loadUtilsOnInit = "intl-tel-input/utils"; + + it("does not load the utils script if `loadUtilsOnInit` option is not set", async () => { + const { iti } = initPlugin({ intlTelInput }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + + await iti.promise; + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + }); + + it("loads the utils script successfully", async () => { + expect(intlTelInput).not.toHaveProperty("utils.isValidNumber"); + + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit }, + }); + + await iti.promise; + expect(intlTelInput).toHaveProperty("utils.isValidNumber"); + }); + + it("waits until the page is loaded before loading utils", async () => { + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(false); + + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + + const loadEvent = new Event("load"); + window.dispatchEvent(loadEvent); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + + await iti.promise; + }); + + it("loads utils immediately if page is already finished loading", async function() { + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(true); + + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + + await iti.promise; + }); + + it("rejects with an error if the utils script cannot load", async function() { + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(true); + + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit: "/some/incorrect/url" }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + + await expect(iti.promise).rejects.toThrow(); + }); + + it("works if loadUtilsOnInit is a function", async function() { + const mockUtils = { default: { mockUtils: true } }; + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(true); + + const { iti } = initPlugin({ + intlTelInput, + options: { + async loadUtilsOnInit () { + return mockUtils; + }, + }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + await iti.promise; + + expect(intlTelInput.utils).toBe(mockUtils.default); + }); + + describe("in 'withUtils' builds", () => { + const intlTelInput = require("intl-tel-input/intlTelInputWithUtils"); + resetPackageAfterEach(intlTelInput); + + it("ignores the `loadUtilsOnInit` option and does not load", async () => { + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + + await iti.promise; + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + }); + + it("Raises an informative error when `utils` is missing", async () => { + delete intlTelInput.utils; + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit }, + }); + + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + await expect(iti.promise).rejects.toThrow("INTENTIONALLY BROKEN"); + }); + }); + +}); diff --git a/tests/static/loadUtils.test.js b/tests/static/loadUtils.test.js new file mode 100644 index 00000000..4d8025d4 --- /dev/null +++ b/tests/static/loadUtils.test.js @@ -0,0 +1,198 @@ +/** + * @jest-environment jsdom + */ + +const intlTelInput = require("intl-tel-input"); +const { initPlugin, resetPackageAfterEach } = require("../helpers/helpers"); +require("../helpers/matchers"); + +describe("loadUtils", function() { + resetPackageAfterEach(intlTelInput); + + describe("calling loadUtils before init plugin", () => { + + let url = "./utils.js?v=1"; + let loadResult; + + beforeEach(() => { + loadResult = intlTelInput.loadUtils(url); + loadResult.catch(() => {}); + }); + + it("starts loading the utils", () => { + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + }); + + it("resolves the promise", async () => { + expect(loadResult).toBeAPromise(); + await expect(loadResult).resolves.toBe(true); + }); + + it("installs the utils module at intlTelInput.utils", async () => { + await loadResult; + + expect(intlTelInput).toHaveProperty("utils.isValidNumber"); + }); + + describe("then init plugin with loadUtilsOnInit option", () => { + + it("resolves the instance's promise", async () => { + const { iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit: "some/other/url/ok" } + }); + await iti.promise; + }); + + }); + + }); + + + + describe("init plugin with loadUtilsOnInit option, but force documentReady=false so it wont fire", function() { + /** @type {jest.Mock<() => Promise>} */ + let utilsLoader; + /** @type {intlTelInput.Iti} */ + let iti; + + beforeEach(function() { + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(false); + utilsLoader = jest.fn(async () => import("intl-tel-input/utils")); + + ({ iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit: utilsLoader }, + })); + }); + + it("does not start loading the utils", function() { + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", false); + }); + + it("does not resolve the promise", async function() { + await expect(iti.promise).toBePending(); + }); + + + + describe("calling loadUtils", function() { + /** @type {Promise} */ + let loadUtilsPromise; + + beforeEach(async function() { + loadUtilsPromise = intlTelInput.loadUtils(utilsLoader); + }); + + it("starts loading the utils", function() { + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + }); + + it("resolves the promise", async function() { + await expect(loadUtilsPromise).resolves.toBe(true); + }); + + + + describe("then init another plugin instance with loadUtilsOnInit option", function() { + + beforeEach(async function() { + // Wait for previous load to finish. + await loadUtilsPromise; + + initPlugin({ + intlTelInput, + options: { loadUtilsOnInit: utilsLoader }, + }); + }); + + it("only loads once", function() { + expect(utilsLoader).toHaveBeenCalledTimes(1); + }); + + }); + + }); + + }); + + + + describe("force documentReady=true then init plugin with loadUtilsOnInit", function() { + + const url3 = "./utils.js?v=3"; + /** @type {intlTelInput.Iti} */ + let iti; + + beforeEach(function() { + jest.spyOn(intlTelInput, "documentReady").mockReturnValue(true); + ({ iti } = initPlugin({ + intlTelInput, + options: { loadUtilsOnInit: url3 }, + })); + }); + + it("resolves the promise immediately", async function() { + await expect(iti.promise).resolves.toBeInstanceOf(Array); + }); + + it("starts loading the utils", function() { + expect(intlTelInput).toHaveProperty("startedLoadingUtilsScript", true); + }); + + }); + + + + describe("calling with a function", function() { + const mockUtils = { default: { mymodule: "fakeutils" } }; + + it("uses the object the function resolves with", async () => { + const result = await intlTelInput.loadUtils(async () => mockUtils); + + expect(result).toEqual(true); + expect(intlTelInput.utils).toBe(mockUtils.default); + }); + + it("rejects if the function rejects", async () => { + const loadPromise = intlTelInput.loadUtils(async () => { + throw new Error("Uhoh!"); + }); + + await expect(loadPromise).rejects.toThrow("Uhoh!"); + }); + + it("rejects if the function throws", async () => { + const loadPromise = intlTelInput.loadUtils(() => { + throw new Error("Uhoh!"); + }); + + await expect(loadPromise).rejects.toThrow("Uhoh!"); + }); + + it("rejects if the function returns a non-promise", async () => { + const loadPromise = intlTelInput.loadUtils(() => ({ + anObject: "That is not a promise", + })); + + await expect(loadPromise).rejects.toThrow(); + }); + + it("rejects if the function resolves to a non-object", async () => { + const loadPromise = intlTelInput.loadUtils(async () => "Hello!"); + + await expect(loadPromise).rejects.toThrow(); + }); + + it("does not call the function a second time", async () => { + const loader = jest.fn(async () => mockUtils); + + await intlTelInput.loadUtils(loader); + await intlTelInput.loadUtils(loader); + + expect(loader).toHaveBeenCalledTimes(1); + }); + + }); + +});