| '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'); |
| }); |
| }); |
| }); |