Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tests for import map composition and a "cascading" composition implementation #167

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e5b5eec
add tests for composition and a "cascading" composition implementation
michaelficarra Jul 18, 2019
3631bc6
fix linting (though the code is arguably worse now)
michaelficarra Jul 24, 2019
c82b348
switch maps from URLs to strings
michaelficarra Aug 2, 2019
e7b5f34
deduplicate logic between resolver and composer
bakkot Aug 8, 2019
bfe8382
empty commit to rerun CI
bakkot Aug 8, 2019
08d220c
Update reference-implementation/__tests__/composition.js
michaelficarra Aug 15, 2019
ccaf0c0
Update reference-implementation/__tests__/composition.js
michaelficarra Aug 15, 2019
4c1136f
remove JSON round-trip
michaelficarra Aug 15, 2019
4555be2
inline JSON representation of input import maps in composition tests
michaelficarra Aug 15, 2019
00cf730
add comment and parameter default for composition test helper function
michaelficarra Aug 15, 2019
b7cfa0f
different representations of URLs
michaelficarra Aug 15, 2019
7c3e1c1
move console warning out of tryURLLikeSpecifierParse
michaelficarra Aug 15, 2019
0e64b9e
assert warnings
michaelficarra Aug 15, 2019
6be838f
assume resolve's scriptURL parameter is a URL
michaelficarra Aug 15, 2019
7c621af
add another test for maps not cascading to themselves
michaelficarra Aug 16, 2019
9977979
add another test for merge-within-scopes strategy (taken from #153)
michaelficarra Aug 16, 2019
fd7d8d3
composition warns if there's a non-URL in the RHS of the result
michaelficarra Aug 16, 2019
47558af
add another test for bare specifier stripping
michaelficarra Aug 16, 2019
e07afd0
bad -> nonURL
michaelficarra Aug 16, 2019
976c90c
Change duplicate test names
domenic Aug 23, 2019
de67f8d
Always use std:blank and variable prefix
domenic Aug 23, 2019
b5bb539
Checkpoint: spec concatenating
domenic Aug 28, 2019
2a94ea6
Checkpoint: spec parsing a specifier
domenic Aug 28, 2019
c0c5772
Checkpoint: specify "get the fallbacks"
domenic Aug 28, 2019
785a235
Spec new "resolve"
domenic Aug 29, 2019
43b564a
Update parser spec
domenic Aug 29, 2019
7a791d4
Properly make the URL types flow through
domenic Aug 29, 2019
ace007a
100% coverage
domenic Aug 29, 2019
842a995
Move composing spec section after parsing
domenic Aug 29, 2019
22f5238
More rearranging
domenic Aug 29, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/reference-implementation/node_modules/
/reference-implementation/coverage/
/out/
/spec.html
/deploy_key
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ node_js:
before_install:
- cd reference-implementation
script:
- npm run lint
- npm test
- cd .. && bash ./deploy.sh

Expand Down
367 changes: 367 additions & 0 deletions reference-implementation/__tests__/composition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
'use strict';
const { URL } = require('url');
const { parseFromString } = require('../lib/parser.js');
const { appendMap } = require('../lib/composer.js');

const mapBaseURL = new URL('https://example.com/app/index.html');

function composeMaps(mapLikes) {
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
if (!Array.isArray(mapLikes) || mapLikes.length < 1) {
throw new Error('composeMaps must be given a non-empty array of mapLikes');
}
let map = parseFromString(JSON.stringify(mapLikes.shift()), mapBaseURL);
for (const mapLike of mapLikes) {
const newMap = parseFromString(JSON.stringify(mapLike), mapBaseURL);
map = appendMap(map, newMap);
}
return JSON.parse(JSON.stringify(map));
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
}

describe('Composition', () => {
it('should compose with the empty map on the left', () => {
const map = parseFromString(JSON.stringify({
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
imports: { 'https://a/': ['https://b/'] },
scopes: {
'https://c/': { 'https://d/': ['https://e/'] }
}
}), mapBaseURL);

const resultMap = appendMap(parseFromString('{}', mapBaseURL), map);

expect(resultMap).toStrictEqual(map);
expect(resultMap.imports).not.toBe(map.imports);
Object.entries(resultMap.imports).forEach(([k, v]) => {
expect(v).not.toBe(map.imports[k]);
});
expect(resultMap.scopes).not.toBe(map.scopes);
Object.entries(resultMap.scopes).forEach(([k, v]) => {
expect(v).not.toBe(map.scopes[k]);
});
});

it('should compose with the empty map on the right', () => {
const map = parseFromString(JSON.stringify({
imports: { 'https://a/': ['https://b/'] },
scopes: {
'https://c/': { 'https://d/': ['https://e/'] }
}
}), mapBaseURL);

const resultMap = appendMap(map, parseFromString('{}', mapBaseURL));

expect(resultMap).toStrictEqual(map);
expect(resultMap.imports).not.toBe(map.imports);
Object.entries(resultMap.imports).forEach(([k, v]) => {
expect(v).not.toBe(map.imports[k]);
});
expect(resultMap.scopes).not.toBe(map.scopes);
Object.entries(resultMap.scopes).forEach(([k, v]) => {
expect(v).not.toBe(map.scopes[k]);
});
});

it('should compose maps that do not interact in any way', () => {
expect(composeMaps([
{
imports: { 'https://a/': 'https://b/' }
},
{
imports: { 'https://c/': 'https://d/' }
},
{
imports: { 'https://e/': 'https://f/' }
}
])).toStrictEqual({
imports: {
'https://a/': ['https://b/'],
'https://c/': ['https://d/'],
'https://e/': ['https://f/']
},
scopes: {}
});
});

it('should compose maps that interact via cascading', () => {
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
expect(composeMaps([
{
imports: { 'https://c/': 'https://d/' }
},
{
imports: { 'https://b/': 'https://c/' }
},
{
imports: { 'https://a/': 'https://b/' }
}
])).toStrictEqual({
imports: {
'https://a/': ['https://d/'],
'https://b/': ['https://d/'],
'https://c/': ['https://d/']
},
scopes: {}
});
});

it('should compose maps with fallbacks that interact via cascading', () => {
expect(composeMaps([
{
imports: { 'https://e/': ['https://g/', 'https://h/'] }
},
{
imports: {
'https://c/': ['https://f/'],
'https://b/': ['https://d/', 'https://e/']
}
},
{
imports: { 'https://a/': ['https://b/', 'https://c/'] }
}
])).toStrictEqual({
imports: {
'https://a/': ['https://d/', 'https://g/', 'https://h/', 'https://f/'],
'https://b/': ['https://d/', 'https://g/', 'https://h/'],
'https://c/': ['https://f/'],
'https://e/': ['https://g/', 'https://h/']
},
scopes: {}
});
});

it('should compose maps that are using the virtualization patterns we expect to see in the wild', () => {
expect(composeMaps([
{
imports: {
'std:built-in': 'https://built-in-enhancement-1/'
},
scopes: {
'https://built-in-enhancement-1/': {
'std:built-in': 'std:built-in'
}
}
},
{
imports: {
'std:built-in': 'https://built-in-enhancement-2/'
},
scopes: {
'https://built-in-enhancement-2/': {
'std:built-in': 'std:built-in'
}
}
},
{
imports: {
'std:built-in': 'https://built-in-enhancement-3/'
},
scopes: {
'https://built-in-enhancement-3/': {
'std:built-in': 'std:built-in'
}
}
}
])).toStrictEqual({
imports: { 'std:built-in': ['https://built-in-enhancement-3/'] },
scopes: {
'https://built-in-enhancement-1/': { 'std:built-in': ['std:built-in'] },
'https://built-in-enhancement-2/': { 'std:built-in': ['https://built-in-enhancement-1/'] },
'https://built-in-enhancement-3/': { 'std:built-in': ['https://built-in-enhancement-2/'] }
}
});
});

it('should compose "nested" scopes', () => {
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
expect(composeMaps([
{
imports: { 'https://a/': 'https://b/' },
scopes: {
'https://example.com/x/y/': { 'https://c/': 'https://d/' },
'https://example.com/x/y/z': { 'https://e/': 'https://f/' }
}
},
{
imports: { 'https://m/': 'https://n/' },
scopes: {
'https://example.com/x/y/z': {
'https://g/': 'https://a/',
'https://h/': 'https://c/',
'https://i/': 'https://e/'
}
}
}
])).toStrictEqual({
imports: {
'https://a/': ['https://b/'],
'https://m/': ['https://n/']
},
scopes: {
'https://example.com/x/y/': { 'https://c/': ['https://d/'] },
'https://example.com/x/y/z': {
'https://e/': ['https://f/'],
'https://g/': ['https://b/'],
'https://h/': ['https://d/'],
'https://i/': ['https://f/']
}
}
});
});

it('should not clobber earlier more-specific scopes with later less-specific scopes', () => {
expect(composeMaps([
{
imports: {},
scopes: {
'https://example.com/x/y/': { 'https://a/': 'https://b/' },
'https://example.com/x/y/z': { 'https://c/': 'https://d/' }
}
},
{
imports: {
'https://a/': 'https://e/'
},
scopes: {
'https://example.com/x/': {
'https://c/': 'https://f/'
}
}
}
])).toStrictEqual({
imports: {
'https://a/': ['https://e/']
},
scopes: {
'https://example.com/x/': { 'https://c/': ['https://f/'] },
'https://example.com/x/y/': { 'https://a/': ['https://b/'] },
'https://example.com/x/y/z': { 'https://c/': ['https://d/'] }
}
});
});

it('should perform package-prefix-relative composition', () => {
expect(composeMaps([
{
imports: {
'moment/': '/node_modules/moment/src/'
},
scopes: {}
},
{
imports: {
'utils/': 'moment/lib/utils/',
'is-date': 'moment/lib/utils/is-date.js'
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
},
scopes: {}
},
{
imports: {
'is-number': 'utils/is-number.js'
}
}
])).toStrictEqual({
imports: {
'moment/': ['https://example.com/node_modules/moment/src/'],
'utils/': ['https://example.com/node_modules/moment/src/lib/utils/'],
'is-date': ['https://example.com/node_modules/moment/src/lib/utils/is-date.js'],
'is-number': ['https://example.com/node_modules/moment/src/lib/utils/is-number.js']
},
scopes: {}
});
});

it('should URL-normalize things which have composed into URLs', () => {
expect(composeMaps([
{
imports: {
'a/': 'https://example.com/x/'
},
scopes: {}
},
{
imports: {
'dot-test': 'a/測試'
}
}
])).toStrictEqual({
imports: {
'a/': ['https://example.com/x/'],
'dot-test': ['https://example.com/x/%E6%B8%AC%E8%A9%A6']
},
scopes: {}
});
});

it('should compose according to the most specific applicable scope', () => {
expect(composeMaps([
{
imports: {
a: 'https://b/'
},
scopes: {
'x/': { a: 'https://c/' },
'x/y/': { a: 'https://d/' },
'x/y/z/': { a: 'https://e/' }
}
},
{
imports: {},
scopes: {
'x/': {
'a-x': 'a'
},
'x/y/': {
'a-y': 'a'
},
'x/y/z/': {
'a-z': 'a'
},
'x/y/w/': {
'a-w': 'a'
}
}
}
])).toStrictEqual({
imports: {
a: ['https://b/']
},
scopes: {
'https://example.com/app/x/': {
a: ['https://c/'],
'a-x': ['https://c/']
},
'https://example.com/app/x/y/': {
a: ['https://d/'],
'a-y': ['https://d/']
},
'https://example.com/app/x/y/z/': {
a: ['https://e/'],
'a-z': ['https://e/']
},
'https://example.com/app/x/y/w/': {
'a-w': ['https://d/']
}
}
});
});

it('should produce maps with scopes in sorted order', () => {
expect(Object.keys(composeMaps([
{
imports: {},
scopes: {
'https://example.com/x/': { 'https://c/': 'https://f/' }
}
},
{
imports: {},
scopes: {
'https://example.com/x/y/': { 'https://a/': 'https://b/' },
'https://example.com/x/y/z': { 'https://c/': 'https://d/' }
}
}
]).scopes)).toStrictEqual([
'https://example.com/x/y/z',
'https://example.com/x/y/',
'https://example.com/x/'
]);
});
});

Loading