servo/tests/wpt/web-platform-tests/import-maps/resources/resolving.js

230 lines
11 KiB
JavaScript

'use strict';
// Imported from:
// https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/resolving.js
// TODO: Upstream local changes.
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/"
}
}`);
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);
});
});
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.mjs",
"./": "/lib/dotslash-only.mjs",
"/test": "/lib/test1.mjs",
"../test": "/lib/test2.mjs"
}
}`);
it('should remap to built-in modules', () => {
expect(resolveUnderTest('/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
expect(resolveUnderTest('https://example.com/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
expect(resolveUnderTest('https://///example.com/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
});
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.mjs');
expect(resolveUnderTest('/')).toMatchURL('https://example.com/lib/slash-only.mjs');
expect(resolveUnderTest('../')).toMatchURL('https://example.com/lib/slash-only.mjs');
expect(resolveUnderTest('https://example.com/app/')).toMatchURL('https://example.com/lib/dotslash-only.mjs');
expect(resolveUnderTest('/app/')).toMatchURL('https://example.com/lib/dotslash-only.mjs');
expect(resolveUnderTest('../app/')).toMatchURL('https://example.com/lib/dotslash-only.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', () => {
const resolveUnderTest = makeResolveUnderTest(`{
"imports": {
"a": "/1",
"a/": "/2/",
"a/b": "/3",
"a/b/": "/4/"
}
}`);
it('most-specific wins', () => {
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');
});
});
});