From 943f00d986b340df4a0707df7a0fc4f94603db5f Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Tue, 10 Dec 2019 00:12:41 +0000 Subject: [PATCH] Bug 1600851 [wpt PR 20574] - [Import Maps] Migrate Jest-based resolution tests into JSON-based tests, a=testonly Automatic update from web-platform-tests [Import Maps] Migrate Jest-based resolution tests into JSON-based tests This CL - Defines JSON test object format (see README.md) that describes the configurations and specifiers to be tested, - Implements the WPT test helper for executing the tests based on the JSON test objects (common-test-helper.js), - Converts Jest-based resolution tests into JSONs (with some refinement, and removing test cases depending on interoperability issues of underlying URL parsers), and - Removes imported resolution tests. The dependency to Blink internals remains after this CL. Removing this dependency is planned and discussed at https://github.com/WICG/import-maps/issues/170. Bug: 1026809, https://github.com/WICG/import-maps/issues/170 Change-Id: I993cc9cd7746d7175142f8296ac434571f5d7157 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1927852 Commit-Queue: Hiroshige Hayashizaki Reviewed-by: Domenic Denicola Cr-Commit-Position: refs/heads/master{#721239} -- wpt-commits: de73fe8cc89c55dea4fee1fc46d98d73dad2b21b wpt-pr: 20574 UltraBlame original commit: 655a6e51517f24120040773fa1aa41f69532af6c --- .../tests/import-maps/common/README.md | 79 ++++++ .../common/resolving.tentative.html | 23 ++ .../common/resources/common-test-helper.js | 163 ++++++++++++ .../common/resources/data-base-url.json | 17 ++ .../common/resources/empty-import-map.json | 56 +++++ .../common/resources/overlapping-entries.json | 25 ++ .../packages-via-trailing-slashes.json | 43 ++++ .../resources/scopes-exact-vs-prefix.json | 134 ++++++++++ .../import-maps/common/resources/scopes.json | 171 +++++++++++++ .../common/resources/tricky-specifiers.json | 43 ++++ .../common/resources/url-specifiers.json | 52 ++++ .../import-maps/common/tools/format_json.py | 27 ++ .../imported/resolving-scopes.tentative.html | 11 - .../imported/resolving.tentative.html | 11 - .../imported/resources/resolving-scopes.js | 222 ----------------- .../imported/resources/resolving.js | 232 ------------------ testing/web-platform/tests/lint.whitelist | 2 + 17 files changed, 835 insertions(+), 476 deletions(-) create mode 100644 testing/web-platform/tests/import-maps/common/README.md create mode 100644 testing/web-platform/tests/import-maps/common/resolving.tentative.html create mode 100644 testing/web-platform/tests/import-maps/common/resources/common-test-helper.js create mode 100644 testing/web-platform/tests/import-maps/common/resources/data-base-url.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/empty-import-map.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/overlapping-entries.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/packages-via-trailing-slashes.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/scopes-exact-vs-prefix.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/scopes.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/tricky-specifiers.json create mode 100644 testing/web-platform/tests/import-maps/common/resources/url-specifiers.json create mode 100644 testing/web-platform/tests/import-maps/common/tools/format_json.py delete mode 100644 testing/web-platform/tests/import-maps/imported/resolving-scopes.tentative.html delete mode 100644 testing/web-platform/tests/import-maps/imported/resolving.tentative.html delete mode 100644 testing/web-platform/tests/import-maps/imported/resources/resolving-scopes.js delete mode 100644 testing/web-platform/tests/import-maps/imported/resources/resolving.js diff --git a/testing/web-platform/tests/import-maps/common/README.md b/testing/web-platform/tests/import-maps/common/README.md new file mode 100644 index 0000000000000..cda42d06099f1 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/README.md @@ -0,0 +1,79 @@ +# Import maps test JSON format + +In this directory, test inputs and expectations are expressed as JSON files. +This is in order to share the same JSON files between WPT tests and Jest-based +tests for the reference JavaScript implementation at [WICG repository](https://github.com/WICG/import-maps/tree/master/reference-implementation). + +## Basics + +A **test object** describes a set of parameters (import maps and base URLs) and specifiers to be tested. +Each JSON file under [resources/](resources/) directory consists of a test object. +A minimum test object would be: + +```json +{ + "name": "Main test name", + "importMapBaseURL": "https://example.com/import-map-base-url/index.html", + "importMap": { + "imports": { + "a": "/mapped-a.mjs" + } + }, + "baseURL": "https://example.com/base-url/app.mjs", + "expectedResults": { + "a": "https://example.com/mapped-a.mjs", + "b": null + } +} +``` + +Required fields: + +- `name`: Test name. + - In WPT tests, this is used for the test name of `promise_test()` together with specifier to be resolved, like `"Main test name: a"`. +- `importMap` (object or string): the import map to be attached. +- `importMapBaseURL` (string): the base URL used for [parsing the import map](https://wicg.github.io/import-maps/#parse-an-import-map-string). +- `baseURL` (string): the base URL used in [resolving a specifier](https://wicg.github.io/import-maps/#resolve-a-module-specifier) for each specifiers. +- `expectedResults` (object; string to (string or null)): test cases. + - The keys are specifiers to be resolved. + - The values are expected resolved URLs. If `null`, resolution should fail. + +Optional fields: + +- `link` and `details` can be used for e.g. linking to specs or adding more detailed descriptions. + - Currently they are simply ignored by the WPT test helper. + +## Nesting and inheritance + +We can organize tests by nesting test objects. +A test object can contain child test objects (*subtests*) using `tests` field. +The Keys of the `tests` value are the names of subtests, and values are test objects. + +For example: + +```json +{ + "name": "Main test name", + "importMapBaseURL": "https://example.com/import-map-base-url/index.html", + "importMap": { + "imports": { + "a": "/mapped-a.mjs" + } + }, + "tests": { + "Subtest1": { + "baseURL": "https://example.com/base-url1/app.mjs", + "expectedResults": { "a": "https://example.com/mapped-a.mjs" } + }, + "Subtest2": { + "baseURL": "https://example.com/base-url2/app.mjs", + "expectedResults": { "b": null } + } + } +} +``` + +The top-level test object contains two sub test objects, named as `Subtest1` and `Subtest2`, respectively. + +Child test objects inherit fields from their parent test object. +In the example above, the child test objects specifies `baseURL` fields, while they inherits other fields (e.g. `importMapBaseURL`) from the top-level test object. diff --git a/testing/web-platform/tests/import-maps/common/resolving.tentative.html b/testing/web-platform/tests/import-maps/common/resolving.tentative.html new file mode 100644 index 0000000000000..c947232e06322 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resolving.tentative.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/testing/web-platform/tests/import-maps/common/resources/common-test-helper.js b/testing/web-platform/tests/import-maps/common/resources/common-test-helper.js new file mode 100644 index 0000000000000..ff9e28ac519df --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/common-test-helper.js @@ -0,0 +1,163 @@ +setup({allow_uncaught_exception : true}); + + +function parse(importMap, importMapBaseURL) { + return new Promise((resolve, reject) => { + const importMapString = JSON.stringify(importMap); + const iframe = document.createElement('iframe'); + + window.addEventListener('message', event => { + if (event.data.type === 'Success') { + resolve(iframe); + } else { + + reject(event.data.error); + } + }, + {once: true}); + + const testHTML = ` + + + `; + + if (new URL(importMapBaseURL).protocol === 'data:') { + iframe.src = 'data:text/html;base64,' + btoa(testHTML); + } else { + iframe.srcdoc = `` + testHTML; + } + + document.body.appendChild(iframe); + + }); +} + + +function resolve(specifier, parsedImportMap, baseURL) { + return new Promise((resolve, reject) => { + window.addEventListener('message', event => { + if (event.data.type === 'ResolutionSuccess') { + resolve(event.data.result); + } else if (event.data.type === 'ResolutionFailure') { + if (event.data.result === 'TypeError') { + reject(new TypeError()); + } else { + reject(new Error(event.data.result)); + } + } else { + assert_unreached('Invalid message: ' + event.data.type); + } + }, + {once: true}); + + parsedImportMap.contentWindow.postMessage( + {specifier: specifier, baseURL: baseURL}, '*'); + }); +} + +function assert_no_extra_properties(object, expectedProperties, description) { + for (const actualProperty in object) { + assert_true(expectedProperties.indexOf(actualProperty) !== -1, + description + ": unexpected property " + actualProperty); + } +} + +async function runTests(j) { + const tests = j.tests; + delete j.tests; + + if (j.importMap) { + assert_own_property(j, 'importMap'); + assert_own_property(j, 'importMapBaseURL'); + j.parsedImportMap = await parse(j.importMap, j.importMapBaseURL); + delete j.importMap; + delete j.importMapBaseURL; + } + + assert_no_extra_properties( + j, + ['expectedResults', 'baseURL', 'name', 'parsedImportMap', + 'importMap', 'importMapBaseURL', + 'link', 'details'], + j.name); + + if (tests) { + + for (const testName in tests) { + let fullTestName = testName; + if (j.name) { + fullTestName = j.name + ': ' + testName; + } + tests[testName].name = fullTestName; + const k = Object.assign(Object.assign({}, j), tests[testName]); + await runTests(k); + } + } else { + + for (const key of + ['expectedResults', 'parsedImportMap', 'baseURL', 'name']) { + assert_own_property(j, key, j.name); + } + + for (const specifier in j.expectedResults) { + const expected = j.expectedResults[specifier]; + promise_test(async t => { + if (expected === null) { + return promise_rejects(t, new TypeError(), + resolve(specifier, j.parsedImportMap, j.baseURL)); + } else { + + const actual = await resolve( + specifier, j.parsedImportMap, j.baseURL); + assert_equals(actual, expected); + } + }, + j.name + ': ' + specifier); + } + } +} + +export async function runTestsFromJSON(jsonURL) { + const response = await fetch(jsonURL); + const json = await response.json(); + await runTests(json); +} diff --git a/testing/web-platform/tests/import-maps/common/resources/data-base-url.json b/testing/web-platform/tests/import-maps/common/resources/data-base-url.json new file mode 100644 index 0000000000000..81fcf087425c7 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/data-base-url.json @@ -0,0 +1,17 @@ +{ + "importMap": { + "imports": { + "foo/": "data:text/javascript,foo/" + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "name": "data: base URL (?)", + "tests": { + "should favor the most-specific key": { + "expectedResults": { + "foo/bar": null + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/empty-import-map.json b/testing/web-platform/tests/import-maps/common/resources/empty-import-map.json new file mode 100644 index 0000000000000..ce6c185498fa3 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/empty-import-map.json @@ -0,0 +1,56 @@ +{ + "importMap": {}, + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "tests": { + "valid relative specifiers": { + "expectedResults": { + "./foo": "https://example.com/js/foo", + "./foo/bar": "https://example.com/js/foo/bar", + "./foo/../bar": "https://example.com/js/bar", + "./foo/../../bar": "https://example.com/bar", + "../foo": "https://example.com/foo", + "../foo/bar": "https://example.com/foo/bar", + "../../../foo/bar": "https://example.com/foo/bar", + "/foo": "https://example.com/foo", + "/foo/bar": "https://example.com/foo/bar", + "/../../foo/bar": "https://example.com/foo/bar", + "/../foo/../bar": "https://example.com/bar" + } + }, + "fetch scheme absolute URLs": { + "expectedResults": { + "about:fetch-scheme": "about:fetch-scheme", + "https://fetch-scheme.net": "https://fetch-scheme.net/", + "https:fetch-scheme.org": "https://fetch-scheme.org/", + "https://fetch%2Dscheme.com/": "https://fetch-scheme.com/", + "https://///fetch-scheme.com///": "https://fetch-scheme.com///" + } + }, + "non-fetch scheme absolute URLs": { + "expectedResults": { + "mailto:non-fetch-scheme": "mailto:non-fetch-scheme", + "import:non-fetch-scheme": "import:non-fetch-scheme", + "javascript:non-fetch-scheme": "javascript:non-fetch-scheme", + "wss:non-fetch-scheme": "wss://non-fetch-scheme/" + } + }, + "valid relative URLs that are invalid as specifiers should fail": { + "expectedResults": { + "invalid-specifier": null, + "\\invalid-specifier": null, + ":invalid-specifier": null, + "@invalid-specifier": null, + "%2E/invalid-specifier": null, + "%2E%2E/invalid-specifier": null, + ".%2Finvalid-specifier": null + } + }, + "invalid absolute URLs should fail": { + "expectedResults": { + "https://invalid-url.com:demo": null, + "http://[invalid-url.com]/": null + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/overlapping-entries.json b/testing/web-platform/tests/import-maps/common/resources/overlapping-entries.json new file mode 100644 index 0000000000000..21354025451cf --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/overlapping-entries.json @@ -0,0 +1,25 @@ +{ + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "name": "should favor the most-specific key", + "tests": { + "Overlapping entries with trailing slashes": { + "importMap": { + "imports": { + "a": "/1", + "a/": "/2/", + "a/b": "/3", + "a/b/": "/4/" + } + }, + "expectedResults": { + "a": "https://example.com/1", + "a/": "https://example.com/2/", + "a/x": "https://example.com/2/x", + "a/b": "https://example.com/3", + "a/b/": "https://example.com/4/", + "a/b/c": "https://example.com/4/c" + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/packages-via-trailing-slashes.json b/testing/web-platform/tests/import-maps/common/resources/packages-via-trailing-slashes.json new file mode 100644 index 0000000000000..6b8f0135f50f5 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/packages-via-trailing-slashes.json @@ -0,0 +1,43 @@ +{ + "importMap": { + "imports": { + "moment": "/node_modules/moment/src/moment.js", + "moment/": "/node_modules/moment/src/", + "lodash-dot": "./node_modules/lodash-es/lodash.js", + "lodash-dot/": "./node_modules/lodash-es/", + "lodash-dotdot": "../node_modules/lodash-es/lodash.js", + "lodash-dotdot/": "../node_modules/lodash-es/" + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "name": "Package-like scenarios", + "link": "https://github.com/WICG/import-maps#packages-via-trailing-slashes", + "tests": { + "package main modules": { + "expectedResults": { + "moment": "https://example.com/node_modules/moment/src/moment.js", + "lodash-dot": "https://example.com/app/node_modules/lodash-es/lodash.js", + "lodash-dotdot": "https://example.com/node_modules/lodash-es/lodash.js" + } + }, + "package submodules": { + "expectedResults": { + "moment/foo": "https://example.com/node_modules/moment/src/foo", + "lodash-dot/foo": "https://example.com/app/node_modules/lodash-es/foo", + "lodash-dotdot/foo": "https://example.com/node_modules/lodash-es/foo" + } + }, + "package names that end in a slash should just pass through": { + "expectedResults": { + "moment/": "https://example.com/node_modules/moment/src/" + } + }, + "package modules that are not declared should fail": { + "expectedResults": { + "underscore/": null, + "underscore/foo": null + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/scopes-exact-vs-prefix.json b/testing/web-platform/tests/import-maps/common/resources/scopes-exact-vs-prefix.json new file mode 100644 index 0000000000000..3d9d50349f0d6 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/scopes-exact-vs-prefix.json @@ -0,0 +1,134 @@ +{ + "name": "Exact vs. prefix based matching", + "details": "Scopes are matched with base URLs that are exactly the same or subpaths under the scopes with trailing shashes", + "link": "https://wicg.github.io/import-maps/#resolve-a-module-specifier Step 8.1", + "tests": { + "Scope without trailing slash only": { + "importMap": { + "scopes": { + "/js": { + "moment": "/only-triggered-by-exact/moment", + "moment/": "/only-triggered-by-exact/moment/" + } + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "tests": { + "Non-trailing-slash base URL (exact match)": { + "baseURL": "https://example.com/js", + "expectedResults": { + "moment": "https://example.com/only-triggered-by-exact/moment", + "moment/foo": "https://example.com/only-triggered-by-exact/moment/foo" + } + }, + "Trailing-slash base URL (fail)": { + "baseURL": "https://example.com/js/", + "expectedResults": { + "moment": null, + "moment/foo": null + } + }, + "Subpath base URL (fail)": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "moment": null, + "moment/foo": null + } + }, + "Non-subpath base URL (fail)": { + "baseURL": "https://example.com/jsiscool", + "expectedResults": { + "moment": null, + "moment/foo": null + } + } + } + }, + "Scope with trailing slash only": { + "importMap": { + "scopes": { + "/js/": { + "moment": "/triggered-by-any-subpath/moment", + "moment/": "/triggered-by-any-subpath/moment/" + } + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "tests": { + "Non-trailing-slash base URL (fail)": { + "baseURL": "https://example.com/js", + "expectedResults": { + "moment": null, + "moment/foo": null + } + }, + "Trailing-slash base URL (exact match)": { + "baseURL": "https://example.com/js/", + "expectedResults": { + "moment": "https://example.com/triggered-by-any-subpath/moment", + "moment/foo": "https://example.com/triggered-by-any-subpath/moment/foo" + } + }, + "Subpath base URL (prefix match)": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "moment": "https://example.com/triggered-by-any-subpath/moment", + "moment/foo": "https://example.com/triggered-by-any-subpath/moment/foo" + } + }, + "Non-subpath base URL (fail)": { + "baseURL": "https://example.com/jsiscool", + "expectedResults": { + "moment": null, + "moment/foo": null + } + } + } + }, + "Scopes with and without trailing slash": { + "importMap": { + "scopes": { + "/js": { + "moment": "/only-triggered-by-exact/moment", + "moment/": "/only-triggered-by-exact/moment/" + }, + "/js/": { + "moment": "/triggered-by-any-subpath/moment", + "moment/": "/triggered-by-any-subpath/moment/" + } + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "tests": { + "Non-trailing-slash base URL (exact match)": { + "baseURL": "https://example.com/js", + "expectedResults": { + "moment": "https://example.com/only-triggered-by-exact/moment", + "moment/foo": "https://example.com/only-triggered-by-exact/moment/foo" + } + }, + "Trailing-slash base URL (exact match)": { + "baseURL": "https://example.com/js/", + "expectedResults": { + "moment": "https://example.com/triggered-by-any-subpath/moment", + "moment/foo": "https://example.com/triggered-by-any-subpath/moment/foo" + } + }, + "Subpath base URL (prefix match)": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "moment": "https://example.com/triggered-by-any-subpath/moment", + "moment/foo": "https://example.com/triggered-by-any-subpath/moment/foo" + } + }, + "Non-subpath base URL (fail)": { + "baseURL": "https://example.com/jsiscool", + "expectedResults": { + "moment": null, + "moment/foo": null + } + } + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/scopes.json b/testing/web-platform/tests/import-maps/common/resources/scopes.json new file mode 100644 index 0000000000000..c266e4c6c1d7d --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/scopes.json @@ -0,0 +1,171 @@ +{ + "importMapBaseURL": "https://example.com/app/index.html", + "tests": { + "Fallback to toplevel and between scopes": { + "importMap": { + "imports": { + "a": "/a-1.mjs", + "b": "/b-1.mjs", + "c": "/c-1.mjs", + "d": "/d-1.mjs" + }, + "scopes": { + "/scope2/": { + "a": "/a-2.mjs", + "d": "/d-2.mjs" + }, + "/scope2/scope3/": { + "b": "/b-3.mjs", + "d": "/d-3.mjs" + } + } + }, + "tests": { + "should fall back to `imports` when no scopes match": { + "baseURL": "https://example.com/scope1/foo.mjs", + "expectedResults": { + "a": "https://example.com/a-1.mjs", + "b": "https://example.com/b-1.mjs", + "c": "https://example.com/c-1.mjs", + "d": "https://example.com/d-1.mjs" + } + }, + "should use a direct scope override": { + "baseURL": "https://example.com/scope2/foo.mjs", + "expectedResults": { + "a": "https://example.com/a-2.mjs", + "b": "https://example.com/b-1.mjs", + "c": "https://example.com/c-1.mjs", + "d": "https://example.com/d-2.mjs" + } + }, + "should use an indirect scope override": { + "baseURL": "https://example.com/scope2/scope3/foo.mjs", + "expectedResults": { + "a": "https://example.com/a-2.mjs", + "b": "https://example.com/b-3.mjs", + "c": "https://example.com/c-1.mjs", + "d": "https://example.com/d-3.mjs" + } + } + } + }, + "Relative URL scope keys": { + "importMap": { + "imports": { + "a": "/a-1.mjs", + "b": "/b-1.mjs", + "c": "/c-1.mjs" + }, + "scopes": { + "": { + "a": "/a-empty-string.mjs" + }, + "./": { + "b": "/b-dot-slash.mjs" + }, + "../": { + "c": "/c-dot-dot-slash.mjs" + } + } + }, + "tests": { + "An empty string scope is a scope with import map base URL": { + "baseURL": "https://example.com/app/index.html", + "expectedResults": { + "a": "https://example.com/a-empty-string.mjs", + "b": "https://example.com/b-dot-slash.mjs", + "c": "https://example.com/c-dot-dot-slash.mjs" + } + }, + "'./' scope is a scope with import map base URL's directory": { + "baseURL": "https://example.com/app/foo.mjs", + "expectedResults": { + "a": "https://example.com/a-1.mjs", + "b": "https://example.com/b-dot-slash.mjs", + "c": "https://example.com/c-dot-dot-slash.mjs" + } + }, + "'../' scope is a scope with import map base URL's parent directory": { + "baseURL": "https://example.com/foo.mjs", + "expectedResults": { + "a": "https://example.com/a-1.mjs", + "b": "https://example.com/b-1.mjs", + "c": "https://example.com/c-dot-dot-slash.mjs" + } + } + } + }, + "Package-like scenarios": { + "importMap": { + "imports": { + "moment": "/node_modules/moment/src/moment.js", + "moment/": "/node_modules/moment/src/", + "lodash-dot": "./node_modules/lodash-es/lodash.js", + "lodash-dot/": "./node_modules/lodash-es/", + "lodash-dotdot": "../node_modules/lodash-es/lodash.js", + "lodash-dotdot/": "../node_modules/lodash-es/" + }, + "scopes": { + "/": { + "moment": "/node_modules_3/moment/src/moment.js", + "vue": "/node_modules_3/vue/dist/vue.runtime.esm.js" + }, + "/js/": { + "lodash-dot": "./node_modules_2/lodash-es/lodash.js", + "lodash-dot/": "./node_modules_2/lodash-es/", + "lodash-dotdot": "../node_modules_2/lodash-es/lodash.js", + "lodash-dotdot/": "../node_modules_2/lodash-es/" + } + } + }, + "tests": { + "Base URLs inside the scope should use the scope if the scope has matching keys": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "lodash-dot": "https://example.com/app/node_modules_2/lodash-es/lodash.js", + "lodash-dot/foo": "https://example.com/app/node_modules_2/lodash-es/foo", + "lodash-dotdot": "https://example.com/node_modules_2/lodash-es/lodash.js", + "lodash-dotdot/foo": "https://example.com/node_modules_2/lodash-es/foo" + } + }, + "Base URLs inside the scope fallback to less specific scope": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "moment": "https://example.com/node_modules_3/moment/src/moment.js", + "vue": "https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js" + } + }, + "Base URLs inside the scope fallback to toplevel": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "moment/foo": "https://example.com/node_modules/moment/src/foo" + } + }, + "Base URLs outside a scope shouldn't use the scope even if the scope has matching keys": { + "baseURL": "https://example.com/app.mjs", + "expectedResults": { + "lodash-dot": "https://example.com/app/node_modules/lodash-es/lodash.js", + "lodash-dotdot": "https://example.com/node_modules/lodash-es/lodash.js", + "lodash-dot/foo": "https://example.com/app/node_modules/lodash-es/foo", + "lodash-dotdot/foo": "https://example.com/node_modules/lodash-es/foo" + } + }, + "Fallback to toplevel or not, depending on trailing slash match": { + "baseURL": "https://example.com/app.mjs", + "expectedResults": { + "moment": "https://example.com/node_modules_3/moment/src/moment.js", + "moment/foo": "https://example.com/node_modules/moment/src/foo" + } + }, + "should still fail for package-like specifiers that are not declared": { + "baseURL": "https://example.com/js/app.mjs", + "expectedResults": { + "underscore/": null, + "underscore/foo": null + } + } + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/tricky-specifiers.json b/testing/web-platform/tests/import-maps/common/resources/tricky-specifiers.json new file mode 100644 index 0000000000000..d9c80c9475000 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/tricky-specifiers.json @@ -0,0 +1,43 @@ +{ + "importMap": { + "imports": { + "package/withslash": "/node_modules/package-with-slash/index.mjs", + "not-a-package": "/lib/not-a-package.mjs", + "only-slash/": "/lib/only-slash/", + ".": "/lib/dot.mjs", + "..": "/lib/dotdot.mjs", + "..\\": "/lib/dotdotbackslash.mjs", + "%2E": "/lib/percent2e.mjs", + "%2F": "/lib/percent2f.mjs" + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "name": "Tricky specifiers", + "tests": { + "explicitly-mapped specifiers that happen to have a slash": { + "expectedResults": { + "package/withslash": "https://example.com/node_modules/package-with-slash/index.mjs" + } + }, + "specifier with punctuation": { + "expectedResults": { + ".": "https://example.com/lib/dot.mjs", + "..": "https://example.com/lib/dotdot.mjs", + "..\\": "https://example.com/lib/dotdotbackslash.mjs", + "%2E": "https://example.com/lib/percent2e.mjs", + "%2F": "https://example.com/lib/percent2f.mjs" + } + }, + "submodule of something not declared with a trailing slash should fail": { + "expectedResults": { + "not-a-package/foo": null + } + }, + "module for which only a trailing-slash version is present should fail": { + "expectedResults": { + "only-slash": null + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/resources/url-specifiers.json b/testing/web-platform/tests/import-maps/common/resources/url-specifiers.json new file mode 100644 index 0000000000000..aff55c4d9dda0 --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/resources/url-specifiers.json @@ -0,0 +1,52 @@ +{ + "importMap": { + "imports": { + "/lib/foo.mjs": "./more/bar.mjs", + "./dotrelative/foo.mjs": "/lib/dot.mjs", + "../dotdotrelative/foo.mjs": "/lib/dotdot.mjs", + "/": "/lib/slash-only/", + "./": "/lib/dotslash-only/", + "/test/": "/lib/url-trailing-slash/", + "./test/": "/lib/url-trailing-slash-dot/", + "/test": "/lib/test1.mjs", + "../test": "/lib/test2.mjs" + } + }, + "importMapBaseURL": "https://example.com/app/index.html", + "baseURL": "https://example.com/js/app.mjs", + "name": "URL-like specifiers", + "tests": { + "Ordinal URL-like specifiers": { + "expectedResults": { + "https://example.com/lib/foo.mjs": "https://example.com/app/more/bar.mjs", + "https://///example.com/lib/foo.mjs": "https://example.com/app/more/bar.mjs", + "/lib/foo.mjs": "https://example.com/app/more/bar.mjs", + "https://example.com/app/dotrelative/foo.mjs": "https://example.com/lib/dot.mjs", + "../app/dotrelative/foo.mjs": "https://example.com/lib/dot.mjs", + "https://example.com/dotdotrelative/foo.mjs": "https://example.com/lib/dotdot.mjs", + "../dotdotrelative/foo.mjs": "https://example.com/lib/dotdot.mjs" + } + }, + "Import map entries just composed from / and .": { + "expectedResults": { + "https://example.com/": "https://example.com/lib/slash-only/", + "/": "https://example.com/lib/slash-only/", + "../": "https://example.com/lib/slash-only/", + "https://example.com/app/": "https://example.com/lib/dotslash-only/", + "/app/": "https://example.com/lib/dotslash-only/", + "../app/": "https://example.com/lib/dotslash-only/" + } + }, + "prefix-matched by keys with trailing slashes": { + "expectedResults": { + "/test/foo.mjs": "https://example.com/lib/url-trailing-slash/foo.mjs", + "https://example.com/app/test/foo.mjs": "https://example.com/lib/url-trailing-slash-dot/foo.mjs" + } + }, + "should use the last entry's address when URL-like specifiers parse to the same absolute URL": { + "expectedResults": { + "/test": "https://example.com/lib/test2.mjs" + } + } + } +} diff --git a/testing/web-platform/tests/import-maps/common/tools/format_json.py b/testing/web-platform/tests/import-maps/common/tools/format_json.py new file mode 100644 index 0000000000000..2808a37906e0f --- /dev/null +++ b/testing/web-platform/tests/import-maps/common/tools/format_json.py @@ -0,0 +1,27 @@ +import collections +import json +import sys +import traceback +""" +Simple JSON formatter, to be used for JSON files under resources/. + +Usage: +$ python tools/format_json.py resources/*.json +""" + + +def main(): + for filename in sys.argv[1:]: + print filename + try: + spec = json.load( + open(filename, 'r'), object_pairs_hook=collections.OrderedDict) + with open(filename, 'w') as f: + f.write(json.dumps(spec, indent=2, separators=(',', ': '))) + f.write('\n') + except: + traceback.print_exc() + + +if __name__ == '__main__': + main() diff --git a/testing/web-platform/tests/import-maps/imported/resolving-scopes.tentative.html b/testing/web-platform/tests/import-maps/imported/resolving-scopes.tentative.html deleted file mode 100644 index e3a6775b2b95b..0000000000000 --- a/testing/web-platform/tests/import-maps/imported/resolving-scopes.tentative.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/testing/web-platform/tests/import-maps/imported/resolving.tentative.html b/testing/web-platform/tests/import-maps/imported/resolving.tentative.html deleted file mode 100644 index da7a3ddd7f50d..0000000000000 --- a/testing/web-platform/tests/import-maps/imported/resolving.tentative.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/testing/web-platform/tests/import-maps/imported/resources/resolving-scopes.js b/testing/web-platform/tests/import-maps/imported/resources/resolving-scopes.js deleted file mode 100644 index d133b50bd2b8d..0000000000000 --- a/testing/web-platform/tests/import-maps/imported/resources/resolving-scopes.js +++ /dev/null @@ -1,222 +0,0 @@ -'use strict'; -const { URL } = require('url'); -const { parseFromString } = require('../lib/parser.js'); -const { resolve } = require('../lib/resolver.js'); - -const mapBaseURL = new URL('https://example.com/app/index.html'); - -function makeResolveUnderTest(mapString) { - const map = parseFromString(mapString, mapBaseURL); - return (specifier, baseURL) => resolve(specifier, map, baseURL); -} - -describe('Mapped using scope instead of "imports"', () => { - const jsNonDirURL = new URL('https://example.com/js'); - const jsPrefixedURL = new URL('https://example.com/jsiscool'); - const inJSDirURL = new URL('https://example.com/js/app.mjs'); - const topLevelURL = new URL('https://example.com/app.mjs'); - - describe('Exact vs. prefix based matching', () => { - it('should match correctly when both are in the map', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "scopes": { - "/js": { - "moment": "/only-triggered-by-exact/moment", - "moment/": "/only-triggered-by-exact/moment/" - }, - "/js/": { - "moment": "/triggered-by-any-subpath/moment", - "moment/": "/triggered-by-any-subpath/moment/" - } - } - }`); - - expect(resolveUnderTest('moment', jsNonDirURL)).toMatchURL('https://example.com/only-triggered-by-exact/moment'); - expect(resolveUnderTest('moment/foo', jsNonDirURL)).toMatchURL('https://example.com/only-triggered-by-exact/moment/foo'); - - expect(resolveUnderTest('moment', inJSDirURL)).toMatchURL('https://example.com/triggered-by-any-subpath/moment'); - expect(resolveUnderTest('moment/foo', inJSDirURL)).toMatchURL('https://example.com/triggered-by-any-subpath/moment/foo'); - - expect(() => resolveUnderTest('moment', jsPrefixedURL)).toThrow(TypeError); - expect(() => resolveUnderTest('moment/foo', jsPrefixedURL)).toThrow(TypeError); - }); - - it('should match correctly when only an exact match is in the map', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "scopes": { - "/js": { - "moment": "/only-triggered-by-exact/moment", - "moment/": "/only-triggered-by-exact/moment/" - } - } - }`); - - expect(resolveUnderTest('moment', jsNonDirURL)).toMatchURL('https://example.com/only-triggered-by-exact/moment'); - expect(resolveUnderTest('moment/foo', jsNonDirURL)).toMatchURL('https://example.com/only-triggered-by-exact/moment/foo'); - - expect(() => resolveUnderTest('moment', inJSDirURL)).toThrow(TypeError); - expect(() => resolveUnderTest('moment/foo', inJSDirURL)).toThrow(TypeError); - - expect(() => resolveUnderTest('moment', jsPrefixedURL)).toThrow(TypeError); - expect(() => resolveUnderTest('moment/foo', jsPrefixedURL)).toThrow(TypeError); - }); - - it('should match correctly when only a prefix match is in the map', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "scopes": { - "/js/": { - "moment": "/triggered-by-any-subpath/moment", - "moment/": "/triggered-by-any-subpath/moment/" - } - } - }`); - - expect(() => resolveUnderTest('moment', jsNonDirURL)).toThrow(TypeError); - expect(() => resolveUnderTest('moment/foo', jsNonDirURL)).toThrow(TypeError); - - expect(resolveUnderTest('moment', inJSDirURL)).toMatchURL('https://example.com/triggered-by-any-subpath/moment'); - expect(resolveUnderTest('moment/foo', inJSDirURL)).toMatchURL('https://example.com/triggered-by-any-subpath/moment/foo'); - - expect(() => resolveUnderTest('moment', jsPrefixedURL)).toThrow(TypeError); - expect(() => resolveUnderTest('moment/foo', jsPrefixedURL)).toThrow(TypeError); - }); - }); - - describe('Package-like scenarios', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "moment": "/node_modules/moment/src/moment.js", - "moment/": "/node_modules/moment/src/", - "lodash-dot": "./node_modules/lodash-es/lodash.js", - "lodash-dot/": "./node_modules/lodash-es/", - "lodash-dotdot": "../node_modules/lodash-es/lodash.js", - "lodash-dotdot/": "../node_modules/lodash-es/" - }, - "scopes": { - "/": { - "moment": "/node_modules_3/moment/src/moment.js", - "vue": "/node_modules_3/vue/dist/vue.runtime.esm.js" - }, - "/js/": { - "lodash-dot": "./node_modules_2/lodash-es/lodash.js", - "lodash-dot/": "./node_modules_2/lodash-es/", - "lodash-dotdot": "../node_modules_2/lodash-es/lodash.js", - "lodash-dotdot/": "../node_modules_2/lodash-es/" - } - } - }`); - - it('should resolve scoped', () => { - expect(resolveUnderTest('lodash-dot', inJSDirURL)).toMatchURL('https://example.com/app/node_modules_2/lodash-es/lodash.js'); - expect(resolveUnderTest('lodash-dotdot', inJSDirURL)).toMatchURL('https://example.com/node_modules_2/lodash-es/lodash.js'); - expect(resolveUnderTest('lodash-dot/foo', inJSDirURL)).toMatchURL('https://example.com/app/node_modules_2/lodash-es/foo'); - expect(resolveUnderTest('lodash-dotdot/foo', inJSDirURL)).toMatchURL('https://example.com/node_modules_2/lodash-es/foo'); - }); - - it('should apply best scope match', () => { - expect(resolveUnderTest('moment', topLevelURL)).toMatchURL('https://example.com/node_modules_3/moment/src/moment.js'); - expect(resolveUnderTest('moment', inJSDirURL)).toMatchURL('https://example.com/node_modules_3/moment/src/moment.js'); - expect(resolveUnderTest('vue', inJSDirURL)).toMatchURL('https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js'); - }); - - it('should fallback to "imports"', () => { - expect(resolveUnderTest('moment/foo', topLevelURL)).toMatchURL('https://example.com/node_modules/moment/src/foo'); - expect(resolveUnderTest('moment/foo', inJSDirURL)).toMatchURL('https://example.com/node_modules/moment/src/foo'); - expect(resolveUnderTest('lodash-dot', topLevelURL)).toMatchURL('https://example.com/app/node_modules/lodash-es/lodash.js'); - expect(resolveUnderTest('lodash-dotdot', topLevelURL)).toMatchURL('https://example.com/node_modules/lodash-es/lodash.js'); - expect(resolveUnderTest('lodash-dot/foo', topLevelURL)).toMatchURL('https://example.com/app/node_modules/lodash-es/foo'); - expect(resolveUnderTest('lodash-dotdot/foo', topLevelURL)).toMatchURL('https://example.com/node_modules/lodash-es/foo'); - }); - - it('should still fail for package-like specifiers that are not declared', () => { - expect(() => resolveUnderTest('underscore/', inJSDirURL)).toThrow(TypeError); - expect(() => resolveUnderTest('underscore/foo', inJSDirURL)).toThrow(TypeError); - }); - }); - - describe('The scope inheritance example from the README', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "a": "/a-1.mjs", - "b": "/b-1.mjs", - "c": "/c-1.mjs", - "d": "/d-1.mjs" - }, - "scopes": { - "/scope2/": { - "a": "/a-2.mjs", - "d": "/d-2.mjs" - }, - "/scope2/scope3/": { - "b": "/b-3.mjs", - "d": "/d-3.mjs" - } - } - }`); - - const scope1URL = new URL('https://example.com/scope1/foo.mjs'); - const scope2URL = new URL('https://example.com/scope2/foo.mjs'); - const scope3URL = new URL('https://example.com/scope2/scope3/foo.mjs'); - - it('should fall back to "imports" when none match', () => { - expect(resolveUnderTest('a', scope1URL)).toMatchURL('https://example.com/a-1.mjs'); - expect(resolveUnderTest('b', scope1URL)).toMatchURL('https://example.com/b-1.mjs'); - expect(resolveUnderTest('c', scope1URL)).toMatchURL('https://example.com/c-1.mjs'); - expect(resolveUnderTest('d', scope1URL)).toMatchURL('https://example.com/d-1.mjs'); - }); - - it('should use a direct scope override', () => { - expect(resolveUnderTest('a', scope2URL)).toMatchURL('https://example.com/a-2.mjs'); - expect(resolveUnderTest('b', scope2URL)).toMatchURL('https://example.com/b-1.mjs'); - expect(resolveUnderTest('c', scope2URL)).toMatchURL('https://example.com/c-1.mjs'); - expect(resolveUnderTest('d', scope2URL)).toMatchURL('https://example.com/d-2.mjs'); - }); - - it('should use an indirect scope override', () => { - expect(resolveUnderTest('a', scope3URL)).toMatchURL('https://example.com/a-2.mjs'); - expect(resolveUnderTest('b', scope3URL)).toMatchURL('https://example.com/b-3.mjs'); - expect(resolveUnderTest('c', scope3URL)).toMatchURL('https://example.com/c-1.mjs'); - expect(resolveUnderTest('d', scope3URL)).toMatchURL('https://example.com/d-3.mjs'); - }); - }); - - describe('Relative URL scope keys', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "a": "/a-1.mjs", - "b": "/b-1.mjs", - "c": "/c-1.mjs" - }, - "scopes": { - "": { - "a": "/a-empty-string.mjs" - }, - "./": { - "b": "/b-dot-slash.mjs" - }, - "../": { - "c": "/c-dot-dot-slash.mjs" - } - } - }`); - const inSameDirAsMap = new URL('./foo.mjs', mapBaseURL); - const inDirAboveMap = new URL('../foo.mjs', mapBaseURL); - - it('should resolve an empty string scope using the import map URL', () => { - expect(resolveUnderTest('a', mapBaseURL)).toMatchURL('https://example.com/a-empty-string.mjs'); - expect(resolveUnderTest('a', inSameDirAsMap)).toMatchURL('https://example.com/a-1.mjs'); - }); - - it('should resolve a ./ scope using the import map URL\'s directory', () => { - expect(resolveUnderTest('b', mapBaseURL)).toMatchURL('https://example.com/b-dot-slash.mjs'); - expect(resolveUnderTest('b', inSameDirAsMap)).toMatchURL('https://example.com/b-dot-slash.mjs'); - }); - - it('should resolve a ../ scope using the import map URL\'s directory', () => { - expect(resolveUnderTest('c', mapBaseURL)).toMatchURL('https://example.com/c-dot-dot-slash.mjs'); - expect(resolveUnderTest('c', inSameDirAsMap)).toMatchURL('https://example.com/c-dot-dot-slash.mjs'); - expect(resolveUnderTest('c', inDirAboveMap)).toMatchURL('https://example.com/c-dot-dot-slash.mjs'); - }); - }); -}); - diff --git a/testing/web-platform/tests/import-maps/imported/resources/resolving.js b/testing/web-platform/tests/import-maps/imported/resources/resolving.js deleted file mode 100644 index 9b435e227ad16..0000000000000 --- a/testing/web-platform/tests/import-maps/imported/resources/resolving.js +++ /dev/null @@ -1,232 +0,0 @@ -'use strict'; -const { URL } = require('url'); -const { parseFromString } = require('../lib/parser.js'); -const { resolve } = require('../lib/resolver.js'); - -const mapBaseURL = new URL('https://example.com/app/index.html'); -const scriptURL = new URL('https://example.com/js/app.mjs'); - -function makeResolveUnderTest(mapString) { - const map = parseFromString(mapString, mapBaseURL); - return specifier => resolve(specifier, map, scriptURL); -} - -describe('Unmapped', () => { - const resolveUnderTest = makeResolveUnderTest(`{}`); - - it('should resolve ./ specifiers as URLs', () => { - expect(resolveUnderTest('./foo')).toMatchURL('https://example.com/js/foo'); - expect(resolveUnderTest('./foo/bar')).toMatchURL('https://example.com/js/foo/bar'); - expect(resolveUnderTest('./foo/../bar')).toMatchURL('https://example.com/js/bar'); - expect(resolveUnderTest('./foo/../../bar')).toMatchURL('https://example.com/bar'); - }); - - it('should resolve ../ specifiers as URLs', () => { - expect(resolveUnderTest('../foo')).toMatchURL('https://example.com/foo'); - expect(resolveUnderTest('../foo/bar')).toMatchURL('https://example.com/foo/bar'); - expect(resolveUnderTest('../../../foo/bar')).toMatchURL('https://example.com/foo/bar'); - }); - - it('should resolve / specifiers as URLs', () => { - expect(resolveUnderTest('/foo')).toMatchURL('https://example.com/foo'); - expect(resolveUnderTest('/foo/bar')).toMatchURL('https://example.com/foo/bar'); - expect(resolveUnderTest('/../../foo/bar')).toMatchURL('https://example.com/foo/bar'); - expect(resolveUnderTest('/../foo/../bar')).toMatchURL('https://example.com/bar'); - }); - - it('should parse absolute fetch-scheme URLs', () => { - expect(resolveUnderTest('about:good')).toMatchURL('about:good'); - expect(resolveUnderTest('https://example.net')).toMatchURL('https://example.net/'); - expect(resolveUnderTest('https://ex%41mple.com/')).toMatchURL('https://example.com/'); - expect(resolveUnderTest('https:example.org')).toMatchURL('https://example.org/'); - expect(resolveUnderTest('https://///example.com///')).toMatchURL('https://example.com///'); - }); - - it('should parse absolute non-fetch-scheme URLs', () => { - expect(resolveUnderTest('mailto:bad')).toMatchURL('mailto:bad'); - expect(resolveUnderTest('import:bad')).toMatchURL('import:bad'); - expect(resolveUnderTest('javascript:bad')).toMatchURL('javascript:bad'); - expect(resolveUnderTest('wss:bad')).toMatchURL('wss://bad/'); - }); - - it('should fail for strings not parseable as absolute URLs and not starting with ./ ../ or /', () => { - expect(() => resolveUnderTest('foo')).toThrow(TypeError); - expect(() => resolveUnderTest('\\foo')).toThrow(TypeError); - expect(() => resolveUnderTest(':foo')).toThrow(TypeError); - expect(() => resolveUnderTest('@foo')).toThrow(TypeError); - expect(() => resolveUnderTest('%2E/foo')).toThrow(TypeError); - expect(() => resolveUnderTest('%2E%2E/foo')).toThrow(TypeError); - expect(() => resolveUnderTest('.%2Ffoo')).toThrow(TypeError); - expect(() => resolveUnderTest('https://ex ample.org/')).toThrow(TypeError); - expect(() => resolveUnderTest('https://example.com:demo')).toThrow(TypeError); - expect(() => resolveUnderTest('http://[www.example.com]/')).toThrow(TypeError); - }); -}); - -describe('Mapped using the "imports" key only (no scopes)', () => { - describe('Package-like scenarios', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "moment": "/node_modules/moment/src/moment.js", - "moment/": "/node_modules/moment/src/", - "lodash-dot": "./node_modules/lodash-es/lodash.js", - "lodash-dot/": "./node_modules/lodash-es/", - "lodash-dotdot": "../node_modules/lodash-es/lodash.js", - "lodash-dotdot/": "../node_modules/lodash-es/" - } - }`); - - it('should work for package main modules', () => { - expect(resolveUnderTest('moment')).toMatchURL('https://example.com/node_modules/moment/src/moment.js'); - expect(resolveUnderTest('lodash-dot')).toMatchURL('https://example.com/app/node_modules/lodash-es/lodash.js'); - expect(resolveUnderTest('lodash-dotdot')).toMatchURL('https://example.com/node_modules/lodash-es/lodash.js'); - }); - - it('should work for package submodules', () => { - expect(resolveUnderTest('moment/foo')).toMatchURL('https://example.com/node_modules/moment/src/foo'); - expect(resolveUnderTest('lodash-dot/foo')).toMatchURL('https://example.com/app/node_modules/lodash-es/foo'); - expect(resolveUnderTest('lodash-dotdot/foo')).toMatchURL('https://example.com/node_modules/lodash-es/foo'); - }); - - it('should work for package names that end in a slash by just passing through', () => { - - expect(resolveUnderTest('moment/')).toMatchURL('https://example.com/node_modules/moment/src/'); - }); - - it('should still fail for package modules that are not declared', () => { - expect(() => resolveUnderTest('underscore/')).toThrow(TypeError); - expect(() => resolveUnderTest('underscore/foo')).toThrow(TypeError); - }); - }); - - describe('Tricky specifiers', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "package/withslash": "/node_modules/package-with-slash/index.mjs", - "not-a-package": "/lib/not-a-package.mjs", - "only-slash/": "/lib/only-slash/", - ".": "/lib/dot.mjs", - "..": "/lib/dotdot.mjs", - "..\\\\": "/lib/dotdotbackslash.mjs", - "%2E": "/lib/percent2e.mjs", - "%2F": "/lib/percent2f.mjs" - } - }`); - - it('should work for explicitly-mapped specifiers that happen to have a slash', () => { - expect(resolveUnderTest('package/withslash')).toMatchURL('https://example.com/node_modules/package-with-slash/index.mjs'); - }); - - it('should work when the specifier has punctuation', () => { - expect(resolveUnderTest('.')).toMatchURL('https://example.com/lib/dot.mjs'); - expect(resolveUnderTest('..')).toMatchURL('https://example.com/lib/dotdot.mjs'); - expect(resolveUnderTest('..\\')).toMatchURL('https://example.com/lib/dotdotbackslash.mjs'); - expect(resolveUnderTest('%2E')).toMatchURL('https://example.com/lib/percent2e.mjs'); - expect(resolveUnderTest('%2F')).toMatchURL('https://example.com/lib/percent2f.mjs'); - }); - - it('should fail for attempting to get a submodule of something not declared with a trailing slash', () => { - expect(() => resolveUnderTest('not-a-package/foo')).toThrow(TypeError); - }); - - it('should fail for attempting to get a module if only a trailing-slash version is present', () => { - expect(() => resolveUnderTest('only-slash')).toThrow(TypeError); - }); - }); - - describe('URL-like specifiers', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "/lib/foo.mjs": "./more/bar.mjs", - "./dotrelative/foo.mjs": "/lib/dot.mjs", - "../dotdotrelative/foo.mjs": "/lib/dotdot.mjs", - - "/": "/lib/slash-only/", - "./": "/lib/dotslash-only/", - - "/test/": "/lib/url-trailing-slash/", - "./test/": "/lib/url-trailing-slash-dot/", - - "/test": "/lib/test1.mjs", - "../test": "/lib/test2.mjs" - } - }`); - - it('should remap to other URLs', () => { - expect(resolveUnderTest('https://example.com/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs'); - expect(resolveUnderTest('https://///example.com/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs'); - expect(resolveUnderTest('/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs'); - - expect(resolveUnderTest('https://example.com/app/dotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dot.mjs'); - expect(resolveUnderTest('../app/dotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dot.mjs'); - - expect(resolveUnderTest('https://example.com/dotdotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dotdot.mjs'); - expect(resolveUnderTest('../dotdotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dotdot.mjs'); - }); - - it('should remap URLs that are just composed from / and .', () => { - expect(resolveUnderTest('https://example.com/')).toMatchURL('https://example.com/lib/slash-only/'); - expect(resolveUnderTest('/')).toMatchURL('https://example.com/lib/slash-only/'); - expect(resolveUnderTest('../')).toMatchURL('https://example.com/lib/slash-only/'); - - expect(resolveUnderTest('https://example.com/app/')).toMatchURL('https://example.com/lib/dotslash-only/'); - expect(resolveUnderTest('/app/')).toMatchURL('https://example.com/lib/dotslash-only/'); - expect(resolveUnderTest('../app/')).toMatchURL('https://example.com/lib/dotslash-only/'); - }); - - it('should remap URLs that are prefix-matched by keys with trailing slashes', () => { - expect(resolveUnderTest('/test/foo.mjs')).toMatchURL('https://example.com/lib/url-trailing-slash/foo.mjs'); - expect(resolveUnderTest('https://example.com/app/test/foo.mjs')).toMatchURL('https://example.com/lib/url-trailing-slash-dot/foo.mjs'); - }); - - it('should use the last entry\'s address when URL-like specifiers parse to the same absolute URL', () => { - expect(resolveUnderTest('/test')).toMatchURL('https://example.com/lib/test2.mjs'); - }); - }); - - describe('Overlapping entries with trailing slashes', () => { - it('should favor the most-specific key', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "a": "/1", - "a/": "/2/", - "a/b": "/3", - "a/b/": "/4/" - } - }`); - - expect(resolveUnderTest('a')).toMatchURL('https://example.com/1'); - expect(resolveUnderTest('a/')).toMatchURL('https://example.com/2/'); - expect(resolveUnderTest('a/b')).toMatchURL('https://example.com/3'); - expect(resolveUnderTest('a/b/')).toMatchURL('https://example.com/4/'); - expect(resolveUnderTest('a/b/c')).toMatchURL('https://example.com/4/c'); - }); - - it('should favor the most-specific key when there are no mappings for less-specific keys', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "a/b": "/3", - "a/b/": "/4/" - } - }`); - - expect(() => resolveUnderTest('a')).toThrow(TypeError); - expect(() => resolveUnderTest('a/')).toThrow(TypeError); - expect(() => resolveUnderTest('a/x')).toThrow(TypeError); - expect(resolveUnderTest('a/b')).toMatchURL('https://example.com/3'); - expect(resolveUnderTest('a/b/')).toMatchURL('https://example.com/4/'); - expect(resolveUnderTest('a/b/c')).toMatchURL('https://example.com/4/c'); - expect(() => resolveUnderTest('a/x/c')).toThrow(TypeError); - }); - }); - - it('should deal with data: URL bases', () => { - const resolveUnderTest = makeResolveUnderTest(`{ - "imports": { - "foo/": "data:text/javascript,foo/" - } - }`); - - expect(() => resolveUnderTest('foo/bar')).toThrow(TypeError); - }); -}); diff --git a/testing/web-platform/tests/lint.whitelist b/testing/web-platform/tests/lint.whitelist index 48d7e9d6a747b..a1b3f63650cb9 100644 --- a/testing/web-platform/tests/lint.whitelist +++ b/testing/web-platform/tests/lint.whitelist @@ -201,6 +201,7 @@ SET TIMEOUT: shadow-dom/slotchange-event.html SET TIMEOUT: trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html SET TIMEOUT: trusted-types/DOMWindowTimers-setTimeout-setInterval.tentative.html SET TIMEOUT: user-timing/* +SET TIMEOUT: web-animations/timing-model/animations/* SET TIMEOUT: webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html SET TIMEOUT: webauthn/*timeout.https.html SET TIMEOUT: webdriver/* @@ -676,6 +677,7 @@ MISSING-LINK: css/filter-effects/*.any.js # Tests that use WebKit/Blink testing APIs LAYOUTTESTS APIS: css/css-regions/interactivity/* LAYOUTTESTS APIS: import-maps/resources/jest-test-helper.js +LAYOUTTESTS APIS: import-maps/common/resources/common-test-helper.js LAYOUTTESTS APIS: resources/chromium/generic_sensor_mocks.js LAYOUTTESTS APIS: resources/chromium/nfc-mock.js LAYOUTTESTS APIS: resources/chromium/webxr-test.js