Merge branch 'master' into devel

Brings changes done on release-0.5.3 into devel.
This commit is contained in:
David Glasser
2013-01-07 12:24:48 -08:00
28 changed files with 325 additions and 113 deletions

View File

@@ -1,12 +1,80 @@
## vNEXT
* `OAuth1Binding` improvements: #539
* `OAuth1Binding.get` and `OAuth1Binding.call` now return the full response
(including headers and statusCode), rather than just the data.
* Introduce `OAuth1Binding.post`.
* `OAuth1Binding.get`, `OAuth1Binding.call` and `OAuth1Binding.post` now take
a `params` argument. This facilitates making calls to the Twitter API.
## v0.5.3
* Add `--settings` argument to `meteor deploy` and `meteor run`. This
allows you to specify deployment-specific information made available
to server code in the variable `Meteor.settings`.
* Support unlimited open tabs in a single browser. Work around the
browser per-hostname connection limit by using randomized hostnames
for deployed apps. #131
* minimongo improvements:
* Allow observing cursors with `skip` or `limit`. #528
* Allow sorting on `dotted.sub.keys`. #533
* Allow querying specific array elements (`foo.1.bar`).
* `$and`, `$or`, and `$nor` no longer accept empty arrays (for consistency
with Mongo)
* Re-rendering a template with Spark no longer reverts changes made by
users to a `preserve`d form element. Instead, the newly rendered value
is only applied if it is different from the previously rendered value.
Additionally, <INPUT> elements with type other than TEXT can now have
reactive values (eg, the labels on submit buttons can now be
reactive). #510 #514 #523 #537 #558
* Support JavaScript RegExp objects in selectors in Collection write
methods on the client, eg `myCollection.remove({foo: /bar/})`. #346
* `meteor` command-line improvements:
* Improve error message when mongod fails to start.
* The `NODE_OPTIONS` environment variable can be used to pass command-line
flags to node (eg, `--debug` or `--debug-brk` to enable the debugger).
* Die with error if an app name is mistakenly passed to `meteor reset`.
* Add support for "offline" access tokens with Google login. #464 #525
* Don't remove `serviceData` fields from previous logins when logging in
with an external service.
* Improve `OAuth1Binding` to allow making authenticated API calls to
OAuth1 providers (eg Twitter). #539
* New login providers automatically work with `{{loginButtons}}` without
needing to edit the `accounts-ui-unstyled` package. #572
* Use `Content-Type: application/json` by default when sending JSON data
with `Meteor.http`.
* Improvements to `jsparse`: hex literals, keywords as property names, ES5 line
continuations, trailing commas in object literals, line numbers in error
messages, decimal literals starting with `.`, regex character classes with
slashes.
* Spark improvements:
* Improve rendering of <SELECT> elements on IE. #496
* Don't lose nested data contexts in IE9/10 after two seconds. #458
* Don't print a stack trace if DOM nodes are manually removed
from the document without calling `Spark.finalize`. #392
* Always use the `autoReconnect` flag when connecting to Mongo. #425
* Fix server-side `observe` with no `added` callback. #589
* Fix re-sending method calls on reconnect. #538
* Remove deprecated `/sockjs` URL support from `Meteor.connect`.
* Avoid losing a few bits of randomness in UUID v4 creation. #519
* Update clean-css package from 0.8.2 to 0.8.3, fixing minification of `0%`
values in `hsl` colors. #515
Patches contributed by GitHub users Ed-von-Schleck, egtann, jwulf, lvbreda,
martin-naumann, meawoppl, nwmartin, timhaines, and zealoushacker.
## v0.5.2

View File

