1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <qdebug.h> |
32 | |
33 | #include <QtScript/qscriptengine.h> |
34 | #include <QtScript/qscriptable.h> |
35 | |
36 | class MyScriptable : public QObject, public QScriptable |
37 | { |
38 | Q_OBJECT |
39 | Q_PROPERTY(int baz READ baz WRITE setBaz) |
40 | Q_PROPERTY(QObject* zab READ zab WRITE setZab) |
41 | Q_PROPERTY(int 0 READ baz) |
42 | Q_PROPERTY(QObject* 1 READ zab) |
43 | Q_PROPERTY(int oof WRITE setOof) |
44 | public: |
45 | MyScriptable(QObject *parent = 0) |
46 | : QObject(parent), m_lastEngine(0) |
47 | { } |
48 | ~MyScriptable() { } |
49 | |
50 | QScriptEngine *lastEngine() const; |
51 | |
52 | void setOof(int) |
53 | { m_oofThisObject = context()->thisObject(); } |
54 | QScriptValue oofThisObject() const |
55 | { return m_oofThisObject; } |
56 | |
57 | void emitSig(int value) |
58 | { emit sig(value); } |
59 | |
60 | public slots: |
61 | void foo(); |
62 | void setX(int x); |
63 | void setX(const QString &x); |
64 | void setX2(int x); |
65 | bool isBar(); |
66 | int baz(); |
67 | void setBaz(int x); |
68 | void evalIsBar(); |
69 | bool useInAnotherEngine(); |
70 | void setOtherEngine(); |
71 | QObject *zab(); |
72 | QObject *setZab(QObject *); |
73 | QScriptValue getArguments(); |
74 | int getArgumentCount(); |
75 | |
76 | QString toString() const; |
77 | int valueOf() const; |
78 | |
79 | signals: |
80 | void sig(int); |
81 | |
82 | private: |
83 | QScriptEngine *m_lastEngine; |
84 | QScriptEngine *m_otherEngine; |
85 | QScriptValue m_oofThisObject; |
86 | }; |
87 | |
88 | QScriptEngine *MyScriptable::lastEngine() const |
89 | { |
90 | return m_lastEngine; |
91 | } |
92 | |
93 | int MyScriptable::baz() |
94 | { |
95 | m_lastEngine = engine(); |
96 | return 123; |
97 | } |
98 | |
99 | void MyScriptable::setBaz(int) |
100 | { |
101 | m_lastEngine = engine(); |
102 | } |
103 | |
104 | QObject *MyScriptable::zab() |
105 | { |
106 | return thisObject().toQObject(); |
107 | } |
108 | |
109 | QObject *MyScriptable::setZab(QObject *) |
110 | { |
111 | return thisObject().toQObject(); |
112 | } |
113 | |
114 | QScriptValue MyScriptable::getArguments() |
115 | { |
116 | return context()->argumentsObject(); |
117 | } |
118 | |
119 | int MyScriptable::getArgumentCount() |
120 | { |
121 | return argumentCount(); |
122 | } |
123 | |
124 | void MyScriptable::foo() |
125 | { |
126 | m_lastEngine = engine(); |
127 | if (engine()) |
128 | context()->throwError(text: "MyScriptable.foo" ); |
129 | } |
130 | |
131 | void MyScriptable::evalIsBar() |
132 | { |
133 | engine()->evaluate(program: "this.isBar()" ); |
134 | m_lastEngine = engine(); |
135 | } |
136 | |
137 | bool MyScriptable::useInAnotherEngine() |
138 | { |
139 | QScriptEngine eng; |
140 | eng.globalObject().setProperty(name: "foo" , value: eng.newQObject(object: this)); |
141 | eng.evaluate(program: "foo.baz()" ); |
142 | m_lastEngine = engine(); |
143 | return (m_otherEngine == &eng); |
144 | } |
145 | |
146 | void MyScriptable::setOtherEngine() |
147 | { |
148 | m_otherEngine = engine(); |
149 | } |
150 | |
151 | void MyScriptable::setX(int x) |
152 | { |
153 | m_lastEngine = engine(); |
154 | if (engine()) |
155 | thisObject().setProperty(name: "x" , value: QScriptValue(engine(), x)); |
156 | } |
157 | |
158 | void MyScriptable::setX(const QString &x) |
159 | { |
160 | m_lastEngine = engine(); |
161 | if (engine()) |
162 | thisObject().setProperty(name: "x" , value: QScriptValue(engine(), x)); |
163 | } |
164 | |
165 | void MyScriptable::setX2(int) |
166 | { |
167 | m_lastEngine = engine(); |
168 | thisObject().setProperty(name: "x" , value: argument(index: 0)); |
169 | } |
170 | |
171 | bool MyScriptable::isBar() |
172 | { |
173 | m_lastEngine = engine(); |
174 | QString str = thisObject().toString(); |
175 | return str.contains(c: QLatin1Char('@')); |
176 | } |
177 | |
178 | QString MyScriptable::toString() const |
179 | { |
180 | return thisObject().property(name: "objectName" ).toString(); |
181 | } |
182 | |
183 | int MyScriptable::valueOf() const |
184 | { |
185 | return thisObject().property(name: "baz" ).toInt32(); |
186 | } |
187 | |
188 | class tst_QScriptable : public QObject |
189 | { |
190 | Q_OBJECT |
191 | |
192 | public: |
193 | tst_QScriptable(); |
194 | virtual ~tst_QScriptable(); |
195 | |
196 | private slots: |
197 | void initTestCase(); |
198 | void cleanupTestCase(); |
199 | |
200 | void engine(); |
201 | void thisObject(); |
202 | void arguments(); |
203 | void throwError(); |
204 | void stringConstructor(); |
205 | void numberConstructor(); |
206 | |
207 | private: |
208 | QScriptEngine m_engine; |
209 | MyScriptable m_scriptable; |
210 | }; |
211 | |
212 | tst_QScriptable::tst_QScriptable() |
213 | { |
214 | } |
215 | |
216 | tst_QScriptable::~tst_QScriptable() |
217 | { |
218 | } |
219 | |
220 | void tst_QScriptable::initTestCase() |
221 | { |
222 | QScriptValue obj = m_engine.newQObject(object: &m_scriptable); |
223 | m_engine.globalObject().setProperty(name: "scriptable" , value: obj); |
224 | } |
225 | |
226 | void tst_QScriptable::cleanupTestCase() |
227 | { |
228 | } |
229 | |
230 | void tst_QScriptable::engine() |
231 | { |
232 | QCOMPARE(m_scriptable.engine(), (QScriptEngine*)0); |
233 | QCOMPARE(m_scriptable.context(), (QScriptContext*)0); |
234 | QCOMPARE(m_scriptable.lastEngine(), (QScriptEngine *)0); |
235 | |
236 | // reading property |
237 | { |
238 | QScriptValue ret = m_engine.evaluate(program: "scriptable.baz" ); |
239 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, 123)), true); |
240 | } |
241 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
242 | { |
243 | QScriptValue ret = m_engine.evaluate(program: "scriptable[0]" ); |
244 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, 123)), true); |
245 | } |
246 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
247 | // when reading from C++, engine() should be 0 |
248 | (void)m_scriptable.property(name: "baz" ); |
249 | QCOMPARE(m_scriptable.lastEngine(), (QScriptEngine *)0); |
250 | |
251 | // writing property |
252 | m_engine.evaluate(program: "scriptable.baz = 123" ); |
253 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
254 | (void)m_scriptable.setProperty(name: "baz" , value: 123); |
255 | QCOMPARE(m_scriptable.lastEngine(), (QScriptEngine *)0); |
256 | |
257 | // calling slot |
258 | m_engine.evaluate(program: "scriptable.setX(123)" ); |
259 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
260 | QCOMPARE(m_engine.evaluate("scriptable.x" ) |
261 | .strictlyEquals(QScriptValue(&m_engine, 123)), true); |
262 | (void)m_scriptable.setProperty(name: "baz" , value: 123); |
263 | QCOMPARE(m_scriptable.lastEngine(), (QScriptEngine *)0); |
264 | |
265 | // calling overloaded slot |
266 | m_engine.evaluate(program: "scriptable.setX('123')" ); |
267 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
268 | QCOMPARE(m_engine.evaluate("scriptable.x" ) |
269 | .strictlyEquals(QScriptValue(&m_engine, QLatin1String("123" ))), true); |
270 | |
271 | // calling a slot from another slot |
272 | m_engine.evaluate(program: "scriptable.evalIsBar()" ); |
273 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
274 | |
275 | // calling a slot that registers m_scriptable in a different engine |
276 | // and calls evaluate() |
277 | { |
278 | QScriptValue ret = m_engine.evaluate(program: "scriptable.useInAnotherEngine()" ); |
279 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
280 | } |
281 | } |
282 | |
283 | void tst_QScriptable::thisObject() |
284 | { |
285 | QVERIFY(!m_scriptable.thisObject().isValid()); |
286 | |
287 | m_engine.evaluate(program: "o = { }" ); |
288 | { |
289 | QScriptValue ret = m_engine.evaluate(program: "o.__proto__ = scriptable;" |
290 | "o.setX(123);" |
291 | "o.__proto__ = Object.prototype;" |
292 | "o.x" ); |
293 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
294 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, 123)), true); |
295 | } |
296 | { |
297 | QScriptValue ret = m_engine.evaluate(program: "o.__proto__ = scriptable;" |
298 | "o.setX2(456);" |
299 | "o.__proto__ = Object.prototype;" |
300 | "o.x" ); |
301 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
302 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, 456)), true); |
303 | } |
304 | m_engine.evaluate(program: "o.__proto__ = scriptable" ); |
305 | { |
306 | QScriptValue ret = m_engine.evaluate(program: "o.isBar()" ); |
307 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
308 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, false)), true); |
309 | } |
310 | { |
311 | QScriptValue ret = m_engine.evaluate(program: "o.toString = function() { return 'foo@bar'; }; o.isBar()" ); |
312 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
313 | QCOMPARE(ret.strictlyEquals(QScriptValue(&m_engine, true)), true); |
314 | } |
315 | |
316 | // property getter |
317 | { |
318 | QScriptValue ret = m_engine.evaluate(program: "scriptable.zab" ); |
319 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
320 | QCOMPARE(ret.isQObject(), true); |
321 | QCOMPARE(ret.toQObject(), (QObject *)&m_scriptable); |
322 | } |
323 | { |
324 | QScriptValue ret = m_engine.evaluate(program: "scriptable[1]" ); |
325 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
326 | QCOMPARE(ret.isQObject(), true); |
327 | QCOMPARE(ret.toQObject(), (QObject *)&m_scriptable); |
328 | } |
329 | { |
330 | QScriptValue ret = m_engine.evaluate(program: "o.zab" ); |
331 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
332 | QCOMPARE(ret.toQObject(), (QObject *)0); |
333 | } |
334 | // property setter |
335 | { |
336 | QScriptValue ret = m_engine.evaluate(program: "scriptable.setZab(null)" ); |
337 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
338 | QCOMPARE(ret.isQObject(), true); |
339 | QCOMPARE(ret.toQObject(), (QObject *)&m_scriptable); |
340 | } |
341 | { |
342 | QVERIFY(!m_scriptable.oofThisObject().isValid()); |
343 | m_engine.evaluate(program: "o.oof = 123" ); |
344 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
345 | QVERIFY(m_scriptable.oofThisObject().strictlyEquals(m_engine.evaluate("o" ))); |
346 | } |
347 | { |
348 | m_engine.evaluate(program: "scriptable.oof = 123" ); |
349 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
350 | QVERIFY(m_scriptable.oofThisObject().strictlyEquals(m_engine.evaluate("scriptable" ))); |
351 | } |
352 | |
353 | // target of signal |
354 | { |
355 | { |
356 | QScriptValue ret = m_engine.evaluate(program: "scriptable.sig.connect(o, scriptable.setX)" ); |
357 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
358 | QVERIFY(ret.isUndefined()); |
359 | } |
360 | QVERIFY(m_engine.evaluate("o.x" ).strictlyEquals(QScriptValue(&m_engine, 456))); |
361 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
362 | m_scriptable.emitSig(value: 654321); |
363 | QVERIFY(m_engine.evaluate("o.x" ).strictlyEquals(QScriptValue(&m_engine, 654321))); |
364 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
365 | { |
366 | QScriptValue ret = m_engine.evaluate(program: "scriptable.sig.disconnect(o, scriptable.setX)" ); |
367 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
368 | QVERIFY(ret.isUndefined()); |
369 | } |
370 | } |
371 | |
372 | m_engine.evaluate(program: "delete o" ); |
373 | } |
374 | |
375 | void tst_QScriptable::arguments() |
376 | { |
377 | // even though the C++ slot accepts zero arguments, it should |
378 | // still be invoked; the arguments should be accessible through |
379 | // the QScriptable API |
380 | QScriptValue args = m_engine.evaluate(program: "scriptable.getArguments(10, 20, 30, 'hi')" ); |
381 | QVERIFY(args.property("length" ).strictlyEquals(QScriptValue(&m_engine, 4))); |
382 | QVERIFY(args.property("0" ).strictlyEquals(QScriptValue(&m_engine, 10))); |
383 | QVERIFY(args.property("1" ).strictlyEquals(QScriptValue(&m_engine, 20))); |
384 | QVERIFY(args.property("2" ).strictlyEquals(QScriptValue(&m_engine, 30))); |
385 | QVERIFY(args.property("3" ).strictlyEquals(QScriptValue(&m_engine, "hi" ))); |
386 | |
387 | QScriptValue argc = m_engine.evaluate(program: "scriptable.getArgumentCount(1, 2, 3)" ); |
388 | QVERIFY(argc.isNumber()); |
389 | QCOMPARE(argc.toInt32(), 3); |
390 | |
391 | QCOMPARE(m_scriptable.argumentCount(), -1); |
392 | QVERIFY(!m_scriptable.argument(-1).isValid()); |
393 | QVERIFY(!m_scriptable.argument(0).isValid()); |
394 | } |
395 | |
396 | void tst_QScriptable::throwError() |
397 | { |
398 | QScriptValue ret = m_engine.evaluate(program: "scriptable.foo()" ); |
399 | QCOMPARE(m_scriptable.lastEngine(), &m_engine); |
400 | QCOMPARE(ret.isError(), true); |
401 | QCOMPARE(ret.toString(), QString("Error: MyScriptable.foo" )); |
402 | } |
403 | |
404 | void tst_QScriptable::stringConstructor() |
405 | { |
406 | m_scriptable.setObjectName("TestObject" ); |
407 | |
408 | m_engine.globalObject().setProperty(name: "js_obj" , value: m_engine.newObject()); |
409 | m_engine.evaluate( |
410 | program: "js_obj.str = scriptable.toString();" |
411 | "js_obj.toString = function() { return this.str }" ); |
412 | |
413 | QCOMPARE(m_engine.evaluate("String(scriptable)" ).toString(), |
414 | m_engine.evaluate("String(js_obj)" ).toString()); |
415 | |
416 | QCOMPARE(m_engine.evaluate("String(scriptable)" ).toString(), |
417 | m_engine.evaluate("scriptable.toString()" ).toString()); |
418 | } |
419 | |
420 | void tst_QScriptable::numberConstructor() |
421 | { |
422 | m_engine.globalObject().setProperty(name: "js_obj" , value: m_engine.newObject()); |
423 | m_engine.evaluate( |
424 | program: "js_obj.num = scriptable.valueOf();" |
425 | "js_obj.valueOf = function() { return this.num }" ); |
426 | |
427 | QCOMPARE(m_engine.evaluate("Number(scriptable)" ).toInt32(), |
428 | m_engine.evaluate("Number(js_obj)" ).toInt32()); |
429 | |
430 | QCOMPARE(m_engine.evaluate("Number(scriptable)" ).toInt32(), |
431 | m_engine.evaluate("scriptable.valueOf()" ).toInt32()); |
432 | } |
433 | |
434 | QTEST_MAIN(tst_QScriptable) |
435 | #include "tst_qscriptable.moc" |
436 | |