From 6323a354681ba5f4d082c7c14d92c95a63139a8b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 24 Oct 2019 20:28:11 -0700 Subject: [PATCH] test(ivy): support `className` in micro benchmarks (#33392) The styling algorithm requires that the `RNode` has a `className` property in order to execute the fast-path. This changes adds the emulation of this property. PR Close #33392 --- karma-js.conf.js | 1 + packages/core/test/render3/perf/BUILD.bazel | 10 +++- .../core/test/render3/perf/noop_renderer.ts | 60 +++++++++++++++---- .../test/render3/perf/noop_renderer_spec.ts | 38 ++++++++++++ packages/core/test/render3/perf/setup.ts | 5 +- 5 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 packages/core/test/render3/perf/noop_renderer_spec.ts diff --git a/karma-js.conf.js b/karma-js.conf.js index 5168d734cb..1da6d90de9 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -79,6 +79,7 @@ module.exports = function(config) { 'dist/all/@angular/compiler/test/render3/**', 'dist/all/@angular/core/test/bundling/**', 'dist/all/@angular/core/test/render3/ivy/**', + 'dist/all/@angular/core/test/render3/perf/**', 'dist/all/@angular/elements/schematics/**', 'dist/all/@angular/examples/**/e2e_test/*', 'dist/all/@angular/language-service/**', diff --git a/packages/core/test/render3/perf/BUILD.bazel b/packages/core/test/render3/perf/BUILD.bazel index 5ce9e99b3a..5813ea2ccd 100644 --- a/packages/core/test/render3/perf/BUILD.bazel +++ b/packages/core/test/render3/perf/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:private"]) -load("//tools:defaults.bzl", "ng_rollup_bundle", "ts_library") +load("//tools:defaults.bzl", "jasmine_node_test", "ng_rollup_bundle", "ts_library") ts_library( name = "perf_lib", @@ -9,10 +9,18 @@ ts_library( ), deps = [ "//packages/core", + "@npm//@types/jasmine", "@npm//@types/node", ], ) +jasmine_node_test( + name = "perf", + deps = [ + ":perf_lib", + ], +) + ng_rollup_bundle( name = "class_binding", entry_point = ":class_binding/index.ts", diff --git a/packages/core/test/render3/perf/noop_renderer.ts b/packages/core/test/render3/perf/noop_renderer.ts index c39f21e310..ef93923c16 100644 --- a/packages/core/test/render3/perf/noop_renderer.ts +++ b/packages/core/test/render3/perf/noop_renderer.ts @@ -7,7 +7,7 @@ */ import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3} from '../../../src/render3/interfaces/renderer'; -export class WebWorkerRenderNode implements RNode, RComment, RText { +export class MicroBenchmarkRenderNode implements RNode, RComment, RText { textContent: string|null = null; parentNode: RNode|null = null; parentElement: RElement|null = null; @@ -15,15 +15,16 @@ export class WebWorkerRenderNode implements RNode, RComment, RText { removeChild(oldChild: RNode): RNode { return oldChild; } insertBefore(newChild: RNode, refChild: RNode|null, isViewRoot: boolean): void {} appendChild(newChild: RNode): RNode { return newChild; } + className: string = ''; } -export class NoopRenderer implements ProceduralRenderer3 { +export class MicroBenchmarkRenderer implements ProceduralRenderer3 { destroy(): void { throw new Error('Method not implemented.'); } - createComment(value: string): RComment { return new WebWorkerRenderNode(); } + createComment(value: string): RComment { return new MicroBenchmarkRenderNode(); } createElement(name: string, namespace?: string|null|undefined): RElement { - return new WebWorkerRenderNode() as any as RElement; + return new MicroBenchmarkRenderNode() as any as RElement; } - createText(value: string): RText { return new WebWorkerRenderNode(); } + createText(value: string): RText { return new MicroBenchmarkRenderNode(); } destroyNode?: ((node: RNode) => void)|null|undefined; appendChild(parent: RElement, newChild: RNode): void {} insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null): void {} @@ -32,10 +33,21 @@ export class NoopRenderer implements ProceduralRenderer3 { parentNode(node: RNode): RElement|null { throw new Error('Method not implemented.'); } nextSibling(node: RNode): RNode|null { throw new Error('Method not implemented.'); } setAttribute(el: RElement, name: string, value: string, namespace?: string|null|undefined): void { + if (name === 'class' && isOurNode(el)) { + el.className = value; + } } removeAttribute(el: RElement, name: string, namespace?: string|null|undefined): void {} - addClass(el: RElement, name: string): void {} - removeClass(el: RElement, name: string): void {} + addClass(el: RElement, name: string): void { + if (isOurNode(el)) { + el.className = el.className === '' ? name : remove(el.className, name) + ' ' + name; + } + } + removeClass(el: RElement, name: string): void { + if (isOurNode(el)) { + el.className = remove(el.className, name); + } + } setStyle(el: RElement, style: string, value: any, flags?: RendererStyleFlags3|undefined): void {} removeStyle(el: RElement, style: string, flags?: RendererStyleFlags3|undefined): void {} setProperty(el: RElement, name: string, value: any): void {} @@ -47,11 +59,39 @@ export class NoopRenderer implements ProceduralRenderer3 { } } -export class NoopRendererFactory implements RendererFactory3 { +export class MicroBenchmarkRendererFactory implements RendererFactory3 { createRenderer(hostElement: RElement|null, rendererType: null): Renderer3 { if (typeof global !== 'undefined') { - (global as any).Node = WebWorkerRenderNode; + (global as any).Node = MicroBenchmarkRenderNode; } - return new NoopRenderer(); + return new MicroBenchmarkRenderer(); } } + +function isOurNode(node: any): node is MicroBenchmarkRenderNode { + return node instanceof MicroBenchmarkRenderNode; +} + +const enum Code { + SPACE = 32, +} + +function remove(text: string, key: string): string { + let wasLastWhitespace = true; + for (let i = 0; i < text.length; i++) { + if (wasLastWhitespace) { + const start = i; + let same = true; + let k = 0; + while (k < key.length && (same = text.charCodeAt(i) === key.charCodeAt(k))) { + k++; + i++; + } + if (same && text.charCodeAt(i) == Code.SPACE) { + return text.substring(0, start) + text.substring(i + 1); + } + } + wasLastWhitespace = text.charCodeAt(i) <= Code.SPACE; + } + return text; +} \ No newline at end of file diff --git a/packages/core/test/render3/perf/noop_renderer_spec.ts b/packages/core/test/render3/perf/noop_renderer_spec.ts new file mode 100644 index 0000000000..63cd2f1a7c --- /dev/null +++ b/packages/core/test/render3/perf/noop_renderer_spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ProceduralRenderer3} from '@angular/core/src/render3/interfaces/renderer'; + +import {MicroBenchmarkRenderNode, MicroBenchmarkRendererFactory} from './noop_renderer'; + +describe('MicroBenchmarkRenderNode', () => { + const renderer = + new MicroBenchmarkRendererFactory().createRenderer(null, null) as ProceduralRenderer3; + describe('className', () => { + it('should be available in global space', () => { + expect(Node).toBeDefined(); + const node: any = new MicroBenchmarkRenderNode(); + expect(node instanceof Node).toBeTruthy(); + }); + + it('should emulate className', () => { + const node: any = new MicroBenchmarkRenderNode(); + expect(node.className).toBe(''); + renderer.setAttribute(node, 'foo', 'A AA BBB'); + expect(node.className).toBe(''); + renderer.setAttribute(node, 'class', 'A AA BBB'); + expect(node.className).toBe('A AA BBB'); + renderer.addClass(node, 'A'); + expect(node.className).toBe('AA BBB A'); + renderer.addClass(node, 'C'); + expect(node.className).toBe('AA BBB A C'); + renderer.removeClass(node, 'A'); + expect(node.className).toBe('AA BBB C'); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/test/render3/perf/setup.ts b/packages/core/test/render3/perf/setup.ts index 009abb4aa3..dbd94f60bc 100644 --- a/packages/core/test/render3/perf/setup.ts +++ b/packages/core/test/render3/perf/setup.ts @@ -12,10 +12,11 @@ import {RendererFactory3, domRendererFactory3} from '../../../src/render3/interf import {LView, LViewFlags, TView} from '../../../src/render3/interfaces/view'; import {insertView} from '../../../src/render3/node_manipulation'; -import {NoopRendererFactory} from './noop_renderer'; +import {MicroBenchmarkRendererFactory} from './noop_renderer'; const isBrowser = typeof process === 'undefined'; -const rendererFactory: RendererFactory3 = isBrowser ? domRendererFactory3 : new NoopRendererFactory; +const rendererFactory: RendererFactory3 = + isBrowser ? domRendererFactory3 : new MicroBenchmarkRendererFactory; const renderer = rendererFactory.createRenderer(null, null); export function createAndRenderLView(