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 | |
32 | #include <QtScript/qscriptengine.h> |
33 | #include <QtScript/qscriptclass.h> |
34 | #include <QtScript/qscriptclasspropertyiterator.h> |
35 | #include <QtScript/qscriptstring.h> |
36 | #include <QtScript/qscriptvalueiterator.h> |
37 | |
38 | Q_DECLARE_METATYPE(QScriptContext*) |
39 | Q_DECLARE_METATYPE(QScriptValueList) |
40 | Q_DECLARE_METATYPE(QScriptValue) |
41 | |
42 | class tst_QScriptClass : public QObject |
43 | { |
44 | Q_OBJECT |
45 | |
46 | public: |
47 | tst_QScriptClass(); |
48 | virtual ~tst_QScriptClass(); |
49 | |
50 | private slots: |
51 | void newInstance(); |
52 | void setScriptClassOfExistingObject(); |
53 | void setScriptClassOfNonQtScriptObject(); |
54 | void getAndSetPropertyFromCpp(); |
55 | void getAndSetPropertyFromJS(); |
56 | void deleteUndeletableProperty(); |
57 | void writeReadOnlyProperty(); |
58 | void writePropertyWithoutWriteAccess(); |
59 | void getProperty_invalidValue(); |
60 | void enumerate(); |
61 | void extension_None(); |
62 | void extension_Callable(); |
63 | void extension_Callable_construct(); |
64 | void extension_HasInstance(); |
65 | void originalProperties1(); |
66 | void originalProperties2(); |
67 | void originalProperties3(); |
68 | void originalProperties4(); |
69 | void defaultImplementations(); |
70 | void scriptClassObjectInPrototype(); |
71 | void scriptClassWithNullEngine(); |
72 | void scriptClassInOtherEngine(); |
73 | }; |
74 | |
75 | tst_QScriptClass::tst_QScriptClass() |
76 | { |
77 | } |
78 | |
79 | tst_QScriptClass::~tst_QScriptClass() |
80 | { |
81 | } |
82 | |
83 | |
84 | |
85 | class TestClass : public QScriptClass |
86 | { |
87 | public: |
88 | struct CustomProperty { |
89 | QueryFlags qflags; |
90 | uint id; |
91 | QScriptValue::PropertyFlags pflags; |
92 | QScriptValue value; |
93 | |
94 | CustomProperty(QueryFlags qf, uint i, QScriptValue::PropertyFlags pf, |
95 | const QScriptValue &val) |
96 | : qflags(qf), id(i), pflags(pf), value(val) { } |
97 | }; |
98 | |
99 | enum CallableMode { |
100 | NotCallable, |
101 | CallableReturnsSum, |
102 | CallableReturnsArgument, |
103 | CallableReturnsInvalidVariant, |
104 | CallableReturnsGlobalObject, |
105 | CallableReturnsThisObject, |
106 | CallableReturnsCallee, |
107 | CallableReturnsArgumentsObject, |
108 | CallableInitializesThisObject |
109 | }; |
110 | |
111 | TestClass(QScriptEngine *engine); |
112 | ~TestClass(); |
113 | |
114 | void addCustomProperty(const QScriptString &name, QueryFlags qflags, |
115 | uint id, QScriptValue::PropertyFlags pflags, |
116 | const QScriptValue &value); |
117 | void removeCustomProperty(const QScriptString &name); |
118 | |
119 | QueryFlags queryProperty(const QScriptValue &object, |
120 | const QScriptString &name, |
121 | QueryFlags flags, uint *id); |
122 | |
123 | QScriptValue property(const QScriptValue &object, |
124 | const QScriptString &name, uint id); |
125 | |
126 | void setProperty(QScriptValue &object, const QScriptString &name, |
127 | uint id, const QScriptValue &value); |
128 | |
129 | QScriptValue::PropertyFlags propertyFlags( |
130 | const QScriptValue &object, const QScriptString &name, uint id); |
131 | |
132 | QScriptClassPropertyIterator *newIterator(const QScriptValue &object); |
133 | |
134 | QScriptValue prototype() const; |
135 | |
136 | QString name() const; |
137 | |
138 | bool supportsExtension(Extension extension) const; |
139 | QVariant extension(Extension extension, |
140 | const QVariant &argument = QVariant()); |
141 | |
142 | QScriptValue lastQueryPropertyObject() const; |
143 | QScriptString lastQueryPropertyName() const; |
144 | QueryFlags lastQueryPropertyFlags() const; |
145 | |
146 | QScriptValue lastPropertyObject() const; |
147 | QScriptString lastPropertyName() const; |
148 | uint lastPropertyId() const; |
149 | |
150 | QScriptValue lastSetPropertyObject() const; |
151 | QScriptString lastSetPropertyName() const; |
152 | uint lastSetPropertyId() const; |
153 | QScriptValue lastSetPropertyValue() const; |
154 | |
155 | QScriptValue lastPropertyFlagsObject() const; |
156 | QScriptString lastPropertyFlagsName() const; |
157 | uint lastPropertyFlagsId() const; |
158 | |
159 | QScriptClass::Extension lastExtensionType() const; |
160 | QVariant lastExtensionArgument() const; |
161 | |
162 | void clearReceivedArgs(); |
163 | |
164 | void setIterationEnabled(bool enable); |
165 | bool isIterationEnabled() const; |
166 | |
167 | void setCallableMode(CallableMode mode); |
168 | CallableMode callableMode() const; |
169 | |
170 | void setHasInstance(bool hasInstance); |
171 | bool hasInstance() const; |
172 | |
173 | private: |
174 | CustomProperty *findCustomProperty(const QScriptString &name); |
175 | |
176 | QHash<QScriptString, CustomProperty*> customProperties; |
177 | |
178 | QScriptValue m_lastQueryPropertyObject; |
179 | QScriptString m_lastQueryPropertyName; |
180 | QScriptClass::QueryFlags m_lastQueryPropertyFlags; |
181 | |
182 | QScriptValue m_lastPropertyObject; |
183 | QScriptString m_lastPropertyName; |
184 | uint m_lastPropertyId; |
185 | |
186 | QScriptValue m_lastSetPropertyObject; |
187 | QScriptString m_lastSetPropertyName; |
188 | uint m_lastSetPropertyId; |
189 | QScriptValue m_lastSetPropertyValue; |
190 | |
191 | QScriptValue m_lastPropertyFlagsObject; |
192 | QScriptString m_lastPropertyFlagsName; |
193 | uint m_lastPropertyFlagsId; |
194 | |
195 | QScriptClass::Extension m_lastExtensionType; |
196 | QVariant m_lastExtensionArgument; |
197 | |
198 | QScriptValue m_prototype; |
199 | bool m_iterationEnabled; |
200 | CallableMode m_callableMode; |
201 | bool m_hasInstance; |
202 | }; |
203 | |
204 | class TestClassPropertyIterator : public QScriptClassPropertyIterator |
205 | { |
206 | public: |
207 | TestClassPropertyIterator(const QHash<QScriptString, TestClass::CustomProperty*> &props, |
208 | const QScriptValue &object); |
209 | ~TestClassPropertyIterator(); |
210 | |
211 | bool hasNext() const; |
212 | void next(); |
213 | |
214 | bool hasPrevious() const; |
215 | void previous(); |
216 | |
217 | void toFront(); |
218 | void toBack(); |
219 | |
220 | QScriptString name() const; |
221 | uint id() const; |
222 | QScriptValue::PropertyFlags flags() const; |
223 | |
224 | private: |
225 | int m_index; |
226 | int m_last; |
227 | QHash<QScriptString, TestClass::CustomProperty*> m_props; |
228 | }; |
229 | |
230 | |
231 | |
232 | TestClass::TestClass(QScriptEngine *engine) |
233 | : QScriptClass(engine), m_iterationEnabled(true), |
234 | m_callableMode(NotCallable), m_hasInstance(false) |
235 | { |
236 | m_prototype = engine->newObject(); |
237 | clearReceivedArgs(); |
238 | } |
239 | |
240 | TestClass::~TestClass() |
241 | { |
242 | qDeleteAll(c: customProperties); |
243 | } |
244 | |
245 | TestClass::CustomProperty* TestClass::findCustomProperty(const QScriptString &name) |
246 | { |
247 | QHash<QScriptString, CustomProperty*>::const_iterator it; |
248 | it = customProperties.constFind(akey: name); |
249 | if (it == customProperties.constEnd()) |
250 | return 0; |
251 | return it.value(); |
252 | |
253 | } |
254 | |
255 | void TestClass::addCustomProperty(const QScriptString &name, QueryFlags qflags, |
256 | uint id, QScriptValue::PropertyFlags pflags, |
257 | const QScriptValue &value) |
258 | { |
259 | customProperties.insert(akey: name, avalue: new CustomProperty(qflags, id, pflags, value)); |
260 | } |
261 | |
262 | void TestClass::removeCustomProperty(const QScriptString &name) |
263 | { |
264 | CustomProperty *prop = customProperties.take(akey: name); |
265 | if (prop) |
266 | delete prop; |
267 | } |
268 | |
269 | QScriptClass::QueryFlags TestClass::queryProperty(const QScriptValue &object, |
270 | const QScriptString &name, |
271 | QueryFlags flags, uint *id) |
272 | { |
273 | m_lastQueryPropertyObject = object; |
274 | m_lastQueryPropertyName = name; |
275 | m_lastQueryPropertyFlags = flags; |
276 | CustomProperty *prop = findCustomProperty(name); |
277 | if (!prop) |
278 | return {}; |
279 | *id = prop->id; |
280 | return prop->qflags & flags; |
281 | } |
282 | |
283 | QScriptValue TestClass::property(const QScriptValue &object, |
284 | const QScriptString &name, uint id) |
285 | { |
286 | m_lastPropertyObject = object; |
287 | m_lastPropertyName = name; |
288 | m_lastPropertyId = id; |
289 | CustomProperty *prop = findCustomProperty(name); |
290 | if (!prop) |
291 | return QScriptValue(); |
292 | return prop->value; |
293 | } |
294 | |
295 | void TestClass::setProperty(QScriptValue &object, const QScriptString &name, |
296 | uint id, const QScriptValue &value) |
297 | { |
298 | m_lastSetPropertyObject = object; |
299 | m_lastSetPropertyName = name; |
300 | m_lastSetPropertyId = id; |
301 | m_lastSetPropertyValue = value; |
302 | CustomProperty *prop = findCustomProperty(name); |
303 | if (!prop) |
304 | return; |
305 | if (prop->pflags & QScriptValue::ReadOnly) |
306 | return; |
307 | if (!value.isValid()) // deleteProperty() requested |
308 | removeCustomProperty(name); |
309 | else |
310 | prop->value = value; |
311 | } |
312 | |
313 | QScriptValue::PropertyFlags TestClass::propertyFlags( |
314 | const QScriptValue &object, const QScriptString &name, uint id) |
315 | { |
316 | m_lastPropertyFlagsObject = object; |
317 | m_lastPropertyFlagsName = name; |
318 | m_lastPropertyFlagsId = id; |
319 | CustomProperty *prop = findCustomProperty(name); |
320 | if (!prop) |
321 | return {}; |
322 | return prop->pflags; |
323 | } |
324 | |
325 | QScriptClassPropertyIterator *TestClass::newIterator(const QScriptValue &object) |
326 | { |
327 | if (!m_iterationEnabled) |
328 | return {}; |
329 | return new TestClassPropertyIterator(customProperties, object); |
330 | } |
331 | |
332 | QScriptValue TestClass::prototype() const |
333 | { |
334 | return m_prototype; |
335 | } |
336 | |
337 | QString TestClass::name() const |
338 | { |
339 | return QLatin1String("TestClass" ); |
340 | } |
341 | |
342 | bool TestClass::supportsExtension(Extension extension) const |
343 | { |
344 | if (extension == Callable) |
345 | return (m_callableMode != NotCallable); |
346 | if (extension == HasInstance) |
347 | return m_hasInstance; |
348 | return false; |
349 | } |
350 | |
351 | QVariant TestClass::extension(Extension extension, |
352 | const QVariant &argument) |
353 | { |
354 | m_lastExtensionType = extension; |
355 | m_lastExtensionArgument = argument; |
356 | if (extension == Callable && m_callableMode != NotCallable) { |
357 | QScriptContext *ctx = qvariant_cast<QScriptContext*>(v: argument); |
358 | if (m_callableMode == CallableReturnsSum) { |
359 | qsreal sum = 0; |
360 | for (int i = 0; i < ctx->argumentCount(); ++i) |
361 | sum += ctx->argument(index: i).toNumber(); |
362 | QScriptValueIterator it(ctx->callee()); |
363 | while (it.hasNext()) { |
364 | it.next(); |
365 | sum += it.value().toNumber(); |
366 | } |
367 | return sum; |
368 | } else if (m_callableMode == CallableReturnsArgument) { |
369 | return QVariant::fromValue(value: ctx->argument(index: 0)); |
370 | } else if (m_callableMode == CallableReturnsInvalidVariant) { |
371 | return QVariant(); |
372 | } else if (m_callableMode == CallableReturnsGlobalObject) { |
373 | return QVariant::fromValue(value: engine()->globalObject()); |
374 | } else if (m_callableMode == CallableReturnsThisObject) { |
375 | return QVariant::fromValue(value: ctx->thisObject()); |
376 | } else if (m_callableMode == CallableReturnsCallee) { |
377 | return QVariant::fromValue(value: ctx->callee()); |
378 | } else if (m_callableMode == CallableReturnsArgumentsObject) { |
379 | return QVariant::fromValue(value: ctx->argumentsObject()); |
380 | } else if (m_callableMode == CallableInitializesThisObject) { |
381 | engine()->newQObject(scriptObject: ctx->thisObject(), qtObject: engine()); |
382 | return QVariant(); |
383 | } |
384 | } else if (extension == HasInstance && m_hasInstance) { |
385 | QScriptValueList args = qvariant_cast<QScriptValueList>(v: argument); |
386 | QScriptValue obj = args.at(i: 0); |
387 | QScriptValue value = args.at(i: 1); |
388 | return value.property(name: "foo" ).equals(other: obj.property(name: "foo" )); |
389 | } |
390 | return QVariant(); |
391 | } |
392 | |
393 | QScriptValue TestClass::lastQueryPropertyObject() const |
394 | { |
395 | return m_lastQueryPropertyObject; |
396 | } |
397 | |
398 | QScriptString TestClass::lastQueryPropertyName() const |
399 | { |
400 | return m_lastQueryPropertyName; |
401 | } |
402 | |
403 | QScriptClass::QueryFlags TestClass::lastQueryPropertyFlags() const |
404 | { |
405 | return m_lastQueryPropertyFlags; |
406 | } |
407 | |
408 | QScriptValue TestClass::lastPropertyObject() const |
409 | { |
410 | return m_lastPropertyObject; |
411 | } |
412 | |
413 | QScriptString TestClass::lastPropertyName() const |
414 | { |
415 | return m_lastPropertyName; |
416 | } |
417 | |
418 | uint TestClass::lastPropertyId() const |
419 | { |
420 | return m_lastPropertyId; |
421 | } |
422 | |
423 | QScriptValue TestClass::lastSetPropertyObject() const |
424 | { |
425 | return m_lastSetPropertyObject; |
426 | } |
427 | |
428 | QScriptString TestClass::lastSetPropertyName() const |
429 | { |
430 | return m_lastSetPropertyName; |
431 | } |
432 | |
433 | uint TestClass::lastSetPropertyId() const |
434 | { |
435 | return m_lastSetPropertyId; |
436 | } |
437 | |
438 | QScriptValue TestClass::lastSetPropertyValue() const |
439 | { |
440 | return m_lastSetPropertyValue; |
441 | } |
442 | |
443 | QScriptValue TestClass::lastPropertyFlagsObject() const |
444 | { |
445 | return m_lastPropertyFlagsObject; |
446 | } |
447 | |
448 | QScriptString TestClass::lastPropertyFlagsName() const |
449 | { |
450 | return m_lastPropertyFlagsName; |
451 | } |
452 | |
453 | uint TestClass::lastPropertyFlagsId() const |
454 | { |
455 | return m_lastPropertyFlagsId; |
456 | } |
457 | |
458 | QScriptClass::Extension TestClass::lastExtensionType() const |
459 | { |
460 | return m_lastExtensionType; |
461 | } |
462 | |
463 | QVariant TestClass::lastExtensionArgument() const |
464 | { |
465 | return m_lastExtensionArgument; |
466 | } |
467 | |
468 | void TestClass::clearReceivedArgs() |
469 | { |
470 | m_lastQueryPropertyObject = QScriptValue(); |
471 | m_lastQueryPropertyName = QScriptString(); |
472 | m_lastQueryPropertyFlags = {}; |
473 | |
474 | m_lastPropertyObject = QScriptValue(); |
475 | m_lastPropertyName = QScriptString(); |
476 | m_lastPropertyId = uint(-1); |
477 | |
478 | m_lastSetPropertyObject = QScriptValue(); |
479 | m_lastSetPropertyName = QScriptString(); |
480 | m_lastSetPropertyId = uint(-1); |
481 | m_lastSetPropertyValue = QScriptValue(); |
482 | |
483 | m_lastPropertyFlagsObject = QScriptValue(); |
484 | m_lastPropertyFlagsName = QScriptString(); |
485 | m_lastPropertyFlagsId = uint(-1); |
486 | |
487 | m_lastExtensionType = static_cast<QScriptClass::Extension>(-1); |
488 | m_lastExtensionArgument = QVariant(); |
489 | } |
490 | |
491 | void TestClass::setIterationEnabled(bool enable) |
492 | { |
493 | m_iterationEnabled = enable; |
494 | } |
495 | |
496 | bool TestClass::isIterationEnabled() const |
497 | { |
498 | return m_iterationEnabled; |
499 | } |
500 | |
501 | void TestClass::setCallableMode(CallableMode mode) |
502 | { |
503 | m_callableMode = mode; |
504 | } |
505 | |
506 | TestClass::CallableMode TestClass::callableMode() const |
507 | { |
508 | return m_callableMode; |
509 | } |
510 | |
511 | void TestClass::setHasInstance(bool hasInstance) |
512 | { |
513 | m_hasInstance = hasInstance; |
514 | } |
515 | |
516 | bool TestClass::hasInstance() const |
517 | { |
518 | return m_hasInstance; |
519 | } |
520 | |
521 | |
522 | TestClassPropertyIterator::TestClassPropertyIterator(const QHash<QScriptString, TestClass::CustomProperty*> &props, |
523 | const QScriptValue &object) |
524 | : QScriptClassPropertyIterator(object) |
525 | { |
526 | m_props = props; |
527 | toFront(); |
528 | } |
529 | |
530 | TestClassPropertyIterator::~TestClassPropertyIterator() |
531 | { |
532 | } |
533 | |
534 | bool TestClassPropertyIterator::hasNext() const |
535 | { |
536 | return m_index < m_props.size(); |
537 | } |
538 | |
539 | void TestClassPropertyIterator::next() |
540 | { |
541 | m_last = m_index; |
542 | ++m_index; |
543 | } |
544 | |
545 | bool TestClassPropertyIterator::hasPrevious() const |
546 | { |
547 | return m_index > 0; |
548 | } |
549 | |
550 | void TestClassPropertyIterator::previous() |
551 | { |
552 | --m_index; |
553 | m_last = m_index; |
554 | } |
555 | |
556 | void TestClassPropertyIterator::toFront() |
557 | { |
558 | m_index = 0; |
559 | m_last = -1; |
560 | } |
561 | |
562 | void TestClassPropertyIterator::toBack() |
563 | { |
564 | m_index = m_props.size(); |
565 | m_last = -1; |
566 | } |
567 | |
568 | QScriptString TestClassPropertyIterator::name() const |
569 | { |
570 | return m_props.keys().value(i: m_last); |
571 | } |
572 | |
573 | uint TestClassPropertyIterator::id() const |
574 | { |
575 | QScriptString key = m_props.keys().value(i: m_last); |
576 | if (!key.isValid()) |
577 | return 0; |
578 | TestClass::CustomProperty *prop = m_props.value(akey: key); |
579 | return prop->id; |
580 | } |
581 | |
582 | QScriptValue::PropertyFlags TestClassPropertyIterator::flags() const |
583 | { |
584 | QScriptString key = m_props.keys().value(i: m_last); |
585 | if (!key.isValid()) |
586 | return {}; |
587 | TestClass::CustomProperty *prop = m_props.value(akey: key); |
588 | return prop->pflags; |
589 | } |
590 | |
591 | |
592 | |
593 | void tst_QScriptClass::newInstance() |
594 | { |
595 | QScriptEngine eng; |
596 | |
597 | TestClass cls(&eng); |
598 | |
599 | QScriptValue obj1 = eng.newObject(scriptClass: &cls); |
600 | QVERIFY(!obj1.data().isValid()); |
601 | QVERIFY(obj1.prototype().strictlyEquals(cls.prototype())); |
602 | QEXPECT_FAIL("" , "QTBUG-17599: classname is not implemented" , Continue); |
603 | QCOMPARE(obj1.toString(), QString::fromLatin1("[object TestClass]" )); |
604 | QCOMPARE(obj1.scriptClass(), (QScriptClass*)&cls); |
605 | |
606 | QScriptValue num(&eng, 456); |
607 | QScriptValue obj2 = eng.newObject(scriptClass: &cls, data: num); |
608 | QVERIFY(obj2.data().strictlyEquals(num)); |
609 | QVERIFY(obj2.prototype().strictlyEquals(cls.prototype())); |
610 | QCOMPARE(obj2.scriptClass(), (QScriptClass*)&cls); |
611 | QVERIFY(!obj2.equals(obj1)); |
612 | QVERIFY(!obj2.strictlyEquals(obj1)); |
613 | } |
614 | |
615 | void tst_QScriptClass::setScriptClassOfExistingObject() |
616 | { |
617 | QScriptEngine eng; |
618 | TestClass cls(&eng); |
619 | QScriptValue obj3 = eng.newObject(); |
620 | QCOMPARE(obj3.scriptClass(), (QScriptClass*)0); |
621 | obj3.setScriptClass(&cls); |
622 | QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls); |
623 | |
624 | obj3.setScriptClass(0); |
625 | QCOMPARE(obj3.scriptClass(), (QScriptClass*)0); |
626 | obj3.setScriptClass(&cls); |
627 | QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls); |
628 | |
629 | TestClass cls2(&eng); |
630 | obj3.setScriptClass(&cls2); |
631 | QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls2); |
632 | } |
633 | |
634 | void tst_QScriptClass::setScriptClassOfNonQtScriptObject() |
635 | { |
636 | QScriptEngine eng; |
637 | TestClass cls(&eng); |
638 | // undefined behavior really, but shouldn't crash |
639 | QScriptValue arr = eng.newArray(); |
640 | QVERIFY(arr.isArray()); |
641 | QCOMPARE(arr.scriptClass(), (QScriptClass*)0); |
642 | QTest::ignoreMessage(type: QtWarningMsg, message: "QScriptValue::setScriptClass() failed: cannot change class of non-QScriptObject" ); |
643 | arr.setScriptClass(&cls); |
644 | QEXPECT_FAIL("" , "Changing class of arbitrary script object is not allowed (it's OK)" , Continue); |
645 | QCOMPARE(arr.scriptClass(), (QScriptClass*)&cls); |
646 | QEXPECT_FAIL("" , "Changing class of arbitrary script object is not allowed (it's OK)" , Continue); |
647 | QVERIFY(!arr.isArray()); |
648 | QVERIFY(arr.isObject()); |
649 | } |
650 | |
651 | void tst_QScriptClass::getAndSetPropertyFromCpp() |
652 | { |
653 | QScriptEngine eng; |
654 | |
655 | TestClass cls(&eng); |
656 | |
657 | QScriptValue obj1 = eng.newObject(scriptClass: &cls); |
658 | QScriptValue obj2 = eng.newObject(scriptClass: &cls); |
659 | QScriptString foo = eng.toStringHandle(str: "foo" ); |
660 | QScriptString bar = eng.toStringHandle(str: "bar" ); |
661 | QScriptValue num(&eng, 123); |
662 | |
663 | // Initially our TestClass instances have no custom properties, |
664 | // and queryProperty() will always return false. |
665 | // Hence, the properties will be created as normal JS properties. |
666 | for (int x = 0; x < 2; ++x) { |
667 | QScriptValue &o = (x == 0) ? obj1 : obj2; |
668 | for (int y = 0; y < 2; ++y) { |
669 | QScriptString &s = (y == 0) ? foo : bar; |
670 | |
671 | // read property |
672 | cls.clearReceivedArgs(); |
673 | QScriptValue ret = o.property(name: s); |
674 | QVERIFY(!ret.isValid()); |
675 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(o)); |
676 | QVERIFY(cls.lastQueryPropertyName() == s); |
677 | QVERIFY(!cls.lastPropertyObject().isValid()); |
678 | QVERIFY(!cls.lastSetPropertyObject().isValid()); |
679 | QVERIFY(cls.lastQueryPropertyFlags() == QScriptClass::HandlesReadAccess); |
680 | |
681 | // write property |
682 | cls.clearReceivedArgs(); |
683 | o.setProperty(name: s, value: num); |
684 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(o)); |
685 | QVERIFY(cls.lastQueryPropertyName() == s); |
686 | QVERIFY(!cls.lastPropertyObject().isValid()); |
687 | QVERIFY(!cls.lastSetPropertyObject().isValid()); |
688 | QVERIFY(cls.lastQueryPropertyFlags() == QScriptClass::HandlesWriteAccess); |
689 | |
690 | // re-read property |
691 | // When a QScriptClass doesn't want to handle a property write, |
692 | // that property becomes a normal property and the QScriptClass |
693 | // shall not be queried about it again. |
694 | cls.clearReceivedArgs(); |
695 | QVERIFY(o.property(s).strictlyEquals(num)); |
696 | QVERIFY(!cls.lastQueryPropertyObject().isValid()); |
697 | } |
698 | } |
699 | |
700 | // add a custom property |
701 | QScriptString foo2 = eng.toStringHandle(str: "foo2" ); |
702 | const uint foo2Id = 123; |
703 | const QScriptValue::PropertyFlags foo2Pflags = QScriptValue::Undeletable; |
704 | QScriptValue foo2Value(&eng, 456); |
705 | cls.addCustomProperty(name: foo2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, |
706 | id: foo2Id, pflags: foo2Pflags, value: foo2Value); |
707 | |
708 | { |
709 | // read property |
710 | cls.clearReceivedArgs(); |
711 | { |
712 | QScriptValue ret = obj1.property(name: foo2); |
713 | QVERIFY(ret.strictlyEquals(foo2Value)); |
714 | } |
715 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
716 | QVERIFY(cls.lastQueryPropertyName() == foo2); |
717 | QVERIFY(cls.lastPropertyObject().strictlyEquals(obj1)); |
718 | QVERIFY(cls.lastPropertyName() == foo2); |
719 | QCOMPARE(cls.lastPropertyId(), foo2Id); |
720 | |
721 | // read flags |
722 | cls.clearReceivedArgs(); |
723 | QCOMPARE(obj1.propertyFlags(foo2), foo2Pflags); |
724 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
725 | QVERIFY(cls.lastQueryPropertyName() == foo2); |
726 | QEXPECT_FAIL("" , "QTBUG-17601: classObject.getOwnPropertyDescriptor() reads the property value" , Continue); |
727 | QVERIFY(!cls.lastPropertyObject().isValid()); |
728 | QVERIFY(cls.lastPropertyFlagsObject().strictlyEquals(obj1)); |
729 | QVERIFY(cls.lastPropertyFlagsName() == foo2); |
730 | QCOMPARE(cls.lastPropertyFlagsId(), foo2Id); |
731 | |
732 | // write property |
733 | cls.clearReceivedArgs(); |
734 | QScriptValue newFoo2Value(&eng, 789); |
735 | obj1.setProperty(name: foo2, value: newFoo2Value); |
736 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
737 | QVERIFY(cls.lastQueryPropertyName() == foo2); |
738 | |
739 | // read property again |
740 | cls.clearReceivedArgs(); |
741 | { |
742 | QScriptValue ret = obj1.property(name: foo2); |
743 | QVERIFY(ret.strictlyEquals(newFoo2Value)); |
744 | } |
745 | QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
746 | QVERIFY(cls.lastQueryPropertyName() == foo2); |
747 | QVERIFY(cls.lastPropertyObject().strictlyEquals(obj1)); |
748 | QVERIFY(cls.lastPropertyName() == foo2); |
749 | QCOMPARE(cls.lastPropertyId(), foo2Id); |
750 | } |
751 | |
752 | // attempt to delete custom property |
753 | obj1.setProperty(name: foo2, value: QScriptValue()); |
754 | // delete real property |
755 | obj1.setProperty(name: foo, value: QScriptValue()); |
756 | QVERIFY(!obj1.property(foo).isValid()); |
757 | obj1.setProperty(name: foo, value: num); |
758 | QVERIFY(obj1.property(foo).equals(num)); |
759 | |
760 | // remove script class; normal properties should remain |
761 | obj1.setScriptClass(0); |
762 | QCOMPARE(obj1.scriptClass(), (QScriptClass*)0); |
763 | QVERIFY(obj1.property(foo).equals(num)); |
764 | QVERIFY(obj1.property(bar).equals(num)); |
765 | obj1.setProperty(name: foo, value: QScriptValue()); |
766 | QVERIFY(!obj1.property(foo).isValid()); |
767 | obj1.setProperty(name: bar, value: QScriptValue()); |
768 | QVERIFY(!obj1.property(bar).isValid()); |
769 | } |
770 | |
771 | void tst_QScriptClass::getAndSetPropertyFromJS() |
772 | { |
773 | QScriptEngine eng; |
774 | TestClass cls(&eng); |
775 | cls.addCustomProperty(name: eng.toStringHandle(str: "x" ), |
776 | qflags: QScriptClass::HandlesReadAccess |
777 | | QScriptClass::HandlesWriteAccess, |
778 | /*id=*/ 1, pflags: QScriptValue::PropertyFlags{}, /*value=*/ 123); |
779 | eng.globalObject().setProperty(name: "o" , value: eng.newObject(scriptClass: &cls)); |
780 | |
781 | // Accessing a custom property |
782 | QCOMPARE(eng.evaluate("o.x" ).toInt32(), 123); |
783 | QCOMPARE(eng.evaluate("o.x = 456; o.x" ).toInt32(), 456); |
784 | |
785 | // Accessing a new JS property |
786 | QVERIFY(eng.evaluate("o.y" ).isUndefined()); |
787 | QCOMPARE(eng.evaluate("o.y = 789; o.y" ).toInt32(), 789); |
788 | |
789 | // Deleting custom property |
790 | QVERIFY(eng.evaluate("delete o.x" ).toBool()); |
791 | QVERIFY(eng.evaluate("o.x" ).isUndefined()); |
792 | |
793 | // Deleting JS property |
794 | QVERIFY(eng.evaluate("delete o.y" ).toBool()); |
795 | QVERIFY(eng.evaluate("o.y" ).isUndefined()); |
796 | } |
797 | |
798 | void tst_QScriptClass::deleteUndeletableProperty() |
799 | { |
800 | QScriptEngine eng; |
801 | TestClass cls(&eng); |
802 | cls.addCustomProperty(name: eng.toStringHandle(str: "x" ), qflags: QScriptClass::HandlesWriteAccess, |
803 | /*id=*/0, pflags: QScriptValue::Undeletable, value: QScriptValue()); |
804 | eng.globalObject().setProperty(name: "o" , value: eng.newObject(scriptClass: &cls)); |
805 | QVERIFY(!eng.evaluate("delete o.x" ).toBool()); |
806 | } |
807 | |
808 | void tst_QScriptClass::writeReadOnlyProperty() |
809 | { |
810 | QScriptEngine eng; |
811 | TestClass cls(&eng); |
812 | cls.addCustomProperty(name: eng.toStringHandle(str: "x" ), |
813 | qflags: QScriptClass::HandlesReadAccess |
814 | | QScriptClass::HandlesWriteAccess, |
815 | /*id=*/0, pflags: QScriptValue::ReadOnly, value: 123); |
816 | eng.globalObject().setProperty(name: "o" , value: eng.newObject(scriptClass: &cls)); |
817 | // Note that if a property is read-only, the setProperty() |
818 | // reimplementation will still get called; it's up to that |
819 | // function to respect the ReadOnly flag. |
820 | QCOMPARE(eng.evaluate("o.x = 456; o.x" ).toInt32(), 123); |
821 | } |
822 | |
823 | void tst_QScriptClass::writePropertyWithoutWriteAccess() |
824 | { |
825 | QScriptEngine eng; |
826 | TestClass cls(&eng); |
827 | cls.addCustomProperty(name: eng.toStringHandle(str: "x" ), |
828 | qflags: QScriptClass::HandlesReadAccess, |
829 | /*id=*/ 0, pflags: QScriptValue::PropertyFlags{}, value: 123); |
830 | eng.globalObject().setProperty(name: "o" , value: eng.newObject(scriptClass: &cls)); |
831 | QCOMPARE(eng.evaluate("o.x" ).toInt32(), 123); |
832 | |
833 | // This will create a JS property on the instance that |
834 | // shadows the custom property. |
835 | // This behavior is not documented. It might be more |
836 | // intuitive to treat a property that only handles read |
837 | // access as a read-only, non-shadowable property. |
838 | QCOMPARE(eng.evaluate("o.x = 456; o.x" ).toInt32(), 456); |
839 | |
840 | QVERIFY(eng.evaluate("delete o.x" ).toBool()); |
841 | // Now the custom property is seen again. |
842 | QCOMPARE(eng.evaluate("o.x" ).toInt32(), 123); |
843 | } |
844 | |
845 | void tst_QScriptClass::getProperty_invalidValue() |
846 | { |
847 | QScriptEngine eng; |
848 | TestClass cls(&eng); |
849 | cls.addCustomProperty(name: eng.toStringHandle(str: "foo" ), qflags: QScriptClass::HandlesReadAccess, |
850 | /*id=*/0, pflags: QScriptValue::ReadOnly, value: QScriptValue()); |
851 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
852 | |
853 | QVERIFY(obj.property("foo" ).isUndefined()); |
854 | |
855 | eng.globalObject().setProperty(name: "obj" , value: obj); |
856 | QVERIFY(eng.evaluate("obj.hasOwnProperty('foo'))" ).toBool()); |
857 | // The JS environment expects that a valid value is returned, |
858 | // otherwise we could crash. |
859 | QVERIFY(eng.evaluate("obj.foo" ).isUndefined()); |
860 | QVERIFY(eng.evaluate("obj.foo + ''" ).isString()); |
861 | QVERIFY(eng.evaluate("Object.getOwnPropertyDescriptor(obj, 'foo').value" ).isUndefined()); |
862 | QVERIFY(eng.evaluate("Object.getOwnPropertyDescriptor(obj, 'foo').value +''" ).isString()); |
863 | } |
864 | |
865 | void tst_QScriptClass::enumerate() |
866 | { |
867 | QScriptEngine eng; |
868 | |
869 | TestClass cls(&eng); |
870 | |
871 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
872 | QScriptString foo = eng.toStringHandle(str: "foo" ); |
873 | obj.setProperty(name: foo, value: QScriptValue(&eng, 123)); |
874 | |
875 | cls.setIterationEnabled(false); |
876 | { |
877 | QScriptValueIterator it(obj); |
878 | QVERIFY(it.hasNext()); |
879 | it.next(); |
880 | QVERIFY(it.scriptName() == foo); |
881 | QVERIFY(!it.hasNext()); |
882 | } |
883 | |
884 | // add a custom property |
885 | QScriptString foo2 = eng.toStringHandle(str: "foo2" ); |
886 | const uint foo2Id = 123; |
887 | const QScriptValue::PropertyFlags foo2Pflags = QScriptValue::Undeletable; |
888 | QScriptValue foo2Value(&eng, 456); |
889 | cls.addCustomProperty(name: foo2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, |
890 | id: foo2Id, pflags: foo2Pflags, value: QScriptValue()); |
891 | |
892 | cls.setIterationEnabled(true); |
893 | QScriptValueIterator it(obj); |
894 | // This test relies on the order in which properties are enumerated, |
895 | // which we don't guarantee. However, for compatibility's sake we prefer |
896 | // that normal JS properties come before QScriptClass properties. |
897 | for (int x = 0; x < 2; ++x) { |
898 | QVERIFY(it.hasNext()); |
899 | it.next(); |
900 | QVERIFY(it.scriptName() == foo); |
901 | QVERIFY(it.hasNext()); |
902 | it.next(); |
903 | QVERIFY(it.scriptName() == foo2); |
904 | QCOMPARE(it.flags(), foo2Pflags); |
905 | QVERIFY(!it.hasNext()); |
906 | |
907 | QVERIFY(it.hasPrevious()); |
908 | it.previous(); |
909 | QVERIFY(it.scriptName() == foo2); |
910 | QCOMPARE(it.flags(), foo2Pflags); |
911 | QVERIFY(it.hasPrevious()); |
912 | it.previous(); |
913 | QVERIFY(it.scriptName() == foo); |
914 | QVERIFY(!it.hasPrevious()); |
915 | } |
916 | } |
917 | |
918 | void tst_QScriptClass::extension_None() |
919 | { |
920 | QScriptEngine eng; |
921 | TestClass cls(&eng); |
922 | cls.setCallableMode(TestClass::NotCallable); |
923 | QVERIFY(!cls.supportsExtension(QScriptClass::Callable)); |
924 | QVERIFY(!cls.supportsExtension(QScriptClass::HasInstance)); |
925 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
926 | QVERIFY(!obj.call().isValid()); |
927 | QCOMPARE((int)cls.lastExtensionType(), -1); |
928 | QVERIFY(!obj.instanceOf(obj)); |
929 | QCOMPARE((int)cls.lastExtensionType(), -1); |
930 | QVERIFY(!obj.construct().isValid()); |
931 | } |
932 | |
933 | void tst_QScriptClass::extension_Callable() |
934 | { |
935 | QScriptEngine eng; |
936 | TestClass cls(&eng); |
937 | cls.setCallableMode(TestClass::CallableReturnsSum); |
938 | QVERIFY(cls.supportsExtension(QScriptClass::Callable)); |
939 | |
940 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
941 | eng.globalObject().setProperty(name: "obj" , value: obj); |
942 | obj.setProperty(name: "one" , value: QScriptValue(&eng, 1)); |
943 | obj.setProperty(name: "two" , value: QScriptValue(&eng, 2)); |
944 | obj.setProperty(name: "three" , value: QScriptValue(&eng, 3)); |
945 | // From C++ |
946 | cls.clearReceivedArgs(); |
947 | { |
948 | QScriptValueList args; |
949 | args << QScriptValue(&eng, 4) << QScriptValue(&eng, 5); |
950 | QScriptValue ret = obj.call(thisObject: obj, args); |
951 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
952 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
953 | QVERIFY(ret.isNumber()); |
954 | QCOMPARE(ret.toNumber(), qsreal(1+2+3+4+5)); |
955 | } |
956 | // From JS |
957 | cls.clearReceivedArgs(); |
958 | { |
959 | QScriptValue ret = eng.evaluate(program: "obj(4, 5)" ); |
960 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
961 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
962 | QVERIFY(ret.isNumber()); |
963 | QCOMPARE(ret.toNumber(), qsreal(1+2+3+4+5)); |
964 | } |
965 | |
966 | cls.setCallableMode(TestClass::CallableReturnsArgument); |
967 | // From C++ |
968 | cls.clearReceivedArgs(); |
969 | { |
970 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << 123); |
971 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
972 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
973 | QVERIFY(ret.isNumber()); |
974 | QCOMPARE(ret.toInt32(), 123); |
975 | } |
976 | cls.clearReceivedArgs(); |
977 | { |
978 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << true); |
979 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
980 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
981 | QVERIFY(ret.isBoolean()); |
982 | QCOMPARE(ret.toBoolean(), true); |
983 | } |
984 | { |
985 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << QString::fromLatin1(str: "ciao" )); |
986 | QVERIFY(ret.isString()); |
987 | QCOMPARE(ret.toString(), QString::fromLatin1("ciao" )); |
988 | } |
989 | { |
990 | QScriptValue objobj = eng.newObject(); |
991 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << objobj); |
992 | QVERIFY(ret.isObject()); |
993 | QVERIFY(ret.strictlyEquals(objobj)); |
994 | } |
995 | { |
996 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << QScriptValue()); |
997 | QVERIFY(ret.isUndefined()); |
998 | } |
999 | // From JS |
1000 | cls.clearReceivedArgs(); |
1001 | { |
1002 | QScriptValue ret = eng.evaluate(program: "obj(123)" ); |
1003 | QVERIFY(ret.isNumber()); |
1004 | QCOMPARE(ret.toInt32(), 123); |
1005 | } |
1006 | |
1007 | cls.setCallableMode(TestClass::CallableReturnsInvalidVariant); |
1008 | { |
1009 | QScriptValue ret = obj.call(thisObject: obj); |
1010 | QVERIFY(ret.isUndefined()); |
1011 | } |
1012 | |
1013 | cls.setCallableMode(TestClass::CallableReturnsThisObject); |
1014 | // From C++ |
1015 | { |
1016 | QScriptValue ret = obj.call(thisObject: obj); |
1017 | QVERIFY(ret.isObject()); |
1018 | QVERIFY(ret.strictlyEquals(obj)); |
1019 | } |
1020 | // From JS |
1021 | { |
1022 | QScriptValue ret = eng.evaluate(program: "obj()" ); |
1023 | QVERIFY(ret.isObject()); |
1024 | QVERIFY(ret.strictlyEquals(eng.globalObject())); |
1025 | } |
1026 | |
1027 | cls.setCallableMode(TestClass::CallableReturnsCallee); |
1028 | // From C++ |
1029 | { |
1030 | QScriptValue ret = obj.call(); |
1031 | QVERIFY(ret.isObject()); |
1032 | QVERIFY(ret.strictlyEquals(obj)); |
1033 | } |
1034 | // From JS |
1035 | { |
1036 | QScriptValue ret = eng.evaluate(program: "obj()" ); |
1037 | QVERIFY(ret.isObject()); |
1038 | QVERIFY(ret.strictlyEquals(obj)); |
1039 | } |
1040 | |
1041 | cls.setCallableMode(TestClass::CallableReturnsArgumentsObject); |
1042 | // From C++ |
1043 | { |
1044 | QScriptValue ret = obj.call(thisObject: obj, args: QScriptValueList() << 123); |
1045 | QVERIFY(ret.isObject()); |
1046 | QVERIFY(ret.property("length" ).isNumber()); |
1047 | QCOMPARE(ret.property("length" ).toInt32(), 1); |
1048 | QVERIFY(ret.property(0).isNumber()); |
1049 | QCOMPARE(ret.property(0).toInt32(), 123); |
1050 | } |
1051 | // From JS |
1052 | { |
1053 | QScriptValue ret = eng.evaluate(program: "obj(123)" ); |
1054 | QVERIFY(ret.isObject()); |
1055 | QVERIFY(ret.property("length" ).isNumber()); |
1056 | QCOMPARE(ret.property("length" ).toInt32(), 1); |
1057 | QVERIFY(ret.property(0).isNumber()); |
1058 | QCOMPARE(ret.property(0).toInt32(), 123); |
1059 | } |
1060 | } |
1061 | |
1062 | void tst_QScriptClass::extension_Callable_construct() |
1063 | { |
1064 | QScriptEngine eng; |
1065 | TestClass cls(&eng); |
1066 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
1067 | eng.globalObject().setProperty(name: "obj" , value: obj); |
1068 | |
1069 | // From C++ |
1070 | cls.clearReceivedArgs(); |
1071 | cls.setCallableMode(TestClass::CallableReturnsGlobalObject); |
1072 | { |
1073 | QScriptValue ret = obj.construct(); |
1074 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
1075 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
1076 | QVERIFY(ret.isObject()); |
1077 | QVERIFY(ret.strictlyEquals(eng.globalObject())); |
1078 | } |
1079 | // From JS |
1080 | cls.clearReceivedArgs(); |
1081 | { |
1082 | QScriptValue ret = eng.evaluate(program: "new obj()" ); |
1083 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
1084 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
1085 | QVERIFY(ret.isObject()); |
1086 | QVERIFY(ret.strictlyEquals(eng.globalObject())); |
1087 | } |
1088 | // From C++ |
1089 | cls.clearReceivedArgs(); |
1090 | cls.setCallableMode(TestClass::CallableInitializesThisObject); |
1091 | { |
1092 | QScriptValue ret = obj.construct(); |
1093 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
1094 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
1095 | QVERIFY(ret.isQObject()); |
1096 | QCOMPARE(ret.toQObject(), (QObject*)&eng); |
1097 | } |
1098 | // From JS |
1099 | cls.clearReceivedArgs(); |
1100 | { |
1101 | QScriptValue ret = eng.evaluate(program: "new obj()" ); |
1102 | QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
1103 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
1104 | QVERIFY(ret.isQObject()); |
1105 | QCOMPARE(ret.toQObject(), (QObject*)&eng); |
1106 | } |
1107 | } |
1108 | |
1109 | void tst_QScriptClass::extension_HasInstance() |
1110 | { |
1111 | QScriptEngine eng; |
1112 | TestClass cls(&eng); |
1113 | cls.setHasInstance(true); |
1114 | QVERIFY(cls.supportsExtension(QScriptClass::HasInstance)); |
1115 | |
1116 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
1117 | obj.setProperty(name: "foo" , value: QScriptValue(&eng, 123)); |
1118 | QScriptValue plain = eng.newObject(); |
1119 | QVERIFY(!plain.instanceOf(obj)); |
1120 | |
1121 | eng.globalObject().setProperty(name: "HasInstanceTester" , value: obj); |
1122 | eng.globalObject().setProperty(name: "hasInstanceValue" , value: plain); |
1123 | cls.clearReceivedArgs(); |
1124 | { |
1125 | QScriptValue ret = eng.evaluate(program: "hasInstanceValue instanceof HasInstanceTester" ); |
1126 | QCOMPARE(cls.lastExtensionType(), QScriptClass::HasInstance); |
1127 | QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptValueList>()); |
1128 | QScriptValueList lst = qvariant_cast<QScriptValueList>(v: cls.lastExtensionArgument()); |
1129 | QCOMPARE(lst.size(), 2); |
1130 | QVERIFY(lst.at(0).strictlyEquals(obj)); |
1131 | QVERIFY(lst.at(1).strictlyEquals(plain)); |
1132 | QVERIFY(ret.isBoolean()); |
1133 | QVERIFY(!ret.toBoolean()); |
1134 | } |
1135 | |
1136 | plain.setProperty(name: "foo" , value: QScriptValue(&eng, 456)); |
1137 | QVERIFY(!plain.instanceOf(obj)); |
1138 | { |
1139 | QScriptValue ret = eng.evaluate(program: "hasInstanceValue instanceof HasInstanceTester" ); |
1140 | QVERIFY(ret.isBoolean()); |
1141 | QVERIFY(!ret.toBoolean()); |
1142 | } |
1143 | |
1144 | plain.setProperty(name: "foo" , value: obj.property(name: "foo" )); |
1145 | QVERIFY(plain.instanceOf(obj)); |
1146 | { |
1147 | QScriptValue ret = eng.evaluate(program: "hasInstanceValue instanceof HasInstanceTester" ); |
1148 | QVERIFY(ret.isBoolean()); |
1149 | QVERIFY(ret.toBoolean()); |
1150 | } |
1151 | } |
1152 | |
1153 | // tests made to match Qt 4.7 (JSC) behaviour |
1154 | void tst_QScriptClass::originalProperties1() |
1155 | { |
1156 | QScriptEngine eng; |
1157 | |
1158 | QScriptString orig1 = eng.toStringHandle(str: "orig1" ); |
1159 | QScriptString orig2 = eng.toStringHandle(str: "orig2" ); |
1160 | QScriptString orig3 = eng.toStringHandle(str: "orig3" ); |
1161 | QScriptString new1 = eng.toStringHandle(str: "new1" ); |
1162 | QScriptString new2 = eng.toStringHandle(str: "new2" ); |
1163 | |
1164 | { |
1165 | TestClass cls1(&eng); |
1166 | cls1.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 89); |
1167 | cls1.addCustomProperty(name: new1, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "hello" ); |
1168 | |
1169 | TestClass cls2(&eng); |
1170 | cls2.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 59); |
1171 | cls2.addCustomProperty(name: new2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "world" ); |
1172 | |
1173 | QScriptValue obj1 = eng.newObject(); |
1174 | obj1.setProperty(name: orig1 , value: 42); |
1175 | obj1.setProperty(name: orig2 , value: "foo" ); |
1176 | obj1.prototype().setProperty(name: orig3, value: "bar" ); |
1177 | |
1178 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1179 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1180 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar" )); |
1181 | QVERIFY(!obj1.property(new1).isValid()); |
1182 | QVERIFY(!obj1.property(new2).isValid()); |
1183 | |
1184 | eng.globalObject().setProperty(name: "obj" , value: obj1); |
1185 | |
1186 | obj1.setScriptClass(&cls1); |
1187 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1188 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1189 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar" )); |
1190 | QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello" )); |
1191 | QVERIFY(!obj1.property(new2).isValid()); |
1192 | |
1193 | QScriptValue obj2 = eng.evaluate(program: "obj" ); |
1194 | QCOMPARE(obj2.scriptClass(), &cls1); |
1195 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1196 | QCOMPARE(obj2.property(orig2).toString(), QString::fromLatin1("foo" )); |
1197 | QCOMPARE(obj2.property(orig3).toString(), QString::fromLatin1("bar" )); |
1198 | QCOMPARE(obj2.property(new1).toString(), QString::fromLatin1("hello" )); |
1199 | QVERIFY(!obj2.property(new2).isValid()); |
1200 | |
1201 | obj1.setScriptClass(&cls2); |
1202 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1203 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1204 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar" )); |
1205 | QVERIFY(!obj1.property(new1).isValid()); |
1206 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world" )); |
1207 | |
1208 | QCOMPARE(obj2.scriptClass(), &cls2); |
1209 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1210 | QCOMPARE(obj2.property(orig2).toString(), QString::fromLatin1("foo" )); |
1211 | QCOMPARE(obj2.property(orig3).toString(), QString::fromLatin1("bar" )); |
1212 | QVERIFY(!obj2.property(new1).isValid()); |
1213 | QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world" )); |
1214 | |
1215 | obj1.setScriptClass(0); |
1216 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1217 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1218 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar" )); |
1219 | QVERIFY(!obj1.property(new1).isValid()); |
1220 | QVERIFY(!obj1.property(new2).isValid()); |
1221 | } |
1222 | } |
1223 | |
1224 | void tst_QScriptClass::originalProperties2() |
1225 | { |
1226 | QScriptEngine eng; |
1227 | |
1228 | QScriptString orig1 = eng.toStringHandle(str: "orig1" ); |
1229 | QScriptString orig2 = eng.toStringHandle(str: "orig2" ); |
1230 | QScriptString orig3 = eng.toStringHandle(str: "orig3" ); |
1231 | QScriptString new1 = eng.toStringHandle(str: "new1" ); |
1232 | QScriptString new2 = eng.toStringHandle(str: "new2" ); |
1233 | |
1234 | { |
1235 | TestClass cls1(&eng); |
1236 | cls1.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 89); |
1237 | cls1.addCustomProperty(name: new1, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "hello" ); |
1238 | |
1239 | TestClass cls2(&eng); |
1240 | cls2.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 59); |
1241 | cls2.addCustomProperty(name: new2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "world" ); |
1242 | |
1243 | QScriptValue obj1 = eng.newObject(); |
1244 | obj1.setProperty(name: orig1 , value: 42); |
1245 | obj1.setProperty(name: orig2 , value: "foo" ); |
1246 | obj1.prototype().setProperty(name: orig3, value: "bar" ); |
1247 | |
1248 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1249 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1250 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar" )); |
1251 | QVERIFY(!obj1.property(new1).isValid()); |
1252 | QVERIFY(!obj1.property(new2).isValid()); |
1253 | |
1254 | obj1.setScriptClass(&cls1); |
1255 | obj1.setProperty(name: orig1 , value: QScriptValue(&eng, 852)); |
1256 | obj1.setProperty(name: orig2 , value: "oli" ); |
1257 | obj1.setProperty(name: orig3 , value: "fu*c" ); |
1258 | obj1.setProperty(name: new1 , value: "moo" ); |
1259 | obj1.setProperty(name: new2 , value: "allo?" ); |
1260 | QCOMPARE(obj1.property(orig1).toInt32(), 852); |
1261 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1262 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c" )); |
1263 | QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("moo" )); |
1264 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?" )); |
1265 | |
1266 | obj1.setScriptClass(&cls2); |
1267 | QCOMPARE(obj1.property(orig1).toInt32(), 852); |
1268 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1269 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c" )); |
1270 | QVERIFY(!obj1.property(new1).isValid()); |
1271 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?" )); |
1272 | |
1273 | obj1.setScriptClass(0); |
1274 | QCOMPARE(obj1.property(orig1).toInt32(), 852); |
1275 | QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo" )); |
1276 | QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c" )); |
1277 | QVERIFY(!obj1.property(new1).isValid()); |
1278 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?" )); |
1279 | } |
1280 | } |
1281 | |
1282 | void tst_QScriptClass::originalProperties3() |
1283 | { |
1284 | QScriptEngine eng; |
1285 | |
1286 | QScriptString orig1 = eng.toStringHandle(str: "orig1" ); |
1287 | QScriptString orig2 = eng.toStringHandle(str: "orig2" ); |
1288 | QScriptString orig3 = eng.toStringHandle(str: "orig3" ); |
1289 | QScriptString new1 = eng.toStringHandle(str: "new1" ); |
1290 | QScriptString new2 = eng.toStringHandle(str: "new2" ); |
1291 | |
1292 | { |
1293 | TestClass cls1(&eng); |
1294 | cls1.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 89); |
1295 | cls1.addCustomProperty(name: new1, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "hello" ); |
1296 | |
1297 | TestClass cls2(&eng); |
1298 | cls2.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 59); |
1299 | cls2.addCustomProperty(name: new2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "world" ); |
1300 | |
1301 | QScriptValue obj1 = eng.newObject(scriptClass: &cls1); |
1302 | QVERIFY(!obj1.property(orig1).isValid()); |
1303 | QCOMPARE(obj1.property(orig2).toInt32(), 89); |
1304 | QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello" )); |
1305 | QVERIFY(!obj1.property(new2).isValid()); |
1306 | obj1.setProperty(name: orig1, value: 42); |
1307 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1308 | |
1309 | eng.globalObject().setProperty(name: "obj" , value: obj1); |
1310 | obj1.setScriptClass(&cls2); |
1311 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1312 | QCOMPARE(obj1.property(orig2).toInt32(), 59); |
1313 | QVERIFY(!obj1.property(new1).isValid()); |
1314 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world" )); |
1315 | |
1316 | QScriptValue obj2 = eng.evaluate(program: "obj" ); |
1317 | QCOMPARE(obj2.scriptClass(), &cls2); |
1318 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1319 | QCOMPARE(obj2.property(orig2).toInt32(), 59); |
1320 | QVERIFY(!obj2.property(new1).isValid()); |
1321 | QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world" )); |
1322 | |
1323 | obj1.setScriptClass(0); |
1324 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1325 | QVERIFY(!obj1.property(orig2).isValid()); |
1326 | QVERIFY(!obj1.property(new1).isValid()); |
1327 | QVERIFY(!obj1.property(new2).isValid()); |
1328 | |
1329 | QCOMPARE(obj2.scriptClass(), (QScriptClass *)0); |
1330 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1331 | QVERIFY(!obj2.property(orig2).isValid()); |
1332 | QVERIFY(!obj2.property(new1).isValid()); |
1333 | QVERIFY(!obj2.property(new2).isValid()); |
1334 | } |
1335 | } |
1336 | |
1337 | void tst_QScriptClass::originalProperties4() |
1338 | { |
1339 | QScriptEngine eng; |
1340 | |
1341 | QScriptString orig1 = eng.toStringHandle(str: "orig1" ); |
1342 | QScriptString orig2 = eng.toStringHandle(str: "orig2" ); |
1343 | QScriptString orig3 = eng.toStringHandle(str: "orig3" ); |
1344 | QScriptString new1 = eng.toStringHandle(str: "new1" ); |
1345 | QScriptString new2 = eng.toStringHandle(str: "new2" ); |
1346 | |
1347 | { |
1348 | TestClass cls1(&eng); |
1349 | cls1.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 89); |
1350 | cls1.addCustomProperty(name: new1, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "hello" ); |
1351 | |
1352 | TestClass cls2(&eng); |
1353 | cls2.addCustomProperty(name: orig2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: 59); |
1354 | cls2.addCustomProperty(name: new2, qflags: QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, id: 1, pflags: {}, value: "world" ); |
1355 | |
1356 | QScriptValue obj1 = eng.newObject(scriptClass: &cls1); |
1357 | QVERIFY(!obj1.property(orig1).isValid()); |
1358 | QCOMPARE(obj1.property(orig2).toInt32(), 89); |
1359 | QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello" )); |
1360 | QVERIFY(!obj1.property(new2).isValid()); |
1361 | |
1362 | eng.globalObject().setProperty(name: "obj" , value: obj1); |
1363 | |
1364 | obj1.setScriptClass(0); |
1365 | QVERIFY(obj1.isObject()); |
1366 | QVERIFY(!obj1.property(orig1).isValid()); |
1367 | QVERIFY(!obj1.property(orig2).isValid()); |
1368 | QVERIFY(!obj1.property(new1).isValid()); |
1369 | QVERIFY(!obj1.property(new2).isValid()); |
1370 | obj1.setProperty(name: orig1, value: 42); |
1371 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1372 | |
1373 | QScriptValue obj2 = eng.evaluate(program: "obj" ); |
1374 | QCOMPARE(obj2.scriptClass(), (QScriptClass *)0); |
1375 | QVERIFY(obj2.isObject()); |
1376 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1377 | QVERIFY(!obj2.property(orig2).isValid()); |
1378 | QVERIFY(!obj2.property(new1).isValid()); |
1379 | QVERIFY(!obj2.property(new2).isValid()); |
1380 | |
1381 | obj1.setScriptClass(&cls2); |
1382 | QCOMPARE(obj1.property(orig1).toInt32(), 42); |
1383 | QCOMPARE(obj1.property(orig2).toInt32(), 59); |
1384 | QVERIFY(!obj1.property(new1).isValid()); |
1385 | QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world" )); |
1386 | |
1387 | QCOMPARE(obj2.scriptClass(), (QScriptClass *)(&cls2)); |
1388 | QCOMPARE(obj2.property(orig1).toInt32(), 42); |
1389 | QCOMPARE(obj2.property(orig2).toInt32(), 59); |
1390 | QVERIFY(!obj2.property(new1).isValid()); |
1391 | QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world" )); |
1392 | } |
1393 | } |
1394 | |
1395 | void tst_QScriptClass::defaultImplementations() |
1396 | { |
1397 | QScriptEngine eng; |
1398 | |
1399 | QScriptClass defaultClass(&eng); |
1400 | QCOMPARE(defaultClass.engine(), &eng); |
1401 | QVERIFY(!defaultClass.prototype().isValid()); |
1402 | QCOMPARE(defaultClass.name(), QString()); |
1403 | |
1404 | QScriptValue obj = eng.newObject(scriptClass: &defaultClass); |
1405 | QCOMPARE(obj.scriptClass(), &defaultClass); |
1406 | |
1407 | QScriptString name = eng.toStringHandle(str: "foo" ); |
1408 | uint id = -1; |
1409 | QCOMPARE(defaultClass.queryProperty(obj, name, QScriptClass::HandlesReadAccess, &id), QScriptClass::QueryFlags{}); |
1410 | QVERIFY(!defaultClass.property(obj, name, id).isValid()); |
1411 | QCOMPARE(defaultClass.propertyFlags(obj, name, id), QScriptValue::PropertyFlags{}); |
1412 | defaultClass.setProperty(object&: obj, name, id, value: 123); |
1413 | QVERIFY(!obj.property(name).isValid()); |
1414 | |
1415 | QCOMPARE(defaultClass.newIterator(obj), (QScriptClassPropertyIterator*)0); |
1416 | |
1417 | QVERIFY(!defaultClass.supportsExtension(QScriptClass::Callable)); |
1418 | QVERIFY(!defaultClass.supportsExtension(QScriptClass::HasInstance)); |
1419 | QVERIFY(!defaultClass.extension(QScriptClass::Callable).isValid()); |
1420 | QVERIFY(!defaultClass.extension(QScriptClass::HasInstance).isValid()); |
1421 | } |
1422 | |
1423 | void tst_QScriptClass::scriptClassObjectInPrototype() |
1424 | { |
1425 | QScriptEngine eng; |
1426 | TestClass cls(&eng); |
1427 | QScriptValue plainObject = eng.newObject(); |
1428 | QScriptValue classObject = eng.newObject(scriptClass: &cls); |
1429 | plainObject.setPrototype(classObject); |
1430 | QVERIFY(plainObject.prototype().equals(classObject)); |
1431 | eng.globalObject().setProperty(name: "plainObject" , value: plainObject); |
1432 | eng.globalObject().setProperty(name: "classObject" , value: classObject); |
1433 | |
1434 | QScriptString name = eng.toStringHandle(str: "x" ); |
1435 | cls.addCustomProperty(name, qflags: QScriptClass::HandlesReadAccess, /*id=*/ 1, |
1436 | pflags: QScriptValue::PropertyFlags{}, /*value=*/ 123); |
1437 | QVERIFY(plainObject.property(name).equals(classObject.property(name))); |
1438 | QVERIFY(eng.evaluate("plainObject.x == classObject.x" ).toBool()); |
1439 | |
1440 | // Add a property that shadows the one in the script class. |
1441 | plainObject.setProperty(name, value: 456); |
1442 | QVERIFY(!plainObject.property(name).equals(classObject.property(name))); |
1443 | QVERIFY(eng.evaluate("plainObject.x != classObject.x" ).toBool()); |
1444 | |
1445 | QVERIFY(eng.evaluate("delete plainObject.x" ).toBool()); |
1446 | QVERIFY(eng.evaluate("plainObject.x == classObject.x" ).toBool()); |
1447 | } |
1448 | |
1449 | void tst_QScriptClass::scriptClassWithNullEngine() |
1450 | { |
1451 | QScriptClass cls(0); |
1452 | QCOMPARE(cls.engine(), (QScriptEngine*)0); |
1453 | QScriptEngine eng; |
1454 | QScriptValue obj = eng.newObject(scriptClass: &cls); |
1455 | QVERIFY(obj.isObject()); |
1456 | QCOMPARE(obj.scriptClass(), &cls); |
1457 | // The class could have been "bound" to the engine at this point, |
1458 | // but it's currently not. |
1459 | // This behavior is not documented and is subject to change. |
1460 | QCOMPARE(cls.engine(), (QScriptEngine*)0); |
1461 | // The engine pointer stored in the QScriptClass is not actually used |
1462 | // during property access, so this still works. |
1463 | obj.setProperty(name: "x" , value: 123); |
1464 | QVERIFY(obj.property("x" ).isNumber()); |
1465 | } |
1466 | |
1467 | void tst_QScriptClass::scriptClassInOtherEngine() |
1468 | { |
1469 | QScriptEngine eng; |
1470 | TestClass cls(&eng); |
1471 | QScriptEngine eng2; |
1472 | // We don't check that the class is associated with another engine, so |
1473 | // we only get a warning when trying to set the prototype of the new |
1474 | // instance. |
1475 | // This behavior is not documented and is subject to change. |
1476 | QTest::ignoreMessage(type: QtWarningMsg, message: "QScriptValue::setPrototype() failed: cannot set a prototype created in a different engine" ); |
1477 | QScriptValue obj = eng2.newObject(scriptClass: &cls); |
1478 | QVERIFY(obj.isObject()); |
1479 | QCOMPARE(obj.scriptClass(), &cls); |
1480 | |
1481 | obj.setProperty(name: "x" , value: 123); |
1482 | QVERIFY(obj.property("x" ).isNumber()); |
1483 | } |
1484 | |
1485 | QTEST_MAIN(tst_QScriptClass) |
1486 | #include "tst_qscriptclass.moc" |
1487 | |