mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into bundle-android
Conflicts: tools/utils.js
This commit is contained in:
2
.mailmap
2
.mailmap
@@ -27,6 +27,7 @@ GITHUB: codeinthehole <david.winterbottom@gmail.com>
|
||||
GITHUB: dandv <ddascalescu+github@gmail.com>
|
||||
GITHUB: davegonzalez <gonzalez.dalex@gmail.com>
|
||||
GITHUB: ducdigital <duc@ducdigital.com>
|
||||
GITHUB: duckspeaker <gallo.j@gmail.com>
|
||||
GITHUB: emgee3 <hello@gravitronic.com>
|
||||
GITHUB: felixrabe <felix@rabe.io>
|
||||
GITHUB: FredericoC <frederico.carvalho@3stack.com.au>
|
||||
@@ -75,3 +76,4 @@ METEOR: sixolet <naomi@meteor.com>
|
||||
METEOR: Slava <slava@meteor.com>
|
||||
METEOR: stubailo <sashko@mit.edu>
|
||||
METEOR: ekatek <ekate@meteor.com>
|
||||
METEOR: mariapacana <maria.pacana@gmail.com>
|
||||
|
||||
78
History.md
78
History.md
@@ -1,4 +1,4 @@
|
||||
## v.NEXT.NEXT
|
||||
## v.NEXT
|
||||
|
||||
* The `appcache` package now defaults to functioning on all browsers that
|
||||
support the AppCache API, rather than a whitelist of browsers. You can still
|
||||
@@ -6,16 +6,90 @@
|
||||
change is that `appcache` is now enabled by default on Firefox, because
|
||||
Firefox no longer makes a confusing popup. #2241
|
||||
|
||||
* When a call to `match` fails in a method or subscription, log the
|
||||
failure on the server. (This matches the behavior described in our docs)
|
||||
|
||||
|
||||
## v.NEXT
|
||||
## v0.8.3
|
||||
|
||||
#### Blaze
|
||||
|
||||
* Refactor Blaze to simplify internals while preserving the public
|
||||
API. `UI.Component` has been replaced with `Blaze.View.`
|
||||
|
||||
* Fix performance issues and memory leaks concerning event handlers.
|
||||
|
||||
* Add `UI.remove`, which removes a template after `UI.render`/`UI.insert`.
|
||||
|
||||
* Add `this.autorun` to the template instance, which is like `Deps.autorun`
|
||||
but is automatically stopped when the template is destroyed.
|
||||
|
||||
* Create `<a>` tags as SVG elements when they have `xlink:href`
|
||||
attributes. (Previously, `<a>` tags inside SVGs were never created as
|
||||
SVG elements.) #2178
|
||||
|
||||
* Throw an error in `{{foo bar}}` if `foo` is missing or not a function.
|
||||
|
||||
* Cursors returned from template helpers for #each should implement
|
||||
the `observeChanges` method and don't have to be Minimongo cursors
|
||||
(allowing new custom data stores for Blaze like Miniredis).
|
||||
|
||||
* Remove warnings when {{#each}} iterates over a list of strings,
|
||||
numbers, or other items that contains duplicates. #1980
|
||||
|
||||
#### Meteor Accounts
|
||||
|
||||
* Fix regression in 0.8.2 where an exception would be thrown if
|
||||
`Meteor.loginWithPassword` didn't have a callback. Callbacks to
|
||||
`Meteor.loginWithPassword` are now optional again. #2255
|
||||
|
||||
* Fix OAuth popup flow in mobile apps that don't support
|
||||
`window.opener`. #2302
|
||||
|
||||
* Fix "Email already exists" error with MongoDB 2.6. #2238
|
||||
|
||||
|
||||
#### mongo-livedata and minimongo
|
||||
|
||||
* Fix performance issue where a large batch of oplog updates could block
|
||||
the node event loop for long periods. #2299.
|
||||
|
||||
* Fix oplog bug resulting in error message "Buffer inexplicably empty". #2274
|
||||
|
||||
* Fix regression from 0.8.2 that caused collections to appear empty in
|
||||
reactive `findOne()` or `fetch` queries that run before a mutator
|
||||
returns. #2275
|
||||
|
||||
|
||||
#### Miscellaneous
|
||||
|
||||
* Stop including code by default that automatically refreshes the page
|
||||
if JavaScript and CSS don't load correctly. While this code is useful
|
||||
in some multi-server deployments, it can cause infinite refresh loops
|
||||
if there are errors on the page. Add the `reload-safetybelt` package
|
||||
to your app if you want to include this code.
|
||||
|
||||
* On the server, `Meteor.startup(c)` now calls `c` immediately if the
|
||||
server has already started up, matching the client behavior. #2239
|
||||
|
||||
* Add support for server-side source maps when debugging with
|
||||
`node-inspector`.
|
||||
|
||||
* Add `WebAppInternals.addStaticJs()` for adding static JavaScript code
|
||||
to be served in the app, inline if allowed by `browser-policy`.
|
||||
|
||||
* Make the `tinytest/run` method return immediately, so that `wait`
|
||||
method calls from client tests don't block on server tests completing.
|
||||
|
||||
* Log errors from method invocations on the client if there is no
|
||||
callback provided.
|
||||
|
||||
* Upgraded dependencies:
|
||||
- node: 0.10.29 (from 0.10.28)
|
||||
- less: 1.7.1 (from 1.6.1)
|
||||
|
||||
Patches contributed by GitHub users Cangit, cmather, duckspeaker, zol.
|
||||
|
||||
|
||||
## v0.8.2
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -2189,6 +2189,12 @@ This property provides access to the data context at the top level of
|
||||
the template. It is updated each time the template is re-rendered.
|
||||
Access is read-only and non-reactive.
|
||||
|
||||
{{> api_box template_autorun}}
|
||||
|
||||
You can use `this.autorun` from a [`created`](#template_created) or
|
||||
[`rendered`](#template_rendered) callback to reactively update the DOM
|
||||
or the template instance. The Computation is automatically stopped
|
||||
when the template is destroyed.
|
||||
|
||||
<h2 id="ui"><span>Template utilities</span></h2>
|
||||
|
||||
@@ -2205,6 +2211,15 @@ any part of the DOM for finer control than just using template inclusions.
|
||||
You can define helpers and event maps on `UI.body` just like on any
|
||||
`Template.myTemplate` object.
|
||||
|
||||
Helpers on `UI.body` are only available in the `<body>` tags of your
|
||||
app. To register a global helper, use
|
||||
[UI.registerHelper](#ui_registerhelper).
|
||||
|
||||
Event maps on `UI.body` don't apply to elements added to the body via
|
||||
`UI.insert`, jQuery, or the DOM API, or to the body element itself.
|
||||
To handle events on the body, window, or document, use jQuery or the
|
||||
DOM API.
|
||||
|
||||
{{> api_box ui_render}}
|
||||
|
||||
This returns an "rendered template" object, which can be passed to
|
||||
@@ -2238,7 +2253,15 @@ changes.
|
||||
|
||||
{{> api_box ui_getelementdata}}
|
||||
|
||||
{{> api_box ui_dynamic}}
|
||||
|
||||
`UI.dynamic` allows you to include a template by name, where the name
|
||||
may be calculated by a helper and may change reactively. The `data`
|
||||
argument is optional, and if it is omitted, the current data context
|
||||
is used.
|
||||
|
||||
For example, if there is a template named "foo", `{{dstache}}> UI.dynamic
|
||||
template="foo"}}` is equivalent to `{{dstache}}> foo}}`.
|
||||
|
||||
{{#api_box eventmaps}}
|
||||
|
||||
@@ -2281,8 +2304,8 @@ Example:
|
||||
// Fires when any element with the 'accept' class is clicked
|
||||
'click .accept': function (event) { ... },
|
||||
|
||||
// Fires when 'accept' is clicked, or a key is pressed
|
||||
'keydown, click .accept': function (event) { ... }
|
||||
// Fires when 'accept' is clicked or focused, or a key is pressed
|
||||
'click .accept, focus .accept, keypress': function (event) { ... }
|
||||
}
|
||||
|
||||
Most events bubble up the document tree from their originating
|
||||
|
||||
@@ -1186,6 +1186,11 @@ Template.api.accounts_ui_config = {
|
||||
type: "Object",
|
||||
descr: "To ask the user for permission to act on their behalf when offline, map the relevant external service to `true`. Currently only supported with Google. See [Meteor.loginWithExternalService](#meteor_loginwithexternalservice) for more details."
|
||||
},
|
||||
{
|
||||
name: "forceApprovalPrompt",
|
||||
type: "Boolean",
|
||||
descr: "If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google."
|
||||
},
|
||||
{
|
||||
name: "passwordSignupFields",
|
||||
type: "String",
|
||||
@@ -1845,6 +1850,17 @@ Template.api.template_data = {
|
||||
descr: ["The data context of this instance's latest invocation."]
|
||||
};
|
||||
|
||||
Template.api.template_autorun = {
|
||||
id: "template_autorun",
|
||||
name: "<em>this</em>.autorun(runFunc)",
|
||||
locus: "Client",
|
||||
descr: ["A version of [Deps.autorun](#deps_autorun) that is stopped when the template is destroyed."],
|
||||
args: [
|
||||
{name: "runFunc",
|
||||
type: "Function",
|
||||
descr: "The function to run. It receives one argument: a Deps.Computation object."}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.ui_registerhelper = {
|
||||
id: "ui_registerhelper",
|
||||
@@ -1862,6 +1878,22 @@ Template.api.ui_registerhelper = {
|
||||
}]
|
||||
};
|
||||
|
||||
Template.api.ui_dynamic = {
|
||||
id: "ui_dynamic",
|
||||
name: "{{> UI.dynamic template=templateName [data=dataContext]}}",
|
||||
locus: "Client",
|
||||
descr: ["Choose a template to include dynamically, by name."],
|
||||
args: [
|
||||
{name: "templateName",
|
||||
type: "String",
|
||||
descr: "The name of the template to include."
|
||||
},
|
||||
{name: "dataContext",
|
||||
type: "Object",
|
||||
descr: "Optional. The data context in which to include the template."
|
||||
}]
|
||||
};
|
||||
|
||||
Template.api.ui_body = {
|
||||
id: "ui_body",
|
||||
name: "UI.body",
|
||||
|
||||
@@ -33,7 +33,7 @@ packages that most any app will use (for example `webapp`, which
|
||||
handles incoming HTTP connections, and `templating`, which lets you
|
||||
make HTML templates that automatically update live as data changes).
|
||||
Then there are optional packages like `email`, which lets your app
|
||||
send emails, or the Meteor Accounts series (`account-password`,
|
||||
send emails, or the Meteor Accounts series (`accounts-password`,
|
||||
`accounts-facebook`, `accounts-ui`, and others) which provide a
|
||||
full-featured user account system that you can drop right into your
|
||||
app. And beyond these "official" packages, there are hundreds of
|
||||
|
||||
@@ -253,7 +253,8 @@ var toc = [
|
||||
{instance: "this", name: "find", id: "template_find"},
|
||||
{instance: "this", name: "firstNode", id: "template_firstNode"},
|
||||
{instance: "this", name: "lastNode", id: "template_lastNode"},
|
||||
{instance: "this", name: "data", id: "template_data"}
|
||||
{instance: "this", name: "data", id: "template_data"},
|
||||
{instance: "this", name: "autorun", id: "template_autorun"}
|
||||
],
|
||||
"UI", [
|
||||
"UI.registerHelper",
|
||||
@@ -262,7 +263,8 @@ var toc = [
|
||||
"UI.renderWithData",
|
||||
"UI.insert",
|
||||
"UI.remove",
|
||||
"UI.getElementData"
|
||||
"UI.getElementData",
|
||||
{name: "{{> UI.dynamic}}", id: "ui_dynamic"}
|
||||
],
|
||||
{type: "spacer"},
|
||||
{name: "Event maps", style: "noncode"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// While galaxy apps are on their own special meteor releases, override
|
||||
// Meteor.release here.
|
||||
if (Meteor.isClient) {
|
||||
Meteor.release = Meteor.release ? "0.8.2" : undefined;
|
||||
Meteor.release = Meteor.release ? "0.8.3" : undefined;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -35,6 +35,16 @@ var randomString = function (length) {
|
||||
return ret;
|
||||
};
|
||||
|
||||
var preCall = function (name) {
|
||||
console.log('> ' + name);
|
||||
};
|
||||
|
||||
var postCall = function (name) {
|
||||
return function (err, callback) {
|
||||
console.log('< ' + name + ' ' + (err ? 'ERR' : 'OK'));
|
||||
};
|
||||
};
|
||||
|
||||
var pickCollection = function () {
|
||||
return Random.choice(Collections);
|
||||
};
|
||||
@@ -96,7 +106,8 @@ if (Meteor.isServer) {
|
||||
Meteor.setInterval(function () {
|
||||
var when = +(new Date) - PARAMS.maxAgeSeconds*1000;
|
||||
_.each(Collections, function (C) {
|
||||
C.remove({when: {$lt: when}});
|
||||
preCall('removeMaxAge');
|
||||
C.remove({when: {$lt: when}}, postCall('removeMaxAge'));
|
||||
});
|
||||
// Clear out 5% of the DB each time, steady state. XXX parameterize?
|
||||
}, 1000*PARAMS.maxAgeSeconds / 20);
|
||||
@@ -121,7 +132,8 @@ if (Meteor.isServer) {
|
||||
doc.when = +(new Date);
|
||||
|
||||
var C = pickCollection();
|
||||
C.insert(doc);
|
||||
preCall('insert');
|
||||
C.insert(doc, postCall('insert'));
|
||||
},
|
||||
update: function (processId, field, value) {
|
||||
check([processId, field, value], [String]);
|
||||
@@ -130,15 +142,18 @@ if (Meteor.isServer) {
|
||||
|
||||
var C = pickCollection();
|
||||
// update one message.
|
||||
C.update({fromProcess: processId}, {$set: modifer}, {multi: false});
|
||||
preCall('update');
|
||||
C.update({fromProcess: processId}, {$set: modifer}, {multi: false}, postCall('update'));
|
||||
},
|
||||
remove: function (processId) {
|
||||
check(processId, String);
|
||||
var C = pickCollection();
|
||||
// remove one message.
|
||||
var obj = C.findOne({fromProcess: processId});
|
||||
if (obj)
|
||||
C.remove(obj._id);
|
||||
if (obj) {
|
||||
preCall('remove');
|
||||
C.remove(obj._id, postCall('remove'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
PORT=9000
|
||||
NUM_CLIENTS=10
|
||||
DURATION=120
|
||||
if [ -z "$NUM_CLIENTS" ]; then
|
||||
NUM_CLIENTS=10
|
||||
fi
|
||||
if [ -z "$DURATION" ]; then
|
||||
DURATION=120
|
||||
fi
|
||||
REPORT_INTERVAL=10
|
||||
|
||||
set -e
|
||||
@@ -20,7 +24,7 @@ pkill -f "$PROJDIR/.meteor/local/db" || true
|
||||
../../../meteor reset || true
|
||||
|
||||
# start the benchmark app
|
||||
../../../meteor --production --settings "scenarios/${SCENARIO}.json" --port 9000 &
|
||||
../../../meteor --production --settings "scenarios/${SCENARIO}.json" --port ${PORT} &
|
||||
OUTER_PID=$!
|
||||
|
||||
echo "Waiting for server to come up"
|
||||
@@ -30,12 +34,13 @@ function wait_for_port {
|
||||
sleep 1
|
||||
N=$(($N+1))
|
||||
if [ $N -ge $2 ] ; then
|
||||
curl -v "$1" || true
|
||||
echo "Timed out waiting for port $1"
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
}
|
||||
wait_for_port "http://localhost:9001" 60
|
||||
wait_for_port "http://localhost:${PORT}" 60
|
||||
|
||||
|
||||
echo "Starting phantoms"
|
||||
|
||||
11
examples/unfinished/benchmark/scenarios/scale10.json
Normal file
11
examples/unfinished/benchmark/scenarios/scale10.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 10,
|
||||
"updatesPerSecond": 10,
|
||||
"removesPerSecond": 1,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
11
examples/unfinished/benchmark/scenarios/scale100.json
Normal file
11
examples/unfinished/benchmark/scenarios/scale100.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 100,
|
||||
"updatesPerSecond": 100,
|
||||
"removesPerSecond": 10,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
11
examples/unfinished/benchmark/scenarios/scale20.json
Normal file
11
examples/unfinished/benchmark/scenarios/scale20.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 20,
|
||||
"updatesPerSecond": 20,
|
||||
"removesPerSecond": 2,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
11
examples/unfinished/benchmark/scenarios/scale40.json
Normal file
11
examples/unfinished/benchmark/scenarios/scale40.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 40,
|
||||
"updatesPerSecond": 40,
|
||||
"removesPerSecond": 4,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
11
examples/unfinished/benchmark/scenarios/scale50.json
Normal file
11
examples/unfinished/benchmark/scenarios/scale50.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 50,
|
||||
"updatesPerSecond": 50,
|
||||
"removesPerSecond": 5,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
|
||||
@@ -2,12 +2,14 @@ Accounts.ui = {};
|
||||
|
||||
Accounts.ui._options = {
|
||||
requestPermissions: {},
|
||||
requestOfflineToken: {}
|
||||
requestOfflineToken: {},
|
||||
forceApprovalPrompt: {}
|
||||
};
|
||||
|
||||
// XXX refactor duplicated code in this function
|
||||
Accounts.ui.config = function(options) {
|
||||
// validate options keys
|
||||
var VALID_KEYS = ['passwordSignupFields', 'requestPermissions', 'requestOfflineToken'];
|
||||
var VALID_KEYS = ['passwordSignupFields', 'requestPermissions', 'requestOfflineToken', 'forceApprovalPrompt'];
|
||||
_.each(_.keys(options), function (key) {
|
||||
if (!_.contains(VALID_KEYS, key))
|
||||
throw new Error("Accounts.ui.config: Invalid key: " + key);
|
||||
@@ -56,6 +58,20 @@ Accounts.ui.config = function(options) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// deal with `forceApprovalPrompt`
|
||||
if (options.forceApprovalPrompt) {
|
||||
_.each(options.forceApprovalPrompt, function (value, service) {
|
||||
if (service !== 'google')
|
||||
throw new Error("Accounts.ui.config: `forceApprovalPrompt` only supported for Google login at the moment.");
|
||||
|
||||
if (Accounts.ui._options.forceApprovalPrompt[service]) {
|
||||
throw new Error("Accounts.ui.config: Can't set `forceApprovalPrompt` more than once for " + service);
|
||||
} else {
|
||||
Accounts.ui._options.forceApprovalPrompt[service] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
passwordSignupFields = function () {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
// XXX it'd be cool to also test that the right thing happens if options
|
||||
// *are* validated, but Accouns.ui._options is global state which makes this hard
|
||||
// *are* validated, but Accounts.ui._options is global state which makes this hard
|
||||
// (impossible?)
|
||||
Tinytest.add('accounts-ui - config validates keys', function (test) {
|
||||
test.throws(function () {
|
||||
@@ -19,4 +19,8 @@ Tinytest.add('accounts-ui - config validates keys', function (test) {
|
||||
test.throws(function () {
|
||||
Accounts.ui.config({requestPermissions: {facebook: "not an array"}});
|
||||
});
|
||||
|
||||
test.throws(function () {
|
||||
Accounts.ui.config({forceApprovalPrompt: {facebook: "only google"}});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,8 @@ Template._loginButtonsLoggedOutSingleLoginButton.events({
|
||||
options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
|
||||
if (Accounts.ui._options.requestOfflineToken[serviceName])
|
||||
options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
|
||||
if (Accounts.ui._options.forceApprovalPrompt[serviceName])
|
||||
options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];
|
||||
|
||||
loginWithService(options, callback);
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ Blaze.InOuterTemplateScope = function (templateView, contentFunc) {
|
||||
parentView = parentView.parentView;
|
||||
|
||||
view.onCreated(function () {
|
||||
this.originalParentView = this.parentView;
|
||||
this.parentView = parentView;
|
||||
});
|
||||
return view;
|
||||
|
||||
@@ -29,6 +29,28 @@ Blaze.DOMRange = function (nodeAndRangeArray) {
|
||||
};
|
||||
var DOMRange = Blaze.DOMRange;
|
||||
|
||||
// In IE 8, don't use empty text nodes as placeholders
|
||||
// in empty DOMRanges, use comment nodes instead. Using
|
||||
// empty text nodes in modern browsers is great because
|
||||
// it doesn't clutter the web inspector. In IE 8, however,
|
||||
// it seems to lead in some roundabout way to the OAuth
|
||||
// pop-up crashing the browser completely. In the past,
|
||||
// we didn't use empty text nodes on IE 8 because they
|
||||
// don't accept JS properties, so just use the same logic
|
||||
// even though we don't need to set properties on the
|
||||
// placeholder anymore.
|
||||
DOMRange._USE_COMMENT_PLACEHOLDERS = (function () {
|
||||
var result = false;
|
||||
var textNode = document.createTextNode("");
|
||||
try {
|
||||
textNode.someProp = true;
|
||||
} catch (e) {
|
||||
// IE 8
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
|
||||
// static methods
|
||||
DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) {
|
||||
var m = rangeOrNode;
|
||||
@@ -118,7 +140,10 @@ DOMRange.prototype.attach = function (parentElement, nextNode, _isMove) {
|
||||
DOMRange._insert(members[i], parentElement, nextNode, _isMove);
|
||||
}
|
||||
} else {
|
||||
var placeholder = document.createTextNode("");
|
||||
var placeholder = (
|
||||
DOMRange._USE_COMMENT_PLACEHOLDERS ?
|
||||
document.createComment("") :
|
||||
document.createTextNode(""));
|
||||
this.emptyRangePlaceholder = placeholder;
|
||||
parentElement.insertBefore(placeholder, nextNode || null);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ var bindIfIsFunction = function (x, target) {
|
||||
};
|
||||
};
|
||||
|
||||
var bindToCurrentDataIfIsFunction = function (x) {
|
||||
// If `x` is a function, binds the value of `this` for that function
|
||||
// to the current data context.
|
||||
var bindDataContext = function (x) {
|
||||
if (typeof x === 'function') {
|
||||
return function () {
|
||||
var data = Blaze.getCurrentData();
|
||||
@@ -22,6 +24,8 @@ var wrapHelper = function (f) {
|
||||
return Blaze.wrapCatchingExceptions(f, 'template helper');
|
||||
};
|
||||
|
||||
// !!! FIX THIS COMMENT !!!
|
||||
//
|
||||
// Implements {{foo}} where `name` is "foo"
|
||||
// and `component` is the component the tag is found in
|
||||
// (the lexical "self," on which to look for methods).
|
||||
@@ -45,11 +49,11 @@ Blaze.View.prototype.lookup = function (name, _options) {
|
||||
return Blaze._parentData(name.length - 1, true /*_functionWrapped*/);
|
||||
|
||||
} else if (template && (name in template)) {
|
||||
return wrapHelper(bindToCurrentDataIfIsFunction(template[name]));
|
||||
return wrapHelper(bindDataContext(template[name]));
|
||||
} else if (lookupTemplate && Template.__lookup__(name)) {
|
||||
return Template.__lookup__(name);
|
||||
} else if (UI._globalHelpers[name]) {
|
||||
return wrapHelper(bindToCurrentDataIfIsFunction(UI._globalHelpers[name]));
|
||||
return wrapHelper(bindDataContext(UI._globalHelpers[name]));
|
||||
} else {
|
||||
return function () {
|
||||
var isCalledAsFunction = (arguments.length > 0);
|
||||
|
||||
@@ -49,13 +49,19 @@ Blaze.DOMMaterializer.def({
|
||||
|
||||
var rawAttrs = tag.attrs;
|
||||
var children = tag.children;
|
||||
if (tagName === 'textarea' && ! (rawAttrs && ('value' in rawAttrs))) {
|
||||
// turn TEXTAREA contents into a value attribute.
|
||||
// Reactivity in the form of nested Views won't work here
|
||||
// because the Views have already been instantiated. To
|
||||
// get Views in a textarea they need to be wrapped in a
|
||||
// function and provided as the "value" attribute by the
|
||||
// compiler.
|
||||
if (tagName === 'textarea' && tag.children.length &&
|
||||
! (rawAttrs && ('value' in rawAttrs))) {
|
||||
// Provide very limited support for TEXTAREA tags with children
|
||||
// rather than a "value" attribute.
|
||||
// Reactivity in the form of Views nested in the tag's children
|
||||
// won't work. Compilers should compile textarea contents into
|
||||
// the "value" attribute of the tag, wrapped in a function if there
|
||||
// is reactivity.
|
||||
if (typeof rawAttrs === 'function' ||
|
||||
HTML.isArray(rawAttrs)) {
|
||||
throw new Error("Can't have reactive children of TEXTAREA node; " +
|
||||
"use the 'value' attribute instead.");
|
||||
}
|
||||
rawAttrs = _.extend({}, rawAttrs || null);
|
||||
rawAttrs.value = Blaze._expand(children, self.parentView);
|
||||
children = [];
|
||||
|
||||
@@ -312,14 +312,21 @@ Blaze.HTMLJSExpander.def({
|
||||
}
|
||||
});
|
||||
|
||||
// Return Blaze.currentView, but only if it is being rendered
|
||||
// (i.e. we are in its render() method).
|
||||
var currentViewIfRendering = function () {
|
||||
var view = Blaze.currentView;
|
||||
return (view && view.isInRender) ? view : null;
|
||||
};
|
||||
|
||||
Blaze._expand = function (htmljs, parentView) {
|
||||
parentView = parentView || Blaze.currentView;
|
||||
parentView = parentView || currentViewIfRendering();
|
||||
return (new Blaze.HTMLJSExpander(
|
||||
{parentView: parentView})).visit(htmljs);
|
||||
};
|
||||
|
||||
Blaze._expandAttributes = function (attrs, parentView) {
|
||||
parentView = parentView || Blaze.currentView;
|
||||
parentView = parentView || currentViewIfRendering();
|
||||
return (new Blaze.HTMLJSExpander(
|
||||
{parentView: parentView})).visitAttributes(attrs);
|
||||
};
|
||||
@@ -383,7 +390,7 @@ Blaze.runTemplate = function (t/*, args*/) {
|
||||
};
|
||||
|
||||
Blaze.render = function (content, parentView) {
|
||||
parentView = parentView || Blaze.currentView;
|
||||
parentView = parentView || currentViewIfRendering();
|
||||
|
||||
var view;
|
||||
if (typeof content === 'function') {
|
||||
@@ -401,7 +408,7 @@ Blaze.render = function (content, parentView) {
|
||||
Blaze.toHTML = function (htmljs, parentView) {
|
||||
if (typeof htmljs === 'function')
|
||||
throw new Error("Blaze.toHTML doesn't take a function, just HTMLjs");
|
||||
parentView = parentView || Blaze.currentView;
|
||||
parentView = parentView || currentViewIfRendering();
|
||||
return HTML.toHTML(Blaze._expand(htmljs, parentView));
|
||||
};
|
||||
|
||||
@@ -414,7 +421,7 @@ Blaze.toText = function (htmljs, parentView, textMode) {
|
||||
textMode = parentView;
|
||||
parentView = null;
|
||||
}
|
||||
parentView = parentView || Blaze.currentView;
|
||||
parentView = parentView || currentViewIfRendering();
|
||||
|
||||
if (! textMode)
|
||||
throw new Error("textMode required");
|
||||
@@ -529,7 +536,11 @@ Blaze._addEventMap = function (view, eventMap, thisInHandler) {
|
||||
function (evt) {
|
||||
if (! range.containsElement(evt.currentTarget))
|
||||
return null;
|
||||
return handler.apply(thisInHandler || this, arguments);
|
||||
var handlerThis = thisInHandler || this;
|
||||
var handlerArgs = arguments;
|
||||
return Blaze.withCurrentView(view, function () {
|
||||
return handler.apply(handlerThis, handlerArgs);
|
||||
});
|
||||
},
|
||||
range, function (r) {
|
||||
return r.parentRange;
|
||||
|
||||
@@ -1456,6 +1456,16 @@ var wrapInternalException = function (exception, context) {
|
||||
if (!exception || exception instanceof Meteor.Error)
|
||||
return exception;
|
||||
|
||||
// tests can set the 'expected' flag on an exception so it won't go to the
|
||||
// server log
|
||||
if (!exception.expected) {
|
||||
Meteor._debug("Exception " + context, exception.stack);
|
||||
if (exception.sanitizedError) {
|
||||
Meteor._debug("Sanitized and reported to the client as:", exception.sanitizedError.message);
|
||||
Meteor._debug();
|
||||
}
|
||||
}
|
||||
|
||||
// Did the error contain more details that could have been useful if caught in
|
||||
// server code (or if thrown from non-client-originated code), but also
|
||||
// provided a "sanitized" version with more context than 500 Internal server
|
||||
@@ -1467,11 +1477,6 @@ var wrapInternalException = function (exception, context) {
|
||||
"is not a Meteor.Error; ignoring");
|
||||
}
|
||||
|
||||
// tests can set the 'expected' flag on an exception so it won't go to the
|
||||
// server log
|
||||
if (!exception.expected)
|
||||
Meteor._debug("Exception " + context, exception.stack);
|
||||
|
||||
return new Meteor.Error(500, "Internal server error");
|
||||
};
|
||||
|
||||
|
||||
@@ -526,16 +526,18 @@ if (Meteor.isClient) {
|
||||
]);
|
||||
|
||||
testAsyncMulti("livedata - publisher errors", (function () {
|
||||
// Use a separate connection so that we can safely check to see if
|
||||
// conn._subscriptions is empty.
|
||||
var conn = new LivedataTest.Connection('/',
|
||||
{reloadWithOutstanding: true});
|
||||
var collName = Random.id();
|
||||
var coll = new Meteor.Collection(collName, {connection: conn});
|
||||
var conn, collName, coll;
|
||||
var errorFromRerun;
|
||||
var gotErrorFromStopper = false;
|
||||
return [
|
||||
function (test, expect) {
|
||||
// Use a separate connection so that we can safely check to see if
|
||||
// conn._subscriptions is empty.
|
||||
conn = new LivedataTest.Connection('/',
|
||||
{reloadWithOutstanding: true});
|
||||
collName = Random.id();
|
||||
coll = new Meteor.Collection(collName, {connection: conn});
|
||||
|
||||
var testSubError = function (options) {
|
||||
conn.subscribe("publisherErrors", collName, options, {
|
||||
onReady: expect(),
|
||||
|
||||
@@ -54,7 +54,7 @@ _.extend(LivedataTest.ClientStream.prototype, {
|
||||
// But _launchConnection calls _cleanup which closes previous connections.
|
||||
// It's our belief that this stifles future 'open' events, but maybe
|
||||
// we are wrong?
|
||||
throw new Error("Got open from inactive client");
|
||||
throw new Error("Got open from inactive client " + !!self.client);
|
||||
}
|
||||
|
||||
if (self._forcedToDisconnect) {
|
||||
|
||||
@@ -14,6 +14,7 @@ Meteor._SynchronousQueue = function () {
|
||||
var self = this;
|
||||
self._tasks = [];
|
||||
self._running = false;
|
||||
self._runTimeout = null;
|
||||
};
|
||||
|
||||
_.extend(Meteor._SynchronousQueue.prototype, {
|
||||
@@ -25,6 +26,15 @@ _.extend(Meteor._SynchronousQueue.prototype, {
|
||||
var tasks = self._tasks;
|
||||
self._tasks = [];
|
||||
self._running = true;
|
||||
|
||||
if (self._runTimeout) {
|
||||
// Since we're going to drain the queue, we can forget about the timeout
|
||||
// which tries to run it. (But if one of our tasks queues something else,
|
||||
// the timeout will be correctly re-created.)
|
||||
clearTimeout(self._runTimeout);
|
||||
self._runTimeout = null;
|
||||
}
|
||||
|
||||
try {
|
||||
while (!_.isEmpty(tasks)) {
|
||||
var t = tasks.shift();
|
||||
@@ -47,12 +57,12 @@ _.extend(Meteor._SynchronousQueue.prototype, {
|
||||
|
||||
queueTask: function (task) {
|
||||
var self = this;
|
||||
var wasEmpty = _.isEmpty(self._tasks);
|
||||
self._tasks.push(task);
|
||||
// Intentionally not using Meteor.setTimeout, because it doesn't like runing
|
||||
// in stubs for now.
|
||||
if (wasEmpty)
|
||||
setTimeout(_.bind(self.flush, self), 0);
|
||||
if (!self._runTimeout) {
|
||||
self._runTimeout = setTimeout(_.bind(self.flush, self), 0);
|
||||
}
|
||||
},
|
||||
|
||||
flush: function () {
|
||||
|
||||
@@ -339,6 +339,21 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
query.movedBefore = wrapCallback(options.movedBefore);
|
||||
}
|
||||
|
||||
if (!options._suppress_initial && !self.collection.paused) {
|
||||
// XXX unify ordered and unordered interface
|
||||
var each = ordered
|
||||
? _.bind(_.each, null, query.results)
|
||||
: _.bind(query.results.forEach, query.results);
|
||||
each(function (doc) {
|
||||
var fields = EJSON.clone(doc);
|
||||
|
||||
delete fields._id;
|
||||
if (ordered)
|
||||
query.addedBefore(doc._id, fields, null);
|
||||
query.added(doc._id, fields);
|
||||
});
|
||||
}
|
||||
|
||||
var handle = new LocalCollection.ObserveHandle;
|
||||
_.extend(handle, {
|
||||
collection: self.collection,
|
||||
@@ -358,30 +373,9 @@ _.extend(LocalCollection.Cursor.prototype, {
|
||||
handle.stop();
|
||||
});
|
||||
}
|
||||
|
||||
if (!options._suppress_initial && !self.collection.paused) {
|
||||
// XXX unify ordered and unordered interface
|
||||
var each = ordered
|
||||
? _.bind(_.each, null, query.results)
|
||||
: _.bind(query.results.forEach, query.results);
|
||||
each(function (doc) {
|
||||
var fields = EJSON.clone(doc);
|
||||
|
||||
delete fields._id;
|
||||
if (ordered)
|
||||
query.addedBefore(doc._id, fields, null);
|
||||
query.added(doc._id, fields);
|
||||
});
|
||||
|
||||
// run the observe callbacks resulting from the initial contents
|
||||
// before we leave the observe.
|
||||
if (self.collection._observeQueue.safeToRunTask()) {
|
||||
self.collection._observeQueue.drain();
|
||||
} else if (options.added || options.addedBefore) {
|
||||
// See #2315.
|
||||
throw Error("observeChanges called from an observe callback on the same collection cannot differentiate between initial and later adds");
|
||||
}
|
||||
}
|
||||
// run the observe callbacks resulting from the initial contents
|
||||
// before we leave the observe.
|
||||
self.collection._observeQueue.drain();
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -3109,28 +3109,3 @@ Tinytest.add("minimongo - fetch in observe", function (test) {
|
||||
observe.stop();
|
||||
computation.stop();
|
||||
});
|
||||
|
||||
Tinytest.add("minimongo - observe in observe", function (test) {
|
||||
var coll = new LocalCollection;
|
||||
coll.insert({foo: 2});
|
||||
|
||||
var observe1AddedCalled = false;
|
||||
var observe1 = coll.find({foo: 1}).observeChanges({
|
||||
added: function (id, fields) {
|
||||
observe1AddedCalled = true;
|
||||
test.equal(fields, {foo: 1});
|
||||
|
||||
// It would be even better if this didn't throw; see #2315.
|
||||
test.throws(function () {
|
||||
coll.find({foo: 2}).observeChanges({
|
||||
added: function () {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
test.isFalse(observe1AddedCalled);
|
||||
coll.insert({foo: 1});
|
||||
test.isTrue(observe1AddedCalled);
|
||||
observe1.stop();
|
||||
});
|
||||
|
||||
@@ -159,7 +159,8 @@ LocalCollection._observeFromObserveChanges = function (cursor, observeCallbacks)
|
||||
var oldDoc = self.docs.get(id);
|
||||
var doc = EJSON.clone(oldDoc);
|
||||
LocalCollection._applyChanges(doc, fields);
|
||||
observeCallbacks.changed(transform(doc), transform(oldDoc));
|
||||
observeCallbacks.changed(transform(doc),
|
||||
transform(EJSON.clone(oldDoc)));
|
||||
}
|
||||
},
|
||||
removed: function (id) {
|
||||
@@ -176,32 +177,5 @@ LocalCollection._observeFromObserveChanges = function (cursor, observeCallbacks)
|
||||
var handle = cursor.observeChanges(changeObserver.applyChange);
|
||||
suppressed = false;
|
||||
|
||||
if (changeObserver.ordered) {
|
||||
// Fetches the current list of documents, in order, as an array. Can be
|
||||
// called at any time. Internal API assumed by the `observe-sequence`
|
||||
// package (used by Meteor UI for `#each` blocks). Only defined on ordered
|
||||
// observes (those that listen on `addedAt` or similar). Continues to work
|
||||
// after `stop()` is called on the handle.
|
||||
//
|
||||
// Because we already materialize the full OrderedDict of all documents, it
|
||||
// seems nice to provide access to the view rather than making the data
|
||||
// consumer reconstitute it. This gives the consumer a shot at doing
|
||||
// something smart with the feed like proxying it, since firing callbacks
|
||||
// like `changed` and `movedTo` basically requires omniscience (knowing old
|
||||
// and new documents, old and new indices, and the correct value for
|
||||
// `before`).
|
||||
//
|
||||
// NOTE: If called from an observe callback for a certain change, the result
|
||||
// is *not* guaranteed to be a snapshot of the cursor up to that
|
||||
// change. This is because the callbacks are invoked before updating docs.
|
||||
handle._fetch = function () {
|
||||
var docsArray = [];
|
||||
changeObserver.docs.forEach(function (doc) {
|
||||
docsArray.push(transform(EJSON.clone(doc)));
|
||||
});
|
||||
return docsArray;
|
||||
};
|
||||
}
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,28 @@
|
||||
if (##SET_CREDENTIAL_TOKEN##) {
|
||||
var credentialToken = ##TOKEN##;
|
||||
var credentialSecret = ##SECRET##;
|
||||
try {
|
||||
localStorage[##LOCAL_STORAGE_PREFIX## + credentialToken] = credentialSecret;
|
||||
} catch (err) {
|
||||
// localStorage didn't work; try window.opener.
|
||||
window.opener &&
|
||||
window.opener.Package.oauth.OAuth._handleCredentialSecret(
|
||||
credentialToken, credentialSecret);
|
||||
// If window.opener isn't set, we can't do much else, but at least
|
||||
// close the popup instead of having it hang around on a blank page.
|
||||
if (window.opener && window.opener.Package &&
|
||||
window.opener.Package.oauth) {
|
||||
window.opener.Package.oauth.OAuth._handleCredentialSecret(
|
||||
credentialToken, credentialSecret);
|
||||
} else {
|
||||
try {
|
||||
localStorage[##LOCAL_STORAGE_PREFIX## + credentialToken] = credentialSecret;
|
||||
} catch (err) {
|
||||
// We can't do much else, but at least close the popup instead
|
||||
// of having it hang around on a blank page.
|
||||
}
|
||||
}
|
||||
}
|
||||
window.close();
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
<body>
|
||||
<p>
|
||||
Login completed. <a href="#" onclick="window.close()">
|
||||
Click here</a> to close this window.
|
||||
</p>
|
||||
<img src="not-a-real-image-for-oauth" style="width:1px;height:1px"
|
||||
onerror="window.close();" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -92,17 +92,16 @@ OAuth._handleCredentialSecret = function (credentialToken, secret) {
|
||||
// Used by accounts-oauth, which needs both a credentialToken and the
|
||||
// corresponding to credential secret to call the `login` method over DDP.
|
||||
OAuth._retrieveCredentialSecret = function (credentialToken) {
|
||||
// Check localStorage first, then check the secrets collected by
|
||||
// OAuth._handleCredentialSecret. This matches what we do in
|
||||
// First check the secrets collected by OAuth._handleCredentialSecret,
|
||||
// then check localStorage. This matches what we do in
|
||||
// end_of_login_response.html.
|
||||
var localStorageKey = OAuth._localStorageTokenPrefix +
|
||||
credentialToken;
|
||||
var secret = Meteor._localStorage.getItem(localStorageKey);
|
||||
|
||||
if (secret) {
|
||||
var secret = credentialSecrets[credentialToken];
|
||||
if (! secret) {
|
||||
var localStorageKey = OAuth._localStorageTokenPrefix +
|
||||
credentialToken;
|
||||
secret = Meteor._localStorage.getItem(localStorageKey);
|
||||
Meteor._localStorage.removeItem(localStorageKey);
|
||||
} else {
|
||||
secret = credentialSecrets[credentialToken];
|
||||
delete credentialSecrets[credentialToken];
|
||||
}
|
||||
return secret;
|
||||
|
||||
@@ -11,7 +11,6 @@ var registeredServices = {};
|
||||
// Internal: Maps from service version to handler function. The
|
||||
// 'oauth1' and 'oauth2' packages manipulate this directly to register
|
||||
// for callbacks.
|
||||
//
|
||||
OAuth._requestHandlers = {};
|
||||
|
||||
|
||||
|
||||
@@ -82,11 +82,10 @@ ObserveSequence = {
|
||||
Deps.nonreactive(function () {
|
||||
var seqArray; // same structure as `lastSeqArray` above.
|
||||
|
||||
// If we were previously observing a cursor, replace lastSeqArray with
|
||||
// more up-to-date information (specifically, the state of the observe
|
||||
// before it was stopped, which may be older than the DB).
|
||||
if (activeObserveHandle) {
|
||||
lastSeqArray = _.map(activeObserveHandle._fetch(), function (doc) {
|
||||
// If we were previously observing a cursor, replace lastSeqArray with
|
||||
// more up-to-date information. Then stop the old observe.
|
||||
lastSeqArray = _.map(lastSeq.fetch(), function (doc) {
|
||||
return {_id: doc._id, item: doc};
|
||||
});
|
||||
activeObserveHandle.stop();
|
||||
@@ -94,78 +93,19 @@ ObserveSequence = {
|
||||
}
|
||||
|
||||
if (!seq) {
|
||||
seqArray = [];
|
||||
diffArray(lastSeqArray, seqArray, callbacks);
|
||||
seqArray = seqChangedToEmpty(lastSeqArray, callbacks);
|
||||
} else if (seq instanceof Array) {
|
||||
var idsUsed = {};
|
||||
seqArray = _.map(seq, function (item, index) {
|
||||
var id;
|
||||
if (typeof item === 'string') {
|
||||
// ensure not empty, since other layers (eg DomRange) assume this as well
|
||||
id = "-" + item;
|
||||
} else if (typeof item === 'number' ||
|
||||
typeof item === 'boolean' ||
|
||||
item === undefined) {
|
||||
id = item;
|
||||
} else if (typeof item === 'object') {
|
||||
id = (item && item._id) || index;
|
||||
} else {
|
||||
throw new Error("{{#each}} doesn't support arrays with " +
|
||||
"elements of type " + typeof item);
|
||||
}
|
||||
|
||||
var idString = idStringify(id);
|
||||
if (idsUsed[idString]) {
|
||||
if (typeof item === 'object' && '_id' in item)
|
||||
warn("duplicate id " + id + " in", seq);
|
||||
id = Random.id();
|
||||
} else {
|
||||
idsUsed[idString] = true;
|
||||
}
|
||||
|
||||
return { _id: id, item: item };
|
||||
});
|
||||
|
||||
diffArray(lastSeqArray, seqArray, callbacks);
|
||||
seqArray = seqChangedToArray(lastSeqArray, seq, callbacks);
|
||||
} else if (isStoreCursor(seq)) {
|
||||
var cursor = seq;
|
||||
seqArray = [];
|
||||
|
||||
var initial = true; // are we observing initial data from cursor?
|
||||
activeObserveHandle = cursor.observe({
|
||||
addedAt: function (document, atIndex, before) {
|
||||
if (initial) {
|
||||
// keep track of initial data so that we can diff once
|
||||
// we exit `observe`.
|
||||
if (before !== null)
|
||||
throw new Error("Expected initial data from observe in order");
|
||||
seqArray.push({ _id: document._id, item: document });
|
||||
} else {
|
||||
callbacks.addedAt(document._id, document, atIndex, before);
|
||||
}
|
||||
},
|
||||
changedAt: function (newDocument, oldDocument, atIndex) {
|
||||
callbacks.changedAt(newDocument._id, newDocument, oldDocument,
|
||||
atIndex);
|
||||
},
|
||||
removedAt: function (oldDocument, atIndex) {
|
||||
callbacks.removedAt(oldDocument._id, oldDocument, atIndex);
|
||||
},
|
||||
movedTo: function (document, fromIndex, toIndex, before) {
|
||||
callbacks.movedTo(
|
||||
document._id, document, fromIndex, toIndex, before);
|
||||
}
|
||||
});
|
||||
initial = false;
|
||||
|
||||
// diff the old sequnce with initial data in the new cursor. this will
|
||||
// fire `addedAt` callbacks on the initial data.
|
||||
diffArray(lastSeqArray, seqArray, callbacks);
|
||||
|
||||
var result /* [seqArray, activeObserveHandle] */ =
|
||||
seqChangedToCursor(lastSeqArray, seq, callbacks);
|
||||
seqArray = result[0];
|
||||
activeObserveHandle = result[1];
|
||||
} else {
|
||||
throw badSequenceError();
|
||||
}
|
||||
|
||||
diffArray(lastSeqArray, seqArray, callbacks);
|
||||
lastSeq = seq;
|
||||
lastSeqArray = seqArray;
|
||||
});
|
||||
@@ -306,3 +246,73 @@ var diffArray = function (lastSeqArray, seqArray, callbacks) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
seqChangedToEmpty = function (lastSeqArray, callbacks) {
|
||||
return [];
|
||||
};
|
||||
|
||||
seqChangedToArray = function (lastSeqArray, array, callbacks) {
|
||||
var idsUsed = {};
|
||||
var seqArray = _.map(array, function (item, index) {
|
||||
var id;
|
||||
if (typeof item === 'string') {
|
||||
// ensure not empty, since other layers (eg DomRange) assume this as well
|
||||
id = "-" + item;
|
||||
} else if (typeof item === 'number' ||
|
||||
typeof item === 'boolean' ||
|
||||
item === undefined) {
|
||||
id = item;
|
||||
} else if (typeof item === 'object') {
|
||||
id = (item && item._id) || index;
|
||||
} else {
|
||||
throw new Error("{{#each}} doesn't support arrays with " +
|
||||
"elements of type " + typeof item);
|
||||
}
|
||||
|
||||
var idString = idStringify(id);
|
||||
if (idsUsed[idString]) {
|
||||
if (typeof item === 'object' && '_id' in item)
|
||||
warn("duplicate id " + id + " in", array);
|
||||
id = Random.id();
|
||||
} else {
|
||||
idsUsed[idString] = true;
|
||||
}
|
||||
|
||||
return { _id: id, item: item };
|
||||
});
|
||||
|
||||
return seqArray;
|
||||
};
|
||||
|
||||
seqChangedToCursor = function (lastSeqArray, cursor, callbacks) {
|
||||
var initial = true; // are we observing initial data from cursor?
|
||||
var seqArray = [];
|
||||
|
||||
var observeHandle = cursor.observe({
|
||||
addedAt: function (document, atIndex, before) {
|
||||
if (initial) {
|
||||
// keep track of initial data so that we can diff once
|
||||
// we exit `observe`.
|
||||
if (before !== null)
|
||||
throw new Error("Expected initial data from observe in order");
|
||||
seqArray.push({ _id: document._id, item: document });
|
||||
} else {
|
||||
callbacks.addedAt(document._id, document, atIndex, before);
|
||||
}
|
||||
},
|
||||
changedAt: function (newDocument, oldDocument, atIndex) {
|
||||
callbacks.changedAt(newDocument._id, newDocument, oldDocument,
|
||||
atIndex);
|
||||
},
|
||||
removedAt: function (oldDocument, atIndex) {
|
||||
callbacks.removedAt(oldDocument._id, oldDocument, atIndex);
|
||||
},
|
||||
movedTo: function (document, fromIndex, toIndex, before) {
|
||||
callbacks.movedTo(
|
||||
document._id, document, fromIndex, toIndex, before);
|
||||
}
|
||||
});
|
||||
initial = false;
|
||||
|
||||
return [seqArray, observeHandle];
|
||||
};
|
||||
|
||||
@@ -439,9 +439,8 @@ Tinytest.add('observe-sequence - cursor to same cursor', function (test) {
|
||||
}, [
|
||||
{addedAt: ["13", {_id: "13", rank: 1}, 0, null]},
|
||||
{addedAt: ["24", {_id: "24", rank: 2}, 1, null]},
|
||||
// even if the cursor changes to the same cursor, we diff to see if we
|
||||
// missed anything during the invalidation, which leads to these
|
||||
// 'changedAt' events.
|
||||
// even if the cursor changes to the same cursor, we do a diff,
|
||||
// which leads to these 'changedAt' events.
|
||||
{changedAt: ["13", {_id: "13", rank: 1}, {_id: "13", rank: 1}, 0]},
|
||||
{changedAt: ["24", {_id: "24", rank: 2}, {_id: "24", rank: 2}, 1]},
|
||||
{addedAt: ["78", {_id: "78", rank: 3}, 2, null]}
|
||||
|
||||
@@ -13,6 +13,7 @@ Package.on_test(function (api) {
|
||||
api.use('test-helpers');
|
||||
api.use('showdown');
|
||||
api.use('minimongo');
|
||||
api.use('deps');
|
||||
|
||||
api.use('templating', 'client');
|
||||
api.add_files([
|
||||
|
||||
@@ -922,3 +922,41 @@ Hi there!
|
||||
<template name="spacebars_test_isolated_lookup3">
|
||||
{{> bar}}--{{> spacebars_test_isolated_lookup_inclusion}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_current_view_in_event">
|
||||
<span>{{.}}</span>
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_textarea_attrs">
|
||||
<textarea {{attrs}}></textarea>
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_textarea_attrs_contents">
|
||||
<textarea {{attrs}}>Hello {{name}}</textarea>
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_textarea_attrs_array_contents">
|
||||
<textarea {{attrs}} class="bar">Hello {{name}}</textarea>
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_autorun">
|
||||
{{#if show}}
|
||||
{{>spacebars_test_autorun_inner}}
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_autorun_inner">
|
||||
Hello
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_contentBlock_arg">
|
||||
{{#spacebars_test_contentBlock_arg_inner}}
|
||||
{{this.bar}}
|
||||
{{/spacebars_test_contentBlock_arg_inner}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_test_contentBlock_arg_inner">
|
||||
{{#with foo="AAA" bar="BBB"}}
|
||||
{{this.foo}} {{> UI.contentBlock this}}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
@@ -2604,3 +2604,163 @@ _.each([1, 2, 3], function (n) {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Tinytest.add('spacebars-tests - template_tests - current view in event handler', function (test) {
|
||||
var tmpl = Template.spacebars_test_current_view_in_event;
|
||||
|
||||
var currentView;
|
||||
var currentData;
|
||||
|
||||
tmpl.events({
|
||||
'click span': function () {
|
||||
currentView = Blaze.getCurrentView();
|
||||
currentData = Blaze.getCurrentData();
|
||||
}
|
||||
});
|
||||
|
||||
var div = renderToDiv(tmpl, 'blah');
|
||||
test.equal(canonicalizeHtml(div.innerHTML), '<span>blah</span>');
|
||||
document.body.appendChild(div);
|
||||
clickElement(div.querySelector('span'));
|
||||
$(div).remove();
|
||||
|
||||
test.isTrue(currentView);
|
||||
test.equal(currentData, 'blah');
|
||||
});
|
||||
|
||||
|
||||
Tinytest.add(
|
||||
"spacebars-tests - template_tests - textarea attrs", function (test) {
|
||||
var tmplNoContents = {
|
||||
tmpl: Template.spacebars_test_textarea_attrs,
|
||||
hasTextAreaContents: false
|
||||
};
|
||||
var tmplWithContents = {
|
||||
tmpl: Template.spacebars_test_textarea_attrs_contents,
|
||||
hasTextAreaContents: true
|
||||
};
|
||||
var tmplWithContentsAndMoreAttrs = {
|
||||
tmpl: Template.spacebars_test_textarea_attrs_array_contents,
|
||||
hasTextAreaContents: true
|
||||
};
|
||||
|
||||
_.each(
|
||||
[tmplNoContents, tmplWithContents,
|
||||
tmplWithContentsAndMoreAttrs],
|
||||
function (tmplInfo) {
|
||||
|
||||
var id = new ReactiveVar("textarea-" + Random.id());
|
||||
var name = new ReactiveVar("one");
|
||||
var attrs = new ReactiveVar({
|
||||
id: "textarea-" + Random.id()
|
||||
});
|
||||
|
||||
var div = renderToDiv(tmplInfo.tmpl, {
|
||||
attrs: function () {
|
||||
return attrs.get();
|
||||
},
|
||||
name: function () {
|
||||
return name.get();
|
||||
}
|
||||
});
|
||||
|
||||
// Check that the id and value attribute are as we expect.
|
||||
// We can't check div.innerHTML because Chrome at least doesn't
|
||||
// appear to put textarea value attributes in innerHTML.
|
||||
var textarea = div.querySelector("textarea");
|
||||
test.equal(textarea.id, attrs.get().id);
|
||||
test.equal(
|
||||
textarea.value, tmplInfo.hasTextAreaContents ? "Hello one" : "");
|
||||
// One of the templates has a separate attribute in addition to
|
||||
// an attributes dictionary.
|
||||
if (tmplInfo === tmplWithContentsAndMoreAttrs) {
|
||||
test.equal($(textarea).attr("class"), "bar");
|
||||
}
|
||||
|
||||
// Change the id, check that the attribute updates reactively.
|
||||
attrs.set({ id: "textarea-" + Random.id() });
|
||||
Deps.flush();
|
||||
test.equal(textarea.id, attrs.get().id);
|
||||
|
||||
// Change the name variable, check that the textarea value
|
||||
// updates reactively.
|
||||
name.set("two");
|
||||
Deps.flush();
|
||||
test.equal(
|
||||
textarea.value, tmplInfo.hasTextAreaContents ? "Hello two" : "");
|
||||
|
||||
if (tmplInfo === tmplWithContentsAndMoreAttrs) {
|
||||
test.equal($(textarea).attr("class"), "bar");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add(
|
||||
"spacebars-tests - template_tests - this.autorun",
|
||||
function (test) {
|
||||
var tmpl = Template.spacebars_test_autorun;
|
||||
var tmplInner = Template.spacebars_test_autorun_inner;
|
||||
|
||||
// Keep track of the value of `UI._templateInstance()` inside the
|
||||
// autorun each time it runs.
|
||||
var autorunTemplateInstances = [];
|
||||
var actualTemplateInstance;
|
||||
var returnedComputation;
|
||||
var computationArg;
|
||||
|
||||
var show = new ReactiveVar(true);
|
||||
var rv = new ReactiveVar("foo");
|
||||
|
||||
tmplInner.created = function () {
|
||||
actualTemplateInstance = this;
|
||||
returnedComputation = this.autorun(function (c) {
|
||||
computationArg = c;
|
||||
rv.get();
|
||||
autorunTemplateInstances.push(UI._templateInstance());
|
||||
});
|
||||
};
|
||||
|
||||
tmpl.helpers({
|
||||
show: function () {
|
||||
return show.get();
|
||||
}
|
||||
});
|
||||
|
||||
var div = renderToDiv(tmpl);
|
||||
test.equal(autorunTemplateInstances.length, 1);
|
||||
test.equal(autorunTemplateInstances[0], actualTemplateInstance);
|
||||
|
||||
// Test that the autorun returned a computation and received a
|
||||
// computation as an argument.
|
||||
test.isTrue(returnedComputation instanceof Deps.Computation);
|
||||
test.equal(returnedComputation, computationArg);
|
||||
|
||||
// Make sure the autorun re-runs when `rv` changes, and that it has
|
||||
// the correct current view.
|
||||
rv.set("bar");
|
||||
Deps.flush();
|
||||
test.equal(autorunTemplateInstances.length, 2);
|
||||
test.equal(autorunTemplateInstances[1], actualTemplateInstance);
|
||||
|
||||
// If the inner template is destroyed, the autorun should be stopped.
|
||||
show.set(false);
|
||||
Deps.flush();
|
||||
rv.set("baz");
|
||||
Deps.flush();
|
||||
|
||||
test.equal(autorunTemplateInstances.length, 2);
|
||||
test.equal(rv.numListeners(), 0);
|
||||
}
|
||||
);
|
||||
|
||||
// Test that argument in {{> UI.contentBlock arg}} is evaluated in
|
||||
// the proper data context.
|
||||
Tinytest.add(
|
||||
"spacebars-tests - template_tests - contentBlock argument",
|
||||
function (test) {
|
||||
var tmpl = Template.spacebars_test_contentBlock_arg;
|
||||
var div = renderToDiv(tmpl);
|
||||
test.equal(canonicalizeHtml(div.innerHTML), 'AAA BBB');
|
||||
});
|
||||
|
||||
@@ -17,10 +17,6 @@ render the template. -->
|
||||
the template to render) and a `data` property, which can be falsey. -->
|
||||
<template name="__dynamicWithDataContext">
|
||||
{{#with chooseTemplate template}}
|
||||
{{#with ../data}} {{! original 'dataContext' argument to __dynamic}}
|
||||
{{> ..}} {{! return value from chooseTemplate(template) }}
|
||||
{{else}} {{! if the 'dataContext' argument was falsey }}
|
||||
{{> .. ../data}} {{! return value from chooseTemplate(template) }}
|
||||
{{/with}}
|
||||
{{> .. ../data}} {{!-- The .. is evaluated inside {{#with ../data}} --}}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
@@ -205,7 +205,32 @@ Spacebars.dot = function (value, id1/*, id2, ...*/) {
|
||||
};
|
||||
|
||||
Spacebars.TemplateWith = function (argFunc, contentBlock) {
|
||||
var w = Blaze.With(argFunc, contentBlock);
|
||||
var w;
|
||||
|
||||
// This is a little messy. When we compile `{{> UI.contentBlock}}`, we
|
||||
// wrap it in Blaze.InOuterTemplateScope in order to skip the intermediate
|
||||
// parent Views in the current template. However, when there's an argument
|
||||
// (`{{> UI.contentBlock arg}}`), the argument needs to be evaluated
|
||||
// in the original scope. There's no good order to nest
|
||||
// Blaze.InOuterTemplateScope and Spacebars.TemplateWith to achieve this,
|
||||
// so we wrap argFunc to run it in the "original parentView" of the
|
||||
// Blaze.InOuterTemplateScope.
|
||||
//
|
||||
// To make this better, reconsider InOuterTemplateScope as a primitive.
|
||||
// Longer term, evaluate expressions in the proper lexical scope.
|
||||
var wrappedArgFunc = function () {
|
||||
var viewToEvaluateArg = null;
|
||||
if (w.parentView && w.parentView.kind === 'InOuterTemplateScope') {
|
||||
viewToEvaluateArg = w.parentView.originalParentView;
|
||||
}
|
||||
if (viewToEvaluateArg) {
|
||||
return Blaze.withCurrentView(viewToEvaluateArg, argFunc);
|
||||
} else {
|
||||
return argFunc();
|
||||
}
|
||||
};
|
||||
|
||||
w = Blaze.With(wrappedArgFunc, contentBlock);
|
||||
w.__isTemplateWith = true;
|
||||
return w;
|
||||
};
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
// 'url' is assigned to in a statement before this.
|
||||
var page = require('webpage').create();
|
||||
page.open(url);
|
||||
setInterval(function() {
|
||||
var ready = page.evaluate(function () {
|
||||
if (typeof Meteor !== 'undefined'
|
||||
&& typeof(Meteor.status) !== 'undefined'
|
||||
&& Meteor.status().connected) {
|
||||
Deps.flush();
|
||||
return DDP._allSubscriptionsReady();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (ready) {
|
||||
var out = page.content;
|
||||
out = out.replace(/<script[^>]+>(.|\n|\r)*?<\/script\s*>/ig, '');
|
||||
out = out.replace('<meta name="fragment" content="!">', '');
|
||||
console.log(out);
|
||||
page.open(url, function(status) {
|
||||
if (status === 'fail')
|
||||
phantom.exit();
|
||||
}
|
||||
}, 100);
|
||||
setInterval(function() {
|
||||
var ready = page.evaluate(function () {
|
||||
if (typeof Meteor !== 'undefined'
|
||||
&& typeof(Meteor.status) !== 'undefined'
|
||||
&& Meteor.status().connected) {
|
||||
Deps.flush();
|
||||
return DDP._allSubscriptionsReady();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (ready) {
|
||||
var out = page.content;
|
||||
out = out.replace(/<script[^>]+>(.|\n|\r)*?<\/script\s*>/ig, '');
|
||||
out = out.replace('<meta name="fragment" content="!">', '');
|
||||
console.log(out);
|
||||
phantom.exit();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ Template.__updateTemplateInstance = function (view) {
|
||||
data: null,
|
||||
firstNode: null,
|
||||
lastNode: null,
|
||||
autorun: function (f) {
|
||||
return view.autorun(f);
|
||||
},
|
||||
__view__: view
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ canonicalizeHtml = function(html) {
|
||||
var h = html;
|
||||
// kill IE-specific comments inserted by DomRange
|
||||
h = h.replace(/<!--IE-->/g, '');
|
||||
h = h.replace(/<!---->/g, '');
|
||||
// ignore exact text of comments
|
||||
h = h.replace(/<!--.*?-->/g, '<!---->');
|
||||
// make all tags lowercase
|
||||
|
||||
@@ -8,18 +8,43 @@ TEST_STATUS = {
|
||||
FAILURES: null
|
||||
};
|
||||
|
||||
// xUnit format uses XML output
|
||||
var XML_CHAR_MAP = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
// Escapes a string for insertion into XML
|
||||
var escapeXml = function (s) {
|
||||
return s.replace(/[<>&"']/g, function (c) {
|
||||
return XML_CHAR_MAP[c];
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a human name for a test
|
||||
var getName = function (result) {
|
||||
return (result.server ? "S: " : "C: ") +
|
||||
result.groupPath.join(" - ") + " - " + result.test;
|
||||
};
|
||||
|
||||
// Calls console.log, but returns silently if console.log is not available
|
||||
var log = function (/*arguments*/) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// Logs xUnit output, if xunit output is enabled
|
||||
// Output is sent to console.log, prefixed with a magic string 'XUNIT '
|
||||
// By grepping for that prefix, the xUnit output can be extracted
|
||||
var xunit = function (s) {
|
||||
if (xunitEnabled) {
|
||||
log('XUNIT ' + s);
|
||||
}
|
||||
};
|
||||
|
||||
var passed = 0;
|
||||
var failed = 0;
|
||||
@@ -31,6 +56,10 @@ var hrefPath = document.location.href.split("/");
|
||||
var platform = decodeURIComponent(hrefPath.length && hrefPath[hrefPath.length - 1]);
|
||||
if (!platform)
|
||||
platform = "local";
|
||||
|
||||
// We enable xUnit output when platform is xunit
|
||||
var xunitEnabled = (platform == 'xunit');
|
||||
|
||||
var doReport = Meteor &&
|
||||
Meteor.settings &&
|
||||
Meteor.settings.public &&
|
||||
@@ -82,10 +111,13 @@ Meteor.startup(function () {
|
||||
status: "PENDING",
|
||||
events: [],
|
||||
server: !!results.server,
|
||||
testPath: testPath
|
||||
testPath: testPath,
|
||||
test: results.test
|
||||
};
|
||||
report(name, false);
|
||||
}
|
||||
// Loop through events, and record status for each test
|
||||
// Also log result if test has finished
|
||||
_.each(results.events, function (event) {
|
||||
resultSet[name].events.push(event);
|
||||
switch (event.type) {
|
||||
@@ -136,6 +168,7 @@ Meteor.startup(function () {
|
||||
});
|
||||
},
|
||||
|
||||
// After test completion, log a quick summary
|
||||
function () {
|
||||
if (failed > 0) {
|
||||
log("~~~~~~~ THERE ARE FAILURES ~~~~~~~");
|
||||
@@ -153,6 +186,43 @@ Meteor.startup(function () {
|
||||
TEST_STATUS.DONE = DONE = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Also log xUnit output
|
||||
xunit('<testsuite errors="" failures="" name="meteor" skips="" tests="" time="">');
|
||||
_.each(resultSet, function (result, name) {
|
||||
var classname = result.testPath.join('.').replace(/ /g, '-') + (result.server ? "-server" : "-client");
|
||||
var name = result.test.replace(/ /g, '-') + (result.server ? "-server" : "-client");
|
||||
var time = "";
|
||||
var error = "";
|
||||
_.each(result.events, function (event) {
|
||||
switch (event.type) {
|
||||
case "finish":
|
||||
var timeMs = event.timeMs;
|
||||
if (timeMs !== undefined) {
|
||||
time = (timeMs / 1000) + "";
|
||||
}
|
||||
break;
|
||||
case "fail":
|
||||
var details = event.details || {};
|
||||
error = (details.message || '?') + " filename=" + (details.filename || '?') + " line=" + (details.line || '?');
|
||||
}
|
||||
});
|
||||
switch (event.status) {
|
||||
case "FAIL":
|
||||
error = error || '?';
|
||||
break;
|
||||
case "EXPECTED":
|
||||
error = "Expected failure";
|
||||
break;
|
||||
}
|
||||
|
||||
xunit('<testcase classname="' + escapeXml(classname) + '" name="' + escapeXml(name) + '" time="' + time + '">');
|
||||
if (error) {
|
||||
xunit(' <failure message="test failure">' + escapeXml(error) + '</failure>');
|
||||
}
|
||||
xunit('</testcase>');
|
||||
});
|
||||
xunit('</testsuite>');
|
||||
},
|
||||
["tinytest"]);
|
||||
});
|
||||
|
||||
@@ -2,23 +2,40 @@
|
||||
// the server. Sets a 'server' flag on test results that came from the
|
||||
// server.
|
||||
//
|
||||
Tinytest._runTestsEverywhere = function (onReport, onComplete, pathPrefix) {
|
||||
// Options:
|
||||
// serial if true, will not run tests in parallel. Currently this means
|
||||
// running the server tests before running the client tests.
|
||||
// Default is currently true (serial operation), but we will likely
|
||||
// change this to false in future.
|
||||
Tinytest._runTestsEverywhere = function (onReport, onComplete, pathPrefix, options) {
|
||||
var runId = Random.id();
|
||||
var localComplete = false;
|
||||
var localStarted = false;
|
||||
var remoteComplete = false;
|
||||
var done = false;
|
||||
|
||||
options = _.extend({
|
||||
serial: true
|
||||
}, options);
|
||||
var serial = !!options.serial;
|
||||
|
||||
var maybeDone = function () {
|
||||
if (!done && localComplete && remoteComplete) {
|
||||
done = true;
|
||||
onComplete && onComplete();
|
||||
}
|
||||
if (serial && remoteComplete && !localStarted) {
|
||||
startLocalTests();
|
||||
}
|
||||
};
|
||||
|
||||
Tinytest._runTests(onReport, function () {
|
||||
localComplete = true;
|
||||
maybeDone();
|
||||
}, pathPrefix);
|
||||
var startLocalTests = function() {
|
||||
localStarted = true;
|
||||
Tinytest._runTests(onReport, function () {
|
||||
localComplete = true;
|
||||
maybeDone();
|
||||
}, pathPrefix);
|
||||
};
|
||||
|
||||
var handle;
|
||||
|
||||
@@ -59,4 +76,8 @@ Tinytest._runTestsEverywhere = function (onReport, onComplete, pathPrefix) {
|
||||
// XXX better report error
|
||||
throw new Error("Test server returned an error");
|
||||
});
|
||||
|
||||
if (!serial) {
|
||||
startLocalTests();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
=> Meteor 0.8.2: Switch `accounts-password` to use bcrypt on the
|
||||
server. User accounts will seamlessly transition to bcrypt on the
|
||||
next login, but this transition is one-way, so you cannot downgrade a
|
||||
production app once you upgrade to 0.8.2.
|
||||
=> Meteor 0.8.3: Performance improvements and a big refactoring of the
|
||||
Blaze internals.
|
||||
|
||||
This release is being downloaded in the background. Update your
|
||||
project to Meteor 0.8.2 by running 'meteor update'.
|
||||
project to Meteor 0.8.3 by running 'meteor update'.
|
||||
|
||||
@@ -141,7 +141,7 @@ Couldn't write the launcher script. Please either:
|
||||
|
||||
(1) Run the following as root:
|
||||
cp ~/.meteor/tools/latest/launch-meteor /usr/bin/meteor
|
||||
(2) Add ~/.meteor to your path, or
|
||||
(2) Add "$HOME/.meteor" to your path, or
|
||||
(3) Rerun this command to try again.
|
||||
|
||||
Then to get started, take a look at 'meteor --help' or see the docs at
|
||||
@@ -153,7 +153,7 @@ else
|
||||
|
||||
Now you need to do one of the following:
|
||||
|
||||
(1) Add ~/.meteor to your path, or
|
||||
(1) Add "$HOME/.meteor" to your path, or
|
||||
(2) Run this command as root:
|
||||
cp ~/.meteor/tools/latest/launch-meteor /usr/bin/meteor
|
||||
|
||||
|
||||
@@ -149,6 +149,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"release": "0.8.3"
|
||||
},
|
||||
{
|
||||
"release": "NEXT"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,15 @@ var configureS3 = function () {
|
||||
return {accessKey: accessKey, secretKey: secretKey};
|
||||
};
|
||||
|
||||
var s3Credentials = getS3Credentials();
|
||||
var s3Credentials;
|
||||
if (process.env.AWS_ACCESS_KEY_ID) {
|
||||
s3Credentials = {};
|
||||
s3Credentials.accessKey = process.env.AWS_ACCESS_KEY_ID;
|
||||
s3Credentials.secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
||||
} else {
|
||||
s3Credentials = getS3Credentials();
|
||||
}
|
||||
|
||||
var s3 = new S3({
|
||||
accessKeyId: s3Credentials.accessKey,
|
||||
secretAccessKey: s3Credentials.secretKey,
|
||||
|
||||
@@ -1026,6 +1026,7 @@ _.extend(Run.prototype, {
|
||||
self._ensureStarted();
|
||||
|
||||
var timeout = self.baseTimeout + self.extraTime;
|
||||
timeout *= utils.timeoutScaleFactor;
|
||||
self.extraTime = 0;
|
||||
return self.stdoutMatcher.match(pattern, timeout, _strict);
|
||||
}),
|
||||
@@ -1036,6 +1037,7 @@ _.extend(Run.prototype, {
|
||||
self._ensureStarted();
|
||||
|
||||
var timeout = self.baseTimeout + self.extraTime;
|
||||
timeout *= utils.timeoutScaleFactor;
|
||||
self.extraTime = 0;
|
||||
return self.stderrMatcher.match(pattern, timeout, _strict);
|
||||
}),
|
||||
@@ -1081,6 +1083,7 @@ _.extend(Run.prototype, {
|
||||
self._ensureStarted();
|
||||
|
||||
var timeout = self.baseTimeout + self.extraTime;
|
||||
timeout *= utils.timeoutScaleFactor;
|
||||
self.extraTime = 0;
|
||||
self.expectExit();
|
||||
|
||||
@@ -1098,6 +1101,7 @@ _.extend(Run.prototype, {
|
||||
|
||||
if (self.exitStatus === undefined) {
|
||||
var timeout = self.baseTimeout + self.extraTime;
|
||||
timeout *= utils.timeoutScaleFactor;
|
||||
self.extraTime = 0;
|
||||
|
||||
var fut = new Future;
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome to Meteor!</h1>
|
||||
|
||||
{{> hello}}
|
||||
</body>
|
||||
|
||||
<template name="hello">
|
||||
<h1>Hello World!</h1>
|
||||
{{greeting}}
|
||||
<input type="button" value="Click" />
|
||||
<button>Click Me</button>
|
||||
<p>You've pressed the button {{counter}} times.</p>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
if (Meteor.isClient) {
|
||||
Template.hello.greeting = function () {
|
||||
return "Welcome to ~name~.";
|
||||
};
|
||||
// counter starts at 0
|
||||
Session.setDefault("counter", 0);
|
||||
|
||||
Template.hello.helpers({
|
||||
counter: function () {
|
||||
return Session.get("counter");
|
||||
}
|
||||
});
|
||||
|
||||
Template.hello.events({
|
||||
'click input': function () {
|
||||
// template data, if any, is available in 'this'
|
||||
if (typeof console !== 'undefined')
|
||||
console.log("You pressed the button");
|
||||
'click button': function () {
|
||||
// increment the counter when button is clicked
|
||||
Session.set("counter", Session.get("counter") + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ var _ = require('underscore');
|
||||
var release = require('./release.js');
|
||||
var uniload = require('./uniload.js');
|
||||
var config = require('./config.js');
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var randomString = function (charsCount) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz';
|
||||
@@ -12,7 +13,7 @@ var randomString = function (charsCount) {
|
||||
return str;
|
||||
};
|
||||
|
||||
exports.accountsCommandTimeoutSecs = 15;
|
||||
exports.accountsCommandTimeoutSecs = 15 * utils.timeoutScaleFactor;
|
||||
|
||||
exports.randomString = randomString;
|
||||
|
||||
|
||||
@@ -357,3 +357,9 @@ exports.ensureOnlyExactVersions = function (dependencies) {
|
||||
});
|
||||
};
|
||||
|
||||
// Allow a simple way to scale up all timeouts from the command line
|
||||
var timeoutScaleFactor = 1.0;
|
||||
if (process.env.TIMEOUT_SCALE_FACTOR) {
|
||||
timeoutScaleFactor = parseFloat(process.env.TIMEOUT_SCALE_FACTOR);
|
||||
}
|
||||
exports.timeoutScaleFactor = timeoutScaleFactor;
|
||||
|
||||
Reference in New Issue
Block a user