diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 1f2840aca..893185ef7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -19,3 +19,4 @@ jobs: - run: npx tslint -c ionic-migration.json -p tsconfig.json - run: npm run test:ci - run: npm run build:prod + - run: result=$(npx check-es-compat www/*.js 2> /dev/null | grep -v -E "Array\.prototype\.includes|Promise\.prototype\.finally|String\.prototype\.(matchAll|trimRight)|globalThis" | grep -Po "(?<=error).*?(?=\s+ecmascript)" | wc -l); test $result -eq 0 diff --git a/browserslist b/browserslist index b15c7fae5..6fdcb3f9c 100644 --- a/browserslist +++ b/browserslist @@ -1,12 +1,15 @@ -# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries +# This file indicates the platforms supported. These targets will only be polyfilled automatically in CSS, +# JavaScript polyfills have to be configured manually in `src/polyfills.ts`. +# +# In order to see which browsers are supported you can run `npx browserslist` (or visit https://browserslist.dev/). +# +# Keep in mind that not all versions of all browsers will be displayed, here's all the versions available: +# https://caniuse.com/ciu/comparison +# +# From time to time, you'll want to update the database by running `npx browserslist@latest --update-db`. +# +# More info: https://github.com/browserslist/browserslist -# You can see what browsers were selected by your queries by running: -# npx browserslist - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 # For IE 9-11 support, remove 'not'. +Android >= 5 +iOS >= 11 +Chrome >= 61 diff --git a/package-lock.json b/package-lock.json index 823f4bc71..7e9ee4e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -139,6 +139,12 @@ "uri-js": "^4.2.2" } }, + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "dev": true + }, "open": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz", @@ -5873,9 +5879,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001144", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz", - "integrity": "sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ==", + "version": "1.0.30001197", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001197.tgz", + "integrity": "sha512-8aE+sqBqtXz4G8g35Eg/XEaFr2N7rd/VQ6eABGBmNtcB8cN6qNJhMi6oSFy4UWWZgqgL3filHT8Nha4meu3tsw==", "dev": true }, "canonical-path": { @@ -5918,6 +5924,577 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "check-es-compat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/check-es-compat/-/check-es-compat-1.1.1.tgz", + "integrity": "sha512-q8k6nnYg6aabuEtx0LyVXP8Q3OQ07eOAAWZ7eD95FiSk+GRIdqouuJHd7gGPzxSUJZRioRrW2GMWu7MHDFsIUw==", + "dev": true, + "requires": { + "eslint": "^6.7.2", + "eslint-plugin-ecmascript-compat": "^1.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "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" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^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" + } + }, + "rxjs": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz", + "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "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" + } + } + } + }, "cheerio": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", @@ -7319,10 +7896,9 @@ } }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.1.tgz", + "integrity": "sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==" }, "core-js-compat": { "version": "3.6.5", @@ -8945,6 +9521,45 @@ } } }, + "eslint-plugin-ecmascript-compat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-ecmascript-compat/-/eslint-plugin-ecmascript-compat-1.1.1.tgz", + "integrity": "sha512-OUytzpOKynpuSfnrGpOfj2QmduJu/0fGfTLf9GwTJzIyvfXfLDGtoZ+ek0sHFh68BLbwG5lylyBNgAxhFh5V8A==", + "dev": true, + "requires": { + "browserslist": "^4.8.0", + "eslint-plugin-es": "^2.0.0", + "lodash": "^4.17.15", + "mdn-browser-compat-data": "^1.0.25" + } + }, + "eslint-plugin-es": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz", + "integrity": "sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ==", + "dev": true, + "requires": { + "eslint-utils": "^1.4.2", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "eslint-plugin-header": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.0.tgz", @@ -14462,6 +15077,15 @@ "safe-buffer": "^5.1.2" } }, + "mdn-browser-compat-data": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-1.1.2.tgz", + "integrity": "sha512-uBNX2P4iu3PZcXP20rL+n7fxN9PWZLj0y43QMe/1aXzqP3H6HbVOeePS0cBZCtMwcfr2Tugf1OHj+/wLam+dUg==", + "dev": true, + "requires": { + "extend": "3.0.2" + } + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -22873,6 +23497,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", diff --git a/package.json b/package.json index fb45dba36..a4b3d2811 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "cordova-sqlite-storage": "^4.0.0", "cordova-support-google-services": "^1.2.1", "cordova.plugins.diagnostic": "^5.0.2", + "core-js": "^3.9.1", "es6-promise-plugin": "^4.2.2", "jszip": "^3.5.0", "moment": "^2.29.0", @@ -138,6 +139,7 @@ "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/parser": "4.3.0", + "check-es-compat": "^1.1.1", "eslint": "^7.21.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-header": "^3.1.0", diff --git a/src/addons/calendar/services/calendar-sync.ts b/src/addons/calendar/services/calendar-sync.ts index c75e4834a..e0e44e6f6 100644 --- a/src/addons/calendar/services/calendar-sync.ts +++ b/src/addons/calendar/services/calendar-sync.ts @@ -297,7 +297,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider + diff --git a/src/core/components/show-password/show-password.scss b/src/core/components/show-password/show-password.scss index a0960f099..ac1c907a3 100644 --- a/src/core/components/show-password/show-password.scss +++ b/src/core/components/show-password/show-password.scss @@ -13,7 +13,7 @@ } } -::slotted(ion-input) { +::ng-deep ion-input { --padding-end: 47px !important; } diff --git a/src/core/components/show-password/show-password.ts b/src/core/components/show-password/show-password.ts index d61856779..01337e88d 100644 --- a/src/core/components/show-password/show-password.ts +++ b/src/core/components/show-password/show-password.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild } from '@angular/core'; import { IonInput } from '@ionic/angular'; import { CoreApp } from '@services/app'; @@ -37,7 +37,6 @@ import { CoreUtils } from '@services/utils/utils'; selector: 'core-show-password', templateUrl: 'core-show-password.html', styleUrls: ['show-password.scss'], - encapsulation: ViewEncapsulation.ShadowDom, }) export class CoreShowPasswordComponent implements OnInit, AfterViewInit { diff --git a/src/core/directives/tests/format-text.test.ts b/src/core/directives/tests/format-text.test.ts index c76c81e34..e8995972a 100644 --- a/src/core/directives/tests/format-text.test.ts +++ b/src/core/directives/tests/format-text.test.ts @@ -81,9 +81,9 @@ describe('CoreFormatTextDirective', () => { // @todo this is done because we cannot mock image being loaded, we should find an alternative... CoreUtils.instance.timeoutPromise = () => Promise.resolve(null as unknown as T); - mockSingleton(CoreFilepool, { getSrcByUrl: jest.fn(() => Promise.resolve('file://local-path')) }); + mockSingleton(CoreFilepool, { getSrcByUrl: () => Promise.resolve('file://local-path') }); mockSingleton(CoreSites, { - getSite: jest.fn(() => Promise.resolve(site)), + getSite: () => Promise.resolve(site), getCurrentSite: () => Promise.resolve(site), }); mockSingleton(CoreFilter, { formatText: (text) => Promise.resolve(text) }); diff --git a/src/core/features/block/services/block-delegate.ts b/src/core/features/block/services/block-delegate.ts index 403584f84..eeb0e8832 100644 --- a/src/core/features/block/services/block-delegate.ts +++ b/src/core/features/block/services/block-delegate.ts @@ -198,4 +198,4 @@ export class CoreBlockDelegateService extends CoreDelegate { } -export const CoreBlockDelegate = makeSingleton(CoreBlockDelegateService, ['blocksUpdateObservable']); +export const CoreBlockDelegate = makeSingleton(CoreBlockDelegateService); diff --git a/src/core/features/comments/services/comments-sync.ts b/src/core/features/comments/services/comments-sync.ts index be1974e52..1c91862d6 100644 --- a/src/core/features/comments/services/comments-sync.ts +++ b/src/core/features/comments/services/comments-sync.ts @@ -315,7 +315,7 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { - handler.page = '/main/home/' + handler.page; + // Re-build the list of tabs. + const handlersMap = CoreUtils.arrayToObject(handlers, 'title'); + const newTabs = handlers.map((handler): CoreTabsOutletTab => { + const tab = this.tabs.find(tab => tab.title == handler.title); - // Check if the handler is already in the tabs list. If so, use it. - const tab = this.tabs.find((tab) => tab.title == handler.title); + // If a handler is already in the list, use existing object to prevent re-creating the tab. + if (tab) { + return tab; + } - return tab || handler; + return { + page: `/main/home/${handler.page}`, + pageParams: handler.pageParams, + title: handler.title, + class: handler.class, + icon: handler.icon, + badge: handler.badge, + }; }); // Sort them by priority so new handlers are in the right position. - newTabs.sort((a, b) => (b.priority || 0) - (a.priority || 0)); + newTabs.sort((a, b) => (handlersMap[b.title].priority || 0) - (handlersMap[a.title].priority || 0)); if (typeof this.selectedTab == 'undefined' && newTabs.length > 0) { let maxPriority = 0; - let maxIndex = 0; - newTabs.forEach((tab, index) => { - if ((tab.selectPriority || 0) > maxPriority) { - maxPriority = tab.selectPriority || 0; - maxIndex = index; + this.selectedTab = Object.entries(newTabs).reduce((maxIndex, [index, tab]) => { + const selectPriority = handlersMap[tab.title].selectPriority ?? 0; + + if (selectPriority > maxPriority) { + maxPriority = selectPriority; + maxIndex = Number(index); } - }); - this.selectedTab = maxIndex; + return maxIndex; + }, 0); } this.tabs = newTabs; diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts index 57a707a95..892b9de56 100644 --- a/src/core/features/siteplugins/services/siteplugins.ts +++ b/src/core/features/siteplugins/services/siteplugins.ts @@ -659,7 +659,7 @@ export class CoreSitePluginsProvider { } -export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider, ['sitePluginsFinishedLoading', 'hasSitePluginsLoaded']); +export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider); /** * Handler of a site plugin. diff --git a/src/core/features/user/services/user-sync.ts b/src/core/features/user/services/user-sync.ts index 2c4819147..7f5eca734 100644 --- a/src/core/features/user/services/user-sync.ts +++ b/src/core/features/user/services/user-sync.ts @@ -104,4 +104,4 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider { } -export const CoreUserSync = makeSingleton(CoreUserSyncProvider, ['component', 'syncInterval']); +export const CoreUserSync = makeSingleton(CoreUserSyncProvider); diff --git a/src/core/services/app.ts b/src/core/services/app.ts index abc92e0b4..f0e715315 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -644,7 +644,7 @@ export class CoreAppProvider { } -export const CoreApp = makeSingleton(CoreAppProvider, ['isAndroid']); +export const CoreApp = makeSingleton(CoreAppProvider); /** * Data stored for a redirect to another page/site. diff --git a/src/core/services/screen.ts b/src/core/services/screen.ts index fbb69ea6c..696abb6cb 100644 --- a/src/core/services/screen.ts +++ b/src/core/services/screen.ts @@ -140,11 +140,4 @@ export class CoreScreenService { } -export const CoreScreen = makeSingleton(CoreScreenService, [ - 'isTablet', - 'isMobile', - 'layout', - 'layoutObservable', - 'breakpoints', - 'breakpointsObservable', -]); +export const CoreScreen = makeSingleton(CoreScreenService); diff --git a/src/core/services/tests/utils/text.test.ts b/src/core/services/tests/utils/text.test.ts index 6fbc20848..1c968dbce 100644 --- a/src/core/services/tests/utils/text.test.ts +++ b/src/core/services/tests/utils/text.test.ts @@ -26,9 +26,9 @@ describe('CoreTextUtilsProvider', () => { let textUtils: CoreTextUtilsProvider; beforeEach(() => { - mockSingleton(CoreApp, [], { isAndroid: jest.fn(() => config.platform === 'android') }); + mockSingleton(CoreApp, [], { isAndroid: () => config.platform === 'android' }); - sanitizer = mock([], { bypassSecurityTrustUrl: jest.fn(url => url) }); + sanitizer = mock([], { bypassSecurityTrustUrl: url => url }); textUtils = new CoreTextUtilsProvider(sanitizer); }); diff --git a/src/core/singletons/array.ts b/src/core/singletons/array.ts index 7f31bafc7..77a1be206 100644 --- a/src/core/singletons/array.ts +++ b/src/core/singletons/array.ts @@ -37,10 +37,6 @@ export class CoreArray { * @return Flattened array. */ static flatten(arr: T[][]): T[] { - if ('flat' in arr) { - return (arr as any).flat(); // eslint-disable-line @typescript-eslint/no-explicit-any - } - return ( []).concat(...arr); } diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index d6628b992..6d15cef42 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -56,39 +56,26 @@ import { Zip as ZipService } from '@ionic-native/zip/ngx'; import { TranslateService } from '@ngx-translate/core'; -const OBJECT_PROTOTYPE = Object.getPrototypeOf(Object); - /** * Injector instance used to resolve singletons. */ let singletonsInjector: Injector | null = null; /** - * Helper to get service class properties that are methods. + * Helper to create a method that proxies calls to the underlying singleton instance. */ -type GetMethods = { - [K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never -}[keyof T]; - -/** - * Helper to get service class properties that are not methods. - */ -type GetNonMethods = { - [K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? never : K -}[keyof T]; +// eslint-disable-next-line +let createSingletonMethodProxy = (instance: any, method: Function, property: string | number | symbol) => method.bind(instance); /** * Singleton proxy created using the factory method. * * @see makeSingleton */ -export type CoreSingletonProxy = - Pick, GetNonMethods>> & - Pick & - { - instance: Service; - setInstance(instance: Service): void; - }; +export type CoreSingletonProxy = Service & { + instance: Service; + setInstance(instance: Service): void; +}; /** * Set the injector that will be used to resolve instances in the singletons of this module. @@ -99,6 +86,15 @@ export function setSingletonsInjector(injector: Injector): void { singletonsInjector = injector; } +/** + * Set the method to create method proxies. + * + * @param method Method. + */ +export function setCreateSingletonMethodProxy(method: typeof createSingletonMethodProxy): void { + createSingletonMethodProxy = method; +} + /** * Make a singleton proxy for the given injection token. * @@ -112,26 +108,19 @@ export function setSingletonsInjector(injector: Injector): void { * @param getters Getter names to proxy. * @return Singleton proxy. */ -export function makeSingleton(injectionToken: Type | Type | string): CoreSingletonProxy; -export function makeSingleton( +export function makeSingleton( // eslint-disable-line @typescript-eslint/ban-types injectionToken: Type | Type | string, - getters: Getters[], -): CoreSingletonProxy; -export function makeSingleton( - injectionToken: Type | Type | string, - getters: Getters[] = [], -): CoreSingletonProxy { - // Define instance manipulation affordances. - const proxy = { +): CoreSingletonProxy { + const singleton = { setInstance(instance: Service) { - Object.defineProperty(proxy, 'instance', { + Object.defineProperty(singleton, 'instance', { value: instance, configurable: true, }); }, - } as CoreSingletonProxy; + } as { instance: Service; setInstance(instance: Service) }; - Object.defineProperty(proxy, 'instance', { + Object.defineProperty(singleton, 'instance', { get: () => { if (!singletonsInjector) { throw new Error('Can\'t resolve a singleton instance without an injector'); @@ -139,70 +128,39 @@ export function makeSingleton( const instance = singletonsInjector.get(injectionToken); - proxy.setInstance(instance); + singleton.setInstance(instance); return instance; }, configurable: true, }); - // Define method and getter proxies. - if (isServiceClass(injectionToken)) { - // Get property descriptors, going all the way up the prototype chain (for services extending other classes). - let parentPrototype = injectionToken; - let descriptors: Record = {}; - - do { - descriptors = { - ...Object.getOwnPropertyDescriptors(parentPrototype.prototype), - ...descriptors, - }; - - parentPrototype = Object.getPrototypeOf(parentPrototype); - } while (parentPrototype !== OBJECT_PROTOTYPE); - - // Don't proxy constructor calls. - delete descriptors['constructor']; - - // Define method proxies. - for (const [property, descriptor] of Object.entries(descriptors)) { - // Skip getters and setters. - if (descriptor.get || descriptor.set) { - continue; + return new Proxy(singleton, { + get(target, property, receiver) { + if (property in target) { + return Reflect.get(target, property, receiver); } - // Define method proxy. - Object.defineProperty(proxy, property, { - value: (...args) => proxy.instance[property].call(proxy.instance, ...args), - configurable: true, - }); - } + const value = target.instance[property]; - // Define getter proxies. - for (const getter of getters) { - Object.defineProperty(proxy, getter, { get: () => proxy.instance[getter] }); - } - } + return typeof value === 'function' + ? createSingletonMethodProxy(target.instance, value, property) + : value; + }, + set(target, property, value, receiver) { + Reflect.set(target.instance, property, value, receiver); - return proxy; -} - -/** - * Type guard to check if an injection token is a service class. - * - * @param injectionToken Injection token. - * @return Whether the token is a class. - */ -function isServiceClass(injectionToken: Type | string): injectionToken is Type { - return typeof injectionToken !== 'string'; + return true; + }, + }) as CoreSingletonProxy; } // Convert ionic-native services to singleton. export const Badge = makeSingleton(BadgeService); export const Chooser = makeSingleton(ChooserService); export const Clipboard = makeSingleton(ClipboardService); -export const Diagnostic = makeSingleton(DiagnosticService, ['permissionStatus']); -export const File = makeSingleton(FileService, ['documentsDirectory', 'externalApplicationStorageDirectory']); +export const Diagnostic = makeSingleton(DiagnosticService); +export const File = makeSingleton(FileService); export const FileOpener = makeSingleton(FileOpenerService); export const FileTransfer = makeSingleton(FileTransferService); export const Geolocation = makeSingleton(GeolocationService); @@ -212,40 +170,24 @@ export const LocalNotifications = makeSingleton(LocalNotificationsService); export const Media = makeSingleton(MediaService); export const MediaCapture = makeSingleton(MediaCaptureService); export const NativeHttp = makeSingleton(HTTP); -export const Network = makeSingleton(NetworkService, ['Connection', 'type']); +export const Network = makeSingleton(NetworkService); export const Push = makeSingleton(PushService); export const QRScanner = makeSingleton(QRScannerService); export const StatusBar = makeSingleton(StatusBarService); export const SplashScreen = makeSingleton(SplashScreenService); export const SQLite = makeSingleton(SQLiteService); -export const WebIntent = makeSingleton(WebIntentService, ['ACTION_VIEW']); +export const WebIntent = makeSingleton(WebIntentService); export const WebView = makeSingleton(WebViewService); export const Zip = makeSingleton(ZipService); -export const Camera = makeSingleton(CameraService, [ - 'DestinationType', - 'Direction', - 'EncodingType', - 'MediaType', - 'PictureSourceType', - 'PopoverArrowDirection', -]); +export const Camera = makeSingleton(CameraService); -export const Device = makeSingleton(DeviceService, [ - 'cordova', - 'isVirtual', - 'manufacturer', - 'model', - 'platform', - 'serial', - 'uuid', - 'version', -]); +export const Device = makeSingleton(DeviceService); // Convert some Angular and Ionic injectables to singletons. export const NgZone = makeSingleton(NgZoneService); export const Http = makeSingleton(HttpClient); -export const Platform = makeSingleton(PlatformService, ['isRTL', 'resume']); +export const Platform = makeSingleton(PlatformService); export const ActionSheetController = makeSingleton(ActionSheetControllerService); export const AlertController = makeSingleton(AlertControllerService); export const LoadingController = makeSingleton(LoadingControllerService); @@ -253,10 +195,10 @@ export const ModalController = makeSingleton(ModalControllerService); export const PopoverController = makeSingleton(PopoverControllerService); export const ToastController = makeSingleton(ToastControllerService); export const GestureController = makeSingleton(GestureControllerService); -export const ApplicationInit = makeSingleton(ApplicationInitStatus, ['donePromise']); +export const ApplicationInit = makeSingleton(ApplicationInitStatus); export const Application = makeSingleton(ApplicationRef); export const NavController = makeSingleton(NavControllerService); -export const Router = makeSingleton(RouterService, ['routerState', 'url']); +export const Router = makeSingleton(RouterService); // Convert external libraries injectables. -export const Translate = makeSingleton(TranslateService, ['onLangChange', 'translations']); +export const Translate = makeSingleton(TranslateService); diff --git a/src/core/singletons/tests/singletons.test.ts b/src/core/singletons/tests/singletons.test.ts index 412a4fe8d..6f6695679 100644 --- a/src/core/singletons/tests/singletons.test.ts +++ b/src/core/singletons/tests/singletons.test.ts @@ -19,12 +19,12 @@ import { MilkyWayService } from './stubs'; describe('Singletons', () => { - let MilkyWay: CoreSingletonProxy; + let MilkyWay: CoreSingletonProxy; beforeEach(() => { setSingletonsInjector(mock({ get: serviceClass => new serviceClass() })); - MilkyWay = makeSingleton(MilkyWayService, ['MEANING_OF_LIFE']); + MilkyWay = makeSingleton(MilkyWayService); }); it('works using the service instance', () => { @@ -35,10 +35,22 @@ describe('Singletons', () => { expect(MilkyWay.getTheMeaningOfLife()).toBe(42); }); + it('works using magic methods defined as getters', () => { + expect(MilkyWay.reduceYears(2)).toBe(-2); + }); + it('works using magic getters', () => { expect(MilkyWay.MEANING_OF_LIFE).toBe(42); }); + it('works using magic getters defined dynamically', () => { + expect(MilkyWay.exists).toBeUndefined(); + + MilkyWay.bigBang(); + + expect(MilkyWay.exists).toBe(true); + }); + it('magic getters use the same instance', () => { expect(MilkyWay.addYears(1)).toBe(1); expect(MilkyWay.instance.addYears(1)).toBe(2); diff --git a/src/core/singletons/tests/stubs.ts b/src/core/singletons/tests/stubs.ts index 7850cf6cd..677bace64 100644 --- a/src/core/singletons/tests/stubs.ts +++ b/src/core/singletons/tests/stubs.ts @@ -22,10 +22,17 @@ export class Galaxy { export class MilkyWayService extends Galaxy { + exists?: boolean; readonly MEANING_OF_LIFE = 42; private years = 0; + reduceYears!: (years: number) => number; + + bigBang(): void { + this.exists = true; + } + getTheMeaningOfLife(): number { return this.MEANING_OF_LIFE; } @@ -37,3 +44,16 @@ export class MilkyWayService extends Galaxy { } } + +Object.defineProperty(MilkyWayService.prototype, 'reduceYears', { + get: () => function(years: number) { + // eslint-disable-next-line no-invalid-this + const self = this as { years: number }; + + self.years -= years; + + return self.years; + }, + enumerable: true, + configurable: true, +}); diff --git a/src/polyfills.ts b/src/polyfills.ts index 584ee02e7..cf90936b0 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -17,3 +17,42 @@ window.__Zone_disable_customElements = true; // Zone JS is required by default for Angular itself. import 'zone.js/dist/zone'; + +// Platform polyfills +import 'core-js/es/array/includes'; +import 'core-js/es/promise/finally'; +import 'core-js/es/string/match-all'; +import 'core-js/es/string/trim-right'; + +polyfillEventComposedPath(); + +/** + * Polyfill Event.composedPath() if necessary. + * + * @see https://github.com/ionic-team/stencil/issues/2681 + */ +function polyfillEventComposedPath() { + const event = new Event('') as { path?: NodeList }; + + if (!('path' in event && event.path instanceof NodeList)) { + return; + } + + Event.prototype.composedPath = function () { + if (this._composedPath) { + return this._composedPath; + } + + let node = this.target; + + for (this._composedPath = []; node.parentNode !== null;) { + this._composedPath.push(node); + + node = node.parentNode; + } + + this._composedPath.push(document, window); + + return this._composedPath; + }; +} diff --git a/src/testing/setup.ts b/src/testing/setup.ts index d181c5f93..9972e2e8e 100644 --- a/src/testing/setup.ts +++ b/src/testing/setup.ts @@ -14,7 +14,18 @@ import 'jest-preset-angular'; +import { setCreateSingletonMethodProxy } from '@singletons'; + // eslint-disable-next-line no-console console.debug = () => { // Silence. }; + +// Override the method to create singleton method proxies in order to facilitate setting up +// test expectations about method calls. +setCreateSingletonMethodProxy( + (instance, method, property) => + instance[`mock_${String(property)}`] = + instance[`mock_${String(property)}`] ?? + jest.fn((...args) => method.call(instance, ...args)), +); diff --git a/src/testing/utils.ts b/src/testing/utils.ts index d27ef231c..0bb4e7fff 100644 --- a/src/testing/utils.ts +++ b/src/testing/utils.ts @@ -40,6 +40,16 @@ export function mock( const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : []; + for (const property of Object.getOwnPropertyNames(instance)) { + const value = instance[property]; + + if (typeof value !== 'function') { + continue; + } + + instance[property] = jest.fn((...args) => value.call(instance, ...args)); + } + for (const method of methods) { instance[method] = jest.fn(); } @@ -66,17 +76,6 @@ export function mockSingleton( singleton.setInstance(mockInstance); - for (const [property, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(singleton))) { - if (typeof descriptor.value !== 'function' || property === 'setInstance') { - continue; - } - - Object.defineProperty(singleton, property, { - value: jest.fn(descriptor.value), - configurable: true, - }); - } - return mockInstance; }