@@ -6,9 +6,13 @@
cd `dirname $0`
METEOR=`pwd`/../meteor
if [ -z "$NODE" ]; then
NODE=`pwd`/node.sh
fi
#If this ever takes more options, use getopt
if [ "$1" == "--global" ]; then
METEOR=/usr/local/bin/meteor
METEOR=meteor
fi
DIR=`mktemp -d -t meteor-cli-test-XXXXXXXX`
@@ -77,7 +81,8 @@ echo "... run"
MONGOMARK='--bind_ip 127.0.0.1 --smallfiles --port 9102'
# kill any old test meteor
# there is probably a better way to do this, but it is at least portable across macos and linux
ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill
# (the || true is needed on linux, whose xargs will invoke kill even with no args)
ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill || true
! $METEOR mongo > /dev/null 2>&1
$METEOR reset > /dev/null 2>&1
@@ -89,7 +94,7 @@ PORT=9100
$METEOR -p $PORT > /dev/null 2>&1 &
METEOR_PID=$!
sleep 1 # XXX XXX lame
sleep 2 # XXX XXX lame
test -d .meteor/local/db
ps ax | grep -e "$MONGOMARK" | grep -v grep > /dev/null
@@ -111,25 +116,31 @@ echo "... rerun"
$METEOR -p $PORT > /dev/null 2>&1 &
METEOR_PID=$!
sleep 1 # XXX XXX lame
sleep 2 # XXX XXX lame
ps ax | grep -e "$MONGOMARK" | grep -v grep > /dev/null
curl -s "http://localhost:$PORT" > /dev/null
kill $METEOR_PID
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # need to make sure these kills take effect
echo "... mongo message"
nc -l localhost $(($PORT + 2)) &
NC_PID=$!
# Run a server on the same port as mongod, so that mongod fails to start up. Rig
# it so that a single connection will cause it to exit.
$NODE -e 'require("net").createServer(function(){process.exit(0)}).listen('$PORT'+2, "127.0.0.1")' &
sleep 1
$METEOR -p $PORT > error.txt || true
grep 'port was closed' error.txt > /dev/null
kill -9 $NC_PID > /dev/null
# Kill the server by connecting to it.
$NODE -e 'require("net").connect({host:"127.0.0.1",port:'$PORT'+2},function(){process.exit(0);})'
echo "... settings"

