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