Update web-platform-tests to revision 2b80e6d28f3c1ca734384ebded282bf07df80657

This commit is contained in:
WPT Sync Bot 2019-10-18 10:32:38 +00:00
parent 32eb858a6a
commit aff72973cf
379 changed files with 13969 additions and 2161 deletions

View file

@ -2,7 +2,7 @@
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
@ -15,16 +15,10 @@
const importMap = `
{
"imports": {
"bare/bare": "./resources/log.js?pipe=sub&name=bare",
"bare/cross-origin-bare": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bare",
"bare/to-data": "data:text/javascript,log.push('dataURL')",
"bare/std-blank": "std:blank",
"bare/blank": "@std/blank",
"bare/std-none": "std:none",
"bare/none": "@std/none",
"bare/to-bare": "bare/bare"
"bare/none": "@std/none"
}
}
`;
@ -41,16 +35,6 @@ const tests = {
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// Bare to HTTP(S).
"bare/bare":
[Result.URL, Result.URL, "log:bare", "log:bare"],
"bare/cross-origin-bare":
[Result.URL, Result.URL, "log:cross-origin-bare", "log:cross-origin-bare"],
// Bare to data:
"bare/to-data":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// Bare to built-in.
"bare/std-blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
@ -60,10 +44,6 @@ const tests = {
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
"bare/none":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
// Bare to bare mapping is disabled.
"bare/to-bare":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);

View file

@ -1,16 +1,16 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
const importMap = `
{
"imports": {
"./resources/log.js?pipe=sub&name=empty": [ "@std/" ],
"./resources/log.js?pipe=sub&name=empty-fallback": [
"../resources/log.js?pipe=sub&name=empty": [ "@std/" ],
"../resources/log.js?pipe=sub&name=empty-fallback": [
"@std/",
"./resources/log.js?pipe=sub&name=empty-fallback"
"../resources/log.js?pipe=sub&name=empty-fallback"
]
}
}
@ -32,9 +32,9 @@ const tests = {
"@std/":
[Result.FETCH_ERROR, Result.PARSE_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
"./resources/log.js?pipe=sub&name=empty":
"../resources/log.js?pipe=sub&name=empty":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
"./resources/log.js?pipe=sub&name=empty-fallback":
"../resources/log.js?pipe=sub&name=empty-fallback":
[Result.URL, Result.URL, Result.URL, Result.URL],
};

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
const tests = {

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
const tests = {

View file

@ -2,7 +2,7 @@
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
@ -15,18 +15,10 @@
const importMap = `
{
"imports": {
"bare": "./resources/log.js?pipe=sub&name=bare",
"data:text/javascript,log.push('data:foo')": "./resources/log.js?pipe=sub&name=foo",
"data:text/javascript,log.push('data:cross-origin-foo')": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo",
"data:text/javascript,log.push('data:to-data')": "data:text/javascript,log.push('dataURL')",
"data:text/javascript,log.push('data:std-blank')": "std:blank",
"data:text/javascript,log.push('data:blank')": "@std/blank",
"data:text/javascript,log.push('data:std-none')": "std:none",
"data:text/javascript,log.push('data:none')": "@std/none",
"data:text/javascript,log.push('data:to-bare')": "bare"
"data:text/javascript,log.push('data:none')": "@std/none"
}
}
`;
@ -43,16 +35,6 @@ const tests = {
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// data: to HTTP(S).
"data:text/javascript,log.push('data:foo')":
[Result.URL, Result.URL, "log:foo", "log:foo"],
"data:text/javascript,log.push('data:cross-origin-foo')":
[Result.URL, Result.URL, "log:cross-origin-foo", "log:cross-origin-foo"],
// data: to data:
"data:text/javascript,log.push('data:to-data')":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// data: to built-in.
"data:text/javascript,log.push('data:std-blank')":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
@ -62,10 +44,6 @@ const tests = {
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
"data:text/javascript,log.push('data:none')":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
// data: to bare mapping is disabled.
"data:text/javascript,log.push('data:to-bare')":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);

View file

@ -2,7 +2,7 @@
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// Fallbacks from external URLs (such as HTTPS URLs) are
@ -18,51 +18,51 @@
const importMap = `
{
"imports": {
"bare": "./resources/log.js?pipe=sub&name=bare",
"bare": "../resources/log.js?pipe=sub&name=bare",
"./resources/log.js?pipe=sub&name=http-to-builtin": [
"./resources/log.js?pipe=sub&name=http-to-builtin",
"../resources/log.js?pipe=sub&name=http-to-builtin": [
"../resources/log.js?pipe=sub&name=http-to-builtin",
"@std/blank"
],
"./resources/log.js?pipe=sub&name=fallback-to-different-url-1": [
"../resources/log.js?pipe=sub&name=fallback-to-different-url-1": [
"@std/blank",
"./resources/log.js?pipe=sub&name=something-different"
"../resources/log.js?pipe=sub&name=something-different"
],
"./resources/log.js?pipe=sub&name=fallback-to-different-url-2": [
"../resources/log.js?pipe=sub&name=fallback-to-different-url-2": [
"@std/none",
"./resources/log.js?pipe=sub&name=something-different2"
"../resources/log.js?pipe=sub&name=something-different2"
],
"./resources/log.js?pipe=sub&name=fallback-to-different-origin-1": [
"../resources/log.js?pipe=sub&name=fallback-to-different-origin-1": [
"@std/blank",
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=fallback-to-different-origin-1"
],
"./resources/log.js?pipe=sub&name=fallback-to-different-origin-2": [
"../resources/log.js?pipe=sub&name=fallback-to-different-origin-2": [
"@std/none",
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=fallback-to-different-origin-2"
],
"./resources/log.js?pipe=sub&name=more-than-two-values-1": [
"../resources/log.js?pipe=sub&name=more-than-two-values-1": [
"@std/none",
"@std/blank",
"./resources/log.js?pipe=sub&name=more-than-two-values-1"
"../resources/log.js?pipe=sub&name=more-than-two-values-1"
],
"./resources/log.js?pipe=sub&name=more-than-two-values-2": [
"../resources/log.js?pipe=sub&name=more-than-two-values-2": [
"@std/none",
"./resources/log.js?pipe=sub&name=more-than-two-values-2",
"../resources/log.js?pipe=sub&name=more-than-two-values-2",
"@std/blank"
],
"./resources/log.js?pipe=sub&name=fallback-from-http": [
"./resources/log.js?pipe=sub&name=non-built-in",
"./resources/log.js?pipe=sub&name=fallback-from-http"
"../resources/log.js?pipe=sub&name=fallback-from-http": [
"../resources/log.js?pipe=sub&name=non-built-in",
"../resources/log.js?pipe=sub&name=fallback-from-http"
],
"./resources/log.js?pipe=sub&name=fallback-from-data-1": [
"../resources/log.js?pipe=sub&name=fallback-from-data-1": [
"data:text/plain,",
"./resources/log.js?pipe=sub&name=fallback-from-http"
"../resources/log.js?pipe=sub&name=fallback-from-http"
],
"./resources/log.js?pipe=sub&name=fallback-from-data-2": [
"../resources/log.js?pipe=sub&name=fallback-from-data-2": [
"data:text/javascript,log.push('dataURL')",
"./resources/log.js?pipe=sub&name=fallback-from-http"
"../resources/log.js?pipe=sub&name=fallback-from-http"
]
}
}

View file

@ -2,7 +2,7 @@
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// This tests is for fallbacks with the pattern of
@ -16,13 +16,13 @@
const importMap = `
{
"imports": {
"./resources/log.js?pipe=sub&name=blank": [
"../resources/log.js?pipe=sub&name=blank": [
"@std/blank",
"./resources/log.js?pipe=sub&name=blank"
"../resources/log.js?pipe=sub&name=blank"
],
"./resources/log.js?pipe=sub&name=none": [
"../resources/log.js?pipe=sub&name=none": [
"@std/none",
"./resources/log.js?pipe=sub&name=none"
"../resources/log.js?pipe=sub&name=none"
],
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-blank": [
"@std/blank",
@ -33,13 +33,13 @@ const importMap = `
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-none"
],
"./resources/log.js?pipe=sub&name=std-blank": [
"../resources/log.js?pipe=sub&name=std-blank": [
"std:blank",
"./resources/log.js?pipe=sub&name=std-blank"
"../resources/log.js?pipe=sub&name=std-blank"
],
"./resources/log.js?pipe=sub&name=std-none": [
"../resources/log.js?pipe=sub&name=std-none": [
"std:none",
"./resources/log.js?pipe=sub&name=std-none"
"../resources/log.js?pipe=sub&name=std-none"
],
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-blank": [
"std:blank",
@ -61,18 +61,18 @@ const tests = {
// - dynamic import.
// Result.URL indicates that the specifier was not re-mapped by import maps,
// i.e. either considered as a relative path, or fallback occured.
"./resources/log.js?pipe=sub&name=blank":
"../resources/log.js?pipe=sub&name=blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
"./resources/log.js?pipe=sub&name=none":
"../resources/log.js?pipe=sub&name=none":
[Result.URL, Result.URL, Result.URL, Result.URL],
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-none":
[Result.URL, Result.URL, Result.URL, Result.URL],
"./resources/log.js?pipe=sub&name=std-blank":
"../resources/log.js?pipe=sub&name=std-blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
"./resources/log.js?pipe=sub&name=std-none":
"../resources/log.js?pipe=sub&name=std-none":
[Result.URL, Result.URL, Result.URL, Result.URL],
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helper.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
@ -14,18 +14,10 @@
const importMap = `
{
"imports": {
"bare": "./resources/log.js?pipe=sub&name=bare",
"./resources/log.js?pipe=sub&name=foo": "./resources/log.js?pipe=sub&name=bar",
"./resources/log.js?pipe=sub&name=cross-origin-foo": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bar",
"./resources/log.js?pipe=sub&name=to-data": "data:text/javascript,log.push('dataURL')",
"./resources/log.js?pipe=sub&name=std-blank": "std:blank",
"./resources/log.js?pipe=sub&name=blank": "@std/blank",
"./resources/log.js?pipe=sub&name=std-none": "std:none",
"./resources/log.js?pipe=sub&name=none": "@std/none",
"./resources/log.js?pipe=sub&name=to-bare": "bare"
"../resources/log.js?pipe=sub&name=std-blank": "std:blank",
"../resources/log.js?pipe=sub&name=blank": "@std/blank",
"../resources/log.js?pipe=sub&name=std-none": "std:none",
"../resources/log.js?pipe=sub&name=none": "@std/none"
}
}
`;
@ -42,16 +34,6 @@ const tests = {
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// HTTP(S) to HTTP(S).
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=foo":
[Result.URL, Result.URL, "log:bar", "log:bar"],
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo":
[Result.URL, Result.URL, "log:cross-origin-bar", "log:cross-origin-bar"],
// HTTP(S) to data:
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-data":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// HTTP(S) to built-in.
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=std-blank":
[Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
@ -61,10 +43,6 @@ const tests = {
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=none":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
// HTTP(S) to bare mapping is disabled.
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-bare":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/parsing-addresses.js
-->
<script type="module" src="resources/parsing-addresses.js"></script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/parsing-schema.js
-->
<script type="module" src="resources/parsing-schema.js"></script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/parsing-scope-keys.js
-->
<script type="module" src="resources/parsing-scope-keys.js"></script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/parsing-specifier-keys.js
-->
<script type="module" src="resources/parsing-specifier-keys.js"></script>

View file

@ -2,7 +2,7 @@
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/jest-test-helper.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--

View file

@ -2,7 +2,7 @@
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/jest-test-helper.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/resolving-scopes.js
-->
<script type="module" src="resources/resolving-scopes.js"></script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/jest-test-helper.js"></script>
<script type="module" src="resources/helpers/parsing.js"></script>
<!--
Imported from https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/resolving.js
-->
<script type="module" src="resources/resolving.js"></script>

View file

@ -0,0 +1,44 @@
'use strict';
const { parseFromString } = require('../../lib/parser.js');
// Local modifications from upstream:
// Currently warnings and scopes are not checked in expectSpecifierMap().
exports.expectSpecifierMap = (input, baseURL, output, warnings = []) => {
expect(parseFromString(`{ "imports": ${input} }`, baseURL))
.toEqual({ imports: output, scopes: {} });
};
exports.expectScopes = (inputArray, baseURL, outputArray, warnings = []) => {
const checkWarnings = testWarningHandler(warnings);
const inputScopesAsStrings = inputArray.map(scopePrefix => `${JSON.stringify(scopePrefix)}: {}`);
const inputString = `{ "scopes": { ${inputScopesAsStrings.join(', ')} } }`;
const outputScopesObject = {};
for (const outputScopePrefix of outputArray) {
outputScopesObject[outputScopePrefix] = {};
}
expect(parseFromString(inputString, baseURL)).toEqual({ imports: {}, scopes: outputScopesObject });
checkWarnings();
};
exports.expectBad = (input, baseURL, warnings = []) => {
const checkWarnings = testWarningHandler(warnings);
expect(() => parseFromString(input, baseURL)).toThrow(TypeError);
checkWarnings();
};
exports.expectWarnings = (input, baseURL, output, warnings = []) => {
const checkWarnings = testWarningHandler(warnings);
expect(parseFromString(input, baseURL)).toEqual(output);
checkWarnings();
};
function testWarningHandler(expectedWarnings) {
// We don't check warnings on WPT tests, because there are no
// ways to catch console warnings from JavaScript.
return () => {};
}

View file

@ -0,0 +1,351 @@
'use strict';
const { expectSpecifierMap } = require('./helpers/parsing.js');
const { BUILT_IN_MODULE_SCHEME } = require('../lib/utils.js');
describe('Relative URL-like addresses', () => {
it('should accept strings prefixed with ./, ../, or /', () => {
expectSpecifierMap(
`{
"dotSlash": "./foo",
"dotDotSlash": "../foo",
"slash": "/foo"
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/foo')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/foo')],
slash: [expect.toMatchURL('https://base.example/foo')]
}
);
});
it('should not accept strings prefixed with ./, ../, or / for data: base URLs', () => {
expectSpecifierMap(
`{
"dotSlash": "./foo",
"dotDotSlash": "../foo",
"slash": "/foo"
}`,
'data:text/html,test',
{
dotSlash: [],
dotDotSlash: [],
slash: []
},
[
`Invalid address "./foo" for the specifier key "dotSlash".`,
`Invalid address "../foo" for the specifier key "dotDotSlash".`,
`Invalid address "/foo" for the specifier key "slash".`
]
);
});
it('should accept the literal strings ./, ../, or / with no suffix', () => {
expectSpecifierMap(
`{
"dotSlash": "./",
"dotDotSlash": "../",
"slash": "/"
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/')],
slash: [expect.toMatchURL('https://base.example/')]
}
);
});
it('should ignore percent-encoded variants of ./, ../, or /', () => {
expectSpecifierMap(
`{
"dotSlash1": "%2E/",
"dotDotSlash1": "%2E%2E/",
"dotSlash2": ".%2F",
"dotDotSlash2": "..%2F",
"slash2": "%2F",
"dotSlash3": "%2E%2F",
"dotDotSlash3": "%2E%2E%2F"
}`,
'https://base.example/path1/path2/path3',
{
dotSlash1: [],
dotDotSlash1: [],
dotSlash2: [],
dotDotSlash2: [],
slash2: [],
dotSlash3: [],
dotDotSlash3: []
},
[
`Invalid address "%2E/" for the specifier key "dotSlash1".`,
`Invalid address "%2E%2E/" for the specifier key "dotDotSlash1".`,
`Invalid address ".%2F" for the specifier key "dotSlash2".`,
`Invalid address "..%2F" for the specifier key "dotDotSlash2".`,
`Invalid address "%2F" for the specifier key "slash2".`,
`Invalid address "%2E%2F" for the specifier key "dotSlash3".`,
`Invalid address "%2E%2E%2F" for the specifier key "dotDotSlash3".`
]
);
});
});
describe('Built-in module addresses', () => {
it('should accept URLs using the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${BUILT_IN_MODULE_SCHEME}:foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo`)]
}
);
});
it('should ignore percent-encoded variants of the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo" for the specifier key "foo".`]
);
});
it('should allow built-in module URLs that contain "/" or "\\"', () => {
expectSpecifierMap(
`{
"slashEnd": "${BUILT_IN_MODULE_SCHEME}:foo/",
"slashMiddle": "${BUILT_IN_MODULE_SCHEME}:foo/bar",
"backslash": "${BUILT_IN_MODULE_SCHEME}:foo\\\\baz"
}`,
'https://base.example/path1/path2/path3',
{
slashEnd: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/`)],
slashMiddle: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/bar`)],
backslash: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo\\baz`)]
}
);
});
});
describe('Absolute URL addresses', () => {
it('should only accept absolute URL addresses with fetch schemes', () => {
expectSpecifierMap(
`{
"about": "about:good",
"blob": "blob:good",
"data": "data:good",
"file": "file:///good",
"filesystem": "filesystem:good",
"http": "http://good/",
"https": "https://good/",
"ftp": "ftp://good/",
"import": "import:bad",
"mailto": "mailto:bad",
"javascript": "javascript:bad",
"wss": "wss:bad"
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
},
[
`Invalid address "import:bad" for the specifier key "import".`,
`Invalid address "mailto:bad" for the specifier key "mailto".`,
`Invalid address "javascript:bad" for the specifier key "javascript".`,
`Invalid address "wss:bad" for the specifier key "wss".`
]
);
});
it('should only accept absolute URL addresses with fetch schemes inside arrays', () => {
expectSpecifierMap(
`{
"about": ["about:good"],
"blob": ["blob:good"],
"data": ["data:good"],
"file": ["file:///good"],
"filesystem": ["filesystem:good"],
"http": ["http://good/"],
"https": ["https://good/"],
"ftp": ["ftp://good/"],
"import": ["import:bad"],
"mailto": ["mailto:bad"],
"javascript": ["javascript:bad"],
"wss": ["wss:bad"]
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
},
[
`Invalid address "import:bad" for the specifier key "import".`,
`Invalid address "mailto:bad" for the specifier key "mailto".`,
`Invalid address "javascript:bad" for the specifier key "javascript".`,
`Invalid address "wss:bad" for the specifier key "wss".`
]
);
});
it('should parse absolute URLs, ignoring unparseable ones', () => {
expectSpecifierMap(
`{
"unparseable1": "https://ex ample.org/",
"unparseable2": "https://example.com:demo",
"unparseable3": "http://[www.example.com]/",
"invalidButParseable1": "https:example.org",
"invalidButParseable2": "https://///example.com///",
"prettyNormal": "https://example.net",
"percentDecoding": "https://ex%41mple.com/",
"noPercentDecoding": "https://example.com/%41"
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
`Invalid address "https://example.com:demo" for the specifier key "unparseable2".`,
`Invalid address "http://[www.example.com]/" for the specifier key "unparseable3".`
]
);
});
it('should parse absolute URLs, ignoring unparseable ones inside arrays', () => {
expectSpecifierMap(
`{
"unparseable1": ["https://ex ample.org/"],
"unparseable2": ["https://example.com:demo"],
"unparseable3": ["http://[www.example.com]/"],
"invalidButParseable1": ["https:example.org"],
"invalidButParseable2": ["https://///example.com///"],
"prettyNormal": ["https://example.net"],
"percentDecoding": ["https://ex%41mple.com/"],
"noPercentDecoding": ["https://example.com/%41"]
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
`Invalid address "https://example.com:demo" for the specifier key "unparseable2".`,
`Invalid address "http://[www.example.com]/" for the specifier key "unparseable3".`
]
);
});
});
describe('Failing addresses: mismatched trailing slashes', () => {
it('should warn for the simple case', () => {
expectSpecifierMap(
`{
"trailer/": "/notrailer",
"${BUILT_IN_MODULE_SCHEME}:trailer/": "/bim-notrailer"
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});
it('should warn for a mismatch alone in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-notrailer"]
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});
it('should warn for a mismatch alongside non-mismatches in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/atrailer/", "/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-atrailer/", "/bim-notrailer"]
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [expect.toMatchURL('https://base.example/atrailer/')],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: [expect.toMatchURL('https://base.example/bim-atrailer/')]
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});
});
describe('Other invalid addresses', () => {
it('should ignore unprefixed strings that are not absolute URLs', () => {
for (const bad of ['bar', '\\bar', '~bar', '#bar', '?bar']) {
expectSpecifierMap(
`{
"foo": ${JSON.stringify(bad)}
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${bad}" for the specifier key "foo".`]
);
}
});
});

View file

@ -0,0 +1,139 @@
'use strict';
const { parseFromString } = require('../lib/parser.js');
const { expectBad, expectWarnings, expectSpecifierMap } = require('./helpers/parsing.js');
const nonObjectStrings = ['null', 'true', '1', '"foo"', '[]'];
test('Invalid JSON', () => {
expect(() => parseFromString('{ imports: {} }', 'https://base.example/')).toThrow(SyntaxError);
});
describe('Mismatching the top-level schema', () => {
it('should throw for top-level non-objects', () => {
for (const nonObject of nonObjectStrings) {
expectBad(nonObject, 'https://base.example/');
}
});
it('should throw if imports is a non-object', () => {
for (const nonObject of nonObjectStrings) {
expectBad(`{ "imports": ${nonObject} }`, 'https://base.example/');
}
});
it('should throw if scopes is a non-object', () => {
for (const nonObject of nonObjectStrings) {
expectBad(`{ "scopes": ${nonObject} }`, 'https://base.example/');
}
});
it('should ignore unspecified top-level entries', () => {
expectWarnings(
`{
"imports": {},
"new-feature": {},
"scops": {}
}`,
'https://base.example/',
{ imports: {}, scopes: {} },
[
`Invalid top-level key "new-feature". Only "imports" and "scopes" can be present.`,
`Invalid top-level key "scops". Only "imports" and "scopes" can be present.`
]
);
});
});
describe('Mismatching the specifier map schema', () => {
const invalidAddressStrings = ['true', '1', '{}'];
const invalidInsideArrayStrings = ['null', 'true', '1', '{}', '[]'];
it('should ignore entries where the address is not a string, array, or null', () => {
for (const invalid of invalidAddressStrings) {
expectSpecifierMap(
`{
"foo": ${invalid},
"bar": ["https://example.com/"]
}`,
'https://base.example/',
{
bar: [expect.toMatchURL('https://example.com/')]
},
[
`Invalid address ${invalid} for the specifier key "foo". ` +
`Addresses must be strings, arrays, or null.`
]
);
}
});
it('should ignore entries where the specifier key is an empty string', () => {
expectSpecifierMap(
`{
"": ["https://example.com/"]
}`,
'https://base.example/',
{},
[`Invalid empty string specifier key.`]
);
});
it('should ignore members of an address array that are not strings', () => {
for (const invalid of invalidInsideArrayStrings) {
expectSpecifierMap(
`{
"foo": ["https://example.com/", ${invalid}],
"bar": ["https://example.com/"]
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/')],
bar: [expect.toMatchURL('https://example.com/')]
},
[
`Invalid address ${invalid} inside the address array for the specifier key "foo". ` +
`Address arrays must only contain strings.`
]
);
}
});
it('should throw if a scope\'s value is not an object', () => {
for (const invalid of nonObjectStrings) {
expectBad(`{ "scopes": { "https://scope.example/": ${invalid} } }`, 'https://base.example/');
}
});
});
describe('Normalization', () => {
it('should normalize empty import maps to have imports and scopes keys', () => {
expect(parseFromString(`{}`, 'https://base.example/'))
.toEqual({ imports: {}, scopes: {} });
});
it('should normalize an import map without imports to have imports', () => {
expect(parseFromString(`{ "scopes": {} }`, 'https://base.example/'))
.toEqual({ imports: {}, scopes: {} });
});
it('should normalize an import map without scopes to have scopes', () => {
expect(parseFromString(`{ "imports": {} }`, 'https://base.example/'))
.toEqual({ imports: {}, scopes: {} });
});
it('should normalize addresses to arrays', () => {
expectSpecifierMap(
`{
"foo": "https://example.com/1",
"bar": ["https://example.com/2"],
"baz": null
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/1')],
bar: [expect.toMatchURL('https://example.com/2')],
baz: []
}
);
});
});

View file

@ -0,0 +1,144 @@
'use strict';
const { expectScopes } = require('./helpers/parsing.js');
describe('Relative URL scope keys', () => {
it('should work with no prefix', () => {
expectScopes(
['foo'],
'https://base.example/path1/path2/path3',
['https://base.example/path1/path2/foo']
);
});
it('should work with ./, ../, and / prefixes', () => {
expectScopes(
['./foo', '../foo', '/foo'],
'https://base.example/path1/path2/path3',
[
'https://base.example/path1/path2/foo',
'https://base.example/path1/foo',
'https://base.example/foo'
]
);
});
it('should work with /s, ?s, and #s', () => {
expectScopes(
['foo/bar?baz#qux'],
'https://base.example/path1/path2/path3',
['https://base.example/path1/path2/foo/bar?baz#qux']
);
});
it('should work with an empty string scope key', () => {
expectScopes(
[''],
'https://base.example/path1/path2/path3',
['https://base.example/path1/path2/path3']
);
});
it('should work with / suffixes', () => {
expectScopes(
['foo/', './foo/', '../foo/', '/foo/', '/foo//'],
'https://base.example/path1/path2/path3',
[
'https://base.example/path1/path2/foo/',
'https://base.example/path1/path2/foo/',
'https://base.example/path1/foo/',
'https://base.example/foo/',
'https://base.example/foo//'
]
);
});
it('should deduplicate based on URL parsing rules', () => {
expectScopes(
['foo/\\', 'foo//', 'foo\\\\'],
'https://base.example/path1/path2/path3',
['https://base.example/path1/path2/foo//']
);
});
});
describe('Absolute URL scope keys', () => {
it('should accept all absolute URL scope keys, with or without fetch schemes', () => {
expectScopes(
[
'about:good',
'blob:good',
'data:good',
'file:///good',
'filesystem:http://example.com/good/',
'http://good/',
'https://good/',
'ftp://good/',
'import:bad',
'mailto:bad',
'javascript:bad',
'wss:ba'
],
'https://base.example/path1/path2/path3',
[
'about:good',
'blob:good',
'data:good',
'file:///good',
'filesystem:http://example.com/good/',
'http://good/',
'https://good/',
'ftp://good/',
'import:bad',
'mailto:bad',
'javascript:bad',
'wss://ba/'
],
[]
);
});
it('should parse absolute URL scope keys, ignoring unparseable ones', () => {
expectScopes(
[
'https://ex ample.org/',
'https://example.com:demo',
'http://[www.example.com]/',
'https:example.org',
'https://///example.com///',
'https://example.net',
'https://ex%41mple.com/foo/',
'https://example.com/%41'
],
'https://base.example/path1/path2/path3',
[
'https://base.example/path1/path2/example.org', // tricky case! remember we have a base URL
'https://example.com///',
'https://example.net/',
'https://example.com/foo/',
'https://example.com/%41'
],
[
'Invalid scope "https://ex ample.org/" (parsed against base URL "https://base.example/path1/path2/path3").',
'Invalid scope "https://example.com:demo" (parsed against base URL "https://base.example/path1/path2/path3").',
'Invalid scope "http://[www.example.com]/" (parsed against base URL "https://base.example/path1/path2/path3").'
]
);
});
it('should ignore relative URL scope keys when the base URL is a data: URL', () => {
expectScopes(
[
'./foo',
'../foo',
'/foo'
],
'data:text/html,test',
[],
[
'Invalid scope "./foo" (parsed against base URL "data:text/html,test").',
'Invalid scope "../foo" (parsed against base URL "data:text/html,test").',
'Invalid scope "/foo" (parsed against base URL "data:text/html,test").'
]
);
});
});

View file

@ -0,0 +1,159 @@
'use strict';
const { expectSpecifierMap } = require('./helpers/parsing.js');
const { BUILT_IN_MODULE_SCHEME } = require('../lib/utils.js');
const BLANK = `${BUILT_IN_MODULE_SCHEME}:blank`;
describe('Relative URL-like specifier keys', () => {
it('should absolutize strings prefixed with ./, ../, or / into the corresponding URLs', () => {
expectSpecifierMap(
`{
"./foo": "/dotslash",
"../foo": "/dotdotslash",
"/foo": "/slash"
}`,
'https://base.example/path1/path2/path3',
{
'https://base.example/path1/path2/foo': [expect.toMatchURL('https://base.example/dotslash')],
'https://base.example/path1/foo': [expect.toMatchURL('https://base.example/dotdotslash')],
'https://base.example/foo': [expect.toMatchURL('https://base.example/slash')]
}
);
});
it('should not absolutize strings prefixed with ./, ../, or / with a data: URL base', () => {
expectSpecifierMap(
`{
"./foo": "https://example.com/dotslash",
"../foo": "https://example.com/dotdotslash",
"/foo": "https://example.com/slash"
}`,
'data:text/html,test',
{
'./foo': [expect.toMatchURL('https://example.com/dotslash')],
'../foo': [expect.toMatchURL('https://example.com/dotdotslash')],
'/foo': [expect.toMatchURL('https://example.com/slash')]
}
);
});
it('should absolutize the literal strings ./, ../, or / with no suffix', () => {
expectSpecifierMap(
`{
"./": "/dotslash/",
"../": "/dotdotslash/",
"/": "/slash/"
}`,
'https://base.example/path1/path2/path3',
{
'https://base.example/path1/path2/': [expect.toMatchURL('https://base.example/dotslash/')],
'https://base.example/path1/': [expect.toMatchURL('https://base.example/dotdotslash/')],
'https://base.example/': [expect.toMatchURL('https://base.example/slash/')]
}
);
});
it('should treat percent-encoded variants of ./, ../, or / as bare specifiers', () => {
expectSpecifierMap(
`{
"%2E/": "/dotSlash1/",
"%2E%2E/": "/dotDotSlash1/",
".%2F": "/dotSlash2",
"..%2F": "/dotDotSlash2",
"%2F": "/slash2",
"%2E%2F": "/dotSlash3",
"%2E%2E%2F": "/dotDotSlash3"
}`,
'https://base.example/path1/path2/path3',
{
'%2E/': [expect.toMatchURL('https://base.example/dotSlash1/')],
'%2E%2E/': [expect.toMatchURL('https://base.example/dotDotSlash1/')],
'.%2F': [expect.toMatchURL('https://base.example/dotSlash2')],
'..%2F': [expect.toMatchURL('https://base.example/dotDotSlash2')],
'%2F': [expect.toMatchURL('https://base.example/slash2')],
'%2E%2F': [expect.toMatchURL('https://base.example/dotSlash3')],
'%2E%2E%2F': [expect.toMatchURL('https://base.example/dotDotSlash3')]
}
);
});
});
describe('Absolute URL specifier keys', () => {
it('should only accept absolute URL specifier keys with fetch schemes, treating others as bare specifiers', () => {
expectSpecifierMap(
`{
"about:good": "/about",
"blob:good": "/blob",
"data:good": "/data",
"file:///good": "/file",
"filesystem:good": "/filesystem",
"http://good/": "/http/",
"https://good/": "/https/",
"ftp://good/": "/ftp/",
"import:bad": "/import",
"mailto:bad": "/mailto",
"javascript:bad": "/javascript",
"wss:bad": "/wss"
}`,
'https://base.example/path1/path2/path3',
{
'about:good': [expect.toMatchURL('https://base.example/about')],
'blob:good': [expect.toMatchURL('https://base.example/blob')],
'data:good': [expect.toMatchURL('https://base.example/data')],
'file:///good': [expect.toMatchURL('https://base.example/file')],
'filesystem:good': [expect.toMatchURL('https://base.example/filesystem')],
'http://good/': [expect.toMatchURL('https://base.example/http/')],
'https://good/': [expect.toMatchURL('https://base.example/https/')],
'ftp://good/': [expect.toMatchURL('https://base.example/ftp/')],
'import:bad': [expect.toMatchURL('https://base.example/import')],
'mailto:bad': [expect.toMatchURL('https://base.example/mailto')],
'javascript:bad': [expect.toMatchURL('https://base.example/javascript')],
'wss:bad': [expect.toMatchURL('https://base.example/wss')]
}
);
});
it('should parse absolute URLs, treating unparseable ones as bare specifiers', () => {
expectSpecifierMap(
`{
"https://ex ample.org/": "/unparseable1/",
"https://example.com:demo": "/unparseable2",
"http://[www.example.com]/": "/unparseable3/",
"https:example.org": "/invalidButParseable1/",
"https://///example.com///": "/invalidButParseable2/",
"https://example.net": "/prettyNormal/",
"https://ex%41mple.com/": "/percentDecoding/",
"https://example.com/%41": "/noPercentDecoding"
}`,
'https://base.example/path1/path2/path3',
{
'https://ex ample.org/': [expect.toMatchURL('https://base.example/unparseable1/')],
'https://example.com:demo': [expect.toMatchURL('https://base.example/unparseable2')],
'http://[www.example.com]/': [expect.toMatchURL('https://base.example/unparseable3/')],
'https://example.org/': [expect.toMatchURL('https://base.example/invalidButParseable1/')],
'https://example.com///': [expect.toMatchURL('https://base.example/invalidButParseable2/')],
'https://example.net/': [expect.toMatchURL('https://base.example/prettyNormal/')],
'https://example.com/': [expect.toMatchURL('https://base.example/percentDecoding/')],
'https://example.com/%41': [expect.toMatchURL('https://base.example/noPercentDecoding')]
}
);
});
it('should parse built-in module specifier keys, including with a "/"', () => {
expectSpecifierMap(
`{
"${BLANK}": "/blank",
"${BLANK}/": "/blank/",
"${BLANK}/foo": "/blank/foo",
"${BLANK}\\\\foo": "/blank/backslashfoo"
}`,
'https://base.example/path1/path2/path3',
{
[BLANK]: [expect.toMatchURL('https://base.example/blank')],
[`${BLANK}/`]: [expect.toMatchURL('https://base.example/blank/')],
[`${BLANK}/foo`]: [expect.toMatchURL('https://base.example/blank/foo')],
[`${BLANK}\\foo`]: [expect.toMatchURL('https://base.example/blank/backslashfoo')]
}
);
});
});

View file

@ -0,0 +1,230 @@
'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');
it('should fail when the mapping is to an empty array', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"scopes": {
"/js/": {
"moment": null,
"lodash": []
}
}
}`);
expect(() => resolveUnderTest('moment', inJSDirURL)).toThrow(TypeError);
expect(() => resolveUnderTest('lodash', inJSDirURL)).toThrow(TypeError);
});
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"
},
"scopes": {
"/scope2/": {
"a": "/a-2.mjs"
},
"/scope2/scope3/": {
"b": "/b-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');
});
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');
});
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');
});
});
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');
});
});
});

View file

@ -0,0 +1,270 @@
'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 fail for absolute non-fetch-scheme URLs', () => {
expect(() => resolveUnderTest('mailto:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('import:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('javascript:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('wss:bad')).toThrow(TypeError);
});
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)', () => {
it('should fail when the mapping is to an empty array', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"moment": null,
"lodash": []
}
}`);
expect(() => resolveUnderTest('moment')).toThrow(TypeError);
expect(() => resolveUnderTest('lodash')).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/",
"nowhere/": []
}
}`);
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', () => {
// TODO: is this the right behavior, or should we throw?
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);
});
it('should fail for package submodules that map to nowhere', () => {
expect(() => resolveUnderTest('nowhere/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",
".": "/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);
});
});
describe('URL-like specifiers', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"/node_modules/als-polyfill/index.mjs": "std:kv-storage",
"/lib/foo.mjs": "./more/bar.mjs",
"./dotrelative/foo.mjs": "/lib/dot.mjs",
"../dotdotrelative/foo.mjs": "/lib/dotdot.mjs",
"/lib/no.mjs": null,
"./dotrelative/no.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 fail for URLs that remap to empty arrays', () => {
expect(() => resolveUnderTest('https://example.com/lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('/lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('../lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('https://example.com/app/dotrelative/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('/app/dotrelative/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('../app/dotrelative/no.mjs')).toThrow(TypeError);
});
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 (no empty arrays)', () => {
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 empty arrays are involved for less-specific keys', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": [],
"a/": [],
"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 favor the most-specific key when empty arrays are involved for more-specific keys', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": "/1",
"a/": "/2/",
"a/b": [],
"a/b/": []
}
}`);
expect(resolveUnderTest('a')).toMatchURL('https://example.com/1');
expect(resolveUnderTest('a/')).toMatchURL('https://example.com/2/');
expect(resolveUnderTest('a/x')).toMatchURL('https://example.com/2/x');
expect(() => resolveUnderTest('a/b')).toThrow(TypeError);
expect(() => resolveUnderTest('a/b/')).toThrow(TypeError);
expect(() => resolveUnderTest('a/b/c')).toThrow(TypeError);
expect(resolveUnderTest('a/x/c')).toMatchURL('https://example.com/2/x/c');
});
});
});

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
// (not relative paths).
const importMap = `
{
"imports": {
"bare/bare": "../resources/log.js?pipe=sub&name=bare",
"bare/cross-origin-bare": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bare",
"bare/to-data": "data:text/javascript,log.push('dataURL')",
"bare/to-bare": "bare/bare"
}
}
`;
const tests = {
// Arrays of expected results for:
// - <script src type="module">,
// - <script src> (classic script),
// - static import, and
// - dynamic import.
// Currently, Chromium's implementation resolves import maps as a part of
// specifier resolution, and thus failure in import map resolution causes
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// Bare to HTTP(S).
"bare/bare":
[Result.URL, Result.URL, "log:bare", "log:bare"],
"bare/cross-origin-bare":
[Result.URL, Result.URL, "log:cross-origin-bare", "log:cross-origin-bare"],
// Bare to data:
"bare/to-data":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// Bare to bare mapping is disabled.
"bare/to-bare":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);
</script>
<body>

