some render tests

This commit is contained in:
David Greenspan
2013-07-24 17:55:29 -07:00
parent 46dcffc5db
commit 4935843374
7 changed files with 207 additions and 101 deletions

View File

@@ -1,3 +1,4 @@
var UI = UI2;
var ATTRIBUTE_NAME_REGEX = /^[^\s"'>/=/]+$/;
@@ -76,7 +77,8 @@ _extend(AttributeManager.prototype, {
var handlers = self.handlers;
component.autorun(function (c) {
if (component.stage !== Component.BUILT ||
if ((! component.isBuilt) ||
component.isDestroyed ||
! component.containsElement(element)) {
c.stop();
return;

View File

@@ -1,3 +1,13 @@
// A very basic operation like Underscore's `_.extend` that
// copies `src`'s own, enumerable properties onto `tgt` and
// returns `tgt`.
_extend = function (tgt, src) {
for (var k in src)
if (src.hasOwnProperty(k))
tgt[k] = src[k];
return tgt;
};
// @export UI2
var UI = UI2 = {
nextGuid: 2, // Component is 1!
@@ -14,78 +24,18 @@ var UI = UI2 = {
// create instances (and the hope is we can gloss over the
// difference in the docs).
Component: {
// If a Component has a `typeName` property set via `extend`,
// we make it use that name when printed in Chrome Dev Tools.
// If you then extend this Component and don't supply any
// new typeName, it should use the same typeName (or the
// most specific one in the case of an `extend` chain with
// `typeName` set at multiple points).
//
// To accomplish this, keeping performance in mind,
// any Component where `typeName` is explicitly set
// also has a function property `_constr` whose source-code
// name is `typeName`. `extend` creates this `_constr`
// function, which can then be used internally as a
// constructor to quickly create new instances that
// pretty-print correctly.
typeName: "Component",
_constr: function Component() {},
Component: (function (constr) {
// Make sure the "class name" that Chrome infers for
// UI.Component is "Component", and that
// `new UI.Component._constr` (which is what `extend`
// does) also produces objects whose inferred class
// name is "Component". Chrome's name inference rules
// are a little mysterious, but a function name in
// the source code (as in `function Component() {}`)
// seems to be reliable and high precedence.
return _extend(new constr, {_constr: constr});
})(function Component() {}),
_super: null,
guid: 1,
extend: function (props) {
// this function should never cause `props` to be
// mutated in case people want to reuse `props` objects
// in a mixin-like way.
if (this.isInited)
// Disallow extending inited Components so that
// inited Components don't inherit instance-specific
// properties from other inited Components, just
// default values.
throw new Error("Can't extend an inited Component");
// Any Component with a typeName of "Foo" (say) is given
// a `._constr` of the form `function Foo() {}`.
if (props && props.typeName)
this._constr =
Function("return function " +
sanitizeTypeName(props.typeName) +
"() {};")();
// We don't know where we're getting `_constr` from --
// it might be from some supertype -- just that it has
// the right function name. So set the `prototype`
// property each time we use it as a constructor.
this._constr.prototype = this;
var c = new this._constr;
if (props)
_extend(c, props);
// for efficient Component instantiations, we assign
// as few things as possible here.
c._super = this;
c.guid = UI.nextGuid++;
return c;
},
// `x.isa(Foo)` where `x` is a Component returns `true`
// if `x` is `Foo` or a Component that descends from
// (transitively extends) `Foo`.
isa: function (obj) {
var x = this;
while (x) {
if (x === obj)
return true;
x = x._super;
}
return false;
}
},
isComponent: function (obj) {
return obj && obj.isa === UI.Component.isa;
},
@@ -102,15 +52,86 @@ var UI = UI2 = {
}
};
// A very basic operation like Underscore's `_.extend` that
// copies `src`'s own, enumerable properties onto `tgt` and
// returns `tgt`.
_extend = function (tgt, src) {
for (var k in src)
if (src.hasOwnProperty(k))
tgt[k] = src[k];
return tgt;
};
_extend(UI.Component, {
// If a Component has a `typeName` property set via `extend`,
// we make it use that name when printed in Chrome Dev Tools.
// If you then extend this Component and don't supply any
// new typeName, it should use the same typeName (or the
// most specific one in the case of an `extend` chain with
// `typeName` set at multiple points).
//
// To accomplish this, keeping performance in mind,
// any Component where `typeName` is explicitly set
// also has a function property `_constr` whose source-code
// name is `typeName`. `extend` creates this `_constr`
// function, which can then be used internally as a
// constructor to quickly create new instances that
// pretty-print correctly.
typeName: "Component",
_constr: function Component() {},
_super: null,
guid: 1,
extend: function (props) {
// this function should never cause `props` to be
// mutated in case people want to reuse `props` objects
// in a mixin-like way.
if (this.isInited)
// Disallow extending inited Components so that
// inited Components don't inherit instance-specific
// properties from other inited Components, just
// default values.
throw new Error("Can't extend an inited Component");
var constr;
var constrMade = false;
// Any Component with a typeName of "Foo" (say) is given
// a `._constr` of the form `function Foo() {}`.
if (props && props.typeName) {
constr = Function("return function " +
sanitizeTypeName(props.typeName) +
"() {};")();
constrMade = true;
} else {
constr = this._constr;
}
// We don't know where we're getting `constr` from --
// it might be from some supertype -- just that it has
// the right function name. So set the `prototype`
// property each time we use it as a constructor.
constr.prototype = this;
var c = new constr;
if (constrMade)
c._constr = constr;
if (props)
_extend(c, props);
// for efficient Component instantiations, we assign
// as few things as possible here.
c._super = this;
c.guid = UI.nextGuid++;
return c;
},
// `x.isa(Foo)` where `x` is a Component returns `true`
// if `x` is `Foo` or a Component that descends from
// (transitively extends) `Foo`.
isa: function (obj) {
var x = this;
while (x) {
if (x === obj)
return true;
x = x._super;
}
return false;
}
});
callChainedCallback = function (comp, propName) {
if (comp._super)
@@ -532,7 +553,6 @@ _extend(UI.Component, {
_attach: function (parentNode, beforeNode) {
var self = this;
this._requireBuilt();
this._requireNotDestroyed();
if (! self.isInited)

View File

@@ -1,8 +1,5 @@
// All `<body>` tags in HTML files are compiled to extend
// Body. If you put helpers and events on Body, they all
// inherit them.
UI.Body = Component.extend({isRoot: true});
var UI = UI2;
var Component = UI.Component;
UI.Text = Component.extend({
typeName: 'Text',
@@ -11,8 +8,8 @@ UI.Text = Component.extend({
return String(x == null ? '' : x);
},
render: function (buf) {
var data = this.data();
buf(this._encodeEntities(this._stringify(data)));
var data = this.get();
buf.write(this._encodeEntities(this._stringify(data)));
}
});
@@ -22,11 +19,11 @@ UI.HTML = Component.extend({
return String(x == null ? '' : x);
},
render: function (buf) {
var data = this.data();
buf(this._stringify(data));
var data = this.get();
buf.write(this._stringify(data));
}
});
/*
UI.If = Component.extend({
typeName: 'If',
init: function () {
@@ -89,4 +86,5 @@ UI.Counter = Component.extend({
self.increment();
});
}
});
});
*/

14
packages/ui2/fields.js Normal file
View File

@@ -0,0 +1,14 @@
var UI = UI2;
_extend(UI.Component, {
get: function (id) {
// this is the (! id) case where `id` is `""` or absent.
// actually it should probably search up the parent tree too.
return (typeof this.data === 'function' ?
this.data() : this.data);
},
// convenient syntax
withData: function (data) {
return this.extend({data: data});
}
});

View File

@@ -8,7 +8,11 @@ Package.on_use(function (api) {
api.use('ejson');
api.use('ordered-dict');
api.add_files(['base.js']);
api.add_files(['base.js',
'attrs.js',
'render.js',
'fields.js',
'components.js']);
});
Package.on_test(function (api) {
@@ -17,6 +21,7 @@ Package.on_test(function (api) {
api.use(['test-helpers', 'underscore'], 'client');
api.add_files([
'base_tests.js'
'base_tests.js',
'render_tests.js'
], 'client');
});

View File

@@ -97,7 +97,7 @@ makeRenderBuffer = function (component, options) {
componentsToAttach[commentString] = arg;
} else if (arg.extend) {
// `{extend: componentOrFunction, props: object}`
if (UI.isComponent(arg.extend)) {
/* if (UI.isComponent(arg.extend)) {
// In `{extend: comp}` with no `props`, it's ok
// for `comp` to be already inited. This lets
// you write `{{> foo}}` to insert already-inited
@@ -133,8 +133,8 @@ makeRenderBuffer = function (component, options) {
var curChild = curType.create(arg.args);
handle(curChild);
} else {
throw new Error("Expected 'type' to be Component or function");
}
throw new Error("Expected 'extend' to be Component or function");
}*/
} else if (arg.attrs) {
// `{attrs: functionOrDictionary }`
// attrs object inserts zero or more `name="value"` items
@@ -175,7 +175,8 @@ makeRenderBuffer = function (component, options) {
}
};
var buf = function (/*args*/) {
var buf = {};
buf.write = function (/*args*/) {
for (var i = 0; i < arguments.length; i++)
handle(arguments[i]);
};
@@ -204,13 +205,13 @@ makeRenderBuffer = function (component, options) {
if (n === root.lastChild)
end = comp;
}
if (comp.stage === Component.INITIAL) {
if (! comp.isInited) {
component.add(comp);
} else if (comp.parent !== component) {
throw new Error("Component used in render must be a child " +
"(or addable as one)");
}
comp.attach(parent, n);
comp._attach(parent, n);
parent.removeChild(n);
delete componentsToAttach[n.nodeValue];
}

View File

@@ -0,0 +1,66 @@
var UI = UI2;
Tinytest.add("ui - render", function (test) {
var c, R;
c = UI.Component.extend({
render: function (buf) {
buf.write("asdf");
}
});
c.build();
test.equal($(c._offscreen).html(), "asdf");
c.destroy();
c = UI.Component.extend({
render: function (buf) {
buf.write("<div>asdf</div>");
}
});
c.build();
test.equal($(c._offscreen).html(), "<div>asdf</div>");
c.destroy();
R = ReactiveVar("blam");
c = UI.Component.extend({
render: function (buf) {
buf.write(
"foo",
UI.Text.withData(function () { return R.get(); }),
"bar");
}
});
c.build();
test.equal($(c._offscreen).html(), "fooblambar");
R.set("ki");
Deps.flush();
test.equal($(c._offscreen).html(), "fookibar");
c.destroy();
R = ReactiveVar("<hr>");
c = UI.Component.extend({
render: function (buf) {
buf.write(
"foo",
UI.HTML.withData(function () { return R.get(); }),
"bar");
}
});
c.build();
test.equal($(c._offscreen).html(), "foo<hr>bar");
R.set("<div>hi</div>");
Deps.flush();
test.equal($(c._offscreen).html(), "foo<div>hi</div>bar");
c.destroy();
});