Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(NodeAttrs): Implement forEach to iterate over attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb authored and mhevery committed Jan 7, 2014
1 parent c6fd557 commit 5c41513
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 110 deletions.
14 changes: 12 additions & 2 deletions lib/core_dom/directive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ abstract class TextChangeListener{
call(String text);
}


/**
* NodeAttrs is a facade for element attributes. The facade is responsible
* for normalizing attribute names as well as allowing access to the
Expand Down Expand Up @@ -55,6 +54,10 @@ class NodeAttrs {
_observers[attributeName].add(notifyFn);
notifyFn(this[attributeName]);
}

forEach(void f(String k, String v)) {
element.attributes.forEach((k, v) => f(_camelCase(k, '-'), v));
}
}

/**
Expand All @@ -71,9 +74,16 @@ class TemplateLoader {
}

var _SNAKE_CASE_REGEXP = new RegExp("[A-Z]");
String _snakeCase(String name, [separator = '_']) {
String _snakeCase(String name, [String separator = '_']) {
_snakeReplace(Match match) =>
(match.start != 0 ? separator : '') + match.group(0).toLowerCase();

return name.replaceAllMapped(_SNAKE_CASE_REGEXP, _snakeReplace);
}

String _camelCase(String name, [String separator = '_']) {
return name.replaceAllMapped(
new RegExp(separator + "([a-z])"),
(match) => match.group(1).toUpperCase()
);
}
118 changes: 118 additions & 0 deletions test/core/core_directive_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
library core_directive_spec;

import '../_specs.dart';

main() => describe('DirectiveMap', () {

beforeEach(module((Module module) {
module
..type(AnnotatedIoComponent);
}));

it('should extract attr map from annotated component', inject((DirectiveMap directives) {
var annotations = directives.annotationsFor(AnnotatedIoComponent);
expect(annotations.length).toEqual(1);
expect(annotations[0] is NgComponent).toBeTruthy();

NgComponent annotation = annotations[0];
expect(annotation.map).toEqual({
'foo': '=>foo',
'attr': '@attr',
'expr': '<=>expr',
'expr-one-way': '=>exprOneWay',
'expr-one-way-one-shot': '=>!exprOneWayOneShot',
'callback': '&callback',
'expr-one-way2': '=>exprOneWay2',
'expr-two-way': '<=>exprTwoWay'
});
}));

describe('exceptions', () {
it('should throw when annotation is for existing mapping', () {
var module = new Module()
..type(DirectiveMap)
..type(Bad1Component)
..type(MetadataExtractor)
..type(FieldMetadataExtractor);

var injector = new DynamicInjector(modules: [module]);
expect(() {
injector.get(DirectiveMap);
}).toThrow('Mapping for attribute foo is already defined (while '
'processing annottation for field foo of Bad1Component)');
});

it('should throw when annotated both getter and setter', () {
var module = new Module()
..type(DirectiveMap)
..type(Bad2Component)
..type(MetadataExtractor)
..type(FieldMetadataExtractor);

var injector = new DynamicInjector(modules: [module]);
expect(() {
injector.get(DirectiveMap);
}).toThrow('Attribute annotation for foo is defined more than once '
'in Bad2Component');
});
});
});

@NgComponent(
selector: 'annotated-io',
template: r'<content></content>',
map: const {
'foo': '=>foo'
}
)
class AnnotatedIoComponent {
AnnotatedIoComponent(Scope scope) {
scope.$root.ioComponent = this;
}

@NgAttr('attr')
String attr;

@NgTwoWay('expr')
String expr;

@NgOneWay('expr-one-way')
String exprOneWay;

@NgOneWayOneTime('expr-one-way-one-shot')
String exprOneWayOneShot;

@NgCallback('callback')
Function callback;

@NgOneWay('expr-one-way2')
set exprOneWay2(val) {}

@NgTwoWay('expr-two-way')
get exprTwoWay => null;
set exprTwoWay(val) {}
}

@NgComponent(
selector: 'bad1',
template: r'<content></content>',
map: const {
'foo': '=>foo'
}
)
class Bad1Component {
@NgOneWay('foo')
String foo;
}

@NgComponent(
selector: 'bad2',
template: r'<content></content>'
)
class Bad2Component {
@NgOneWay('foo')
get foo => null;

@NgOneWay('foo')
set foo(val) {}
}
131 changes: 23 additions & 108 deletions test/core_dom/directive_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,117 +2,32 @@ library directive_spec;

import '../_specs.dart';

main() => describe('DirectiveMap', () {

beforeEach(module((Module module) {
module
..type(AnnotatedIoComponent);
}));

it('should extract attr map from annotated component', inject((DirectiveMap directives) {
var annotations = directives.annotationsFor(AnnotatedIoComponent);
expect(annotations.length).toEqual(1);
expect(annotations[0] is NgComponent).toBeTruthy();

NgComponent annotation = annotations[0];
expect(annotation.map).toEqual({
'foo': '=>foo',
'attr': '@attr',
'expr': '<=>expr',
'expr-one-way': '=>exprOneWay',
'expr-one-way-one-shot': '=>!exprOneWayOneShot',
'callback': '&callback',
'expr-one-way2': '=>exprOneWay2',
'expr-two-way': '<=>exprTwoWay'
main() {
describe('NodeAttrs', () {
var element;
var nodeAttrs;
TestBed _;

beforeEach(inject((TestBed tb) {
_ = tb;
element = _.compile('<div foo="bar" foo-bar="baz" foo-bar-baz="foo"></div>');
nodeAttrs = new NodeAttrs(element);
}));

it('should transform names to camel case', () {
expect(nodeAttrs['foo']).toEqual('bar');
expect(nodeAttrs['fooBar']).toEqual('baz');
expect(nodeAttrs['fooBarBaz']).toEqual('foo');
});
}));

describe('exceptions', () {
it('should throw when annotation is for existing mapping', () {
var module = new Module()
..type(DirectiveMap)
..type(Bad1Component)
..type(MetadataExtractor)
..type(FieldMetadataExtractor);

var injector = new DynamicInjector(modules: [module]);
expect(() {
injector.get(DirectiveMap);
}).toThrow('Mapping for attribute foo is already defined (while '
'processing annottation for field foo of Bad1Component)');
it('should return null for unexistent attributes', () {
expect(nodeAttrs['baz']).toBeNull();
});

it('should throw when annotated both getter and setter', () {
var module = new Module()
..type(DirectiveMap)
..type(Bad2Component)
..type(MetadataExtractor)
..type(FieldMetadataExtractor);

var injector = new DynamicInjector(modules: [module]);
expect(() {
injector.get(DirectiveMap);
}).toThrow('Attribute annotation for foo is defined more than once '
'in Bad2Component');
it('should provide a forEach function to iterate over attributes', () {
Map<String, String> attrMap = new Map();
nodeAttrs.forEach((k, v) => attrMap[k] = v);
expect(attrMap).toEqual({'foo': 'bar', 'fooBar': 'baz', 'fooBarBaz': 'foo'});
});
});
});

@NgComponent(
selector: 'annotated-io',
template: r'<content></content>',
map: const {
'foo': '=>foo'
}
)
class AnnotatedIoComponent {
AnnotatedIoComponent(Scope scope) {
scope.$root.ioComponent = this;
}

@NgAttr('attr')
String attr;

@NgTwoWay('expr')
String expr;

@NgOneWay('expr-one-way')
String exprOneWay;

@NgOneWayOneTime('expr-one-way-one-shot')
String exprOneWayOneShot;

@NgCallback('callback')
Function callback;

@NgOneWay('expr-one-way2')
set exprOneWay2(val) {}

@NgTwoWay('expr-two-way')
get exprTwoWay => null;
set exprTwoWay(val) {}
}

@NgComponent(
selector: 'bad1',
template: r'<content></content>',
map: const {
'foo': '=>foo'
}
)
class Bad1Component {
@NgOneWay('foo')
String foo;
}

@NgComponent(
selector: 'bad2',
template: r'<content></content>'
)
class Bad2Component {
@NgOneWay('foo')
get foo => null;

@NgOneWay('foo')
set foo(val) {}
}
}

0 comments on commit 5c41513

Please sign in to comment.