Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: tests/html/custom_element_bindings_test.dart

Issue 14795005: Add support for custom elements to MDV (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tests/html/custom_element_bindings_test.dart
diff --git a/tests/html/custom_element_bindings_test.dart b/tests/html/custom_element_bindings_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a5d96e1ab189379c304c75c4757642d55801a3bb
--- /dev/null
+++ b/tests/html/custom_element_bindings_test.dart
@@ -0,0 +1,286 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library custom_element_bindings_test;
+
+import 'dart:async';
+import 'dart:html';
+import 'package:mdv_observe/mdv_observe.dart';
+import 'package:unittest/html_config.dart';
+import 'package:unittest/unittest.dart';
+import 'mdv_observe_utils.dart';
+
+main() {
+ useHtmlConfiguration();
+ group('Custom Element Bindings', customElementBindingsTest);
+}
+
+sym(x) => new Symbol(x);
+
+customElementBindingsTest() {
+ var testDiv;
+
+ setUp(() {
+ document.body.nodes.add(testDiv = new DivElement());
blois 2013/05/13 17:45:13 Better to use: document.body.append() (not import
Jennifer Messerly 2013/05/13 19:42:41 agreed. I didn't actually write this one though :)
Jennifer Messerly 2013/05/13 20:02:40 update. I fixed this one manually, also sent out:
+ });
+
+ tearDown(() {
+ testDiv.remove();
+ testDiv = null;
+ });
+
+ createTestHtml(s) {
+ var div = new DivElement();
+ div.innerHtml = s;
+ testDiv.nodes.add(div);
+
+ for (var node in div.queryAll('*')) {
+ if (node.isTemplate) TemplateElement.decorate(node);
+ }
+
+ return div;
+ }
+
+
+ test('override bind/unbind/unbindAll', () {
+ var element = new MyCustomElement();
+ var model = toSymbolMap({'a': new Point(123, 444), 'b': new Monster(100)});
+
+ element.bind('my-point', model, 'a');
+ element.bind('scary-monster', model, 'b');
+
+ expect(element.attributes, isNot(contains('my-point')));
+ expect(element.attributes, isNot(contains('scary-monster')));
+
+ expect(element.myPoint, model[sym('a')]);
+ expect(element.scaryMonster, model[sym('b')]);
+
+ model[sym('a')] = null;
+ deliverChangeRecords();
+ expect(element.myPoint, null);
+ element.unbind('my-point');
+
+ model[sym('a')] = new Point(1, 2);
+ model[sym('b')] = new Monster(200);
+ deliverChangeRecords();
+ expect(element.scaryMonster, model[sym('b')]);
+ expect(element.myPoint, null, reason: 'a was unbound');
+
+ element.unbindAll();
+ model[sym('b')] = null;
+ deliverChangeRecords();
+ expect(element.scaryMonster.health, 200);
+ });
+
+ test('override attribute setter', () {
+ var element = new WithAttrsCustomElement().real;
+ var model = toSymbolMap({'a': 1, 'b': 2});
+ element.bind('hidden?', model, 'a');
+ element.bind('id', model, 'b');
+
+ expect(element.attributes, contains('hidden'));
+ expect(element.attributes['hidden'], '');
+ expect(element.id, '2');
+
+ model[sym('a')] = null;
+ deliverChangeRecords();
+ expect(element.attributes, isNot(contains('hidden')),
+ reason: 'null is false-y');
+
+ model[sym('a')] = false;
+ deliverChangeRecords();
+ expect(element.attributes, isNot(contains('hidden')));
+
+ model[sym('a')] = 'foo';
+ model[sym('b')] = 'x';
+ deliverChangeRecords();
+ expect(element.attributes, contains('hidden'));
+ expect(element.attributes['hidden'], '');
+ expect(element.id, 'x');
+
+ expect(element.xtag.attributes.log, [
+ ['remove', 'hidden?'],
+ ['[]=', 'hidden', ''],
+ ['remove', 'id'],
+ ['[]=', 'id', '2'],
+ ['remove', 'hidden'],
+ ['remove', 'hidden'],
+ ['[]=', 'hidden', ''],
+ ['[]=', 'id', 'x'],
+ ]);
+ });
+
+ test('template bind uses overridden custom element bind', () {
+
+ var model = toSymbolMap({'a': new Point(123, 444), 'b': new Monster(100)});
+
+ var div = createTestHtml('<template bind>'
+ '<my-custom-element my-point="{{a}}" scary-monster="{{b}}">'
+ '</my-custom-element>'
+ '</template>');
+
+ TemplateElement.instanceCreated.listen((fragment) {
+ for (var e in fragment.queryAll('my-custom-element')) {
blois 2013/05/13 17:45:13 What will happen when custom elements are register
Jennifer Messerly 2013/05/13 19:42:41 good question, I'm not sure offhand. I'm more focu
+ new MyCustomElement.attach(e);
+ }
+ });
+
+ div.query('template').model = model;
+ deliverChangeRecords();
+
+ var element = div.nodes[1];
+
+ expect(element.xtag is MyCustomElement, true,
+ reason: '${element.xtag} should be a MyCustomElement');
+
+ expect(element.xtag.myPoint, model[sym('a')]);
+ expect(element.xtag.scaryMonster, model[sym('b')]);
+
+ expect(element.attributes, isNot(contains('my-point')));
+ expect(element.attributes, isNot(contains('scary-monster')));
+
+ model[sym('a')] = null;
+ deliverChangeRecords();
+ expect(element.xtag.myPoint, null);
+
+ div.query('template').model = null;
+ deliverChangeRecords();
+
+ expect(element.parentNode, null, reason: 'element was detached');
+
+ model[sym('a')] = new Point(1, 2);
+ model[sym('b')] = new Monster(200);
+ deliverChangeRecords();
+
+ expect(element.xtag.myPoint, null, reason: 'model was unbound');
+ expect(element.xtag.scaryMonster.health, 100, reason: 'model was unbound');
+ });
+
+}
+
+class Monster {
+ int health;
+ Monster(this.health);
+}
+
+/** Demonstrates a custom element overriding bind/unbind/unbindAll. */
+class MyCustomElement implements Element {
+ final Element real;
+
+ Point myPoint;
+ Monster scaryMonster;
+
+ StreamSubscription _sub1, _sub2;
+
+ MyCustomElement() : this.attach(new Element.tag('my-custom-element'));
+
+ MyCustomElement.attach(this.real) {
+ real.xtag = this;
+ }
+
+
+ get attributes => real.attributes;
+
+ void bind(String name, model, String path) {
+ switch (name) {
+ case 'my-point':
+ unbind('my-point');
+ attributes.remove('my-point');
+
+ _sub1 = new PathObserver(model, path).bindSync((v) {
+ myPoint = v;
+ });
+ return;
+ case 'scary-monster':
+ unbind('scary-monster');
+ attributes.remove('scary-monster');
+
+ _sub2 = new PathObserver(model, path).bindSync((v) {
+ scaryMonster = v;
+ });
+ return;
+ }
+ real.bind(name, model, path);
+ }
+
+ void unbind(String name) {
+ switch (name) {
+ case 'my-point':
+ if (_sub1 != null) {
+ print('!!! unbind $name');
+ _sub1.cancel();
+ _sub1 = null;
+ }
+ return;
+ case 'scary-monster':
+ if (_sub2 != null) {
+ print('!!! unbind $name');
+ _sub2.cancel();
+ _sub2 = null;
+ }
+ return;
+ }
+ real.unbind(name);
+ }
+
+ void unbindAll() {
+ unbind('my-point');
+ unbind('scary-monster');
+ real.unbindAll();
+ }
+}
+
+/**
+ * Demonstrates a custom element can override attributes []= and remove.
+ * and see changes that the data binding system is making to the attributes.
+ */
+class WithAttrsCustomElement implements Element {
+ final Element real;
+ final AttributeMapWrapper<String, String> attributes;
Siggi Cherem (dart-lang) 2013/05/13 16:42:13 consider adding a comment here reinforcing what yo
+
+ factory WithAttrsCustomElement() {
+ var real = new Element.tag('with-attrs-custom-element');
+ var attributes = new AttributeMapWrapper(real.attributes);
+ return new WithAttrsCustomElement._(real, attributes);
+ }
+
+ WithAttrsCustomElement._(this.real, this.attributes) {
+ real.xtag = this;
+ }
+
+ void bind(String name, model, String path) => real.bind(name, model, path);
+ void unbind(String name) => real.unbind(name);
+ void unbindAll() => real.unbindAll();
+}
+
+// TODO(jmesserly): would be nice to use mocks when mirrors work on dart2js.
+class AttributeMapWrapper<K, V> implements Map<K, V> {
+ final List log = [];
+ Map<K, V> _map;
+
+ AttributeMapWrapper(this._map);
+
+ bool containsValue(V value) => _map.containsValue(value);
+ bool containsKey(K key) => _map.containsKey(key);
+ V operator [](K key) => _map[key];
+
+ void operator []=(K key, V value) {
+ log.add(['[]=', key, value]);
+ _map[key] = value;
+ }
+
+ V putIfAbsent(K key, V ifAbsent()) => _map.putIfAbsent(key, ifAbsent);
+
+ V remove(K key) {
+ log.add(['remove', key]);
+ _map.remove(key);
+ }
+
+ void clear() => _map.clear();
+ void forEach(void f(K key, V value)) => _map.forEach(f);
+ Iterable<K> get keys => _map.keys;
+ Iterable<V> get values => _map.values;
+ int get length => _map.length;
+ bool get isEmpty => _map.isEmpty;
+}

Powered by Google App Engine
This is Rietveld 408576698