b6c4597563
I think the issue is that IE9/10 don't have native mutation observers, so the timing of mutation observers isn't (necessarily) any faster than "new Future". What we do now is wait for the "option" to get expanded before trying to run the test. R=sigmund@google.com Review URL: https://codereview.chromium.org//86153003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@30646 260f80e4-7a28-3924-810f-c04153c831b5
1887 lines
53 KiB
Dart
1887 lines
53 KiB
Dart
// 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 template_binding.test.template_binding_test;
|
|
|
|
import 'dart:async';
|
|
import 'dart:collection';
|
|
import 'dart:html';
|
|
import 'dart:math' as math;
|
|
import 'package:observe/observe.dart';
|
|
import 'package:template_binding/template_binding.dart';
|
|
import 'package:unittest/html_config.dart';
|
|
import 'package:unittest/unittest.dart';
|
|
|
|
// TODO(jmesserly): merge this file?
|
|
import 'binding_syntax.dart' show syntaxTests;
|
|
import 'utils.dart';
|
|
|
|
// Note: this file ported from
|
|
// https://github.com/Polymer/TemplateBinding/blob/ed3266266e751b5ab1f75f8e0509d0d5f0ef35d8/tests/tests.js
|
|
|
|
// TODO(jmesserly): submit a small cleanup patch to original. I fixed some
|
|
// cases where "div" and "t" were unintentionally using the JS global scope;
|
|
// look for "assertNodesAre".
|
|
|
|
main() {
|
|
useHtmlConfiguration();
|
|
|
|
// Load MutationObserver polyfill in case IE needs it.
|
|
var script = new ScriptElement()
|
|
..src = '/root_dart/pkg/mutation_observer/lib/mutation_observer.min.js';
|
|
var polyfillLoaded = script.onLoad.first;
|
|
document.head.append(script);
|
|
|
|
setUp(() => polyfillLoaded.then((_) {
|
|
document.body.append(testDiv = new DivElement());
|
|
}));
|
|
|
|
tearDown(() {
|
|
testDiv.remove();
|
|
testDiv = null;
|
|
});
|
|
|
|
test('MutationObserver is supported', () {
|
|
expect(MutationObserver.supported, true, reason: 'polyfill was loaded.');
|
|
});
|
|
|
|
group('Template Instantiation', templateInstantiationTests);
|
|
|
|
group('Binding Delegate API', () {
|
|
group('with Observable', () {
|
|
syntaxTests(([f, b]) => new FooBarModel(f, b));
|
|
});
|
|
|
|
group('with ChangeNotifier', () {
|
|
syntaxTests(([f, b]) => new FooBarNotifyModel(f, b));
|
|
});
|
|
});
|
|
|
|
group('Compat', compatTests);
|
|
}
|
|
|
|
var expando = new Expando('test');
|
|
void addExpandos(node) {
|
|
while (node != null) {
|
|
expando[node] = node.text;
|
|
node = node.nextNode;
|
|
}
|
|
}
|
|
|
|
void checkExpandos(node) {
|
|
expect(node, isNotNull);
|
|
while (node != null) {
|
|
expect(expando[node], node.text);
|
|
node = node.nextNode;
|
|
}
|
|
}
|
|
|
|
templateInstantiationTests() {
|
|
|
|
observeTest('Template', () {
|
|
var div = createTestHtml('<template bind={{}}>text</template>');
|
|
templateBind(div.firstChild).model = {};
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'text');
|
|
|
|
templateBind(div.firstChild).model = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('Template bind, no parent', () {
|
|
var div = createTestHtml('<template bind>text</template>');
|
|
var template = div.firstChild;
|
|
template.remove();
|
|
|
|
templateBind(template).model = {};
|
|
performMicrotaskCheckpoint();
|
|
expect(template.nodes.length, 0);
|
|
expect(template.nextNode, null);
|
|
});
|
|
|
|
observeTest('Template bind, no defaultView', () {
|
|
var div = createTestHtml('<template bind>text</template>');
|
|
var template = div.firstChild;
|
|
var doc = document.implementation.createHtmlDocument('');
|
|
doc.adoptNode(div);
|
|
recursivelySetTemplateModel(template, {});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('Template-Empty Bind', () {
|
|
var div = createTestHtml('<template bind>text</template>');
|
|
var template = div.firstChild;
|
|
templateBind(template).model = {};
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'text');
|
|
});
|
|
|
|
observeTest('Template Bind If', () {
|
|
var div = createTestHtml('<template bind if="{{ foo }}">text</template>');
|
|
// Note: changed this value from 0->null because zero is not falsey in Dart.
|
|
// See https://code.google.com/p/dart/issues/detail?id=11956
|
|
var m = toObservable({ 'foo': null });
|
|
var template = div.firstChild;
|
|
templateBind(template).model = m;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
m['foo'] = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.lastChild.text, 'text');
|
|
|
|
templateBind(template).model = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('Template Bind If, 2', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{ foo }}" if="{{ bar }}">{{ bat }}</template>');
|
|
var m = toObservable({ 'bar': null, 'foo': { 'bat': 'baz' } });
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
m['bar'] = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.lastChild.text, 'baz');
|
|
});
|
|
|
|
observeTest('Template If', () {
|
|
var div = createTestHtml('<template if="{{ foo }}">{{ value }}</template>');
|
|
// Note: changed this value from 0->null because zero is not falsey in
|
|
// Dart. See https://code.google.com/p/dart/issues/detail?id=11956
|
|
var m = toObservable({ 'foo': null, 'value': 'foo' });
|
|
var template = div.firstChild;
|
|
templateBind(template).model = m;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
m['foo'] = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.lastChild.text, 'foo');
|
|
|
|
templateBind(template).model = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('Template Empty-If', () {
|
|
var div = createTestHtml('<template if>{{ value }}</template>');
|
|
var m = toObservable({ 'value': 'foo' });
|
|
recursivelySetTemplateModel(div, null);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.lastChild.text, 'foo');
|
|
});
|
|
|
|
observeTest('Template Repeat If', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{ foo }}" if="{{ bar }}">{{ }}</template>');
|
|
// Note: changed this value from 0->null because zero is not falsey in Dart.
|
|
// See https://code.google.com/p/dart/issues/detail?id=11956
|
|
var m = toObservable({ 'bar': null, 'foo': [1, 2, 3] });
|
|
var template = div.firstChild;
|
|
templateBind(template).model = m;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
m['bar'] = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[1].text, '1');
|
|
expect(div.nodes[2].text, '2');
|
|
expect(div.nodes[3].text, '3');
|
|
|
|
templateBind(template).model = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('TextTemplateWithNullStringBinding', () {
|
|
var div = createTestHtml('<template bind={{}}>a{{b}}c</template>');
|
|
var model = toObservable({'b': 'B'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'aBc');
|
|
|
|
model['b'] = 'b';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'abc');
|
|
|
|
model['b'] = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'ac');
|
|
|
|
model = null;
|
|
performMicrotaskCheckpoint();
|
|
// setting model isn't observable.
|
|
expect(div.nodes.last.text, 'ac');
|
|
});
|
|
|
|
observeTest('TextTemplateWithBindingPath', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{ data }}">a{{b}}c</template>');
|
|
var model = toObservable({ 'data': {'b': 'B'} });
|
|
var template = div.firstChild;
|
|
templateBind(template).model = model;
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'aBc');
|
|
|
|
model['data']['b'] = 'b';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'abc');
|
|
|
|
model['data'] = toObservable({'b': 'X'});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'aXc');
|
|
|
|
// Dart note: changed from `null` since our null means don't render a model.
|
|
model['data'] = toObservable({});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'ac');
|
|
|
|
model['data'] = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('TextTemplateWithBindingAndConditional', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}" if="{{ d }}">a{{b}}c</template>');
|
|
var model = toObservable({'b': 'B', 'd': 1});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'aBc');
|
|
|
|
model['b'] = 'b';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'abc');
|
|
|
|
// TODO(jmesserly): MDV set this to empty string and relies on JS conversion
|
|
// rules. Is that intended?
|
|
// See https://github.com/toolkitchen/mdv/issues/59
|
|
model['d'] = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
model['d'] = 'here';
|
|
model['b'] = 'd';
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'adc');
|
|
});
|
|
|
|
observeTest('TemplateWithTextBinding2', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{ b }}">a{{value}}c</template>');
|
|
expect(div.nodes.length, 1);
|
|
var model = toObservable({'b': {'value': 'B'}});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.text, 'aBc');
|
|
|
|
model['b'] = toObservable({'value': 'b'});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.text, 'abc');
|
|
});
|
|
|
|
observeTest('TemplateWithAttributeBinding', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">'
|
|
'<div foo="a{{b}}c"></div>'
|
|
'</template>');
|
|
var model = toObservable({'b': 'B'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.attributes['foo'], 'aBc');
|
|
|
|
model['b'] = 'b';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.attributes['foo'], 'abc');
|
|
|
|
model['b'] = 'X';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.attributes['foo'], 'aXc');
|
|
});
|
|
|
|
observeTest('TemplateWithConditionalBinding', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">'
|
|
'<div foo?="{{b}}"></div>'
|
|
'</template>');
|
|
var model = toObservable({'b': 'b'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.attributes['foo'], '');
|
|
expect(div.nodes.last.attributes, isNot(contains('foo?')));
|
|
|
|
model['b'] = null;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.attributes, isNot(contains('foo')));
|
|
});
|
|
|
|
observeTest('Repeat', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}"">text</template>');
|
|
|
|
var model = toObservable([0, 1, 2]);
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
|
|
model.length = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
|
|
model.addAll(toObservable([3, 4]));
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
|
|
model.removeRange(1, 2);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 3);
|
|
});
|
|
|
|
observeTest('Repeat - Reuse Instances', () {
|
|
var div = createTestHtml('<template repeat>{{ val }}</template>');
|
|
|
|
var model = toObservable([
|
|
{'val': 10},
|
|
{'val': 5},
|
|
{'val': 2},
|
|
{'val': 8},
|
|
{'val': 1}
|
|
]);
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 6);
|
|
var template = div.firstChild;
|
|
|
|
addExpandos(template.nextNode);
|
|
checkExpandos(template.nextNode);
|
|
|
|
model.sort((a, b) => a['val'] - b['val']);
|
|
performMicrotaskCheckpoint();
|
|
checkExpandos(template.nextNode);
|
|
|
|
model = toObservable(model.reversed);
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
checkExpandos(template.nextNode);
|
|
|
|
for (var item in model) {
|
|
item['val'] += 1;
|
|
}
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, "11");
|
|
expect(div.nodes[2].text, "9");
|
|
expect(div.nodes[3].text, "6");
|
|
expect(div.nodes[4].text, "3");
|
|
expect(div.nodes[5].text, "2");
|
|
});
|
|
|
|
observeTest('Bind - Reuse Instance', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{ foo }}">{{ bar }}</template>');
|
|
|
|
var model = toObservable({ 'foo': { 'bar': 5 }});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
var template = div.firstChild;
|
|
|
|
addExpandos(template.nextNode);
|
|
checkExpandos(template.nextNode);
|
|
|
|
model = toObservable({'foo': model['foo']});
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
checkExpandos(template.nextNode);
|
|
});
|
|
|
|
observeTest('Repeat-Empty', () {
|
|
var div = createTestHtml(
|
|
'<template repeat>text</template>');
|
|
|
|
var model = toObservable([0, 1, 2]);
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
|
|
model.length = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
|
|
model.addAll(toObservable([3, 4]));
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
|
|
model.removeRange(1, 2);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 3);
|
|
});
|
|
|
|
observeTest('Removal from iteration needs to unbind', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}"><a>{{v}}</a></template>');
|
|
var model = toObservable([{'v': 0}, {'v': 1}, {'v': 2}, {'v': 3},
|
|
{'v': 4}]);
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var nodes = div.nodes.skip(1).toList();
|
|
var vs = model.toList();
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
expect(nodes[i].text, '$i');
|
|
}
|
|
|
|
model.length = 3;
|
|
performMicrotaskCheckpoint();
|
|
for (var i = 0; i < 5; i++) {
|
|
expect(nodes[i].text, '$i');
|
|
}
|
|
|
|
vs[3]['v'] = 33;
|
|
vs[4]['v'] = 44;
|
|
performMicrotaskCheckpoint();
|
|
for (var i = 0; i < 5; i++) {
|
|
expect(nodes[i].text, '$i');
|
|
}
|
|
});
|
|
|
|
observeTest('DOM Stability on Iteration', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}">{{}}</template>');
|
|
var model = toObservable([1, 2, 3, 4, 5]);
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
|
|
// Note: the node at index 0 is the <template>.
|
|
var nodes = div.nodes.toList();
|
|
expect(nodes.length, 6, reason: 'list has 5 items');
|
|
|
|
model.removeAt(0);
|
|
model.removeLast();
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4, reason: 'list has 3 items');
|
|
expect(identical(div.nodes[1], nodes[2]), true, reason: '2 not removed');
|
|
expect(identical(div.nodes[2], nodes[3]), true, reason: '3 not removed');
|
|
expect(identical(div.nodes[3], nodes[4]), true, reason: '4 not removed');
|
|
|
|
model.insert(0, 5);
|
|
model[2] = 6;
|
|
model.add(7);
|
|
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 6, reason: 'list has 5 items');
|
|
expect(nodes.contains(div.nodes[1]), false, reason: '5 is a new node');
|
|
expect(identical(div.nodes[2], nodes[2]), true);
|
|
expect(nodes.contains(div.nodes[3]), false, reason: '6 is a new node');
|
|
expect(identical(div.nodes[4], nodes[4]), true);
|
|
expect(nodes.contains(div.nodes[5]), false, reason: '7 is a new node');
|
|
|
|
nodes = div.nodes.toList();
|
|
|
|
model.insert(2, 8);
|
|
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 7, reason: 'list has 6 items');
|
|
expect(identical(div.nodes[1], nodes[1]), true);
|
|
expect(identical(div.nodes[2], nodes[2]), true);
|
|
expect(nodes.contains(div.nodes[3]), false, reason: '8 is a new node');
|
|
expect(identical(div.nodes[4], nodes[3]), true);
|
|
expect(identical(div.nodes[5], nodes[4]), true);
|
|
expect(identical(div.nodes[6], nodes[5]), true);
|
|
});
|
|
|
|
observeTest('Repeat2', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}">{{value}}</template>');
|
|
expect(div.nodes.length, 1);
|
|
|
|
var model = toObservable([
|
|
{'value': 0},
|
|
{'value': 1},
|
|
{'value': 2}
|
|
]);
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[1].text, '0');
|
|
expect(div.nodes[2].text, '1');
|
|
expect(div.nodes[3].text, '2');
|
|
|
|
model[1]['value'] = 'One';
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[1].text, '0');
|
|
expect(div.nodes[2].text, 'One');
|
|
expect(div.nodes[3].text, '2');
|
|
|
|
model.replaceRange(0, 1, toObservable([{'value': 'Zero'}]));
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[1].text, 'Zero');
|
|
expect(div.nodes[2].text, 'One');
|
|
expect(div.nodes[3].text, '2');
|
|
});
|
|
|
|
observeTest('TemplateWithInputValue', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">'
|
|
'<input value="{{x}}">'
|
|
'</template>');
|
|
var model = toObservable({'x': 'hi'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes.last.value, 'hi');
|
|
|
|
model['x'] = 'bye';
|
|
expect(div.nodes.last.value, 'hi');
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.value, 'bye');
|
|
|
|
div.nodes.last.value = 'hello';
|
|
dispatchEvent('input', div.nodes.last);
|
|
expect(model['x'], 'hello');
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.last.value, 'hello');
|
|
});
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
observeTest('Decorated', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{ XX }}" id="t1">'
|
|
'<p>Crew member: {{name}}, Job title: {{title}}</p>'
|
|
'</template>'
|
|
'<template bind="{{ XY }}" id="t2" ref="t1"></template>');
|
|
|
|
var model = toObservable({
|
|
'XX': {'name': 'Leela', 'title': 'Captain'},
|
|
'XY': {'name': 'Fry', 'title': 'Delivery boy'},
|
|
'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
|
|
});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
|
|
var t1 = document.getElementById('t1');
|
|
var instance = t1.nextElementSibling;
|
|
expect(instance.text, 'Crew member: Leela, Job title: Captain');
|
|
|
|
var t2 = document.getElementById('t2');
|
|
instance = t2.nextElementSibling;
|
|
expect(instance.text, 'Crew member: Fry, Job title: Delivery boy');
|
|
|
|
expect(div.children.length, 4);
|
|
expect(div.nodes.length, 4);
|
|
|
|
expect(div.nodes[1].tagName, 'P');
|
|
expect(div.nodes[3].tagName, 'P');
|
|
});
|
|
|
|
observeTest('DefaultStyles', () {
|
|
var t = new Element.tag('template');
|
|
TemplateBindExtension.decorate(t);
|
|
|
|
document.body.append(t);
|
|
expect(t.getComputedStyle().display, 'none');
|
|
|
|
t.remove();
|
|
});
|
|
|
|
|
|
observeTest('Bind', () {
|
|
var div = createTestHtml('<template bind="{{}}">Hi {{ name }}</template>');
|
|
var model = toObservable({'name': 'Leela'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, 'Hi Leela');
|
|
});
|
|
|
|
observeTest('BindImperative', () {
|
|
var div = createTestHtml(
|
|
'<template>'
|
|
'Hi {{ name }}'
|
|
'</template>');
|
|
var t = div.nodes.first;
|
|
|
|
var model = toObservable({'name': 'Leela'});
|
|
nodeBind(t).bind('bind', model, '');
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, 'Hi Leela');
|
|
});
|
|
|
|
observeTest('BindPlaceHolderHasNewLine', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">Hi {{\nname\n}}</template>');
|
|
var model = toObservable({'name': 'Leela'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, 'Hi Leela');
|
|
});
|
|
|
|
observeTest('BindWithRef', () {
|
|
var id = 't${new math.Random().nextInt(100)}';
|
|
var div = createTestHtml(
|
|
'<template id="$id">'
|
|
'Hi {{ name }}'
|
|
'</template>'
|
|
'<template ref="$id" bind="{{}}"></template>');
|
|
|
|
var t1 = div.nodes.first;
|
|
var t2 = div.nodes[1];
|
|
|
|
expect(templateBind(t2).ref, t1);
|
|
|
|
var model = toObservable({'name': 'Fry'});
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(t2.nextNode.text, 'Hi Fry');
|
|
});
|
|
|
|
observeTest('BindWithDynamicRef', () {
|
|
var id = 't${new math.Random().nextInt(100)}';
|
|
var div = createTestHtml(
|
|
'<template id="$id">'
|
|
'Hi {{ name }}'
|
|
'</template>'
|
|
'<template ref="{{ id }}" bind="{{}}"></template>');
|
|
|
|
var t1 = div.firstChild;
|
|
var t2 = div.nodes[1];
|
|
var model = toObservable({'name': 'Fry', 'id': id });
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(t2.nextNode.text, 'Hi Fry');
|
|
});
|
|
|
|
observeTest('BindChanged', () {
|
|
var model = toObservable({
|
|
'XX': {'name': 'Leela', 'title': 'Captain'},
|
|
'XY': {'name': 'Fry', 'title': 'Delivery boy'},
|
|
'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
|
|
});
|
|
|
|
var div = createTestHtml(
|
|
'<template bind="{{ XX }}">Hi {{ name }}</template>');
|
|
|
|
recursivelySetTemplateModel(div, model);
|
|
|
|
var t = div.nodes.first;
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 2);
|
|
expect(t.nextNode.text, 'Hi Leela');
|
|
|
|
nodeBind(t).bind('bind', model, 'XZ');
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 2);
|
|
expect(t.nextNode.text, 'Hi Zoidberg');
|
|
});
|
|
|
|
assertNodesAre(div, [arguments]) {
|
|
var expectedLength = arguments.length;
|
|
expect(div.nodes.length, expectedLength + 1);
|
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
var targetNode = div.nodes[i + 1];
|
|
expect(targetNode.text, arguments[i]);
|
|
}
|
|
}
|
|
|
|
observeTest('Repeat3', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{ contacts }}">Hi {{ name }}</template>');
|
|
var t = div.nodes.first;
|
|
|
|
var m = toObservable({
|
|
'contacts': [
|
|
{'name': 'Raf'},
|
|
{'name': 'Arv'},
|
|
{'name': 'Neal'}
|
|
]
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
|
|
|
|
m['contacts'].add(toObservable({'name': 'Alex'}));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
|
|
|
|
m['contacts'].replaceRange(0, 2,
|
|
toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
|
|
|
|
m['contacts'].removeRange(1, 3);
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
|
|
|
|
m['contacts'].insertAll(1,
|
|
toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
|
|
|
|
m['contacts'].replaceRange(0, 1,
|
|
toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri',
|
|
'Hi Alex']);
|
|
|
|
m['contacts'] = toObservable([{'name': 'Alex'}]);
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Alex']);
|
|
|
|
m['contacts'].length = 0;
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, []);
|
|
});
|
|
|
|
observeTest('RepeatModelSet', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{ contacts }}">'
|
|
'Hi {{ name }}'
|
|
'</template>');
|
|
var m = toObservable({
|
|
'contacts': [
|
|
{'name': 'Raf'},
|
|
{'name': 'Arv'},
|
|
{'name': 'Neal'}
|
|
]
|
|
});
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
performMicrotaskCheckpoint();
|
|
var t = div.nodes.first;
|
|
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
|
|
});
|
|
|
|
observeTest('RepeatEmptyPath', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}">Hi {{ name }}</template>');
|
|
var t = div.nodes.first;
|
|
|
|
var m = toObservable([
|
|
{'name': 'Raf'},
|
|
{'name': 'Arv'},
|
|
{'name': 'Neal'}
|
|
]);
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
performMicrotaskCheckpoint();
|
|
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
|
|
|
|
m.add(toObservable({'name': 'Alex'}));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
|
|
|
|
m.replaceRange(0, 2, toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
|
|
|
|
m.removeRange(1, 3);
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
|
|
|
|
m.insertAll(1, toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
|
|
|
|
m.replaceRange(0, 1, toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri',
|
|
'Hi Alex']);
|
|
|
|
m.length = 0;
|
|
m.add(toObservable({'name': 'Alex'}));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Alex']);
|
|
});
|
|
|
|
observeTest('RepeatNullModel', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}">Hi {{ name }}</template>');
|
|
var t = div.nodes.first;
|
|
|
|
var m = null;
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
expect(div.nodes.length, 1);
|
|
|
|
t.attributes['iterate'] = '';
|
|
m = toObservable({});
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
});
|
|
|
|
observeTest('RepeatReuse', () {
|
|
var div = createTestHtml(
|
|
'<template repeat="{{}}">Hi {{ name }}</template>');
|
|
var t = div.nodes.first;
|
|
|
|
var m = toObservable([
|
|
{'name': 'Raf'},
|
|
{'name': 'Arv'},
|
|
{'name': 'Neal'}
|
|
]);
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
|
|
var node1 = div.nodes[1];
|
|
var node2 = div.nodes[2];
|
|
var node3 = div.nodes[3];
|
|
|
|
m.replaceRange(1, 2, toObservable([{'name': 'Erik'}]));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Raf', 'Hi Erik', 'Hi Neal']);
|
|
expect(div.nodes[1], node1,
|
|
reason: 'model[0] did not change so the node should not have changed');
|
|
expect(div.nodes[2], isNot(equals(node2)),
|
|
reason: 'Should not reuse when replacing');
|
|
expect(div.nodes[3], node3,
|
|
reason: 'model[2] did not change so the node should not have changed');
|
|
|
|
node2 = div.nodes[2];
|
|
m.insert(0, toObservable({'name': 'Alex'}));
|
|
performMicrotaskCheckpoint();
|
|
assertNodesAre(div, ['Hi Alex', 'Hi Raf', 'Hi Erik', 'Hi Neal']);
|
|
});
|
|
|
|
observeTest('TwoLevelsDeepBug', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}"><span><span>{{ foo }}</span></span></template>');
|
|
|
|
var model = toObservable({'foo': 'bar'});
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes[1].nodes[0].nodes[0].text, 'bar');
|
|
});
|
|
|
|
observeTest('Checked', () {
|
|
var div = createTestHtml(
|
|
'<template>'
|
|
'<input type="checkbox" checked="{{a}}">'
|
|
'</template>');
|
|
var t = div.nodes.first;
|
|
var m = toObservable({
|
|
'a': true
|
|
});
|
|
nodeBind(t).bind('bind', m, '');
|
|
performMicrotaskCheckpoint();
|
|
|
|
var instanceInput = t.nextNode;
|
|
expect(instanceInput.checked, true);
|
|
|
|
instanceInput.click();
|
|
expect(instanceInput.checked, false);
|
|
|
|
instanceInput.click();
|
|
expect(instanceInput.checked, true);
|
|
});
|
|
|
|
nestedHelper(s, start) {
|
|
var div = createTestHtml(s);
|
|
|
|
var m = toObservable({
|
|
'a': {
|
|
'b': 1,
|
|
'c': {'d': 2}
|
|
},
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var i = start;
|
|
expect(div.nodes[i++].text, '1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, '2');
|
|
|
|
m['a']['b'] = 11;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[start].text, '11');
|
|
|
|
m['a']['c'] = toObservable({'d': 22});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[start + 2].text, '22');
|
|
}
|
|
|
|
observeTest('Nested', () {
|
|
nestedHelper(
|
|
'<template bind="{{a}}">'
|
|
'{{b}}'
|
|
'<template bind="{{c}}">'
|
|
'{{d}}'
|
|
'</template>'
|
|
'</template>', 1);
|
|
});
|
|
|
|
observeTest('NestedWithRef', () {
|
|
nestedHelper(
|
|
'<template id="inner">{{d}}</template>'
|
|
'<template id="outer" bind="{{a}}">'
|
|
'{{b}}'
|
|
'<template ref="inner" bind="{{c}}"></template>'
|
|
'</template>', 2);
|
|
});
|
|
|
|
nestedIterateInstantiateHelper(s, start) {
|
|
var div = createTestHtml(s);
|
|
|
|
var m = toObservable({
|
|
'a': [
|
|
{
|
|
'b': 1,
|
|
'c': {'d': 11}
|
|
},
|
|
{
|
|
'b': 2,
|
|
'c': {'d': 22}
|
|
}
|
|
]
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var i = start;
|
|
expect(div.nodes[i++].text, '1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, '11');
|
|
expect(div.nodes[i++].text, '2');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, '22');
|
|
|
|
m['a'][1] = toObservable({
|
|
'b': 3,
|
|
'c': {'d': 33}
|
|
});
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[start + 3].text, '3');
|
|
expect(div.nodes[start + 5].text, '33');
|
|
}
|
|
|
|
observeTest('NestedRepeatBind', () {
|
|
nestedIterateInstantiateHelper(
|
|
'<template repeat="{{a}}">'
|
|
'{{b}}'
|
|
'<template bind="{{c}}">'
|
|
'{{d}}'
|
|
'</template>'
|
|
'</template>', 1);
|
|
});
|
|
|
|
observeTest('NestedRepeatBindWithRef', () {
|
|
nestedIterateInstantiateHelper(
|
|
'<template id="inner">'
|
|
'{{d}}'
|
|
'</template>'
|
|
'<template repeat="{{a}}">'
|
|
'{{b}}'
|
|
'<template ref="inner" bind="{{c}}"></template>'
|
|
'</template>', 2);
|
|
});
|
|
|
|
nestedIterateIterateHelper(s, start) {
|
|
var div = createTestHtml(s);
|
|
|
|
var m = toObservable({
|
|
'a': [
|
|
{
|
|
'b': 1,
|
|
'c': [{'d': 11}, {'d': 12}]
|
|
},
|
|
{
|
|
'b': 2,
|
|
'c': [{'d': 21}, {'d': 22}]
|
|
}
|
|
]
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var i = start;
|
|
expect(div.nodes[i++].text, '1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, '11');
|
|
expect(div.nodes[i++].text, '12');
|
|
expect(div.nodes[i++].text, '2');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, '21');
|
|
expect(div.nodes[i++].text, '22');
|
|
|
|
m['a'][1] = toObservable({
|
|
'b': 3,
|
|
'c': [{'d': 31}, {'d': 32}, {'d': 33}]
|
|
});
|
|
|
|
i = start + 4;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[start + 4].text, '3');
|
|
expect(div.nodes[start + 6].text, '31');
|
|
expect(div.nodes[start + 7].text, '32');
|
|
expect(div.nodes[start + 8].text, '33');
|
|
}
|
|
|
|
observeTest('NestedRepeatBind', () {
|
|
nestedIterateIterateHelper(
|
|
'<template repeat="{{a}}">'
|
|
'{{b}}'
|
|
'<template repeat="{{c}}">'
|
|
'{{d}}'
|
|
'</template>'
|
|
'</template>', 1);
|
|
});
|
|
|
|
observeTest('NestedRepeatRepeatWithRef', () {
|
|
nestedIterateIterateHelper(
|
|
'<template id="inner">'
|
|
'{{d}}'
|
|
'</template>'
|
|
'<template repeat="{{a}}">'
|
|
'{{b}}'
|
|
'<template ref="inner" repeat="{{c}}"></template>'
|
|
'</template>', 2);
|
|
});
|
|
|
|
observeTest('NestedRepeatSelfRef', () {
|
|
var div = createTestHtml(
|
|
'<template id="t" repeat="{{}}">'
|
|
'{{name}}'
|
|
'<template ref="t" repeat="{{items}}"></template>'
|
|
'</template>');
|
|
|
|
var m = toObservable([
|
|
{
|
|
'name': 'Item 1',
|
|
'items': [
|
|
{
|
|
'name': 'Item 1.1',
|
|
'items': [
|
|
{
|
|
'name': 'Item 1.1.1',
|
|
'items': []
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'name': 'Item 1.2'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'name': 'Item 2',
|
|
'items': []
|
|
},
|
|
]);
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var i = 1;
|
|
expect(div.nodes[i++].text, 'Item 1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, 'Item 1.1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, 'Item 1.1.1');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, 'Item 1.2');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, 'Item 2');
|
|
|
|
m[0] = toObservable({'name': 'Item 1 changed'});
|
|
|
|
i = 1;
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[i++].text, 'Item 1 changed');
|
|
expect(div.nodes[i++].tagName, 'TEMPLATE');
|
|
expect(div.nodes[i++].text, 'Item 2');
|
|
});
|
|
|
|
// Note: we don't need a zone for this test, and we don't want to alter timing
|
|
// since we're testing a rather subtle relationship between select and option.
|
|
test('Attribute Template Option/Optgroup', () {
|
|
var div = createTestHtml(
|
|
'<template bind>'
|
|
'<select selectedIndex="{{ selected }}">'
|
|
'<optgroup template repeat="{{ groups }}" label="{{ name }}">'
|
|
'<option template repeat="{{ items }}">{{ val }}</option>'
|
|
'</optgroup>'
|
|
'</select>'
|
|
'</template>');
|
|
|
|
var m = toObservable({
|
|
'selected': 1,
|
|
'groups': [{
|
|
'name': 'one', 'items': [{ 'val': 0 }, { 'val': 1 }]
|
|
}],
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
var completer = new Completer();
|
|
|
|
new MutationObserver((records, observer) {
|
|
var select = div.nodes[0].nextNode;
|
|
if (select == null || select.querySelector('option') == null) return;
|
|
|
|
observer.disconnect();
|
|
new Future(() {
|
|
expect(select.nodes.length, 2);
|
|
|
|
expect(select.selectedIndex, 1, reason: 'selected index should update '
|
|
'after template expands.');
|
|
|
|
expect(select.nodes[0].tagName, 'TEMPLATE');
|
|
expect((templateBind(templateBind(select.nodes[0]).ref)
|
|
.content.nodes[0] as Element).tagName, 'OPTGROUP');
|
|
|
|
var optgroup = select.nodes[1];
|
|
expect(optgroup.nodes[0].tagName, 'TEMPLATE');
|
|
expect(optgroup.nodes[1].tagName, 'OPTION');
|
|
expect(optgroup.nodes[1].text, '0');
|
|
expect(optgroup.nodes[2].tagName, 'OPTION');
|
|
expect(optgroup.nodes[2].text, '1');
|
|
|
|
completer.complete();
|
|
});
|
|
})..observe(div, childList: true, subtree: true);
|
|
|
|
Observable.dirtyCheck();
|
|
|
|
return completer.future;
|
|
});
|
|
|
|
observeTest('NestedIterateTableMixedSemanticNative', () {
|
|
if (!parserHasNativeTemplate) return;
|
|
|
|
var div = createTestHtml(
|
|
'<table><tbody>'
|
|
'<template repeat="{{}}">'
|
|
'<tr>'
|
|
'<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
|
|
'</tr>'
|
|
'</template>'
|
|
'</tbody></table>');
|
|
|
|
var m = toObservable([
|
|
[{ 'val': 0 }, { 'val': 1 }],
|
|
[{ 'val': 2 }, { 'val': 3 }]
|
|
]);
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var tbody = div.nodes[0].nodes[0];
|
|
|
|
// 1 for the <tr template>, 2 * (1 tr)
|
|
expect(tbody.nodes.length, 3);
|
|
|
|
// 1 for the <td template>, 2 * (1 td)
|
|
expect(tbody.nodes[1].nodes.length, 3);
|
|
|
|
expect(tbody.nodes[1].nodes[1].text, '0');
|
|
expect(tbody.nodes[1].nodes[2].text, '1');
|
|
|
|
// 1 for the <td template>, 2 * (1 td)
|
|
expect(tbody.nodes[2].nodes.length, 3);
|
|
expect(tbody.nodes[2].nodes[1].text, '2');
|
|
expect(tbody.nodes[2].nodes[2].text, '3');
|
|
|
|
// Asset the 'class' binding is retained on the semantic template (just
|
|
// check the last one).
|
|
expect(tbody.nodes[2].nodes[2].attributes["class"], '3');
|
|
});
|
|
|
|
observeTest('NestedIterateTable', () {
|
|
var div = createTestHtml(
|
|
'<table><tbody>'
|
|
'<tr template repeat="{{}}">'
|
|
'<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
|
|
'</tr>'
|
|
'</tbody></table>');
|
|
|
|
var m = toObservable([
|
|
[{ 'val': 0 }, { 'val': 1 }],
|
|
[{ 'val': 2 }, { 'val': 3 }]
|
|
]);
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var i = 1;
|
|
var tbody = div.nodes[0].nodes[0];
|
|
|
|
// 1 for the <tr template>, 2 * (1 tr)
|
|
expect(tbody.nodes.length, 3);
|
|
|
|
// 1 for the <td template>, 2 * (1 td)
|
|
expect(tbody.nodes[1].nodes.length, 3);
|
|
expect(tbody.nodes[1].nodes[1].text, '0');
|
|
expect(tbody.nodes[1].nodes[2].text, '1');
|
|
|
|
// 1 for the <td template>, 2 * (1 td)
|
|
expect(tbody.nodes[2].nodes.length, 3);
|
|
expect(tbody.nodes[2].nodes[1].text, '2');
|
|
expect(tbody.nodes[2].nodes[2].text, '3');
|
|
|
|
// Asset the 'class' binding is retained on the semantic template (just
|
|
// check the last one).
|
|
expect(tbody.nodes[2].nodes[2].attributes['class'], '3');
|
|
});
|
|
|
|
observeTest('NestedRepeatDeletionOfMultipleSubTemplates', () {
|
|
var div = createTestHtml(
|
|
'<ul>'
|
|
'<template repeat="{{}}" id=t1>'
|
|
'<li>{{name}}'
|
|
'<ul>'
|
|
'<template ref=t1 repaet="{{items}}"></template>'
|
|
'</ul>'
|
|
'</li>'
|
|
'</template>'
|
|
'</ul>');
|
|
|
|
var m = toObservable([
|
|
{
|
|
'name': 'Item 1',
|
|
'items': [
|
|
{
|
|
'name': 'Item 1.1'
|
|
}
|
|
]
|
|
}
|
|
]);
|
|
|
|
recursivelySetTemplateModel(div, m);
|
|
|
|
performMicrotaskCheckpoint();
|
|
m.removeAt(0);
|
|
performMicrotaskCheckpoint();
|
|
});
|
|
|
|
observeTest('DeepNested', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{a}}">'
|
|
'<p>'
|
|
'<template bind="{{b}}">'
|
|
'{{ c }}'
|
|
'</template>'
|
|
'</p>'
|
|
'</template>');
|
|
|
|
var m = toObservable({
|
|
'a': {
|
|
'b': {
|
|
'c': 42
|
|
}
|
|
}
|
|
});
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes[1].tagName, 'P');
|
|
expect(div.nodes[1].nodes.first.tagName, 'TEMPLATE');
|
|
expect(div.nodes[1].nodes[1].text, '42');
|
|
});
|
|
|
|
observeTest('TemplateContentRemoved', () {
|
|
var div = createTestHtml('<template bind="{{}}">{{ }}</template>');
|
|
var model = 42;
|
|
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, '42');
|
|
expect(div.nodes[0].text, '');
|
|
});
|
|
|
|
observeTest('TemplateContentRemovedEmptyArray', () {
|
|
var div = createTestHtml('<template iterate>Remove me</template>');
|
|
var model = toObservable([]);
|
|
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
expect(div.nodes[0].text, '');
|
|
});
|
|
|
|
observeTest('TemplateContentRemovedNested', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">'
|
|
'{{ a }}'
|
|
'<template bind="{{}}">'
|
|
'{{ b }}'
|
|
'</template>'
|
|
'</template>');
|
|
|
|
var model = toObservable({
|
|
'a': 1,
|
|
'b': 2
|
|
});
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes[0].text, '');
|
|
expect(div.nodes[1].text, '1');
|
|
expect(div.nodes[2].text, '');
|
|
expect(div.nodes[3].text, '2');
|
|
});
|
|
|
|
observeTest('BindWithUndefinedModel', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}" if="{{}}">{{ a }}</template>');
|
|
|
|
var model = toObservable({'a': 42});
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, '42');
|
|
|
|
model = null;
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 1);
|
|
|
|
model = toObservable({'a': 42});
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes[1].text, '42');
|
|
});
|
|
|
|
observeTest('BindNested', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}">'
|
|
'Name: {{ name }}'
|
|
'<template bind="{{wife}}" if="{{wife}}">'
|
|
'Wife: {{ name }}'
|
|
'</template>'
|
|
'<template bind="{{child}}" if="{{child}}">'
|
|
'Child: {{ name }}'
|
|
'</template>'
|
|
'</template>');
|
|
|
|
var m = toObservable({
|
|
'name': 'Hermes',
|
|
'wife': {
|
|
'name': 'LaBarbara'
|
|
}
|
|
});
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 5);
|
|
expect(div.nodes[1].text, 'Name: Hermes');
|
|
expect(div.nodes[3].text, 'Wife: LaBarbara');
|
|
|
|
m['child'] = toObservable({'name': 'Dwight'});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 6);
|
|
expect(div.nodes[5].text, 'Child: Dwight');
|
|
|
|
m.remove('wife');
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 5);
|
|
expect(div.nodes[4].text, 'Child: Dwight');
|
|
});
|
|
|
|
observeTest('BindRecursive', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}}" if="{{}}" id="t">'
|
|
'Name: {{ name }}'
|
|
'<template bind="{{friend}}" if="{{friend}}" ref="t"></template>'
|
|
'</template>');
|
|
|
|
var m = toObservable({
|
|
'name': 'Fry',
|
|
'friend': {
|
|
'name': 'Bender'
|
|
}
|
|
});
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 5);
|
|
expect(div.nodes[1].text, 'Name: Fry');
|
|
expect(div.nodes[3].text, 'Name: Bender');
|
|
|
|
m['friend']['friend'] = toObservable({'name': 'Leela'});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 7);
|
|
expect(div.nodes[5].text, 'Name: Leela');
|
|
|
|
m['friend'] = toObservable({'name': 'Leela'});
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 5);
|
|
expect(div.nodes[3].text, 'Name: Leela');
|
|
});
|
|
|
|
observeTest('Template - Self is terminator', () {
|
|
var div = createTestHtml(
|
|
'<template repeat>{{ foo }}'
|
|
'<template bind></template>'
|
|
'</template>');
|
|
|
|
var m = toObservable([{ 'foo': 'bar' }]);
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
m.add(toObservable({ 'foo': 'baz' }));
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 5);
|
|
expect(div.nodes[1].text, 'bar');
|
|
expect(div.nodes[3].text, 'baz');
|
|
});
|
|
|
|
observeTest('Template - Same Contents, Different Array has no effect', () {
|
|
if (!MutationObserver.supported) return;
|
|
|
|
var div = createTestHtml('<template repeat>{{ foo }}</template>');
|
|
|
|
var m = toObservable([{ 'foo': 'bar' }, { 'foo': 'bat'}]);
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var observer = new MutationObserver((records, _) {});
|
|
observer.observe(div, childList: true);
|
|
|
|
var template = div.firstChild;
|
|
nodeBind(template).bind('repeat', toObservable(m.toList()), '');
|
|
performMicrotaskCheckpoint();
|
|
var records = observer.takeRecords();
|
|
expect(records.length, 0);
|
|
});
|
|
|
|
observeTest('RecursiveRef', () {
|
|
var div = createTestHtml(
|
|
'<template bind>'
|
|
'<template id=src>{{ foo }}</template>'
|
|
'<template bind ref=src></template>'
|
|
'</template>');
|
|
|
|
var m = toObservable({'foo': 'bar'});
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[3].text, 'bar');
|
|
});
|
|
|
|
observeTest('ChangeFromBindToRepeat', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{a}}">'
|
|
'{{ length }}'
|
|
'</template>');
|
|
var template = div.nodes.first;
|
|
|
|
// Note: this test data is a little different from the JS version, because
|
|
// we allow binding to the "length" field of the Map in preference to
|
|
// binding keys.
|
|
var m = toObservable({
|
|
'a': [
|
|
[],
|
|
{ 'b': [1,2,3,4] },
|
|
// Note: this will use the Map "length" property, not the "length" key.
|
|
{'length': 42, 'c': 123}
|
|
]
|
|
});
|
|
recursivelySetTemplateModel(div, m);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes[1].text, '3');
|
|
|
|
nodeBind(template)
|
|
..unbind('bind')
|
|
..bind('repeat', m, 'a');
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 4);
|
|
expect(div.nodes[1].text, '0');
|
|
expect(div.nodes[2].text, '1');
|
|
expect(div.nodes[3].text, '2');
|
|
|
|
nodeBind(template).unbind('repeat');
|
|
nodeBind(template).bind('bind', m, 'a.1.b');
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(div.nodes.length, 2);
|
|
expect(div.nodes[1].text, '4');
|
|
});
|
|
|
|
observeTest('ChangeRefId', () {
|
|
var div = createTestHtml(
|
|
'<template id="a">a:{{ }}</template>'
|
|
'<template id="b">b:{{ }}</template>'
|
|
'<template repeat="{{}}">'
|
|
'<template ref="a" bind="{{}}"></template>'
|
|
'</template>');
|
|
var model = toObservable([]);
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 3);
|
|
|
|
document.getElementById('a').id = 'old-a';
|
|
document.getElementById('b').id = 'a';
|
|
|
|
model..add(1)..add(2);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.nodes.length, 7);
|
|
expect(div.nodes[4].text, 'b:1');
|
|
expect(div.nodes[6].text, 'b:2');
|
|
});
|
|
|
|
observeTest('Content', () {
|
|
var div = createTestHtml(
|
|
'<template><a></a></template>'
|
|
'<template><b></b></template>');
|
|
var templateA = div.nodes.first;
|
|
var templateB = div.nodes.last;
|
|
var contentA = templateBind(templateA).content;
|
|
var contentB = templateBind(templateB).content;
|
|
expect(contentA, isNotNull);
|
|
|
|
expect(templateA.ownerDocument, isNot(equals(contentA.ownerDocument)));
|
|
expect(templateB.ownerDocument, isNot(equals(contentB.ownerDocument)));
|
|
|
|
expect(templateB.ownerDocument, templateA.ownerDocument);
|
|
expect(contentB.ownerDocument, contentA.ownerDocument);
|
|
|
|
expect(templateA.ownerDocument.window, window);
|
|
expect(templateB.ownerDocument.window, window);
|
|
|
|
expect(contentA.ownerDocument.window, null);
|
|
expect(contentB.ownerDocument.window, null);
|
|
|
|
expect(contentA.nodes.last, contentA.nodes.first);
|
|
expect(contentA.nodes.first.tagName, 'A');
|
|
|
|
expect(contentB.nodes.last, contentB.nodes.first);
|
|
expect(contentB.nodes.first.tagName, 'B');
|
|
});
|
|
|
|
observeTest('NestedContent', () {
|
|
var div = createTestHtml(
|
|
'<template>'
|
|
'<template></template>'
|
|
'</template>');
|
|
var templateA = div.nodes.first;
|
|
var templateB = templateBind(templateA).content.nodes.first;
|
|
|
|
expect(templateB.ownerDocument, templateBind(templateA)
|
|
.content.ownerDocument);
|
|
expect(templateBind(templateB).content.ownerDocument,
|
|
templateBind(templateA).content.ownerDocument);
|
|
});
|
|
|
|
observeTest('BindShadowDOM', () {
|
|
if (ShadowRoot.supported) {
|
|
var root = createShadowTestHtml(
|
|
'<template bind="{{}}">Hi {{ name }}</template>');
|
|
var model = toObservable({'name': 'Leela'});
|
|
recursivelySetTemplateModel(root, model);
|
|
performMicrotaskCheckpoint();
|
|
expect(root.nodes[1].text, 'Hi Leela');
|
|
}
|
|
});
|
|
|
|
// Dart note: this test seems gone from JS. Keeping for posterity sake.
|
|
observeTest('BindShadowDOM createInstance', () {
|
|
if (ShadowRoot.supported) {
|
|
var model = toObservable({'name': 'Leela'});
|
|
var template = new Element.html('<template>Hi {{ name }}</template>');
|
|
var root = createShadowTestHtml('');
|
|
root.nodes.add(templateBind(template).createInstance(model));
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(root.text, 'Hi Leela');
|
|
|
|
model['name'] = 'Fry';
|
|
performMicrotaskCheckpoint();
|
|
expect(root.text, 'Hi Fry');
|
|
}
|
|
});
|
|
|
|
observeTest('BindShadowDOM Template Ref', () {
|
|
if (ShadowRoot.supported) {
|
|
var root = createShadowTestHtml(
|
|
'<template id=foo>Hi</template><template bind ref=foo></template>');
|
|
recursivelySetTemplateModel(root, toObservable({}));
|
|
performMicrotaskCheckpoint();
|
|
expect(root.nodes.length, 3);
|
|
}
|
|
});
|
|
|
|
// https://github.com/toolkitchen/mdv/issues/8
|
|
observeTest('UnbindingInNestedBind', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{outer}}" if="{{outer}}" syntax="testHelper">'
|
|
'<template bind="{{inner}}" if="{{inner}}">'
|
|
'{{ age }}'
|
|
'</template>'
|
|
'</template>');
|
|
|
|
var syntax = new UnbindingInNestedBindSyntax();
|
|
var model = toObservable({
|
|
'outer': {
|
|
'inner': {
|
|
'age': 42
|
|
}
|
|
}
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, model, syntax);
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(syntax.count, 1);
|
|
|
|
var inner = model['outer']['inner'];
|
|
model['outer'] = null;
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(syntax.count, 1);
|
|
|
|
model['outer'] = toObservable({'inner': {'age': 2}});
|
|
syntax.expectedAge = 2;
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(syntax.count, 2);
|
|
});
|
|
|
|
// https://github.com/toolkitchen/mdv/issues/8
|
|
observeTest('DontCreateInstancesForAbandonedIterators', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{}} {{}}">'
|
|
'<template bind="{{}}">Foo'
|
|
'</template>'
|
|
'</template>');
|
|
recursivelySetTemplateModel(div, null);
|
|
performMicrotaskCheckpoint();
|
|
});
|
|
|
|
observeTest('CreateInstance', () {
|
|
var div = createTestHtml(
|
|
'<template bind="{{a}}">'
|
|
'<template bind="{{b}}">'
|
|
'{{ foo }}:{{ replaceme }}'
|
|
'</template>'
|
|
'</template>');
|
|
var outer = templateBind(div.nodes.first);
|
|
var model = toObservable({'b': {'foo': 'bar'}});
|
|
|
|
var host = new DivElement();
|
|
var instance = outer.createInstance(model, new TestBindingSyntax());
|
|
expect(outer.content.nodes.first,
|
|
templateBind(instance.nodes.first).ref);
|
|
|
|
host.append(instance);
|
|
performMicrotaskCheckpoint();
|
|
expect(host.firstChild.nextNode.text, 'bar:replaced');
|
|
});
|
|
|
|
observeTest('Bootstrap', () {
|
|
var div = new DivElement();
|
|
div.innerHtml =
|
|
'<template>'
|
|
'<div></div>'
|
|
'<template>'
|
|
'Hello'
|
|
'</template>'
|
|
'</template>';
|
|
|
|
TemplateBindExtension.bootstrap(div);
|
|
var template = templateBind(div.nodes.first);
|
|
expect(template.content.nodes.length, 2);
|
|
var template2 = templateBind(template.content.nodes.first.nextNode);
|
|
expect(template2.content.nodes.length, 1);
|
|
expect(template2.content.nodes.first.text, 'Hello');
|
|
|
|
template = new Element.tag('template');
|
|
template.innerHtml =
|
|
'<template>'
|
|
'<div></div>'
|
|
'<template>'
|
|
'Hello'
|
|
'</template>'
|
|
'</template>';
|
|
|
|
TemplateBindExtension.bootstrap(template);
|
|
template2 = templateBind(templateBind(template).content.nodes.first);
|
|
expect(template2.content.nodes.length, 2);
|
|
var template3 = templateBind(template2.content.nodes.first.nextNode);
|
|
expect(template3.content.nodes.length, 1);
|
|
expect(template3.content.nodes.first.text, 'Hello');
|
|
});
|
|
|
|
observeTest('issue-285', () {
|
|
var div = createTestHtml(
|
|
'<template>'
|
|
'<template bind if="{{show}}">'
|
|
'<template id=del repeat="{{items}}">'
|
|
'{{}}'
|
|
'</template>'
|
|
'</template>'
|
|
'</template>');
|
|
|
|
var template = div.firstChild;
|
|
|
|
var model = toObservable({
|
|
'show': true,
|
|
'items': [1]
|
|
});
|
|
|
|
div.append(templateBind(template).createInstance(model,
|
|
new Issue285Syntax()));
|
|
|
|
performMicrotaskCheckpoint();
|
|
expect(template.nextNode.nextNode.nextNode.text, '2');
|
|
model['show'] = false;
|
|
performMicrotaskCheckpoint();
|
|
model['show'] = true;
|
|
performMicrotaskCheckpoint();
|
|
expect(template.nextNode.nextNode.nextNode.text, '2');
|
|
});
|
|
|
|
observeTest('issue-141', () {
|
|
var div = createTestHtml(
|
|
'<template bind>' +
|
|
'<div foo="{{foo1}} {{foo2}}" bar="{{bar}}"></div>' +
|
|
'</template>');
|
|
|
|
var model = toObservable({
|
|
'foo1': 'foo1Value',
|
|
'foo2': 'foo2Value',
|
|
'bar': 'barValue'
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
expect(div.lastChild.attributes['bar'], 'barValue');
|
|
});
|
|
}
|
|
|
|
compatTests() {
|
|
observeTest('underbar bindings', () {
|
|
var div = createTestHtml(
|
|
'<template bind>'
|
|
'<div _style="color: {{ color }};"></div>'
|
|
'<img _src="{{ url }}">'
|
|
'<a _href="{{ url2 }}">Link</a>'
|
|
'<input type="number" _value="{{ number }}">'
|
|
'</template>');
|
|
|
|
var model = toObservable({
|
|
'color': 'red',
|
|
'url': 'pic.jpg',
|
|
'url2': 'link.html',
|
|
'number': 4
|
|
});
|
|
|
|
recursivelySetTemplateModel(div, model);
|
|
performMicrotaskCheckpoint();
|
|
|
|
var subDiv = div.firstChild.nextNode;
|
|
expect(subDiv.attributes['style'], 'color: red;');
|
|
|
|
var img = subDiv.nextNode;
|
|
expect(img.attributes['src'], 'pic.jpg');
|
|
|
|
var a = img.nextNode;
|
|
expect(a.attributes['href'], 'link.html');
|
|
|
|
var input = a.nextNode;
|
|
expect(input.value, '4');
|
|
});
|
|
}
|
|
|
|
class Issue285Syntax extends BindingDelegate {
|
|
prepareInstanceModel(template) {
|
|
if (template.id == 'del') return (val) => val * 2;
|
|
}
|
|
}
|
|
|
|
class TestBindingSyntax extends BindingDelegate {
|
|
prepareBinding(String path, name, node) {
|
|
if (path.trim() == 'replaceme') {
|
|
return (x, y) => new ObservableBox('replaced');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class UnbindingInNestedBindSyntax extends BindingDelegate {
|
|
int expectedAge = 42;
|
|
int count = 0;
|
|
|
|
prepareBinding(path, name, node) {
|
|
if (name != 'text' || path != 'age') return null;
|
|
|
|
return (model, node) {
|
|
expect(model['age'], expectedAge);
|
|
count++;
|
|
return model;
|
|
};
|
|
}
|
|
}
|