js: Add by-position-or-by-name calling convention

Fixes #699
This commit is contained in:
Stein Magnus Jodal 2014-06-18 22:31:22 +02:00
parent 149287c06a
commit 225f41f999
5 changed files with 141 additions and 3 deletions

View File

@ -116,8 +116,20 @@ When creating an instance, you can specify the following settings:
we try to reconnect. Defaults to 64000.
``callingConvention``
Which calling convention to use when calling methods. The default is
"by-position-only".
Which calling convention to use when calling methods.
If set to "by-position-only", methods expect to be called with positional
arguments, like ``mopidy.foo.bar(null, true, 2)``.
If set to "by-position-or-by-name", methods expect to be called either with
an array of position arguments, like ``mopidy.foo.bar([null, true, 2])``,
or with an object of named arguments, like ``mopidy.foo.bar({id: 2})``. The
advantage of the "by-position-or-by-name" calling convention is that
arguments with default values can be left out of the named argument object.
Using named arguments also makes the code more readable, and more resistent
to future API changes.
For backwards compatibility, the default is "by-position-only".
``webSocket``
An existing WebSocket object to be used instead of creating a new

View File

@ -92,6 +92,10 @@ Feature release.
``Mopidy.ServerError``, and connection related errors are of the type
``Mopidy.ConnectionError``.
- Add support for method calls with by-name arguments. The old calling
convention, by-position-only, is still the default. See the :ref:`mopidy-js`
docs for details.
**MPD frontend**
- Proper command tokenization for MPD requests. This replaces the old regex

View File

@ -80,6 +80,11 @@ To run other [grunt](http://gruntjs.com/) targets which isn't predefined in
Changelog
---------
### 0.4.0 (UNRELEASED)
- Add support for method calls with by-name arguments. The old calling
convention, by-position-only, is still the default. See the docs for details.
### 0.3.0 (2014-06-16)
- Upgrade to when.js 3, which brings great performance improvements and better

View File

@ -253,13 +253,30 @@ Mopidy.prototype._getApiSpec = function () {
};
Mopidy.prototype._createApi = function (methods) {
var byPositionOrByName = (
this._settings.callingConvention === "by-position-or-by-name");
var caller = function (method) {
return function () {
var message = {method: method};
if (arguments.length === 0) {
return this._send(message);
}
message.params = Array.prototype.slice.call(arguments);
if (!byPositionOrByName) {
message.params = Array.prototype.slice.call(arguments);
return this._send(message);
}
if (arguments.length > 1) {
return when.reject(new Error(
"Expected zero arguments, a single array, " +
"or a single object."));
}
if (!Array.isArray(arguments[0]) &&
arguments[0] !== Object(arguments[0])) {
return when.reject(new TypeError(
"Expected an array or an object."));
}
message.params = arguments[0];
return this._send(message);
}.bind(this);
}.bind(this);

View File

@ -797,6 +797,106 @@ buster.testCase("Mopidy", {
params: [31, 97]
});
},
},
"by-position-or-by-name calling convention": {
setUp: function () {
this.mopidy = new Mopidy({
webSocket: this.webSocket,
callingConvention: "by-position-or-by-name"
});
this.mopidy._createApi({
foo: {
params: ["bar", "baz"]
}
});
this.sendStub = this.stub(this.mopidy, "_send");
},
"must be turned on manually": function () {
assert.equals(
this.mopidy._settings.callingConvention,
"by-position-or-by-name");
},
"sends no params if no arguments passed to function": function () {
this.mopidy.foo();
assert.calledOnceWith(this.sendStub, {method: "foo"});
},
"sends by-position if argument is a list": function () {
this.mopidy.foo([31, 97]);
assert.calledOnceWith(this.sendStub, {
method: "foo",
params: [31, 97]
});
},
"sends by-name if argument is an object": function () {
this.mopidy.foo({bar: 31, baz: 97});
assert.calledOnceWith(this.sendStub, {
method: "foo",
params: {bar: 31, baz: 97}
});
},
"rejects with error if more than one argument": function (done) {
var promise = this.mopidy.foo([1, 2], {c: 3, d: 4});
refute.called(this.sendStub);
promise.done(
done(function () {
assert(false);
}),
done(function (error) {
assert(error instanceof Error);
assert.equals(
error.message,
"Expected zero arguments, a single array, " +
"or a single object.");
})
);
},
"rejects with error if string": function (done) {
var promise = this.mopidy.foo("hello");
refute.called(this.sendStub);
promise.done(
done(function () {
assert(false);
}),
done(function (error) {
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equals(
error.message, "Expected an array or an object.");
})
);
},
"rejects with error if number": function (done) {
var promise = this.mopidy.foo(1337);
refute.called(this.sendStub);
promise.done(
done(function () {
assert(false);
}),
done(function (error) {
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equals(
error.message, "Expected an array or an object.");
})
);
}
}
}
});