File indexing completed on 2024-05-05 16:15:13

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 2008 Harri Porten (porten@kde.org)
0004  *
0005  *  This library is free software; you can redistribute it and/or
0006  *  modify it under the terms of the GNU Library General Public
0007  *  License as published by the Free Software Foundation; either
0008  *  version 2 of the License, or (at your option) any later version.
0009  *
0010  *  This library is distributed in the hope that it will be useful,
0011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  *  Library General Public License for more details.
0014  *
0015  *  You should have received a copy of the GNU Library General Public License
0016  *  along with this library; see the file COPYING.LIB.  If not, write to
0017  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  *  Boston, MA 02110-1301, USA.
0019  *
0020  */
0021 
0022 #include "kjsobject.h"
0023 #include "kjsprototype.h"
0024 #include "kjsarguments.h"
0025 #include "kjsinterpreter.h"
0026 
0027 #include "qtest.h"
0028 
0029 #include <QRegularExpression>
0030 
0031 class KJSApiTest : public QObject
0032 {
0033     Q_OBJECT
0034 
0035 private Q_SLOTS:
0036     void objectConstruction();
0037     void interpreterEvaluate();
0038     void interpreterNormalizeCode();
0039     void objectProperties();
0040     void prototypeConstants();
0041     void prototypeProperties();
0042     void prototypeFunctions();
0043     void globalObject();
0044 };
0045 
0046 void KJSApiTest::objectConstruction()
0047 {
0048     KJSInterpreter ip;
0049     KJSContext *ctx = ip.globalContext();
0050 
0051     // Object
0052     QVERIFY2(KJSObject().isObject(), "Broken default object");
0053 
0054     // undefined
0055     QVERIFY2(KJSUndefined().isUndefined(),
0056              "Undefined object is not undefined");
0057     // null
0058     QVERIFY2(KJSNull().isNull(),
0059              "Null object is not null");
0060 
0061     // Boolean
0062     KJSBoolean boolObject(true);
0063     QVERIFY2(boolObject.isBoolean(), "Boolean object is not of boolean type");
0064     QVERIFY2(boolObject.toBoolean(ctx), "Boolean object has wrong value");
0065     QVERIFY2(!ctx->hasException(), "Boolean conversion threw exception");
0066 
0067     // Number
0068     KJSNumber numObject(42.0);
0069     QVERIFY2(numObject.isNumber(), "Number object is not of number type");
0070     QCOMPARE(numObject.toNumber(ctx), 42.0);
0071     QCOMPARE(numObject.toInt32(ctx), 42);
0072     QVERIFY2(!ctx->hasException(), "Number conversion threw exception");
0073 
0074     // String
0075     KJSString stringObject("Trunk");
0076     QVERIFY2(stringObject.isString(), "String object is not of string type");
0077     QCOMPARE(stringObject.toString(ctx), QLatin1String("Trunk"));
0078     QVERIFY2(!ctx->hasException(), "String conversion threw exception");
0079 
0080     // Array
0081     KJSArray arrayObject(ctx, 3);
0082     QVERIFY2(arrayObject.isObject(), "Array object is not of object type");
0083     QCOMPARE(arrayObject.property(ctx, "length").toNumber(ctx), 3.0);
0084     QCOMPARE(arrayObject.toString(ctx), QLatin1String(",,"));
0085     QVERIFY2(!ctx->hasException(), "Array conversion threw exception");
0086 
0087     // copying
0088     KJSObject copy(stringObject);
0089     QCOMPARE(copy.toString(ctx), QLatin1String("Trunk"));
0090     copy = numObject;
0091     QCOMPARE(copy.toNumber(ctx), 42.0);
0092 }
0093 
0094 void KJSApiTest::interpreterEvaluate()
0095 {
0096     KJSInterpreter ip;
0097     KJSContext *ctx = ip.globalContext();
0098     KJSResult res;
0099 
0100     // syntax error
0101     res = ip.evaluate(")(");
0102     QVERIFY2(res.isException(), "Syntax error not caught");
0103 
0104     res = ip.evaluate("11+22");
0105     QVERIFY2(!res.isException(), "Evaluation returned non-number object");
0106     QCOMPARE(res.value().toNumber(ctx), 33.0);
0107 }
0108 
0109 void KJSApiTest::interpreterNormalizeCode()
0110 {
0111     int errLine = -1;
0112     QString errMsg;
0113     QString norm;
0114     bool ok;
0115 
0116     // syntax error case
0117     ok = KJSInterpreter::normalizeCode(")(", &norm, &errLine, &errMsg);
0118     QVERIFY(!ok);
0119     QVERIFY(!errMsg.isEmpty());
0120     QVERIFY(errLine >= 0 && errLine <= 2); // ### imprecise
0121 
0122     // success case
0123     ok = KJSInterpreter::normalizeCode("foo(); bar();", &norm);
0124     QVERIFY(ok);
0125     QVERIFY(!norm.isEmpty());
0126     QStringList lines = norm.split('\n');
0127     QVERIFY(lines.size() >= 2); // ### imprecise
0128     int fooLine = lines.indexOf(QRegularExpression(QStringLiteral(" *foo\\(\\);")));
0129     int barLine = lines.indexOf(QRegularExpression(QStringLiteral(" *bar\\(\\);")));
0130     QVERIFY(fooLine >= 0);
0131     QVERIFY(barLine > fooLine);
0132 }
0133 
0134 void KJSApiTest::objectProperties()
0135 {
0136     KJSInterpreter ip;
0137     KJSContext *ctx = ip.globalContext();
0138 
0139     KJSObject global = ip.globalObject();
0140     KJSObject v;
0141 
0142     // bool
0143     global.setProperty(ctx, "myprop", true);
0144     v = global.property(ctx, "myprop");
0145     QVERIFY(v.isBoolean());
0146     QCOMPARE(v.toBoolean(ctx), true);
0147 
0148     // double
0149     global.setProperty(ctx, "myprop", 21.0);
0150     v = global.property(ctx, "myprop");
0151     QVERIFY(v.isNumber());
0152     QCOMPARE(v.toNumber(ctx), 21.0);
0153 
0154     // int
0155     global.setProperty(ctx, "myprop", 22);
0156     v = global.property(ctx, "myprop");
0157     QVERIFY(v.isNumber());
0158     QCOMPARE(v.toNumber(ctx), 22.0);
0159 
0160     // string (8-bit)
0161     global.setProperty(ctx, "myprop", "myvalue8");
0162     v = global.property(ctx, "myprop");
0163     QVERIFY(v.isString());
0164     QCOMPARE(v.toString(ctx), QLatin1String("myvalue8"));
0165 
0166     // string (Unicode)
0167     global.setProperty(ctx, "myprop", QLatin1String("myvalue16"));
0168     v = global.property(ctx, "myprop");
0169     QVERIFY(v.isString());
0170     QCOMPARE(v.toString(ctx), QLatin1String("myvalue16"));
0171 }
0172 
0173 void KJSApiTest::prototypeConstants()
0174 {
0175     KJSInterpreter ip;
0176     KJSContext *ctx = ip.globalContext();
0177 
0178     KJSPrototype proto;
0179 
0180     proto.defineConstant("d0", 44.4);
0181     proto.defineConstant("s0", QLatin1String("XYZ"));
0182 
0183     KJSObject obj = proto.constructObject(ctx, nullptr);
0184 
0185     QCOMPARE(obj.property(ctx, "d0").toNumber(ctx), 44.4);
0186     QCOMPARE(obj.property(ctx, "s0").toString(ctx), QLatin1String("XYZ"));
0187 }
0188 
0189 static struct O {
0190     int x;
0191 } o0 = { 42 };
0192 
0193 static KJSObject getX(KJSContext * /*context*/, void *object)
0194 {
0195     O *o = reinterpret_cast<O *>(object);
0196     int x = o->x;
0197     return KJSNumber(x);
0198 }
0199 
0200 static void setX(KJSContext *context, void *object, KJSObject value)
0201 {
0202     O *o = reinterpret_cast<O *>(object);
0203     double n = value.toNumber(context);
0204     o->x = n;
0205 }
0206 
0207 void KJSApiTest::prototypeProperties()
0208 {
0209     KJSInterpreter ip;
0210     KJSContext *ctx = ip.globalContext();
0211 
0212     KJSPrototype proto;
0213 
0214     proto.defineProperty(ctx, "x", getX, setX);
0215     proto.defineProperty(ctx, "readOnlyX", getX);
0216 
0217     KJSObject obj = proto.constructObject(ctx, &o0);
0218 
0219     // read/write property
0220     QCOMPARE(obj.property(ctx, "x").toNumber(ctx), 42.0);
0221     obj.setProperty(ctx, "x", KJSNumber(43));
0222     QCOMPARE(obj.property(ctx, "x").toNumber(ctx), 43.0);
0223 
0224     QCOMPARE(obj.property(ctx, "readOnlyX").toNumber(ctx), 43.0);
0225     obj.setProperty(ctx, "readOnlyX", KJSNumber(44));
0226     QVERIFY2(ctx->hasException(), "Write access caused no exception");
0227     QCOMPARE(obj.property(ctx, "readOnlyX").toNumber(ctx), 43.0);
0228 }
0229 
0230 static KJSObject multiply(KJSContext *context, void *object,
0231                           const KJSArguments &arguments)
0232 {
0233     double factor = *reinterpret_cast<double *>(object);
0234 
0235     // test number of arguments
0236     if (arguments.count() != 1) {
0237         return context->throwException("Missing argument");
0238     }
0239 
0240     KJSObject a0 = arguments.at(0);
0241     if (!a0.isNumber()) {
0242         return KJSNumber(-2);
0243     }
0244 
0245     double v0 = a0.toNumber(context);
0246 
0247     return KJSNumber(factor * v0);
0248 }
0249 
0250 void KJSApiTest::prototypeFunctions()
0251 {
0252     KJSInterpreter ip;
0253     KJSContext *ctx = ip.globalContext();
0254 
0255     KJSPrototype proto;
0256 
0257     proto.defineFunction(ctx, "multiply", multiply);
0258 
0259     double factor = 3.0;
0260     KJSObject obj = proto.constructObject(ctx, &factor);
0261     ip.globalObject().setProperty(ctx, "obj", obj);
0262 
0263     KJSResult res = ip.evaluate("obj.multiply(4)");
0264     QCOMPARE(res.value().toNumber(ctx), 12.0);
0265 
0266     // expect exception
0267     res = ip.evaluate("obj.multiply()");
0268     QVERIFY2(res.isException(), "Exception did not work");
0269 }
0270 
0271 void KJSApiTest::globalObject()
0272 {
0273     KJSPrototype proto;
0274     proto.defineConstant("g0", 55.5);
0275 
0276     KJSGlobalObject glob = proto.constructGlobalObject(nullptr);
0277 
0278     KJSInterpreter ip(glob);
0279     KJSResult res = ip.evaluate("2 * g0");
0280     QCOMPARE(res.value().toNumber(ip.globalContext()), 111.0);
0281 }
0282 
0283 QTEST_MAIN(KJSApiTest)
0284 
0285 #include "kjsapitest.moc"