From d5a12d59ca33ecd00223e644e17fe85f9aa984b9 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Wed, 17 Dec 2014 10:01:08 +0100 Subject: [PATCH] feat(ng-if): an implementation of ng-if Closes #317 --- modules/directives/src/ng_if.js | 30 ++++ modules/directives/test/ng_if_spec.js | 217 ++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 modules/directives/src/ng_if.js create mode 100644 modules/directives/test/ng_if_spec.js diff --git a/modules/directives/src/ng_if.js b/modules/directives/src/ng_if.js new file mode 100644 index 0000000000..8a24bd4060 --- /dev/null +++ b/modules/directives/src/ng_if.js @@ -0,0 +1,30 @@ +import {Template} from 'core/annotations/annotations'; +import {OnChange} from 'core/compiler/interfaces'; +import {ViewPort} from 'core/compiler/viewport'; +import {isBlank} from 'facade/lang'; + +@Template({ + selector: '[ng-if]', + bind: { + 'ng-if': 'condition' + } +}) +export class NgIf { + viewPort: ViewPort; + prevCondition: boolean; + + constructor(viewPort: ViewPort) { + this.viewPort = viewPort; + this.prevCondition = null; + } + + set condition(newCondition) { + if (newCondition && (isBlank(this.prevCondition) || !this.prevCondition)) { + this.prevCondition = true; + this.viewPort.create(); + } else if (!newCondition && (isBlank(this.prevCondition) || this.prevCondition)) { + this.prevCondition = false; + this.viewPort.clear(); + } + } +} diff --git a/modules/directives/test/ng_if_spec.js b/modules/directives/test/ng_if_spec.js new file mode 100644 index 0000000000..e6324f0a3c --- /dev/null +++ b/modules/directives/test/ng_if_spec.js @@ -0,0 +1,217 @@ +import {describe, xit, it, expect, beforeEach, ddescribe, iit, IS_DARTIUM} from 'test_lib/test_lib'; + +import {DOM} from 'facade/dom'; + +import {Injector} from 'di/di'; +import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; + +import {Compiler, CompilerCache} from 'core/compiler/compiler'; +import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; + +import {Component} from 'core/annotations/annotations'; +import {TemplateConfig} from 'core/annotations/template_config'; + +import {NgIf} from 'directives/ng_if'; + +export function main() { + describe('ng-if', () => { + var view, cd, compiler, component; + beforeEach(() => { + compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); + }); + + function createElement(html) { + return DOM.createTemplate(html).content.firstChild; + } + + function createView(pv) { + component = new TestComponent(); + view = pv.instantiate(null); + view.hydrate(new Injector([]), null, component); + cd = view.changeDetector; + } + + function compileWithTemplate(template) { + return compiler.compile(TestComponent, createElement(template)); + } + + it('should work in a template attribute', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + cd.detectChanges(); + + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + done(); + }); + }); + + it('should work in a template element', (done) => { + compileWithTemplate('
').then((pv) => { + createView(pv); + cd.detectChanges(); + + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello2'); + done(); + }); + }); + + it('should toggle node when condition changes', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + + component.booleanCondition = false; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.getText(view.nodes[0])).toEqual(''); + + + component.booleanCondition = true; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + + component.booleanCondition = false; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.getText(view.nodes[0])).toEqual(''); + + done(); + }); + }); + + it('should update several nodes with ng-if', (done) => { + var templateString = + '
' + + 'helloNumber' + + 'helloString' + + 'helloFunction' + + '
'; + compileWithTemplate(templateString).then((pv) => { + createView(pv); + + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(3); + expect(DOM.getText(view.nodes[0])).toEqual('helloNumberhelloStringhelloFunction'); + + component.numberCondition = 0; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('helloString'); + + component.numberCondition = 1; + component.stringCondition = "bar"; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('helloNumber'); + done(); + }); + }); + + + if (!IS_DARTIUM) { + it('should leave the element if the condition is a non-empty string (JS)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + cd.detectChanges(); + + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + done(); + }); + }); + + it('should leave the element if the condition is an object (JS)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + cd.detectChanges(); + + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + done(); + }); + }); + + it('should remove the element if the condition is null (JS)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + cd.detectChanges(); + + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.getText(view.nodes[0])).toEqual(''); + done(); + }); + }); + + it('should not add the element twice if the condition goes from true to true (JS)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + + component.numberCondition = 2; + cd.detectChanges(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(1); + expect(DOM.getText(view.nodes[0])).toEqual('hello'); + + done(); + }); + }); + + it('should not recreate the element if the condition goes from true to true (JS)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + + cd.detectChanges(); + DOM.addClass(view.nodes[0].childNodes[1], "foo"); + + component.numberCondition = 2; + cd.detectChanges(); + expect(DOM.hasClass(view.nodes[0].childNodes[1], "foo")).toBe(true); + + done(); + }); + }); + } else { + it('should not create the element if the condition is not a boolean (DART)', (done) => { + compileWithTemplate('
hello
').then((pv) => { + createView(pv); + expect(function(){cd.detectChanges();}).toThrowError(); + expect(view.nodes[0].querySelectorAll('copy-me').length).toEqual(0); + expect(DOM.getText(view.nodes[0])).toEqual(''); + done(); + }); + }); + } + + }); +} + +@Component({ + selector: 'test-cmp', + template: new TemplateConfig({ + inline: '', // each test swaps with a custom template. + directives: [NgIf] + }) +}) +class TestComponent { + booleanCondition: boolean; + numberCondition: number; + stringCondition: string; + functionCondition: Function; + objectCondition: any; + nullCondition: any; + constructor() { + this.booleanCondition = true; + this.numberCondition = 1; + this.stringCondition = "foo"; + this.functionCondition = function(s, n){ + return s == "foo" && n == 1; + }; + this.objectCondition = {}; + this.nullCondition = null; + } +}