View file

@ -0,0 +1 @@
Content-Type: text/javascript

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
// (not relative paths).
const importMap = `
{
"imports": {
"bare": "../resources/log.js?pipe=sub&name=bare",
"data:text/javascript,log.push('data:foo')": "../resources/log.js?pipe=sub&name=foo",
"data:text/javascript,log.push('data:cross-origin-foo')": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo",
"data:text/javascript,log.push('data:to-data')": "data:text/javascript,log.push('dataURL')",
"data:text/javascript,log.push('data:to-bare')": "bare"
}
}
`;
const tests = {
// Arrays of expected results for:
// - <script src type="module">,
// - <script src> (classic script),
// - static import, and
// - dynamic import.
// Currently, Chromium's implementation resolves import maps as a part of
// specifier resolution, and thus failure in import map resolution causes
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// data: to HTTP(S).
"data:text/javascript,log.push('data:foo')":
[Result.URL, Result.URL, "log:foo", "log:foo"],
"data:text/javascript,log.push('data:cross-origin-foo')":
[Result.URL, Result.URL, "log:cross-origin-foo", "log:cross-origin-foo"],
// data: to data:
"data:text/javascript,log.push('data:to-data')":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// data: to bare mapping is disabled.
"data:text/javascript,log.push('data:to-bare')":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);
</script>
<body>

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-helper.js"></script>
<script>
// "bare/..." (i.e. without leading "./") are bare specifiers
// (not relative paths).
const importMap = `
{
"imports": {
"bare": "../resources/log.js?pipe=sub&name=bare",
"../resources/log.js?pipe=sub&name=foo": "../resources/log.js?pipe=sub&name=bar",
"../resources/log.js?pipe=sub&name=cross-origin-foo": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bar",
"../resources/log.js?pipe=sub&name=to-data": "data:text/javascript,log.push('dataURL')",
"../resources/log.js?pipe=sub&name=to-bare": "bare"
}
}
`;
const tests = {
// Arrays of expected results for:
// - <script src type="module">,
// - <script src> (classic script),
// - static import, and
// - dynamic import.
// Currently, Chromium's implementation resolves import maps as a part of
// specifier resolution, and thus failure in import map resolution causes
// a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
// below. https://crbug.com/928435
// HTTP(S) to HTTP(S).
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=foo":
[Result.URL, Result.URL, "log:bar", "log:bar"],
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo":
[Result.URL, Result.URL, "log:cross-origin-bar", "log:cross-origin-bar"],
// HTTP(S) to data:
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-data":
[Result.URL, Result.URL, "dataURL", "dataURL"],
// HTTP(S) to bare mapping is disabled.
"{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-bare":
[Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
};
doTests(importMap, null, tests);
</script>
<body>

View file

@ -5,7 +5,7 @@
<script type="importmap">
{
"imports": {
"./resources/log.js?pipe=sub&name=A": "./resources/log.js?pipe=sub&name=B"
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
@ -17,8 +17,8 @@ const log = [];
// key will become the URL/specifier BEFORE import map resolution.
// https://crbug.com/928435
promise_test(() => {
return import("./resources/log.js?pipe=sub&name=A")
.then(() => import("./resources/log.js?pipe=sub&name=B"))
return import("../resources/log.js?pipe=sub&name=A")
.then(() => import("../resources/log.js?pipe=sub&name=B"))
.then(() => assert_array_equals(log, ["log:B"]))
},
"Module map's key is the URL after import map resolution");

View file

@ -0,0 +1 @@
import "{{GET[url]}}";

View file

@ -38,13 +38,7 @@ exports.expectWarnings = (input, baseURL, output, warnings = []) => {
};
function testWarningHandler(expectedWarnings) {
const warnings = [];
const { warn } = console;
console.warn = warning => {
warnings.push(warning);
};
return () => {
console.warn = warn;
expect(warnings).toEqual(expectedWarnings);
};
// We don't check warnings on WPT tests, because there are no
// ways to catch console warnings from JavaScript.
return () => {};
}

View file

@ -1,6 +1,5 @@
'use strict';
const { expectSpecifierMap } = require('./helpers/parsing.js');
const { BUILT_IN_MODULE_SCHEME } = require('../lib/utils.js');
describe('Relative URL-like addresses', () => {
it('should accept strings prefixed with ./, ../, or /', () => {
@ -12,9 +11,9 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/foo')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/foo')],
slash: [expect.toMatchURL('https://base.example/foo')]
dotSlash: expect.toMatchURL('https://base.example/path1/path2/foo'),
dotDotSlash: expect.toMatchURL('https://base.example/path1/foo'),
slash: expect.toMatchURL('https://base.example/foo')
}
);
});
@ -28,9 +27,6 @@ describe('Relative URL-like addresses', () => {
}`,
'data:text/html,test',
{
dotSlash: [],
dotDotSlash: [],
slash: []
},
[
`Invalid address "./foo" for the specifier key "dotSlash".`,
@ -49,9 +45,9 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/')],
slash: [expect.toMatchURL('https://base.example/')]
dotSlash: expect.toMatchURL('https://base.example/path1/path2/'),
dotDotSlash: expect.toMatchURL('https://base.example/path1/'),
slash: expect.toMatchURL('https://base.example/')
}
);
});
@ -69,13 +65,6 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash1: [],
dotDotSlash1: [],
dotSlash2: [],
dotDotSlash2: [],
slash2: [],
dotSlash3: [],
dotDotSlash3: []
},
[
`Invalid address "%2E/" for the specifier key "dotSlash1".`,
@ -90,49 +79,6 @@ describe('Relative URL-like addresses', () => {
});
});
describe('Built-in module addresses', () => {
it('should accept URLs using the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${BUILT_IN_MODULE_SCHEME}:foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo`)]
}
);
});
it('should ignore percent-encoded variants of the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo" for the specifier key "foo".`]
);
});
it('should allow built-in module URLs that contain "/" or "\\"', () => {
expectSpecifierMap(
`{
"slashEnd": "${BUILT_IN_MODULE_SCHEME}:foo/",
"slashMiddle": "${BUILT_IN_MODULE_SCHEME}:foo/bar",
"backslash": "${BUILT_IN_MODULE_SCHEME}:foo\\\\baz"
}`,
'https://base.example/path1/path2/path3',
{
slashEnd: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/`)],
slashMiddle: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/bar`)],
backslash: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo\\baz`)]
}
);
});
});
describe('Absolute URL addresses', () => {
it('should only accept absolute URL addresses with fetch schemes', () => {
expectSpecifierMap(
@ -141,7 +87,7 @@ describe('Absolute URL addresses', () => {
"blob": "blob:good",
"data": "data:good",
"file": "file:///good",
"filesystem": "filesystem:good",
"filesystem": "filesystem:http://example.com/good/",
"http": "http://good/",
"https": "https://good/",
"ftp": "ftp://good/",
@ -152,65 +98,20 @@ describe('Absolute URL addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
about: expect.toMatchURL('about:good'),
blob: expect.toMatchURL('blob:good'),
data: expect.toMatchURL('data:good'),
file: expect.toMatchURL('file:///good'),
filesystem: expect.toMatchURL('filesystem:http://example.com/good/'),
http: expect.toMatchURL('http://good/'),
https: expect.toMatchURL('https://good/'),
ftp: expect.toMatchURL('ftp://good/'),
import: expect.toMatchURL('import:bad'),
javascript: expect.toMatchURL('javascript:bad'),
mailto: expect.toMatchURL('mailto:bad'),
wss: expect.toMatchURL('wss://bad/')
},
[
`Invalid address "import:bad" for the specifier key "import".`,
`Invalid address "mailto:bad" for the specifier key "mailto".`,
`Invalid address "javascript:bad" for the specifier key "javascript".`,
`Invalid address "wss:bad" for the specifier key "wss".`
]
);
});
it('should only accept absolute URL addresses with fetch schemes inside arrays', () => {
expectSpecifierMap(
`{
"about": ["about:good"],
"blob": ["blob:good"],
"data": ["data:good"],
"file": ["file:///good"],
"filesystem": ["filesystem:good"],
"http": ["http://good/"],
"https": ["https://good/"],
"ftp": ["ftp://good/"],
"import": ["import:bad"],
"mailto": ["mailto:bad"],
"javascript": ["javascript:bad"],
"wss": ["wss:bad"]
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
},
[
`Invalid address "import:bad" for the specifier key "import".`,
`Invalid address "mailto:bad" for the specifier key "mailto".`,
`Invalid address "javascript:bad" for the specifier key "javascript".`,
`Invalid address "wss:bad" for the specifier key "wss".`
]
[]
);
});
@ -228,45 +129,11 @@ describe('Absolute URL addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
`Invalid address "https://example.com:demo" for the specifier key "unparseable2".`,
`Invalid address "http://[www.example.com]/" for the specifier key "unparseable3".`
]
);
});
it('should parse absolute URLs, ignoring unparseable ones inside arrays', () => {
expectSpecifierMap(
`{
"unparseable1": ["https://ex ample.org/"],
"unparseable2": ["https://example.com:demo"],
"unparseable3": ["http://[www.example.com]/"],
"invalidButParseable1": ["https:example.org"],
"invalidButParseable2": ["https://///example.com///"],
"prettyNormal": ["https://example.net"],
"percentDecoding": ["https://ex%41mple.com/"],
"noPercentDecoding": ["https://example.com/%41"]
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
invalidButParseable1: expect.toMatchURL('https://example.org/'),
invalidButParseable2: expect.toMatchURL('https://example.com///'),
prettyNormal: expect.toMatchURL('https://example.net/'),
percentDecoding: expect.toMatchURL('https://example.com/'),
noPercentDecoding: expect.toMatchURL('https://example.com/%41')
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
@ -281,54 +148,12 @@ describe('Failing addresses: mismatched trailing slashes', () => {
it('should warn for the simple case', () => {
expectSpecifierMap(
`{
"trailer/": "/notrailer",
"${BUILT_IN_MODULE_SCHEME}:trailer/": "/bim-notrailer"
"trailer/": "/notrailer"
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});
it('should warn for a mismatch alone in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-notrailer"]
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});
it('should warn for a mismatch alongside non-mismatches in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/atrailer/", "/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-atrailer/", "/bim-notrailer"]
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [expect.toMatchURL('https://base.example/atrailer/')],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: [expect.toMatchURL('https://base.example/bim-atrailer/')]
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
[`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`]
);
});
});
@ -342,7 +167,6 @@ describe('Other invalid addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${bad}" for the specifier key "foo".`]
);

