Commit 8c2ebe7e authored by Romain Courteaud's avatar Romain Courteaud

Add an internal gadget state.

Changes on this state should be done by calling the changeState method.

If modifications are detected, an onStateChange callback will be triggered,
with the modified state keys as parameter.
parent d95c6fb7
......@@ -451,6 +451,16 @@
this.__ready_list.push(callback);
return this;
};
RenderJSGadget.setState = function (state_dict) {
var json_state = JSON.stringify(state_dict);
return this.ready(function () {
this.state = JSON.parse(json_state);
});
};
RenderJSGadget.onStateChange = function (callback) {
this.prototype.__state_change_callback = callback;
return this;
};
RenderJSGadget.__service_list = [];
RenderJSGadget.declareService = function (callback) {
......@@ -569,6 +579,21 @@
throw new Error("No element defined");
}
return this.element;
})
.declareMethod('changeState', function (state_dict) {
var key,
modified = false,
modification_dict = {};
for (key in state_dict) {
if (state_dict[key] !== this.state[key]) {
this.state[key] = state_dict[key];
modification_dict[key] = state_dict[key];
modified = true;
}
}
if (modified && this.__state_change_callback !== undefined) {
return this.__state_change_callback(modification_dict);
}
});
/////////////////////////////////////////////////////////////////
......@@ -657,6 +682,10 @@
RenderJSGadget.__service_list.slice();
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.setState =
RenderJSGadget.setState;
RenderJSEmbeddedGadget.onStateChange =
RenderJSGadget.onStateChange;
RenderJSEmbeddedGadget.declareService =
RenderJSGadget.declareService;
RenderJSEmbeddedGadget.onEvent =
......@@ -690,6 +719,7 @@
gadget_loading_klass = Klass;
gadget_instance = new Klass();
gadget_instance.element = options.element;
gadget_instance.state = {};
for (i = 0; i < template_node_list.length; i += 1) {
gadget_instance.element.appendChild(
template_node_list[i].cloneNode(true)
......@@ -733,6 +763,10 @@
RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.setState =
RenderJSGadget.setState;
RenderJSIframeGadget.onStateChange =
RenderJSGadget.onStateChange;
RenderJSIframeGadget.__service_list = RenderJSGadget.__service_list.slice();
RenderJSIframeGadget.declareService =
RenderJSGadget.declareService;
......@@ -766,6 +800,7 @@
iframe.setAttribute("src", url);
gadget_instance.__path = url;
gadget_instance.element = options.element;
gadget_instance.state = {};
// Attach it to the DOM
options.element.appendChild(iframe);
......@@ -1130,6 +1165,10 @@
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.setState =
RenderJSGadget.setState;
tmp_constructor.onStateChange =
RenderJSGadget.onStateChange;
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.onEvent =
......@@ -1306,6 +1345,8 @@
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.setState = RenderJSGadget.setState;
tmp_constructor.onStateChange = RenderJSGadget.onStateChange;
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareService =
RenderJSGadget.declareService;
......@@ -1460,6 +1501,7 @@
}
tmp_constructor.__template_element = document.createElement("div");
root_gadget.element = document.body;
root_gadget.state = {};
for (j = 0; j < root_gadget.element.childNodes.length; j += 1) {
tmp_constructor.__template_element.appendChild(
root_gadget.element.childNodes[j].cloneNode(true)
......
......@@ -6,11 +6,21 @@
ready_called = false,
service_started = false,
job_started = false,
event_started = false;
event_started = false,
state_change_callback_called = false,
state_change_count = 0,
init_state = {bar: 'foo'},
state_change_callback = function (modification_dict) {
state_change_callback_called = (state_change_count === 0) &&
(modification_dict.foo === 'bar');
state_change_count += 1;
};
gk.ready(function (g) {
ready_called = true;
})
.setState(init_state)
.onStateChange(state_change_callback)
.onEvent('bar', function () {
event_started = true;
})
......@@ -19,6 +29,15 @@
var event = new Event("bar");
this.element.dispatchEvent(event);
})
.declareMethod('wasStateInitialized', function () {
return ((this.hasOwnProperty("state")) &&
(JSON.stringify(this.state) === '{"bar":"foo"}')) &&
(this.state !== init_state);
})
.declareMethod('wasStateHandlerDeclared', function () {
return ((!this.hasOwnProperty("__state_change_callback")) &&
(this.__state_change_callback === state_change_callback));
})
.declareMethod('wasReadyCalled', function () {
return ready_called;
})
......@@ -34,6 +53,12 @@
.declareMethod('wasJobStarted', function () {
return job_started;
})
.declareMethod('triggerStateChange', function () {
return this.changeState({foo: 'bar'});
})
.declareMethod('wasStateChangeHandled', function () {
return state_change_callback_called;
})
.declareJob('runJob', function () {
job_started = true;
})
......
......@@ -1201,6 +1201,76 @@
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.changeState
/////////////////////////////////////////////////////////////////
module("RenderJSGadget.changeState", {
setup: function () {
renderJS.clearGadgetKlassList();
}
});
test('update state with changed keys', function () {
var gadget = new RenderJSGadget();
gadget.state = {foo: 'bar', bar: 'foo'};
stop();
gadget.changeState({bar: 'barbar'})
.then(function () {
deepEqual(gadget.state, {foo: 'bar', bar: 'barbar'});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test('trigger onStateChange if changed keys', function () {
var gadget = new RenderJSGadget(),
callback_called = false;
gadget.state = {foo: 'bar', bar: 'foo'};
gadget.__state_change_callback = function (modification_dict) {
deepEqual(gadget.state, {foo: 'bar', bar: 'barbar'});
deepEqual(modification_dict, {bar: 'barbar'});
equal(this, gadget);
return RSVP.Queue()
.push(function () {
callback_called = true;
});
};
stop();
gadget.changeState({bar: 'barbar'})
.then(function () {
ok(callback_called);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test('do not trigger onStateChange if no changed keys', function () {
var gadget = new RenderJSGadget(),
callback_called = false;
gadget.state = {foo: 'bar', bar: 'foo'};
gadget.__state_change_callback = function () {
callback_called = true;
};
stop();
gadget.changeState({bar: 'foo'})
.then(function () {
ok(!callback_called);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadgetKlass.declareAcquiredMethod
/////////////////////////////////////////////////////////////////
......@@ -1615,6 +1685,101 @@
deepEqual(Klass.__ready_list, [callback]);
});
/////////////////////////////////////////////////////////////////
// RenderJSGadgetKlass.setState
/////////////////////////////////////////////////////////////////
module("RenderJSGadgetKlass.setState", {
setup: function () {
renderJS.clearGadgetKlassList();
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.server.autoRespondAfter = 5;
},
teardown: function () {
this.server.restore();
delete this.server;
}
});
test('is chainable', function () {
// Check that setState is chainable
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, result;
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.__ready_list = [];
Klass.ready = RenderJSGadget.ready;
Klass.setState = RenderJSGadget.setState;
result = Klass.setState({});
equal(result, Klass);
});
test('create callback in the ready_list property', function () {
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
};
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.__ready_list = [];
Klass.ready = RenderJSGadget.ready;
Klass.setState = RenderJSGadget.setState;
Klass.setState({});
equal(Klass.__ready_list.length, 1);
});
/////////////////////////////////////////////////////////////////
// RenderJSGadgetKlass.onStateChange
/////////////////////////////////////////////////////////////////
module("RenderJSGadgetKlass.onStateChange", {
setup: function () {
renderJS.clearGadgetKlassList();
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.server.autoRespondAfter = 5;
},
teardown: function () {
this.server.restore();
delete this.server;
}
});
test('is chainable', function () {
// Check that onStateChange is chainable
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, result;
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.__ready_list = [];
Klass.onStateChange = RenderJSGadget.onStateChange;
result = Klass.onStateChange();
equal(result, Klass);
});
test('create callback in the __state_change_callback property', function () {
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
},
callback = {};
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.onStateChange = RenderJSGadget.onStateChange;
Klass.onStateChange(callback);
equal(Klass.prototype.__state_change_callback, callback);
});
/////////////////////////////////////////////////////////////////
// RenderJSGadgetKlass.declareService
/////////////////////////////////////////////////////////////////
......@@ -3469,6 +3634,76 @@
});
});
test('Set a default state', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
gadget1,
gadget2,
html_url = 'https://example.org/files/qunittest/test98.html';
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(html_url);
})
.then(function (result) {
gadget1 = result;
return gadget.declareGadget(html_url);
})
.then(function (result) {
gadget2 = result;
ok(gadget1.hasOwnProperty('state'));
deepEqual(gadget1.state, {});
ok(gadget2.hasOwnProperty('state'));
deepEqual(gadget2.state, {});
// Instance should have a copy of the init state
ok(gadget1.state !== gadget2.state);
})
.fail(function (e) {
ok(false);
})
.always(function () {
start();
});
});
test('Set the gadget state defined by setState', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
init_state = {foo: 'bar'},
html_url = 'https://example.org/files/qunittest/test98.html';
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
// Create a ready function
Klass.setState(init_state);
return gadget.declareGadget(html_url);
})
.then(function (result) {
ok(result.hasOwnProperty('state'));
deepEqual(result.state, {foo: 'bar'});
// Instance should have a copy of the init state
ok(result.state !== init_state);
})
.fail(function (e) {
ok(false);
})
.always(function () {
start();
});
});
test('Can take a DOM element options', function () {
// Subclass RenderJSGadget to not pollute its namespace
......@@ -4199,6 +4434,41 @@
equal(result, true);
})
// Check that state is initialized
.push(function () {
return new_gadget.wasStateInitialized();
})
.push(function (result) {
equal(result, true);
})
// Check that state handler is initialized
.push(function () {
return new_gadget.wasStateHandlerDeclared();
})
.push(function (result) {
equal(result, true);
})
// Check that state change was not triggered
.push(function () {
return new_gadget.wasStateChangeHandled();
})
.push(function (result) {
equal(result, false);
})
// Check that change state
.push(function () {
return new_gadget.triggerStateChange();
})
.push(function () {
return new_gadget.wasStateChangeHandled();
})
.push(function (result) {
equal(result, true);
})
// Check that job was not started
.push(function () {
return new_gadget.wasJobStarted();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment