1 #include "controllers/controllerengine.h"
2
3 #include <QScopedPointer>
4 #include <QTemporaryFile>
5 #include <QThread>
6 #include <QtDebug>
7 #include <memory>
8
9 #include "control/controlobject.h"
10 #include "control/controlpotmeter.h"
11 #include "controllers/controllerdebug.h"
12 #include "controllers/softtakeover.h"
13 #include "preferences/usersettings.h"
14 #include "test/mixxxtest.h"
15 #include "util/color/colorpalette.h"
16 #include "util/time.h"
17
18 typedef std::unique_ptr<QTemporaryFile> ScopedTemporaryFile;
19
20 class ControllerEngineTest : public MixxxTest {
21 protected:
makeTemporaryFile(const QString & contents)22 static ScopedTemporaryFile makeTemporaryFile(const QString& contents) {
23 QByteArray contentsBa = contents.toLocal8Bit();
24 ScopedTemporaryFile pFile = std::make_unique<QTemporaryFile>();
25 pFile->open();
26 pFile->write(contentsBa);
27 pFile->close();
28 return pFile;
29 }
30
SetUp()31 void SetUp() override {
32 mixxx::Time::setTestMode(true);
33 mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10));
34 QThread::currentThread()->setObjectName("Main");
35 cEngine = new ControllerEngine(nullptr, config());
36 pScriptEngine = cEngine->m_pEngine;
37 ControllerDebug::enable();
38 cEngine->setPopups(false);
39 }
40
TearDown()41 void TearDown() override {
42 cEngine->gracefulShutdown();
43 delete cEngine;
44 mixxx::Time::setTestMode(false);
45 }
46
execute(const QString & functionName)47 bool execute(const QString& functionName) {
48 QScriptValue function = cEngine->wrapFunctionCode(functionName, 0);
49 return cEngine->internalExecute(QScriptValue(), function,
50 QScriptValueList());
51 }
52
processEvents()53 void processEvents() {
54 // QCoreApplication::processEvents() only processes events that were
55 // queued when the method was called. Hence, all subsequent events that
56 // are emitted while processing those queued events will not be
57 // processed and are enqueued for the next event processing cycle.
58 // Calling processEvents() twice ensures that at least all queued and
59 // the next round of emitted events are processed.
60 application()->processEvents();
61 application()->processEvents();
62 }
63
64 ControllerEngine *cEngine;
65 QScriptEngine *pScriptEngine;
66 };
67
TEST_F(ControllerEngineTest,commonScriptHasNoErrors)68 TEST_F(ControllerEngineTest, commonScriptHasNoErrors) {
69 QString commonScript = "./res/controllers/common-controller-scripts.js";
70 cEngine->evaluate(commonScript);
71 EXPECT_FALSE(cEngine->hasErrors(commonScript));
72 }
73
TEST_F(ControllerEngineTest,setValue)74 TEST_F(ControllerEngineTest, setValue) {
75 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
76 EXPECT_TRUE(execute("function() { engine.setValue('[Test]', 'co', 1.0); }"));
77 EXPECT_DOUBLE_EQ(1.0, co->get());
78 }
79
TEST_F(ControllerEngineTest,setValue_InvalidControl)80 TEST_F(ControllerEngineTest, setValue_InvalidControl) {
81 EXPECT_TRUE(execute("function() { engine.setValue('[Nothing]', 'nothing', 1.0); }"));
82 }
83
TEST_F(ControllerEngineTest,getValue_InvalidControl)84 TEST_F(ControllerEngineTest, getValue_InvalidControl) {
85 EXPECT_TRUE(execute("function() { return engine.getValue('[Nothing]', 'nothing'); }"));
86 }
87
TEST_F(ControllerEngineTest,setValue_IgnoresNaN)88 TEST_F(ControllerEngineTest, setValue_IgnoresNaN) {
89 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
90 co->set(10.0);
91 EXPECT_TRUE(execute("function() { engine.setValue('[Test]', 'co', NaN); }"));
92 EXPECT_DOUBLE_EQ(10.0, co->get());
93 }
94
95
TEST_F(ControllerEngineTest,getSetValue)96 TEST_F(ControllerEngineTest, getSetValue) {
97 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
98 EXPECT_TRUE(execute("function() { engine.setValue('[Test]', 'co', engine.getValue('[Test]', 'co') + 1); }"));
99 EXPECT_DOUBLE_EQ(1.0, co->get());
100 }
101
TEST_F(ControllerEngineTest,setParameter)102 TEST_F(ControllerEngineTest, setParameter) {
103 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
104 -10.0, 10.0);
105 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 1.0); }"));
106 EXPECT_DOUBLE_EQ(10.0, co->get());
107 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 0.0); }"));
108 EXPECT_DOUBLE_EQ(-10.0, co->get());
109 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 0.5); }"));
110 EXPECT_DOUBLE_EQ(0.0, co->get());
111 }
112
TEST_F(ControllerEngineTest,setParameter_OutOfRange)113 TEST_F(ControllerEngineTest, setParameter_OutOfRange) {
114 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
115 -10.0, 10.0);
116 EXPECT_TRUE(execute("function () { engine.setParameter('[Test]', 'co', 1000); }"));
117 EXPECT_DOUBLE_EQ(10.0, co->get());
118 EXPECT_TRUE(execute("function () { engine.setParameter('[Test]', 'co', -1000); }"));
119 EXPECT_DOUBLE_EQ(-10.0, co->get());
120 }
121
TEST_F(ControllerEngineTest,setParameter_NaN)122 TEST_F(ControllerEngineTest, setParameter_NaN) {
123 // Test that NaNs are ignored.
124 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
125 -10.0, 10.0);
126 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', NaN); }"));
127 EXPECT_DOUBLE_EQ(0.0, co->get());
128 }
129
TEST_F(ControllerEngineTest,getSetParameter)130 TEST_F(ControllerEngineTest, getSetParameter) {
131 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
132 -10.0, 10.0);
133 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', "
134 " engine.getParameter('[Test]', 'co') + 0.1); }"));
135 EXPECT_DOUBLE_EQ(2.0, co->get());
136 }
137
TEST_F(ControllerEngineTest,softTakeover_setValue)138 TEST_F(ControllerEngineTest, softTakeover_setValue) {
139 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
140 -10.0, 10.0);
141 co->setParameter(0.0);
142 EXPECT_TRUE(execute("function() {"
143 " engine.softTakeover('[Test]', 'co', true);"
144 " engine.setValue('[Test]', 'co', 0.0); }"));
145 // The first set after enabling is always ignored.
146 EXPECT_DOUBLE_EQ(-10.0, co->get());
147
148 // Change the control internally (putting it out of sync with the
149 // ControllerEngine).
150 co->setParameter(0.5);
151
152 // Time elapsed is not greater than the threshold, so we do not ignore this
153 // set.
154 EXPECT_TRUE(execute("function() { engine.setValue('[Test]', 'co', -10.0); }"));
155 EXPECT_DOUBLE_EQ(-10.0, co->get());
156
157 // Advance time to 2x the threshold.
158 mixxx::Time::setTestElapsedTime(SoftTakeover::TestAccess::getTimeThreshold() * 2);
159
160 // Change the control internally (putting it out of sync with the
161 // ControllerEngine).
162 co->setParameter(0.5);
163
164 // Ignore the change since it occurred after the threshold and is too large.
165 EXPECT_TRUE(execute("function() { engine.setValue('[Test]', 'co', -10.0); }"));
166 EXPECT_DOUBLE_EQ(0.0, co->get());
167 }
168
TEST_F(ControllerEngineTest,softTakeover_setParameter)169 TEST_F(ControllerEngineTest, softTakeover_setParameter) {
170 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
171 -10.0, 10.0);
172 co->setParameter(0.0);
173 EXPECT_TRUE(execute("function() {"
174 " engine.softTakeover('[Test]', 'co', true);"
175 " engine.setParameter('[Test]', 'co', 1.0); }"));
176 // The first set after enabling is always ignored.
177 EXPECT_DOUBLE_EQ(-10.0, co->get());
178
179 // Change the control internally (putting it out of sync with the
180 // ControllerEngine).
181 co->setParameter(0.5);
182
183 // Time elapsed is not greater than the threshold, so we do not ignore this
184 // set.
185 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 0.0); }"));
186 EXPECT_DOUBLE_EQ(-10.0, co->get());
187
188 // Advance time to 2x the threshold.
189 mixxx::Time::setTestElapsedTime(SoftTakeover::TestAccess::getTimeThreshold() * 2);
190
191 // Change the control internally (putting it out of sync with the
192 // ControllerEngine).
193 co->setParameter(0.5);
194
195 // Ignore the change since it occurred after the threshold and is too large.
196 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 0.0); }"));
197 EXPECT_DOUBLE_EQ(0.0, co->get());
198 }
199
TEST_F(ControllerEngineTest,softTakeover_ignoreNextValue)200 TEST_F(ControllerEngineTest, softTakeover_ignoreNextValue) {
201 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
202 -10.0, 10.0);
203 co->setParameter(0.0);
204 EXPECT_TRUE(execute("function() {"
205 " engine.softTakeover('[Test]', 'co', true);"
206 " engine.setParameter('[Test]', 'co', 1.0); }"));
207 // The first set after enabling is always ignored.
208 EXPECT_DOUBLE_EQ(-10.0, co->get());
209
210 // Change the control internally (putting it out of sync with the
211 // ControllerEngine).
212 co->setParameter(0.5);
213
214 EXPECT_TRUE(execute("function() { engine.softTakeoverIgnoreNextValue('[Test]', 'co'); }"));
215
216 // We would normally allow this set since it is below the time threshold,
217 // but we are ignoring the next value.
218 EXPECT_TRUE(execute("function() { engine.setParameter('[Test]', 'co', 0.0); }"));
219 EXPECT_DOUBLE_EQ(0.0, co->get());
220 }
221
TEST_F(ControllerEngineTest,reset)222 TEST_F(ControllerEngineTest, reset) {
223 // Test that NaNs are ignored.
224 auto co = std::make_unique<ControlPotmeter>(ConfigKey("[Test]", "co"),
225 -10.0, 10.0);
226 co->setParameter(1.0);
227 EXPECT_TRUE(execute("function() { engine.reset('[Test]', 'co'); }"));
228 EXPECT_DOUBLE_EQ(0.0, co->get());
229 }
230
TEST_F(ControllerEngineTest,log)231 TEST_F(ControllerEngineTest, log) {
232 EXPECT_TRUE(execute("function() { engine.log('Test that logging works.'); }"));
233 }
234
TEST_F(ControllerEngineTest,trigger)235 TEST_F(ControllerEngineTest, trigger) {
236 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
237 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
238
239 ScopedTemporaryFile script(makeTemporaryFile(
240 "var reaction = function(value) { "
241 " var pass = engine.getValue('[Test]', 'passed');"
242 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
243 "var connection = engine.connectControl('[Test]', 'co', reaction);"
244 "engine.trigger('[Test]', 'co');"));
245 cEngine->evaluate(script->fileName());
246 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
247 // ControlObjectScript connections are processed via QueuedConnection. Use
248 // processEvents() to cause Qt to deliver them.
249 processEvents();
250 // The counter should have been incremented exactly once.
251 EXPECT_DOUBLE_EQ(1.0, pass->get());
252 }
253
254 // ControllerEngine::connectControl has a lot of quirky, inconsistent legacy behaviors
255 // depending on how it is invoked, so we need a lot of tests to make sure old scripts
256 // do not break.
257
TEST_F(ControllerEngineTest,connectControl_ByString)258 TEST_F(ControllerEngineTest, connectControl_ByString) {
259 // Test that connecting and disconnecting by function name works.
260 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
261 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
262
263 ScopedTemporaryFile script(makeTemporaryFile(
264 "var reaction = function(value) { "
265 " var pass = engine.getValue('[Test]', 'passed');"
266 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
267 "engine.connectControl('[Test]', 'co', 'reaction');"
268 "engine.trigger('[Test]', 'co');"
269 "function disconnect() { "
270 " engine.connectControl('[Test]', 'co', 'reaction', 1);"
271 " engine.trigger('[Test]', 'co'); }"));
272
273 cEngine->evaluate(script->fileName());
274 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
275 // ControlObjectScript connections are processed via QueuedConnection. Use
276 // processEvents() to cause Qt to deliver them.
277 processEvents();
278 EXPECT_TRUE(execute("disconnect"));
279 processEvents();
280 // The counter should have been incremented exactly once.
281 EXPECT_DOUBLE_EQ(1.0, pass->get());
282 }
283
TEST_F(ControllerEngineTest,connectControl_ByStringForbidDuplicateConnections)284 TEST_F(ControllerEngineTest, connectControl_ByStringForbidDuplicateConnections) {
285 // Test that connecting a control to a callback specified by a string
286 // does not make duplicate connections. This behavior is inconsistent
287 // with the behavior when specifying a callback as a function, but
288 // this is how it has been done, so keep the behavior to ensure old scripts
289 // do not break.
290 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
291 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
292
293 ScopedTemporaryFile script(makeTemporaryFile(
294 "var reaction = function(value) { "
295 " var pass = engine.getValue('[Test]', 'passed');"
296 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
297 "engine.connectControl('[Test]', 'co', 'reaction');"
298 "engine.connectControl('[Test]', 'co', 'reaction');"
299 "engine.trigger('[Test]', 'co');"));
300
301 cEngine->evaluate(script->fileName());
302 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
303 // ControlObjectScript connections are processed via QueuedConnection. Use
304 // processEvents() to cause Qt to deliver them.
305 processEvents();
306 // The counter should have been incremented exactly once.
307 EXPECT_DOUBLE_EQ(1.0, pass->get());
308 }
309
TEST_F(ControllerEngineTest,connectControl_ByStringRedundantConnectionObjectsAreNotIndependent)310 TEST_F(ControllerEngineTest,
311 connectControl_ByStringRedundantConnectionObjectsAreNotIndependent) {
312 // Test that multiple connections are not allowed when passing
313 // the callback to engine.connectControl as a function name string.
314 // This is weird and inconsistent, but it is how it has been done,
315 // so keep this behavior to make sure old scripts do not break.
316 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
317 auto counter = std::make_unique<ControlObject>(ConfigKey("[Test]", "counter"));
318
319 ScopedTemporaryFile script(makeTemporaryFile(
320 "var incrementCounterCO = function () {"
321 " var counter = engine.getValue('[Test]', 'counter');"
322 " engine.setValue('[Test]', 'counter', counter + 1);"
323 "};"
324 "var connection1 = engine.connectControl('[Test]', 'co', 'incrementCounterCO');"
325 // Make a second connection with the same ControlObject
326 // to check that disconnecting one does not disconnect both.
327 "var connection2 = engine.connectControl('[Test]', 'co', 'incrementCounterCO');"
328 "function changeTestCoValue() {"
329 " var testCoValue = engine.getValue('[Test]', 'co');"
330 " engine.setValue('[Test]', 'co', testCoValue + 1);"
331 "};"
332 "function disconnectConnection2() {"
333 " connection2.disconnect();"
334 "};"
335 ));
336
337 cEngine->evaluate(script->fileName());
338 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
339 execute("changeTestCoValue");
340 // ControlObjectScript connections are processed via QueuedConnection. Use
341 // processEvents() to cause Qt to deliver them.
342 processEvents();
343 EXPECT_EQ(1.0, counter->get());
344
345 execute("disconnectConnection2");
346 // The connection objects should refer to the same connection,
347 // so disconnecting one should disconnect both.
348 execute("changeTestCoValue");
349 processEvents();
350 EXPECT_EQ(1.0, counter->get());
351 }
352
TEST_F(ControllerEngineTest,connectControl_ByFunction)353 TEST_F(ControllerEngineTest, connectControl_ByFunction) {
354 // Test that connecting and disconnecting with a function value works.
355 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
356 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
357
358 ScopedTemporaryFile script(makeTemporaryFile(
359 "var reaction = function(value) { "
360 " var pass = engine.getValue('[Test]', 'passed');"
361 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
362 "var connection = engine.connectControl('[Test]', 'co', reaction);"
363 "connection.trigger();"));
364
365 cEngine->evaluate(script->fileName());
366 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
367 // ControlObjectScript connections are processed via QueuedConnection. Use
368 // processEvents() to cause Qt to deliver them.
369 processEvents();
370 // The counter should have been incremented exactly once.
371 EXPECT_DOUBLE_EQ(1.0, pass->get());
372 }
373
TEST_F(ControllerEngineTest,connectControl_ByFunctionAllowDuplicateConnections)374 TEST_F(ControllerEngineTest, connectControl_ByFunctionAllowDuplicateConnections) {
375 // Test that duplicate connections are allowed when passing callbacks as functions.
376 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
377 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
378
379 ScopedTemporaryFile script(makeTemporaryFile(
380 "var reaction = function(value) { "
381 " var pass = engine.getValue('[Test]', 'passed');"
382 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
383 "engine.connectControl('[Test]', 'co', reaction);"
384 "engine.connectControl('[Test]', 'co', reaction);"
385 // engine.trigger() has no way to know which connection to a ControlObject
386 // to trigger, so it should trigger all of them.
387 "engine.trigger('[Test]', 'co');"));
388
389 cEngine->evaluate(script->fileName());
390 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
391 // ControlObjectScript connections are processed via QueuedConnection. Use
392 // processEvents() to cause Qt to deliver them.
393 processEvents();
394 // The counter should have been incremented exactly twice.
395 EXPECT_DOUBLE_EQ(2.0, pass->get());
396 }
397
TEST_F(ControllerEngineTest,connectControl_toDisconnectRemovesAllConnections)398 TEST_F(ControllerEngineTest, connectControl_toDisconnectRemovesAllConnections) {
399 // Test that every connection to a ControlObject is disconnected
400 // by calling engine.connectControl(..., true). Individual connections
401 // can only be disconnected by storing the connection object returned by
402 // engine.connectControl and calling that object's 'disconnect' method.
403 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
404 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
405
406 ScopedTemporaryFile script(makeTemporaryFile(
407 "var reaction = function(value) { "
408 " var pass = engine.getValue('[Test]', 'passed');"
409 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
410 "engine.connectControl('[Test]', 'co', reaction);"
411 "engine.connectControl('[Test]', 'co', reaction);"
412 "engine.trigger('[Test]', 'co');"
413 "function disconnect() { "
414 " engine.connectControl('[Test]', 'co', reaction, 1);"
415 " engine.trigger('[Test]', 'co'); }"));
416
417 cEngine->evaluate(script->fileName());
418 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
419 // ControlObjectScript connections are processed via QueuedConnection. Use
420 // processEvents() to cause Qt to deliver them.
421 processEvents();
422 EXPECT_TRUE(execute("disconnect"));
423 processEvents();
424 // The counter should have been incremented exactly twice.
425 EXPECT_DOUBLE_EQ(2.0, pass->get());
426 }
427
TEST_F(ControllerEngineTest,connectControl_ByLambda)428 TEST_F(ControllerEngineTest, connectControl_ByLambda) {
429 // Test that connecting with an anonymous function works.
430 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
431 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
432
433 ScopedTemporaryFile script(makeTemporaryFile(
434 "var connection = engine.connectControl('[Test]', 'co', function(value) { "
435 " var pass = engine.getValue('[Test]', 'passed');"
436 " engine.setValue('[Test]', 'passed', pass + 1.0); });"
437 "connection.trigger();"
438 "function disconnect() { "
439 " connection.disconnect();"
440 " engine.trigger('[Test]', 'co'); }"));
441
442 cEngine->evaluate(script->fileName());
443 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
444 // ControlObjectScript connections are processed via QueuedConnection. Use
445 // processEvents() to cause Qt to deliver them.
446 processEvents();
447 EXPECT_TRUE(execute("disconnect"));
448 processEvents();
449 // The counter should have been incremented exactly once.
450 EXPECT_DOUBLE_EQ(1.0, pass->get());
451 }
452
TEST_F(ControllerEngineTest,connectionObject_Disconnect)453 TEST_F(ControllerEngineTest, connectionObject_Disconnect) {
454 // Test that disconnecting using the 'disconnect' method on the connection
455 // object returned from connectControl works.
456 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
457 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
458
459 ScopedTemporaryFile script(makeTemporaryFile(
460 "var reaction = function(value) { "
461 " var pass = engine.getValue('[Test]', 'passed');"
462 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
463 "var connection = engine.makeConnection('[Test]', 'co', reaction);"
464 "connection.trigger();"
465 "function disconnect() { "
466 " connection.disconnect();"
467 " engine.trigger('[Test]', 'co'); }"));
468
469 cEngine->evaluate(script->fileName());
470 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
471 // ControlObjectScript connections are processed via QueuedConnection. Use
472 // processEvents() to cause Qt to deliver them.
473 processEvents();
474 EXPECT_TRUE(execute("disconnect"));
475 processEvents();
476 // The counter should have been incremented exactly once.
477 EXPECT_DOUBLE_EQ(1.0, pass->get());
478 }
TEST_F(ControllerEngineTest,connectionObject_reflectDisconnect)479 TEST_F(ControllerEngineTest, connectionObject_reflectDisconnect) {
480 // Test that checks if disconnecting yields the appropriate feedback
481 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
482 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
483
484 ScopedTemporaryFile script(makeTemporaryFile(
485 "var reaction = function(success) { "
486 " if (success) {"
487 " var pass = engine.getValue('[Test]', 'passed');"
488 " engine.setValue('[Test]', 'passed', pass + 1.0); "
489 " }"
490 "};"
491 "var dummy_callback = function(value) {};"
492 "var connection = engine.makeConnection('[Test]', 'co', dummy_callback);"
493 "reaction(connection);"
494 "reaction(connection.isConnected);"
495 "var successful_disconnect = connection.disconnect();"
496 "reaction(successful_disconnect);"
497 "reaction(!connection.isConnected);"
498 ));
499 cEngine->evaluate(script->fileName());
500 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
501 processEvents();
502 EXPECT_DOUBLE_EQ(4.0, pass->get());
503 }
504
505
TEST_F(ControllerEngineTest,connectionObject_DisconnectByPassingToConnectControl)506 TEST_F(ControllerEngineTest, connectionObject_DisconnectByPassingToConnectControl) {
507 // Test that passing a connection object back to engine.connectControl
508 // removes the connection
509 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
510 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
511 // The connections should be removed from the ControlObject which they were
512 // actually connected to, regardless of the group and item arguments passed
513 // to engine.connectControl() to remove the connection. All that should matter
514 // is that a valid ControlObject is specified.
515 auto dummy = std::make_unique<ControlObject>(ConfigKey("[Test]", "dummy"));
516
517 ScopedTemporaryFile script(makeTemporaryFile(
518 "var reaction = function(value) { "
519 " var pass = engine.getValue('[Test]', 'passed');"
520 " engine.setValue('[Test]', 'passed', pass + 1.0); };"
521 "var connection1 = engine.connectControl('[Test]', 'co', reaction);"
522 "var connection2 = engine.connectControl('[Test]', 'co', reaction);"
523 "function disconnectConnection1() { "
524 " engine.connectControl('[Test]',"
525 " 'dummy',"
526 " connection1);"
527 " engine.trigger('[Test]', 'co'); }"
528 // Whether a 4th argument is passed to engine.connectControl does not matter.
529 "function disconnectConnection2() { "
530 " engine.connectControl('[Test]',"
531 " 'dummy',"
532 " connection2, true);"
533 " engine.trigger('[Test]', 'co'); }"));
534
535 cEngine->evaluate(script->fileName());
536 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
537 // ControlObjectScript connections are processed via QueuedConnection. Use
538 // processEvents() to cause Qt to deliver them.
539 processEvents();
540 EXPECT_TRUE(execute("disconnectConnection1"));
541 processEvents();
542 // The counter should have been incremented once by connection2.
543 EXPECT_DOUBLE_EQ(1.0, pass->get());
544 EXPECT_TRUE(execute("disconnectConnection2"));
545 processEvents();
546 // The counter should not have changed.
547 EXPECT_DOUBLE_EQ(1.0, pass->get());
548 }
549
TEST_F(ControllerEngineTest,connectionObject_MakesIndependentConnection)550 TEST_F(ControllerEngineTest, connectionObject_MakesIndependentConnection) {
551 // Test that multiple connections can be made to the same CO with
552 // the same callback function and that calling their 'disconnect' method
553 // only disconnects the callback for that object.
554 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
555 auto counter = std::make_unique<ControlObject>(ConfigKey("[Test]", "counter"));
556
557 ScopedTemporaryFile script(makeTemporaryFile(
558 "var incrementCounterCO = function () {"
559 " var counter = engine.getValue('[Test]', 'counter');"
560 " engine.setValue('[Test]', 'counter', counter + 1);"
561 "};"
562 "var connection1 = engine.makeConnection('[Test]', 'co', incrementCounterCO);"
563 // Make a second connection with the same ControlObject
564 // to check that disconnecting one does not disconnect both.
565 "var connection2 = engine.makeConnection('[Test]', 'co', incrementCounterCO);"
566 "function changeTestCoValue() {"
567 " var testCoValue = engine.getValue('[Test]', 'co');"
568 " engine.setValue('[Test]', 'co', testCoValue + 1);"
569 "}"
570 "function disconnectConnection1() {"
571 " connection1.disconnect();"
572 "}"
573 ));
574
575 cEngine->evaluate(script->fileName());
576 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
577 execute("changeTestCoValue");
578 // ControlObjectScript connections are processed via QueuedConnection. Use
579 // processEvents() to cause Qt to deliver them.
580 processEvents();
581 EXPECT_EQ(2.0, counter->get());
582
583 execute("disconnectConnection1");
584 // Only the callback for connection1 should have disconnected;
585 // the callback for connection2 should still be connected, so
586 // changing the CO they were both connected to should
587 // increment the counter once.
588 execute("changeTestCoValue");
589 processEvents();
590 EXPECT_EQ(3.0, counter->get());
591 }
592
TEST_F(ControllerEngineTest,connectionObject_trigger)593 TEST_F(ControllerEngineTest, connectionObject_trigger) {
594 // Test that triggering using the 'trigger' method on the connection
595 // object returned from connectControl works.
596 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
597 auto counter = std::make_unique<ControlObject>(ConfigKey("[Test]", "counter"));
598
599 ScopedTemporaryFile script(makeTemporaryFile(
600 "var incrementCounterCO = function () {"
601 " var counter = engine.getValue('[Test]', 'counter');"
602 " engine.setValue('[Test]', 'counter', counter + 1);"
603 "};"
604 "var connection1 = engine.makeConnection('[Test]', 'co', incrementCounterCO);"
605 // Make a second connection with the same ControlObject
606 // to check that triggering a connection object only triggers that callback,
607 // not every callback connected to its ControlObject.
608 "var connection2 = engine.makeConnection('[Test]', 'co', incrementCounterCO);"
609 "connection1.trigger();"
610 ));
611
612 cEngine->evaluate(script->fileName());
613 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
614 // The counter should have been incremented exactly once.
615 EXPECT_DOUBLE_EQ(1.0, counter->get());
616 }
617
618
TEST_F(ControllerEngineTest,connectionExecutesWithCorrectThisObject)619 TEST_F(ControllerEngineTest, connectionExecutesWithCorrectThisObject) {
620 // Test that callback functions are executed with JavaScript's
621 // 'this' keyword referring to the object in which the connection
622 // was created.
623 auto co = std::make_unique<ControlObject>(ConfigKey("[Test]", "co"));
624 auto pass = std::make_unique<ControlObject>(ConfigKey("[Test]", "passed"));
625
626 ScopedTemporaryFile script(makeTemporaryFile(
627 "var TestObject = function () {"
628 " this.executeTheCallback = true;"
629 " this.connection = engine.makeConnection('[Test]', 'co', function () {"
630 " if (this.executeTheCallback) {"
631 " engine.setValue('[Test]', 'passed', 1);"
632 " }"
633 " });"
634 "};"
635 "var someObject = new TestObject();"
636 "someObject.connection.trigger();"));
637
638 cEngine->evaluate(script->fileName());
639 EXPECT_FALSE(cEngine->hasErrors(script->fileName()));
640 // ControlObjectScript connections are processed via QueuedConnection. Use
641 // processEvents() to cause Qt to deliver them.
642 processEvents();
643 // The counter should have been incremented exactly once.
644 EXPECT_DOUBLE_EQ(1.0, pass->get());
645 }
646