
committed by
Victor Berchet

parent
a63b764b54
commit
ac2b04a5ab
@ -24,6 +24,7 @@ ts_library(
|
||||
"//packages/animations/browser/testing",
|
||||
"//packages/common",
|
||||
"//packages/core",
|
||||
"//packages/core/testing",
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-browser/animations",
|
||||
"//packages/platform-browser/testing",
|
||||
|
@ -0,0 +1,5 @@
|
||||
This folder contains canonical examples of how the Ivy compiler translates annotations into code
|
||||
|
||||
- The specs are marked with `NORMATIVE` => `/NORMATIVE` comments which designates what the compiler is expected to generate.
|
||||
- All local variable names are considered non-normative (informative).
|
||||
|
199
packages/core/test/render3/compiler_canonical/small_app_spec.ts
Normal file
199
packages/core/test/render3/compiler_canonical/small_app_spec.ts
Normal file
@ -0,0 +1,199 @@
|
||||
/**
|
||||
* @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 {NgForOf, NgForOfContext} from '@angular/common';
|
||||
import {Component, ContentChild, Directive, EventEmitter, Injectable, Input, NgModule, OnDestroy, Optional, Output, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {withBody} from '@angular/core/testing';
|
||||
|
||||
import * as r3 from '../../../src/render3/index';
|
||||
|
||||
|
||||
|
||||
// TODO: remove once https://github.com/angular/angular/pull/22005 lands
|
||||
export class pending_pull_22005 {
|
||||
static defineInjectable<T>({scope, factory}: {scope?: Type<any>, factory: () => T}):
|
||||
{scope: Type<any>| null, factory: () => T} {
|
||||
return {scope: scope || null, factory: factory};
|
||||
}
|
||||
|
||||
static defineInjector<T>({factory, providers}: {factory: () => T, providers: any[]}):
|
||||
{factory: () => T, providers: any[]} {
|
||||
return {factory: factory, providers: providers};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface ToDo {
|
||||
text: string;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class AppState {
|
||||
todos: ToDo[] = [
|
||||
{text: 'Demonstrate Components', done: false},
|
||||
{text: 'Demonstrate Structural Directives', done: false},
|
||||
{text: 'Demonstrate NgModules', done: false},
|
||||
{text: 'Demonstrate zoneless changed detection', done: false},
|
||||
{text: 'Demonstrate internationalization', done: false},
|
||||
];
|
||||
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = pending_pull_22005.defineInjectable({factory: () => new AppState()});
|
||||
// /NORMATIVE
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'todo-app',
|
||||
template: `
|
||||
<h1>ToDo Application</h1>
|
||||
<div>
|
||||
<todo *ngFor="let todo of appState.todos" [todo]="todo" (archive)="onArchive($event)"></todo>
|
||||
</div>
|
||||
<span>count: {{appState.todos.length}}.</span>
|
||||
`
|
||||
})
|
||||
class ToDoAppComponent {
|
||||
constructor(public appState: AppState) {}
|
||||
|
||||
onArchive(item: ToDo) {
|
||||
const todos = this.appState.todos;
|
||||
todos.splice(todos.indexOf(item));
|
||||
r3.markDirty(this);
|
||||
}
|
||||
|
||||
// NORMATIVE
|
||||
static ngComponentDef = r3.defineComponent({
|
||||
type: ToDoAppComponent,
|
||||
tag: 'todo-app',
|
||||
factory: function ToDoAppComponent_Factory() {
|
||||
return new ToDoAppComponent(r3.inject(AppState));
|
||||
},
|
||||
template: function ToDoAppComponent_Template(ctx: ToDoAppComponent, cm: boolean) {
|
||||
if (cm) {
|
||||
const ToDoAppComponent_NgForOf_Template = function ToDoAppComponent_NgForOf_Template(
|
||||
ctx1: NgForOfContext<ToDo>, cm: boolean) {
|
||||
if (cm) {
|
||||
r3.E(0, ToDoItemComponent);
|
||||
r3.L('archive', ctx.onArchive.bind(ctx));
|
||||
r3.e();
|
||||
}
|
||||
r3.p(0, 'todo', r3.b(ctx1.$implicit));
|
||||
};
|
||||
r3.E(0, 'h1');
|
||||
r3.T(1, 'ToDo Application');
|
||||
r3.e();
|
||||
r3.E(2, 'div');
|
||||
r3.C(3, c3_directives, ToDoAppComponent_NgForOf_Template);
|
||||
r3.e();
|
||||
r3.E(4, 'span');
|
||||
r3.T(5);
|
||||
r3.e();
|
||||
}
|
||||
r3.t(5, r3.i1('count: ', ctx.appState.todos.length, ''));
|
||||
}
|
||||
});
|
||||
// /NORMATIVE
|
||||
}
|
||||
|
||||
// NORMATIVE
|
||||
const c3_directives = [NgForOf as r3.DirectiveType<NgForOf<ToDo>>];
|
||||
// /NORMATIVE
|
||||
|
||||
@Component({
|
||||
selector: 'todo',
|
||||
template: `
|
||||
<div [class.done]="todo.done">
|
||||
<input type="checkbox" [value]="todo.done" (click)="onCheckboxClick()"></input>
|
||||
<span>{{todo.text}}</span>
|
||||
<button (click)="onArchiveClick()">archive</button>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ToDoItemComponent {
|
||||
static DEFAULT_TODO: ToDo = {text: '', done: false};
|
||||
|
||||
@Input()
|
||||
todo: ToDo = ToDoItemComponent.DEFAULT_TODO;
|
||||
|
||||
@Output()
|
||||
archive = new EventEmitter();
|
||||
|
||||
onCheckboxClick() {
|
||||
this.todo.done = !this.todo.done;
|
||||
r3.markDirty(this);
|
||||
}
|
||||
|
||||
onArchiveClick() { this.archive.emit(this.todo); }
|
||||
|
||||
// NORMATIVE
|
||||
static ngComponentDef = r3.defineComponent({
|
||||
type: ToDoItemComponent,
|
||||
tag: 'todo',
|
||||
factory: function ToDoItemComponent_Factory() { return new ToDoItemComponent(); },
|
||||
template: function ToDoItemComponent_Template(ctx: ToDoItemComponent, cm: boolean) {
|
||||
if (cm) {
|
||||
r3.E(0, 'div');
|
||||
r3.E(1, 'input', e1_attrs);
|
||||
r3.L('click', ctx.onCheckboxClick.bind(ctx));
|
||||
r3.e();
|
||||
r3.E(2, 'span');
|
||||
r3.T(3);
|
||||
r3.e();
|
||||
r3.E(4, 'button');
|
||||
r3.L('click', ctx.onArchiveClick.bind(ctx));
|
||||
r3.T(5, 'archive');
|
||||
r3.e();
|
||||
r3.e();
|
||||
}
|
||||
r3.p(1, 'value', r3.b(ctx.todo.done));
|
||||
r3.t(3, r3.b(ctx.todo.text));
|
||||
},
|
||||
inputs: {todo: 'todo'},
|
||||
});
|
||||
// /NORMATIVE
|
||||
}
|
||||
// NORMATIVE
|
||||
const e1_attrs = ['type', 'checkbox'];
|
||||
// /NORMATIVE
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ToDoAppComponent, ToDoItemComponent],
|
||||
providers: [AppState],
|
||||
})
|
||||
class ToDoAppModule {
|
||||
// NORMATIVE
|
||||
static ngInjectorDef = pending_pull_22005.defineInjector({
|
||||
factory: () => new ToDoAppModule(),
|
||||
providers: [AppState],
|
||||
});
|
||||
// /NORMATIVE
|
||||
}
|
||||
|
||||
|
||||
describe('small_app', () => {
|
||||
xit('should render',
|
||||
() => withBody('<todo-app></todo-app>', async() => {
|
||||
// TODO: Implement this method once all of the pieces of this application can execute.
|
||||
// TODO: add i18n example by translating to french.
|
||||
const todoApp = r3.renderComponent(ToDoAppComponent);
|
||||
await r3.whenRendered(todoApp);
|
||||
expect(r3.getRenderedText(todoApp)).toEqual('...');
|
||||
const firstCheckBox =
|
||||
r3.getHostElement(todoApp).querySelector('input[type=checkbox]') as HTMLElement;
|
||||
firstCheckBox.click();
|
||||
await r3.whenRendered(todoApp);
|
||||
expect(r3.getRenderedText(todoApp)).toEqual('...');
|
||||
const firstArchive = r3.getHostElement(todoApp).querySelector('button') as HTMLElement;
|
||||
firstArchive.click;
|
||||
await r3.whenRendered(todoApp);
|
||||
expect(r3.getRenderedText(todoApp)).toEqual('...');
|
||||
}));
|
||||
});
|
@ -6,7 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ViewEncapsulation} from '../../src/core';
|
||||
import {withBody} from '@angular/core/testing';
|
||||
|
||||
import {DoCheck, ViewEncapsulation} from '../../src/core';
|
||||
import {detectChanges, getRenderedText, whenRendered} from '../../src/render3/component';
|
||||
import {defineComponent, markDirty} from '../../src/render3/index';
|
||||
import {bind, componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
||||
import {createRendererType2} from '../../src/view/index';
|
||||
@ -45,12 +48,12 @@ describe('component', () => {
|
||||
const component = renderComponent(CounterComponent);
|
||||
expect(toHtml(containerEl)).toEqual('0');
|
||||
component.count = 123;
|
||||
markDirty(component, requestAnimationFrame);
|
||||
markDirty(component);
|
||||
expect(toHtml(containerEl)).toEqual('0');
|
||||
requestAnimationFrame.flush();
|
||||
expect(toHtml(containerEl)).toEqual('123');
|
||||
component.increment();
|
||||
markDirty(component, requestAnimationFrame);
|
||||
markDirty(component);
|
||||
expect(toHtml(containerEl)).toEqual('123');
|
||||
requestAnimationFrame.flush();
|
||||
expect(toHtml(containerEl)).toEqual('124');
|
||||
@ -232,4 +235,69 @@ describe('encapsulation', () => {
|
||||
.toMatch(
|
||||
/<div host="" _nghost-c(\d+)=""><leaf _ngcontent-c\1="" _nghost-c(\d+)=""><span _ngcontent-c\2="">bar<\/span><\/leaf><\/div>/);
|
||||
});
|
||||
|
||||
describe('markDirty, detectChanges, whenRendered, getRenderedText', () => {
|
||||
class MyComponent implements DoCheck {
|
||||
value: string = 'works';
|
||||
doCheckCount = 0;
|
||||
ngDoCheck(): void { this.doCheckCount++; }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-comp',
|
||||
factory: () => new MyComponent(),
|
||||
template: (ctx: MyComponent, cm: boolean) => {
|
||||
if (cm) {
|
||||
elementStart(0, 'span');
|
||||
text(1);
|
||||
elementEnd();
|
||||
}
|
||||
textBinding(1, bind(ctx.value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should mark a component dirty and schedule change detection', withBody('my-comp', () => {
|
||||
const myComp = renderComponent(MyComponent);
|
||||
expect(getRenderedText(myComp)).toEqual('works');
|
||||
myComp.value = 'updated';
|
||||
markDirty(myComp);
|
||||
expect(getRenderedText(myComp)).toEqual('works');
|
||||
requestAnimationFrame.flush();
|
||||
expect(getRenderedText(myComp)).toEqual('updated');
|
||||
}));
|
||||
|
||||
it('should detectChanges on a component', withBody('my-comp', () => {
|
||||
const myComp = renderComponent(MyComponent);
|
||||
expect(getRenderedText(myComp)).toEqual('works');
|
||||
myComp.value = 'updated';
|
||||
detectChanges(myComp);
|
||||
expect(getRenderedText(myComp)).toEqual('updated');
|
||||
}));
|
||||
|
||||
it('should detectChanges only once if markDirty is called multiple times',
|
||||
withBody('my-comp', () => {
|
||||
const myComp = renderComponent(MyComponent);
|
||||
expect(getRenderedText(myComp)).toEqual('works');
|
||||
expect(myComp.doCheckCount).toBe(1);
|
||||
myComp.value = 'ignore';
|
||||
markDirty(myComp);
|
||||
myComp.value = 'updated';
|
||||
markDirty(myComp);
|
||||
expect(getRenderedText(myComp)).toEqual('works');
|
||||
requestAnimationFrame.flush();
|
||||
expect(getRenderedText(myComp)).toEqual('updated');
|
||||
expect(myComp.doCheckCount).toBe(2);
|
||||
}));
|
||||
|
||||
it('should notify whenRendered', withBody('my-comp', async() => {
|
||||
const myComp = renderComponent(MyComponent);
|
||||
await whenRendered(myComp);
|
||||
myComp.value = 'updated';
|
||||
markDirty(myComp);
|
||||
setTimeout(requestAnimationFrame.flush, 0);
|
||||
await whenRendered(myComp);
|
||||
expect(getRenderedText(myComp)).toEqual('updated');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -324,7 +324,7 @@ describe('di', () => {
|
||||
|
||||
describe('getOrCreateNodeInjector', () => {
|
||||
it('should handle initial undefined state', () => {
|
||||
const contentView = createLView(-1, null !, createTView());
|
||||
const contentView = createLView(-1, null !, createTView(), null, null);
|
||||
const oldView = enterView(contentView, null !);
|
||||
try {
|
||||
const parent = createLNode(0, LNodeFlags.Element, null, null);
|
||||
|
@ -27,4 +27,6 @@ if (typeof window == 'undefined') {
|
||||
// For animation tests, see
|
||||
// https://github.com/angular/angular/blob/master/packages/animations/browser/src/render/shared.ts#L140
|
||||
(global as any).Element = domino.impl.Element;
|
||||
(global as any).isBrowser = false;
|
||||
(global as any).isNode = true;
|
||||
}
|
||||
|
@ -61,8 +61,11 @@ export function renderToHtml(
|
||||
beforeEach(resetDOM);
|
||||
|
||||
export function renderComponent<T>(type: ComponentType<T>, rendererFactory?: RendererFactory3): T {
|
||||
return _renderComponent(
|
||||
type, {rendererFactory: rendererFactory || testRendererFactory, host: containerEl});
|
||||
return _renderComponent(type, {
|
||||
rendererFactory: rendererFactory || testRendererFactory,
|
||||
host: containerEl,
|
||||
scheduler: requestAnimationFrame,
|
||||
});
|
||||
}
|
||||
|
||||
export function toHtml<T>(componentOrElement: T | RElement): string {
|
||||
|
@ -181,7 +181,7 @@ describe('animation renderer factory', () => {
|
||||
expect(toHtml(containerEl)).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should work with animated components', (done) => {
|
||||
isBrowser && it('should work with animated components', (done) => {
|
||||
const factory = getAnimationRendererFactory2(document);
|
||||
const component = renderComponent(SomeComponentWithAnimation, factory);
|
||||
expect(toHtml(containerEl))
|
||||
|
48
packages/core/test/render3/testing_spec.ts
Normal file
48
packages/core/test/render3/testing_spec.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @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 {withBody} from '@angular/core/testing';
|
||||
|
||||
describe('testing', () => {
|
||||
describe('withBody', () => {
|
||||
let passed: boolean;
|
||||
|
||||
beforeEach(() => passed = false);
|
||||
afterEach(() => expect(passed).toEqual(true));
|
||||
|
||||
it('should set up body', withBody('<span>works!</span>', () => {
|
||||
expect(document.body.innerHTML).toEqual('<span>works!</span>');
|
||||
passed = true;
|
||||
}));
|
||||
|
||||
it('should support promises', withBody('<span>works!</span>', () => {
|
||||
return Promise.resolve(true).then(() => passed = true);
|
||||
}));
|
||||
|
||||
it('should support async and await', withBody('<span>works!</span>', async() => {
|
||||
await Promise.resolve(true);
|
||||
passed = true;
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('domino', () => {
|
||||
it('should have document present', () => {
|
||||
// In Browser this tests passes, bun we also want to make sure we pass in node.js
|
||||
// We expect that node.js will load domino for us.
|
||||
expect(document).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestAnimationFrame', () => {
|
||||
it('should have requestAnimationFrame', (done) => {
|
||||
// In Browser we have requestAnimationFrame, but verify that we also have it node.js
|
||||
requestAnimationFrame(done);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user