diff --git a/.pullapprove.yml b/.pullapprove.yml index e5ec0433d69684..54b5a0f87493e8 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -279,6 +279,14 @@ groups: - IgorMinar #fallback - mhevery #fallback + elements: + conditions: + files: + - "packages/elements/*" + users: + - mhevery #primary + - IgorMinar #fallback + benchpress: conditions: files: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a7ca51af1d3b1..4278b5716062e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,6 +211,7 @@ The following is the list of supported scopes: * **compiler** * **compiler-cli** * **core** +* **elements** * **forms** * **http** * **language-service** diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index 3fa94b3c87cf44..e11b87c34bbe93 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -48,6 +48,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) 'common/testing/index.ts', 'core/index.ts', 'core/testing/index.ts', + 'elements/index.ts', 'forms/index.ts', 'http/index.ts', 'http/testing/index.ts', diff --git a/aio/tools/transforms/authors-package/api-package.js b/aio/tools/transforms/authors-package/api-package.js index 541d1ac45e4a1d..5f07254b226486 100644 --- a/aio/tools/transforms/authors-package/api-package.js +++ b/aio/tools/transforms/authors-package/api-package.js @@ -11,15 +11,16 @@ const { API_SOURCE_PATH } = require('../config'); const packageMap = { animations: ['animations/index.ts', 'animations/browser/index.ts', 'animations/browser/testing/index.ts'], - common: ['common/index.ts', 'common/testing/index.ts'], + common: ['common/index.ts', 'common/testing/index.ts', 'common/http/index.ts', 'common/http/testing/index.ts'], core: ['core/index.ts', 'core/testing/index.ts'], + elements: ['elements/index.ts'], forms: ['forms/index.ts'], http: ['http/index.ts', 'http/testing/index.ts'], 'platform-browser': ['platform-browser/index.ts', 'platform-browser/animations/index.ts', 'platform-browser/testing/index.ts'], 'platform-browser-dynamic': ['platform-browser-dynamic/index.ts', 'platform-browser-dynamic/testing/index.ts'], 'platform-server': ['platform-server/index.ts', 'platform-server/testing/index.ts'], 'platform-webworker': ['platform-webworker/index.ts'], - 'platform-webworker-dynamic': 'platform-webworker-dynamic/index.ts', + 'platform-webworker-dynamic': ['platform-webworker-dynamic/index.ts'], router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'], 'service-worker': ['service-worker/index.ts'], upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts'] diff --git a/build.sh b/build.sh index 654c861b96e7da..2a73041cdef632 100755 --- a/build.sh +++ b/build.sh @@ -24,7 +24,8 @@ PACKAGES=(core compiler-cli language-service benchpress - service-worker) + service-worker + elements) TSC_PACKAGES=(compiler-cli language-service diff --git a/karma-js.conf.js b/karma-js.conf.js index 1f9429f1869ec0..347b5ab6a48a0f 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -38,6 +38,15 @@ module.exports = function(config) { 'test-events.js', 'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js', + + // Serve polyfills necessary for testing the `elements` package. + { + pattern: 'node_modules/@webcomponents/custom-elements/**/*.js', + included: false, + watched: false + }, + {pattern: 'node_modules/mutation-observer/index.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true}, 'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', diff --git a/package.json b/package.json index 7eb1238574870b..b8e68d2962de00 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/selenium-webdriver": "3.0.7", "@types/source-map": "^0.5.1", "@types/systemjs": "0.19.32", + "@webcomponents/custom-elements": "^1.0.4", "angular": "1.5.0", "angular-animate": "1.5.0", "angular-mocks": "1.5.0", @@ -81,6 +82,7 @@ "karma-sourcemap-loader": "0.3.6", "madge": "0.5.0", "minimist": "1.2.0", + "mutation-observer": "^1.0.3", "node-uuid": "1.4.8", "protractor": "5.1.2", "rewire": "2.5.2", diff --git a/test-main.js b/test-main.js index 6ce3887dabbf6e..a4208e1bfd259d 100644 --- a/test-main.js +++ b/test-main.js @@ -62,25 +62,40 @@ System.config({ '@angular/platform-server': {main: 'index.js', defaultExtension: 'js'}, '@angular/platform-webworker': {main: 'index.js', defaultExtension: 'js'}, '@angular/platform-webworker-dynamic': {main: 'index.js', defaultExtension: 'js'}, + '@angular/elements': {main: 'index.js', defaultExtension: 'js'}, } }); -// Set up the test injector, then import all the specs, execute their `main()` -// method and kick off Karma (Jasmine). -System.import('@angular/core/testing') - .then(function(coreTesting) { - return Promise - .all([ - System.import('@angular/platform-browser-dynamic/testing'), - System.import('@angular/platform-browser/animations') - ]) - .then(function(mods) { - coreTesting.TestBed.initTestEnvironment( - [mods[0].BrowserDynamicTestingModule, mods[1].NoopAnimationsModule], - mods[0].platformBrowserDynamicTesting()); - }); +// Load browser-specific CustomElement polyfills, set up the test injector, import all the specs, +// execute their `main()` method and kick off Karma (Jasmine). +Promise + .resolve() + + // Load browser-specific polyfills for custom elements. + .then(function() { return loadCustomElementsPolyfills(); }) + + // Load necessary testing packages. + .then(function() { + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing'), + System.import('@angular/platform-browser/animations') + ]); }) + + // Set up the test injector. + .then(function(mods) { + var coreTesting = mods[0]; + var pbdTesting = mods[1]; + var pbAnimations = mods[2]; + + coreTesting.TestBed.initTestEnvironment( + [pbdTesting.BrowserDynamicTestingModule, pbAnimations.NoopAnimationsModule], + pbdTesting.platformBrowserDynamicTesting()); + }) + + // Import all the specs and execute their `main()` method. .then(function() { return Promise.all(Object .keys(window.__karma__.files) // All files served by Karma. @@ -97,9 +112,106 @@ System.import('@angular/core/testing') }); })); }) + + // Kick off karma (Jasmine). .then(function() { __karma__.start(); }, function(error) { console.error(error); }); +function loadCustomElementsPolyfills() { + var loadedPromise = Promise.resolve(); + + // The custom elements polyfill relies on `MutationObserver`. + if (!window.MutationObserver) { + loadedPromise = + loadedPromise + .then(function() { return System.import('node_modules/mutation-observer/index.js'); }) + .then(function(MutationObserver) { window.MutationObserver = MutationObserver; }); + } + + // The custom elements polyfill relies on `Object.setPrototypeOf()`. + if (!Object.setPrototypeOf) { + var getDescriptor = function getDescriptor(obj, prop) { + var descriptor; + while (obj && !descriptor) { + descriptor = Object.getOwnPropertyDescriptor(obj, prop); + obj = Object.getPrototypeOf(obj); + } + return descriptor || {}; + }; + var setPrototypeOf = function setPrototypeOf(obj, proto) { + for (var prop in proto) { + if (!obj.hasOwnProperty(prop)) { + Object.defineProperty(obj, prop, getDescriptor(proto, prop)); + } + } + return obj; + }; + + Object.defineProperty(setPrototypeOf, '$$shimmed', {value: true}); + Object.setPrototypeOf = setPrototypeOf; + } + + // The custom elements polyfill will patch `(HTML)Element` properties, including `innerHTML`: + // https://github.com/webcomponents/custom-elements/blob/32f043c3a5e5fc3e035342c0ef10c6786fa416d7/src/Patch/Element.js#L28-L78 + // The patched `innerHTML` setter will try to traverse the DOM (via `nextSibling`), which leads to + // infinite loops when testing `HtmlSanitizer` with cloberred elements on browsers that do not + // support the `