View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Requires s3cmd to be installed and an appropriate ~/.s3cfg.
# Usage:
# admin/copy-release-from-jenkins.sh [--prod] BUILDNUMBER
# where BUILDNUMBER is the small integer Jenkins build number.
set -e
set -u
cd `dirname $0`
TARGET="s3://com.meteor.static/test/"
TEST=no
if [ $# -ge 1 -a $1 = '--prod' ]; then
shift
TARGET="s3://com.meteor.static/"
else
TEST=yes
fi
if [ $# -ne 1 ]; then
echo "usage: $0 [--prod] jenkins-build-number" 1>&2
exit 1
fi
DIRNAME=$(s3cmd ls s3://com.meteor.jenkins/ | perl -nle 'print $1 if m!/(release-.+--'$1'--.+)/!')
if [ -z "$DIRNAME" ]; then
echo "build not found" 1>&2
exit 1
fi
echo Found build $DIRNAME
# Check to make sure the proper number of each kind of file is there.
s3cmd ls s3://com.meteor.jenkins/$DIRNAME/ | \
perl -nle '++$RPM if /\.rpm/; ++$DEB if /\.deb/; ++$TAR if /\.tar\.gz/; ++$DIR if /DIR/; END { exit !($RPM == 2 && $DEB == 2 && $TAR == 3 && $DIR == 1) }'
echo Copying to $TARGET
s3cmd -P cp -r s3://com.meteor.jenkins/$DIRNAME/ $TARGET
if [ $TEST = 'yes' ]; then
echo Uploading modified install-s3.sh and manifest.json
OUTDIR=$(mktemp -dt meteor-crfj)
perl -pe 's!https://d3sqy0vbqsdhku.cloudfront.net!https://s3.amazonaws.com/com.meteor.static/test!g' install-s3.sh >$OUTDIR/install-s3.sh
perl -pe 's!https://d3sqy0vbqsdhku.cloudfront.net!https://s3.amazonaws.com/com.meteor.static/test!g' manifest.json >$OUTDIR/manifest.json
cd $OUTDIR
s3cmd -P put install-s3.sh s3://com.meteor.static/test/update/
s3cmd -P put manifest.json s3://com.meteor.static/test/update/
fi

View File

@@ -1,4 +1,4 @@
meteor (0.5.2-1) unstable; urgency=low
meteor (0.5.3-1) unstable; urgency=low
* Automated debian build.

View File

@@ -5,7 +5,7 @@
## example.
URLBASE="https://d3sqy0vbqsdhku.cloudfront.net"
VERSION="0.5.2"
VERSION="0.5.3"
PKGVERSION="${VERSION}-1"
UNAME=`uname`

View File

@@ -1,6 +1,6 @@
{
"version": "0.5.2",
"deb_version": "0.5.2-1",
"rpm_version": "0.5.2-1",
"version": "0.5.3",
"deb_version": "0.5.3-1",
"rpm_version": "0.5.3-1",
"urlbase": "https://d3sqy0vbqsdhku.cloudfront.net"
}

View File

@@ -5,7 +5,7 @@
Summary: Meteor platform and JavaScript application server
Vendor: Meteor
Name: meteor
Version: 0.5.2
Version: 0.5.3
Release: 1
License: MIT
Group: Networking/WWW

View File

@@ -20,6 +20,7 @@ if [ "$EMACS" == t ]; then
fi
"$TOPDIR/dev_bundle/bin/node" "$@"
EXITSTATUS=$?
# Node sets stdin to non-blocking, which causes Emacs shell to die after it
# exits. Work around this by setting stdin to blocking again.
@@ -27,3 +28,5 @@ if [ "$EMACS" == t ]; then
perl -MFcntl=F_GETFL,F_SETFL,O_NONBLOCK -e \
'fcntl(STDIN, F_SETFL, ~O_NONBLOCK & fcntl(STDIN, F_GETFL, 0))'
fi
exit $EXITSTATUS

View File

@@ -1,6 +1,6 @@
var fs = require("fs");
var path = require("path");
var spawn = require('child_process').spawn;
var child_process = require('child_process');
var files = require(path.join(__dirname, '..', 'lib', 'files.js'));
@@ -15,37 +15,34 @@ var _ = require('underscore');
*/
var find_mongo_pids = function (app_dir, port, callback) {
// 'ps ax' should be standard across all MacOS and Linux.
var proc = spawn('ps', ['ax']);
var data = '';
proc.stdout.on('data', function (d) {
data += d;
});
child_process.exec('ps ax',
function (error, stdout, stderr) {
if (error) {
callback({reason: error});
} else if (stderr) {
callback({reason: 'ps produced stderr ' + stderr});
} else {
var pids = [];
proc.on('exit', function (code, signal) {
if (code === 0) {
var pids = [];
_.each(stdout.split('\n'), function (ps_line) {
// matches mongos we start.
var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db\s*$/);
if (m && m.length === 4) {
var found_pid = parseInt(m[1]);
var found_port = parseInt(m[2]);
var found_path = m[3];
_.each(data.split('\n'), function (ps_line) {
// matches mongos we start.
var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db\s*$/);
if (m && m.length === 4) {
var found_pid = parseInt(m[1]);
var found_port = parseInt(m[2]);
var found_path = m[3];
if ( (!port || port === found_port) &&
(!app_dir || app_dir === found_path)) {
pids.push({
pid: found_pid, port: found_port, app_dir: found_path});
if ( (!port || port === found_port) &&
(!app_dir || app_dir === found_path)) {
pids.push({
pid: found_pid, port: found_port, app_dir: found_path});
}
}
}
});
});
callback(null, pids);
} else {
callback({reason: 'ps exit code ' + code});
}
});
callback(null, pids);
}
});
};
@@ -162,7 +159,7 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
return;
}
var proc = spawn(mongod_path, [
var proc = child_process.spawn(mongod_path, [
'--bind_ip', '127.0.0.1',
'--smallfiles',
'--port', port,

View File

@@ -1,4 +1,8 @@
exports.CURRENT_VERSION = "0.5.2";
// During automated QA of the updater, modify this file to set testingUpdater to
// true. This will make it act as if it is at version 0.1.0 and use test URLs
// for update checks.
var testingUpdater = false;
exports.CURRENT_VERSION = testingUpdater ? "0.1.0" : "0.5.3";
var fs = require("fs");
var http = require("http");
@@ -8,11 +12,10 @@ var semver = require("semver");
var files = require(path.join(__dirname, 'files.js'));
var manifest_options = {
/* uncomment for testing
var manifest_options = testingUpdater ? {
host: 's3.amazonaws.com',
path: '/com.meteor.static/test/update/manifest.json'
*/
} : {
host: 'update.meteor.com',
path: '/manifest.json'
};

View File

@@ -86,26 +86,6 @@ Fiber(function () {
process.exit(1);
};
var getSettings = function (filename) {
var str;
try {
str = fs.readFileSync(filename, "utf8");
} catch (e) {
throw new Error("Could not find settings file " + filename);
}
if (str.length > 0x10000) {
throw new Error("Settings file must be less than 64 KB long");
}
// Ensure that the string is parseable in JSON, but there's
// no reason to use the object value of it yet.
if (str.match(/\S/)) {
JSON.parse(str);
return str;
} else {
return "";
}
};
// XXX when the pass unexpected argument or unrecognized flags, print
// an error and fail out
@@ -121,6 +101,10 @@ Fiber(function () {
.boolean('production')
.describe('production', 'Run in production mode. Minify and bundle CSS and JS files.')
.describe('settings', 'Set optional data for Meteor.settings on the server')
// With --once, meteor does not re-run the project if it crashes and
// does not monitor for file changes. Intentionally undocumented:
// intended for automated testing (eg, cli-test.sh), not end-user
// use.
.boolean('once')
.usage(
"Usage: meteor run [options]\n" +
@@ -143,14 +127,11 @@ Fiber(function () {
process.stdout.write(opt.help());
process.exit(1);
}
if (new_argv.settings) {
settings = getSettings(new_argv.settings);
}
var app_dir = path.resolve(require_project("run", true)); // app or package
var bundle_opts = { no_minify: !new_argv.production, symlink_dev_bundle: true };
runner.run(app_dir, bundle_opts, new_argv.port, new_argv.once, settings);
runner.run(app_dir, bundle_opts, new_argv.port, new_argv.once, new_argv.settings);
}
});
@@ -581,7 +562,7 @@ Fiber(function () {
} else {
var settings = undefined;
if (new_argv.settings)
settings = getSettings(new_argv.settings);
settings = runner.getSettings(new_argv.settings);
// accept packages iff we're deploying tests
var project_dir = path.resolve(require_project("bundle", new_argv.tests));
deploy.deploy_app(new_argv._[1], project_dir, new_argv.debug,

View File

@@ -2,7 +2,7 @@ try {
// XXX can't get this from updater.js because in 0.3.7 and before the
// updater didn't have the right NODE_PATH set. At some point we can
// remove this and just use updater.CURRENT_VERSION.
var VERSION = "0.5.2";
var VERSION = "0.5.3";
var fs = require('fs');
var path = require('path');

View File

@@ -215,7 +215,7 @@ var log_to_clients = function (msg) {
// [nodeOptions]
// [runOnce]: boolean; default false; if true doesn't ever try to restart, and
// forwards server exit code.
// [settings]
// [settingsFile]
var start_server = function (options) {
// environment
@@ -234,8 +234,12 @@ var start_server = function (options) {
env.PORT = options.innerPort;
env.MONGO_URL = options.mongoURL;
env.ROOT_URL = env.ROOT_URL || ('http://localhost:' + options.outerPort);
if (options.settings)
env.METEOR_SETTINGS = options.settings;
if (options.settingsFile) {
// Re-read the settings file each time we call start_server.
var settings = exports.getSettings(options.settingsFile);
if (settings)
env.METEOR_SETTINGS = settings;
}
var nodeOptions = _.clone(options.nodeOptions);
nodeOptions.push(path.join(options.bundlePath, 'main.js'));
@@ -308,8 +312,11 @@ var kill_server = function (handle) {
////////// Watching dependencies //////////
// deps is the data from dependencies.json in the bundle
// app_dir is the root of the app
// relativeFiles are any other files to watch, relative to the current
// directory (eg, the --settings file)
// on_change is only fired once
var DependencyWatcher = function (deps, app_dir, on_change) {
var DependencyWatcher = function (deps, app_dir, relativeFiles, on_change) {
var self = this;
self.app_dir = app_dir;
@@ -342,6 +349,10 @@ var DependencyWatcher = function (deps, app_dir, on_change) {
});
};
_.each(relativeFiles, function (file) {
self.specific_files[file] = true;
});
// Things that are never interesting.
self.exclude_patterns = _.map((deps.exclude || []), function (pattern) {
return new RegExp(pattern);
@@ -507,12 +518,34 @@ var start_update_checks = function () {
///////////////////////////////////////////////////////////////////////////////
// Also used by "meteor deploy" in meteor.js.
exports.getSettings = function (filename) {
var str;
try {
str = fs.readFileSync(filename, "utf8");
} catch (e) {
throw new Error("Could not find settings file " + filename);
}
if (str.length > 0x10000) {
throw new Error("Settings file must be less than 64 KB long");
}
// Ensure that the string is parseable in JSON, but there's
// no reason to use the object value of it yet.
if (str.match(/\S/)) {
JSON.parse(str);
return str;
} else {
return "";
}
};
// XXX leave a pidfile and check if we are already running
// This function never returns and will call process.exit() if it
// can't continue. If you change this, remember to call
// watcher.destroy() as appropriate.
exports.run = function (app_dir, bundle_opts, port, once, settings) {
exports.run = function (app_dir, bundle_opts, port, once, settingsFile) {
var outer_port = port || 3000;
var inner_port = outer_port + 1;
var mongo_port = outer_port + 2;
@@ -553,7 +586,13 @@ exports.run = function (app_dir, bundle_opts, port, once, settings) {
if (watcher)
watcher.destroy();
watcher = new DependencyWatcher(deps_info, app_dir, function () {
var relativeFiles;
if (settingsFile) {
relativeFiles = [settingsFile];
}
watcher = new DependencyWatcher(deps_info, app_dir, relativeFiles,
function () {
if (Status.crashing)
log_to_clients({'system': "=> Modified -- restarting."});
Status.reset();
@@ -634,7 +673,7 @@ exports.run = function (app_dir, bundle_opts, port, once, settings) {
},
nodeOptions: getNodeOptionsFromEnvironment(),
runOnce: once,
settings: settings
settingsFile: settingsFile
});

View File

@@ -528,11 +528,9 @@ In this release, Minimongo has some limitations:
* `$elemMatch` is not supported in selectors.
* `$pull` in modifiers can only accept certain kinds
of selectors.
* In selectors, dot notation and ordinal indexing may not work correctly.
* In selectors, dot notation may not work correctly.
* `$` to denote the matched array position is not
supported in modifier.
* Sort does not support subkeys (you can sort on `a`,
but not `a.b`).
* `findAndModify`, upsert, aggregate functions, and
map/reduce aren't supported.
* The supported types are String, Number, Boolean, Array,

View File

@@ -11,7 +11,7 @@
</div>
<div id="main">
<div id="top"></div>
<h1 class="main-headline">Meteor 0.5.2</h1>
<h1 class="main-headline">Meteor 0.5.3</h1>
{{> introduction }}
{{> concepts }}
{{> api }}

View File

@@ -1,4 +1,4 @@
METEOR_VERSION = "0.5.2";
METEOR_VERSION = "0.5.3";
Meteor.startup(function () {
// XXX this is broken by the new multi-page layout. Also, it was

View File

@@ -7,7 +7,7 @@
</body>
<template name="radio">
<span class="radio"><input id="{{key}}:{{value}}" {{maybeChecked}} type="radio" name="{{key}}" value="{{value}}" />{{! no whitespace}}<label for="{{key}}:{{value}}">{{label}}</label></span>
<span class="radio"><input id="{{key}}:{{value}}" {{{maybeChecked}}} type="radio" name="{{key}}" value="{{value}}" />{{! no whitespace}}<label for="{{key}}:{{value}}">{{label}}</label></span>
</template>
<template name="button">

View File

@@ -4,12 +4,12 @@ Meteor.http = Meteor.http || {};
(function() {
Meteor.http._encodeParams = function(params) {
self = this;
var buf = [];
_.each(params, function(value, key) {
if (buf.length)
buf.push('&');
buf.push(self._encodeString(key), '=', self._encodeString(value));
buf.push(Meteor.http._encodeString(key), '=',
Meteor.http._encodeString(value));
});
return buf.join('').replace(/%20/g, '+');
};

View File

@@ -222,7 +222,8 @@ testAsyncMulti("httpcall - methods", [
test.equal(result.statusCode, 200);
var data = result.data;
test.equal(data.body, {greeting: "Hello World!"});
test.equal(data.headers['content-type'], 'application/json');
// nb: some browsers include a charset here too.
test.matches(data.headers['content-type'], /^application\/json\b/);
}));
Meteor.http.call(
@@ -235,7 +236,8 @@ testAsyncMulti("httpcall - methods", [
test.equal(result.statusCode, 200);
var data = result.data;
test.equal(data.body, {greeting: "Hello World!"});
test.equal(data.headers['content-type'], 'text/stupid');
// nb: some browsers include a charset here too.
test.matches(data.headers['content-type'], /^text\/stupid\b/);
}));
}
]);

View File

@@ -798,6 +798,7 @@ Tinytest.add("minimongo - selector_compiler", function (test) {
match({"dogs.0.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]});
match({"dogs.1.name": "Rex"}, {dogs: [{name: "Fido"}, {name: "Rex"}]});
nomatch({"dogs.1.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]});
match({"room.1b": "bla"}, {room: {"1b": "bla"}});
// XXX still needs tests:
// - $elemMatch

View File

@@ -398,7 +398,7 @@ LocalCollection._exprForKeypathPredicate = function (keypath, value, literals) {
while (keyparts.length) {
var part = keyparts.pop();
var thisPartIsNumber = false;
if (/^\d+/.test(part)) {
if (/^\d+$/.test(part)) {
part = +part;
thisPartIsNumber = true;
}

View File

@@ -715,10 +715,12 @@ _.extend(LiveResultsSet.prototype, {
--self._addHandleTasksScheduledButNotPerformed;
// Send initial adds.
_.each(self._results, function (doc, i) {
handle._added(LocalCollection._deepcopy(doc),
self._ordered ? i : undefined);
});
if (handle._added) {
_.each(self._results, function (doc, i) {
handle._added(LocalCollection._deepcopy(doc),
self._ordered ? i : undefined);
});
}
});
},

View File

@@ -421,16 +421,19 @@ if (Meteor.isServer) {
var run = test.runId();
var coll = new Meteor.Collection("cursorDedup-"+run);
var observer = function () {
var observer = function (noAdded) {
var output = [];
var handle = coll.find({foo: 22}).observe({
added: function (doc) {
output.push({added: doc._id});
},
var callbacks = {
changed: function (newDoc) {
output.push({changed: newDoc._id});
}
});
};
if (!noAdded) {
callbacks.added = function (doc) {
output.push({added: doc._id});
};
}
var handle = coll.find({foo: 22}).observe(callbacks);
return {output: output, handle: handle};
};
@@ -510,7 +513,13 @@ if (Meteor.isServer) {
test.length(o2.output, 0);
// White-box: Different LiveResultsSet.
test.isTrue(liveResultsSet !== o3.handle._liveResultsSet);
// Start another handle with no added callback. Regression test for #589.
var o4 = observer(true);
o3.handle.stop();
o4.handle.stop();
onComplete();
});
}

View File

@@ -374,10 +374,15 @@ Spark._Patcher._copyAttributes = function(tgt, src) {
tgt._sparkOriginalRenderedChecked[0];
var srcOriginalChecked = !!src._sparkOriginalRenderedChecked &&
src._sparkOriginalRenderedChecked[0];
// For radio buttons, we previously saved the checkedness in an expando
// property before doing some DOM operations that could wipe it out. For
// checkboxes, we can just use the checked property directly.
var tgtCurrentChecked = tgt._currentChecked ?
tgt._currentChecked[0] : tgt.checked;
if (tgtOriginalChecked === srcOriginalChecked) {
finalChecked = !!tgt.checked;
finalChecked = tgtCurrentChecked;
} else {
finalChecked = !!srcOriginalChecked;
finalChecked = srcOriginalChecked;
tgt._sparkOriginalRenderedChecked = [finalChecked];
}
}

View File

@@ -132,7 +132,9 @@ Tinytest.add("spark - patch - copyAttributes", function(test) {
buf.push('<', tagName);
_.each(kv, function(v,k) {
allAttrNames[k] = true;
buf.push(' ', k, '="', v, '"');
buf.push(' ', k);
if (v !== 'NO_VALUE')
buf.push('="', v, '"');
});
buf.push('></', tagName, '>');
var nodeHtml = buf.join('');
@@ -160,18 +162,20 @@ Tinytest.add("spark - patch - copyAttributes", function(test) {
check: function() {
_.each(lastAttrs, function(v,k) {
var actualAttr;
var expectedAttr = v || "";
if (k === "style") {
actualAttr = node.style.cssText;
} else if (k === "class") {
actualAttr = node.className;
} else if (k === "checked") {
actualAttr = String(node.getAttribute(k) || "");
if (expectedAttr === "NO_VALUE")
expectedAttr = "checked";
if (actualAttr === "true")
actualAttr = "checked"; // save IE's butt
} else {
actualAttr = String(node.getAttribute(k) || "");
}
var expectedAttr = v || "";
test.equal(actualAttr, expectedAttr, k);
});
},
@@ -228,6 +232,12 @@ Tinytest.add("spark - patch - copyAttributes", function(test) {
c.copy({type:'checkbox', name:'foo', checked:'checked'});
c.check();
test.equal(c.node().checked, true);
c.copy({type:'checkbox', name:'foo'});
c.check();
test.equal(c.node().checked, false);
c.copy({type:'checkbox', name:'foo', checked:'NO_VALUE'});
c.check();
test.equal(c.node().checked, true);
c.copy({type:'checkbox', name:'bar'});
test.expect_fail(); // changing "name" on a form control won't take in IE

View File

@@ -318,7 +318,7 @@ _.extend(Spark._Renderer.prototype, {
});
_.each(DomUtils.findAll(ret, 'input[type=checkbox], input[type=radio]'),
function (node) {
node._sparkOriginalRenderedChecked = [node.checked];
node._sparkOriginalRenderedChecked = [!!node.checked];
});
return ret;
@@ -583,6 +583,18 @@ Spark.renderToRange = function (range, htmlFunc) {
notes.originalRange = landmarkRange;
});
// Once we render the new fragment, as soon as it is placed into the DOM (even
// temporarily), if any radio buttons in the new framgent are checked, any
// radio buttons with the same name in the entire document will be unchecked
// (since only one radio button of a given name can be checked at a time). So
// we save the current checked value of all radio buttons in an expando.
var radios = DomUtils.findAllClipped(
range.containerNode(), 'input[type=radio]',
range.firstNode(), range.lastNode());
_.each(radios, function (node) {
node._currentChecked = [!!node.checked];
});
var frag = renderer.materialize(htmlFunc);
DomUtils.wrapFragmentForContainer(frag, range.containerNode());

View File

@@ -2640,8 +2640,12 @@ testAsyncMulti(
Tinytest.add("spark - controls - radio", function(test) {
var R = ReactiveVar("");
var R2 = ReactiveVar("");
var change_buf = [];
var div = OnscreenDiv(renderWithPreservation(function() {
// Re-render when R2 is changed, even though it doesn't affect HTML.
R2.get();
var buf = [];
buf.push("Band: ");
_.each(["AM", "FM", "XM"], function(band) {
@@ -2688,6 +2692,12 @@ Tinytest.add("spark - controls - radio", function(test) {
test.equal(_.pluck(btns, 'checked'), [true, false, false]);
test.equal(div.text(), "Band: AM");
R2.set("change");
Meteor.flush();
test.length(change_buf, 0);
test.equal(_.pluck(btns, 'checked'), [true, false, false]);
test.equal(div.text(), "Band: AM");
clickElement(btns[1]);
test.equal(change_buf, ['FM']);
change_buf.length = 0;

View File

@@ -137,6 +137,14 @@ _.extend(TestCaseResults.prototype, {
this.fail({type: "instanceOf"}); // XXX what other data?
},
matches: function (actual, regexp, message) {
if (regexp.test(actual))
this.ok();
else
this.fail({type: "matches", message: message,
actual: actual, regexp: regexp.toString()});
},
// XXX nodejs assert.throws can take an expected error, as a class,
// regular expression, or predicate function..
throws: function (f) {