View file

@ -45,24 +45,20 @@ describe('Mismatching the top-level schema', () => {
});
describe('Mismatching the specifier map schema', () => {
const invalidAddressStrings = ['true', '1', '{}'];
const invalidInsideArrayStrings = ['null', 'true', '1', '{}', '[]'];
const invalidAddressStrings = ['null', 'true', '1', '{}', '[]', '["https://example.com/"]'];
it('should ignore entries where the address is not a string, array, or null', () => {
it('should ignore entries where the address is not a string', () => {
for (const invalid of invalidAddressStrings) {
expectSpecifierMap(
`{
"foo": ${invalid},
"bar": ["https://example.com/"]
"bar": "https://example.com/"
}`,
'https://base.example/',
{
bar: [expect.toMatchURL('https://example.com/')]
bar: expect.toMatchURL('https://example.com/')
},
[
`Invalid address ${invalid} for the specifier key "foo". ` +
`Addresses must be strings, arrays, or null.`
]
[`Invalid address ${invalid} for the specifier key "foo". Addresses must be strings.`]
);
}
});
@ -70,7 +66,7 @@ describe('Mismatching the specifier map schema', () => {
it('should ignore entries where the specifier key is an empty string', () => {
expectSpecifierMap(
`{
"": ["https://example.com/"]
"": "https://example.com/"
}`,
'https://base.example/',
{},
@ -78,26 +74,6 @@ describe('Mismatching the specifier map schema', () => {
);
});
it('should ignore members of an address array that are not strings', () => {
for (const invalid of invalidInsideArrayStrings) {
expectSpecifierMap(
`{
"foo": ["https://example.com/", ${invalid}],
"bar": ["https://example.com/"]
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/')],
bar: [expect.toMatchURL('https://example.com/')]
},
[
`Invalid address ${invalid} inside the address array for the specifier key "foo". ` +
`Address arrays must only contain strings.`
]
);
}
});
it('should throw if a scope\'s value is not an object', () => {
for (const invalid of nonObjectStrings) {
expectBad(`{ "scopes": { "https://scope.example/": ${invalid} } }`, 'https://base.example/');
@ -120,20 +96,4 @@ describe('Normalization', () => {
expect(parseFromString(`{ "imports": {} }`, 'https://base.example/'))
.toEqual({ imports: {}, scopes: {} });
});
it('should normalize addresses to arrays', () => {
expectSpecifierMap(
`{
"foo": "https://example.com/1",
"bar": ["https://example.com/2"],
"baz": null
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/1')],
bar: [expect.toMatchURL('https://example.com/2')],
baz: []
}
);
});
});

View file

@ -62,14 +62,14 @@ describe('Relative URL scope keys', () => {
});
describe('Absolute URL scope keys', () => {
it('should only accept absolute URL scope keys with fetch schemes', () => {
it('should accept all absolute URL scope keys, with or without fetch schemes', () => {
expectScopes(
[
'about:good',
'blob:good',
'data:good',
'file:///good',
'filesystem:good',
'filesystem:http://example.com/good/',
'http://good/',
'https://good/',
'ftp://good/',
@ -84,17 +84,16 @@ describe('Absolute URL scope keys', () => {
'blob:good',
'data:good',
'file:///good',
'filesystem:good',
'filesystem:http://example.com/good/',
'http://good/',
'https://good/',
'ftp://good/'
'ftp://good/',
'import:bad',
'mailto:bad',
'javascript:bad',
'wss://ba/'
],
[
'Invalid scope "import:bad". Scope URLs must have a fetch scheme.',
'Invalid scope "mailto:bad". Scope URLs must have a fetch scheme.',
'Invalid scope "javascript:bad". Scope URLs must have a fetch scheme.',
'Invalid scope "wss://ba/". Scope URLs must have a fetch scheme.'
]
[]
);
});

View file

@ -1,8 +1,5 @@
'use strict';
const { expectSpecifierMap } = require('./helpers/parsing.js');
const { BUILT_IN_MODULE_SCHEME } = require('../lib/utils.js');
const BLANK = `${BUILT_IN_MODULE_SCHEME}:blank`;
describe('Relative URL-like specifier keys', () => {
it('should absolutize strings prefixed with ./, ../, or / into the corresponding URLs', () => {
@ -14,9 +11,9 @@ describe('Relative URL-like specifier keys', () => {
}`,
'https://base.example/path1/path2/path3',
{
'https://base.example/path1/path2/foo': [expect.toMatchURL('https://base.example/dotslash')],
'https://base.example/path1/foo': [expect.toMatchURL('https://base.example/dotdotslash')],
'https://base.example/foo': [expect.toMatchURL('https://base.example/slash')]
'https://base.example/path1/path2/foo': expect.toMatchURL('https://base.example/dotslash'),
'https://base.example/path1/foo': expect.toMatchURL('https://base.example/dotdotslash'),
'https://base.example/foo': expect.toMatchURL('https://base.example/slash')
}
);
});
@ -30,9 +27,9 @@ describe('Relative URL-like specifier keys', () => {
}`,
'data:text/html,test',
{
'./foo': [expect.toMatchURL('https://example.com/dotslash')],
'../foo': [expect.toMatchURL('https://example.com/dotdotslash')],
'/foo': [expect.toMatchURL('https://example.com/slash')]
'./foo': expect.toMatchURL('https://example.com/dotslash'),
'../foo': expect.toMatchURL('https://example.com/dotdotslash'),
'/foo': expect.toMatchURL('https://example.com/slash')
}
);
});
@ -46,9 +43,9 @@ describe('Relative URL-like specifier keys', () => {
}`,
'https://base.example/path1/path2/path3',
{
'https://base.example/path1/path2/': [expect.toMatchURL('https://base.example/dotslash/')],
'https://base.example/path1/': [expect.toMatchURL('https://base.example/dotdotslash/')],
'https://base.example/': [expect.toMatchURL('https://base.example/slash/')]
'https://base.example/path1/path2/': expect.toMatchURL('https://base.example/dotslash/'),
'https://base.example/path1/': expect.toMatchURL('https://base.example/dotdotslash/'),
'https://base.example/': expect.toMatchURL('https://base.example/slash/')
}
);
});
@ -66,27 +63,27 @@ describe('Relative URL-like specifier keys', () => {
}`,
'https://base.example/path1/path2/path3',
{
'%2E/': [expect.toMatchURL('https://base.example/dotSlash1/')],
'%2E%2E/': [expect.toMatchURL('https://base.example/dotDotSlash1/')],
'.%2F': [expect.toMatchURL('https://base.example/dotSlash2')],
'..%2F': [expect.toMatchURL('https://base.example/dotDotSlash2')],
'%2F': [expect.toMatchURL('https://base.example/slash2')],
'%2E%2F': [expect.toMatchURL('https://base.example/dotSlash3')],
'%2E%2E%2F': [expect.toMatchURL('https://base.example/dotDotSlash3')]
'%2E/': expect.toMatchURL('https://base.example/dotSlash1/'),
'%2E%2E/': expect.toMatchURL('https://base.example/dotDotSlash1/'),
'.%2F': expect.toMatchURL('https://base.example/dotSlash2'),
'..%2F': expect.toMatchURL('https://base.example/dotDotSlash2'),
'%2F': expect.toMatchURL('https://base.example/slash2'),
'%2E%2F': expect.toMatchURL('https://base.example/dotSlash3'),
'%2E%2E%2F': expect.toMatchURL('https://base.example/dotDotSlash3')
}
);
});
});
describe('Absolute URL specifier keys', () => {
it('should only accept absolute URL specifier keys with fetch schemes, treating others as bare specifiers', () => {
it('Accept all absolute URL specifier keys even with fetch schemes as URLs', () => {
expectSpecifierMap(
`{
"about:good": "/about",
"blob:good": "/blob",
"data:good": "/data",
"file:///good": "/file",
"filesystem:good": "/filesystem",
"filesystem:http://example.com/good/": "/filesystem/",
"http://good/": "/http/",
"https://good/": "/https/",
"ftp://good/": "/ftp/",
@ -97,18 +94,18 @@ describe('Absolute URL specifier keys', () => {
}`,
'https://base.example/path1/path2/path3',
{
'about:good': [expect.toMatchURL('https://base.example/about')],
'blob:good': [expect.toMatchURL('https://base.example/blob')],
'data:good': [expect.toMatchURL('https://base.example/data')],
'file:///good': [expect.toMatchURL('https://base.example/file')],
'filesystem:good': [expect.toMatchURL('https://base.example/filesystem')],
'http://good/': [expect.toMatchURL('https://base.example/http/')],
'https://good/': [expect.toMatchURL('https://base.example/https/')],
'ftp://good/': [expect.toMatchURL('https://base.example/ftp/')],
'import:bad': [expect.toMatchURL('https://base.example/import')],
'mailto:bad': [expect.toMatchURL('https://base.example/mailto')],
'javascript:bad': [expect.toMatchURL('https://base.example/javascript')],
'wss:bad': [expect.toMatchURL('https://base.example/wss')]
'about:good': expect.toMatchURL('https://base.example/about'),
'blob:good': expect.toMatchURL('https://base.example/blob'),
'data:good': expect.toMatchURL('https://base.example/data'),
'file:///good': expect.toMatchURL('https://base.example/file'),
'filesystem:http://example.com/good/': expect.toMatchURL('https://base.example/filesystem/'),
'http://good/': expect.toMatchURL('https://base.example/http/'),
'https://good/': expect.toMatchURL('https://base.example/https/'),
'ftp://good/': expect.toMatchURL('https://base.example/ftp/'),
'import:bad': expect.toMatchURL('https://base.example/import'),
'mailto:bad': expect.toMatchURL('https://base.example/mailto'),
'javascript:bad': expect.toMatchURL('https://base.example/javascript'),
'wss://bad/': expect.toMatchURL('https://base.example/wss')
}
);
});
@ -127,32 +124,40 @@ describe('Absolute URL specifier keys', () => {
}`,
'https://base.example/path1/path2/path3',
{
'https://ex ample.org/': [expect.toMatchURL('https://base.example/unparseable1/')],
'https://example.com:demo': [expect.toMatchURL('https://base.example/unparseable2')],
'http://[www.example.com]/': [expect.toMatchURL('https://base.example/unparseable3/')],
'https://example.org/': [expect.toMatchURL('https://base.example/invalidButParseable1/')],
'https://example.com///': [expect.toMatchURL('https://base.example/invalidButParseable2/')],
'https://example.net/': [expect.toMatchURL('https://base.example/prettyNormal/')],
'https://example.com/': [expect.toMatchURL('https://base.example/percentDecoding/')],
'https://example.com/%41': [expect.toMatchURL('https://base.example/noPercentDecoding')]
'https://ex ample.org/': expect.toMatchURL('https://base.example/unparseable1/'),
'https://example.com:demo': expect.toMatchURL('https://base.example/unparseable2'),
'http://[www.example.com]/': expect.toMatchURL('https://base.example/unparseable3/'),
'https://example.org/': expect.toMatchURL('https://base.example/invalidButParseable1/'),
'https://example.com///': expect.toMatchURL('https://base.example/invalidButParseable2/'),
'https://example.net/': expect.toMatchURL('https://base.example/prettyNormal/'),
'https://example.com/': expect.toMatchURL('https://base.example/percentDecoding/'),
'https://example.com/%41': expect.toMatchURL('https://base.example/noPercentDecoding')
}
);
});
it('should parse built-in module specifier keys, including with a "/"', () => {
it('should sort correctly (issue #181)', () => {
expectSpecifierMap(
`{
"${BLANK}": "/blank",
"${BLANK}/": "/blank/",
"${BLANK}/foo": "/blank/foo",
"${BLANK}\\\\foo": "/blank/backslashfoo"
"https://example.com/aaa": "https://example.com/aaa",
"https://example.com/a": "https://example.com/a"
}`,
'https://base.example/path1/path2/path3',
'https://base.example/',
{
[BLANK]: [expect.toMatchURL('https://base.example/blank')],
[`${BLANK}/`]: [expect.toMatchURL('https://base.example/blank/')],
[`${BLANK}/foo`]: [expect.toMatchURL('https://base.example/blank/foo')],
[`${BLANK}\\foo`]: [expect.toMatchURL('https://base.example/blank/backslashfoo')]
'https://example.com/aaa': expect.toMatchURL('https://example.com/aaa'),
'https://example.com/a': expect.toMatchURL('https://example.com/a')
}
);
expectSpecifierMap(
`{
"https://example.com/a": "https://example.com/a",
"https://example.com/aaa": "https://example.com/aaa"
}`,
'https://base.example/',
{
'https://example.com/aaa': expect.toMatchURL('https://example.com/aaa'),
'https://example.com/a': expect.toMatchURL('https://example.com/a')
}
);
});

View file

@ -16,20 +16,6 @@ describe('Mapped using scope instead of "imports"', () => {
const inJSDirURL = new URL('https://example.com/js/app.mjs');
const topLevelURL = new URL('https://example.com/app.mjs');
it('should fail when the mapping is to an empty array', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"scopes": {
"/js/": {
"moment": null,
"lodash": []
}
}
}`);
expect(() => resolveUnderTest('moment', inJSDirURL)).toThrow(TypeError);
expect(() => resolveUnderTest('lodash', inJSDirURL)).toThrow(TypeError);
});
describe('Exact vs. prefix based matching', () => {
it('should match correctly when both are in the map', () => {
const resolveUnderTest = makeResolveUnderTest(`{
@ -153,14 +139,17 @@ describe('Mapped using scope instead of "imports"', () => {
"imports": {
"a": "/a-1.mjs",
"b": "/b-1.mjs",
"c": "/c-1.mjs"
"c": "/c-1.mjs",
"d": "/d-1.mjs"
},
"scopes": {
"/scope2/": {
"a": "/a-2.mjs"
"a": "/a-2.mjs",
"d": "/d-2.mjs"
},
"/scope2/scope3/": {
"b": "/b-3.mjs"
"b": "/b-3.mjs",
"d": "/d-3.mjs"
}
}
}`);
@ -173,18 +162,21 @@ describe('Mapped using scope instead of "imports"', () => {
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');
});
});

View file

@ -42,11 +42,11 @@ describe('Unmapped', () => {
expect(resolveUnderTest('https://///example.com///')).toMatchURL('https://example.com///');
});
it('should fail for absolute non-fetch-scheme URLs', () => {
expect(() => resolveUnderTest('mailto:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('import:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('javascript:bad')).toThrow(TypeError);
expect(() => resolveUnderTest('wss:bad')).toThrow(TypeError);
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 /', () => {
@ -64,18 +64,6 @@ describe('Unmapped', () => {
});
describe('Mapped using the "imports" key only (no scopes)', () => {
it('should fail when the mapping is to an empty array', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"moment": null,
"lodash": []
}
}`);
expect(() => resolveUnderTest('moment')).toThrow(TypeError);
expect(() => resolveUnderTest('lodash')).toThrow(TypeError);
});
describe('Package-like scenarios', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
@ -84,8 +72,7 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
"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/",
"nowhere/": []
"lodash-dotdot/": "../node_modules/lodash-es/"
}
}`);
@ -110,10 +97,6 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
expect(() => resolveUnderTest('underscore/')).toThrow(TypeError);
expect(() => resolveUnderTest('underscore/foo')).toThrow(TypeError);
});
it('should fail for package submodules that map to nowhere', () => {
expect(() => resolveUnderTest('nowhere/foo')).toThrow(TypeError);
});
});
describe('Tricky specifiers', () => {
@ -121,6 +104,7 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
"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",
@ -144,20 +128,19 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
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": {
"/node_modules/als-polyfill/index.mjs": "std:kv-storage",
"/lib/foo.mjs": "./more/bar.mjs",
"./dotrelative/foo.mjs": "/lib/dot.mjs",
"../dotdotrelative/foo.mjs": "/lib/dotdot.mjs",
"/lib/no.mjs": null,
"./dotrelative/no.mjs": [],
"/": "/lib/slash-only/",
"./": "/lib/dotslash-only/",
@ -181,16 +164,6 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
expect(resolveUnderTest('../dotdotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dotdot.mjs');
});
it('should fail for URLs that remap to empty arrays', () => {
expect(() => resolveUnderTest('https://example.com/lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('/lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('../lib/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('https://example.com/app/dotrelative/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('/app/dotrelative/no.mjs')).toThrow(TypeError);
expect(() => resolveUnderTest('../app/dotrelative/no.mjs')).toThrow(TypeError);
});
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/');
@ -212,7 +185,7 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
});
describe('Overlapping entries with trailing slashes', () => {
it('should favor the most-specific key (no empty arrays)', () => {
it('should favor the most-specific key', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": "/1",
@ -229,11 +202,9 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
expect(resolveUnderTest('a/b/c')).toMatchURL('https://example.com/4/c');
});
it('should favor the most-specific key when empty arrays are involved for less-specific keys', () => {
it('should favor the most-specific key when there are no mappings for less-specific keys', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": [],
"a/": [],
"a/b": "/3",
"a/b/": "/4/"
}
@ -247,24 +218,15 @@ describe('Mapped using the "imports" key only (no scopes)', () => {
expect(resolveUnderTest('a/b/c')).toMatchURL('https://example.com/4/c');
expect(() => resolveUnderTest('a/x/c')).toThrow(TypeError);
});
});
it('should favor the most-specific key when empty arrays are involved for more-specific keys', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": "/1",
"a/": "/2/",
"a/b": [],
"a/b/": []
}
}`);
it('should deal with data: URL bases', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"foo/": "data:text/javascript,foo/"
}
}`);
expect(resolveUnderTest('a')).toMatchURL('https://example.com/1');
expect(resolveUnderTest('a/')).toMatchURL('https://example.com/2/');
expect(resolveUnderTest('a/x')).toMatchURL('https://example.com/2/x');
expect(() => resolveUnderTest('a/b')).toThrow(TypeError);
expect(() => resolveUnderTest('a/b/')).toThrow(TypeError);
expect(() => resolveUnderTest('a/b/c')).toThrow(TypeError);
expect(resolveUnderTest('a/x/c')).toMatchURL('https://example.com/2/x/c');
});
expect(() => resolveUnderTest('foo/bar')).toThrow(TypeError);
});
});

View file

@ -15,6 +15,22 @@ function require(name) {
}, exports);
}
// Sort keys and then stringify for comparison.
function stringifyImportMap(importMap) {
function getKeys(m) {
if (typeof m !== 'object')
return [];
let keys = [];
for (const key in m) {
keys.push(key);
keys = keys.concat(getKeys(m[key]));
}
return keys;
}
return JSON.stringify(importMap, getKeys(importMap).sort());
}
function expect(v) {
return {
toMatchURL: expected => assert_equals(v, expected),
@ -34,10 +50,8 @@ function expect(v) {
const actualParsedImportMap = JSON.parse(
internals.getParsedImportMap(v.contentDocument));
assert_equals(
JSON.stringify(actualParsedImportMap,
Object.keys(actualParsedImportMap).sort()),
JSON.stringify(expected.imports,
Object.keys(expected.imports).sort())
stringifyImportMap(actualParsedImportMap),
stringifyImportMap(expected)
);
} else {
assert_object_equals(v, expected);
@ -75,6 +89,11 @@ function it(message, f) {
// Currently document.write() is used to make everything synchronous, which
// is just needed for running the existing Jest-based tests easily.
function parseFromString(mapString, mapBaseURL) {
// We can't test data: base URLs because <base> rejects data: URLs.
if (new URL(mapBaseURL).protocol === 'data:') {
throw Error('test helper does not support data: base URLs');
}
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.contentDocument.write(`

View file

@ -153,7 +153,7 @@ function testStaticImport(importMapString, importMapBaseURL, specifier, expected
const script = document.createElement("script");
script.setAttribute("type", "module");
script.setAttribute("src",
"/import-maps/static-import.js?pipe=sub(none)&url=" +
"static-import.js?pipe=sub(none)&url=" +
encodeURIComponent("${specifier}"));
script.addEventListener("load", handlers[Handler.ScriptLoadEvent]);
script.addEventListener("error", handlers[Handler.ScriptErrorEvent]);