File
Blob: src/workerd/jsg/jsvalue-test.c++
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #include "jsg-test.h" |
| 6 | #include "jsvalue.h" |
| 7 | namespace workerd::jsg::test { |
| 8 | namespace { |
| 9 | |
| 10 | V8System v8System; |
| 11 | class ContextGlobalObject: public Object, public ContextGlobal {}; |
| 12 | |
| 13 | struct JsValueContext: public ContextGlobalObject { |
| 14 | JsRef<JsValue> persisted; |
| 15 | JsValue takeJsValue(Lock& js, JsValue v) { |
| 16 | KJ_ASSERT(!v.isTruthy(js)); |
| 17 | KJ_ASSERT(v.typeOf(js) == "boolean"_kj); |
| 18 | KJ_ASSERT(v.isBoolean()); |
| 19 | KJ_ASSERT(!v.isObject()); |
| 20 | JsBoolean b = KJ_ASSERT_NONNULL(v.tryCast<JsBoolean>()); |
| 21 | KJ_ASSERT(!b.value(js)); |
| 22 | |
| 23 | return b; |
| 24 | } |
| 25 | JsValue takeJsString(Lock& js, Optional<JsString> v) { |
| 26 | return v.orDefault([&] { return js.str("bar"_kj); }); |
| 27 | } |
| 28 | JsValue takeJsNumber(Lock& js, Optional<JsNumber> v) { |
| 29 | return v.orDefault([&] { return js.num(42.0); }); |
| 30 | } |
| 31 | JsBoolean takeJsBoolean(Lock& js, JsBoolean v, const TypeHandler<bool>& handler) { |
| 32 | auto ref = v.addRef(js); |
| 33 | |
| 34 | // Because Js* types are trivially assignable to v8::Local<v8::Value>, |
| 35 | // they work out of the box with the existing TypeHandler<T> model |
| 36 | // and can be converted into more specific types easily. |
| 37 | bool result = KJ_ASSERT_NONNULL(handler.tryUnwrap(js, v)); |
| 38 | KJ_ASSERT(result == v.value(js)); |
| 39 | |
| 40 | return ref.getHandle(js); |
| 41 | } |
| 42 | JsObject takeJsObject(JsObject v) { |
| 43 | return v; |
| 44 | } |
| 45 | JsArray takeJsArray(Lock& js, JsArray v) { |
| 46 | KJ_ASSERT(v.size() == 3); |
| 47 | JsValue val = v.get(js, 0); |
| 48 | KJ_ASSERT(val.isNumber()); |
| 49 | KJ_ASSERT(kj::str(val) == "1"); |
| 50 | return v; |
| 51 | } |
| 52 | JsValue getString(Lock& js) { |
| 53 | return js.str("foo"_kj); |
| 54 | } |
| 55 | JsValue getStringIntern(Lock& js) { |
| 56 | return js.strIntern("foo"); |
| 57 | } |
| 58 | JsMap getMap(Lock& js) { |
| 59 | auto map = js.map(); |
| 60 | map.set(js, "foo", js.num(1)); |
| 61 | return map; |
| 62 | } |
| 63 | JsArray getArray(Lock& js) { |
| 64 | return js.arr(js.undefined(), js.null(), js.num(1)); |
| 65 | } |
| 66 | JsSet getSet(Lock& js) { |
| 67 | return js.set(js.num(1), js.num(1), js.str("foo"_kj), js.str("foo"_kj)); |
| 68 | } |
| 69 | void setRef(Lock& js, JsRef<JsString> value) { |
| 70 | JsValue v = value.getHandle(js); |
| 71 | persisted = v.addRef(js); |
| 72 | } |
| 73 | JsValue getRef(Lock& js) { |
| 74 | return persisted.getHandle(js); |
| 75 | } |
| 76 | JsDate getDate(Lock& js) { |
| 77 | return js.date(0); |
| 78 | } |
| 79 | |
| 80 | JsValue callFunction(Lock& js, JsFunction fn) { |
| 81 | return fn.callNoReceiver(js, js.num(1)); |
| 82 | } |
| 83 | |
| 84 | struct Foo: public Object { |
| 85 | JSG_RESOURCE_TYPE(Foo) {} |
| 86 | }; |
| 87 | |
| 88 | JsValue checkProxyPrototype(Lock& js, JsValue value) { |
| 89 | JSG_REQUIRE(value.isProxy(), TypeError, "not a proxy"); |
| 90 | auto obj = KJ_ASSERT_NONNULL(value.tryCast<JsObject>()); |
| 91 | return obj.getPrototype(js); |
| 92 | } |
| 93 | |
| 94 | JSG_RESOURCE_TYPE(JsValueContext) { |
| 95 | JSG_METHOD(takeJsValue); |
| 96 | JSG_METHOD(takeJsString); |
| 97 | JSG_METHOD(takeJsNumber); |
| 98 | JSG_METHOD(takeJsBoolean); |
| 99 | JSG_METHOD(takeJsObject); |
| 100 | JSG_METHOD(takeJsArray); |
| 101 | JSG_METHOD(getString); |
| 102 | JSG_METHOD(getStringIntern); |
| 103 | JSG_METHOD(getMap); |
| 104 | JSG_METHOD(getArray); |
| 105 | JSG_METHOD(getSet); |
| 106 | JSG_METHOD(setRef); |
| 107 | JSG_METHOD(getRef); |
| 108 | JSG_METHOD(getDate); |
| 109 | JSG_METHOD(checkProxyPrototype); |
| 110 | JSG_METHOD(callFunction); |
| 111 | JSG_NESTED_TYPE(Foo); |
| 112 | } |
| 113 | }; |
| 114 | JSG_DECLARE_ISOLATE_TYPE(JsValueIsolate, JsValueContext, JsValueContext::Foo); |
| 115 | |
| 116 | KJ_TEST("simple") { |
| 117 | Evaluator<JsValueContext, JsValueIsolate> e(v8System); |
| 118 | e.expectEval("takeJsValue(false)", "boolean", "false"); |
| 119 | e.expectEval("takeJsString(123)", "string", "123"); |
| 120 | e.expectEval("takeJsString()", "string", "bar"); |
| 121 | e.expectEval("takeJsNumber(5)", "number", "5"); |
| 122 | e.expectEval("takeJsNumber()", "number", "42"); |
| 123 | // Empty string coerces to 0 value in JS. Ex: Number('') === 0 |
| 124 | e.expectEval("takeJsNumber('')", "number", "0"); |
| 125 | // NaN is still a JsNumber. To check "safety", call toSafeInteger() method. |
| 126 | e.expectEval("takeJsNumber(NaN)", "number", "NaN"); |
| 127 | e.expectEval("Number({[Symbol.toPrimitive]() { return 1 }})", "number", "1"); |
| 128 | e.expectEval("takeJsBoolean(true)", "boolean", "true"); |
| 129 | e.expectEval("takeJsBoolean('hi')", "boolean", "true"); |
| 130 | e.expectEval("takeJsBoolean('')", "boolean", "false"); |
| 131 | e.expectEval("const o = {}; o === takeJsObject(o);", "boolean", "true"); |
| 132 | e.expectEval("const a = [1,2,3]; a[1] === takeJsArray(a)[1]", "boolean", "true"); |
| 133 | e.expectEval("getString()", "string", "foo"); |
| 134 | e.expectEval("getStringIntern()", "string", "foo"); |
| 135 | e.expectEval("const m = getMap(); m.get('foo')", "number", "1"); |
| 136 | e.expectEval("const s = getSet(); s.size === 2 && s.has(1) && s.has('foo') && !s.has('bar')", |
| 137 | "boolean", "true"); |
| 138 | e.expectEval("const a = getArray(); a[2];", "number", "1"); |
| 139 | e.expectEval("setRef('foo'); getRef('foo')", "string", "foo"); |
| 140 | e.expectEval("takeJsObject(undefined)", "throws", |
| 141 | "TypeError: Failed to execute 'takeJsObject' on 'JsValueContext': parameter 1 " |
| 142 | "is not of type 'JsObject'."); |
| 143 | e.expectEval("getDate() instanceof Date", "boolean", "true"); |
| 144 | e.expectEval( |
| 145 | "checkProxyPrototype(new Proxy(class extends Foo{}, {})) === Foo", "boolean", "true"); |
| 146 | e.expectEval("checkProxyPrototype(new Proxy({}, { getPrototypeOf() { return Foo; } } )) === Foo", |
| 147 | "boolean", "true"); |
| 148 | e.expectEval("checkProxyPrototype(new Proxy({}, { getPrototypeOf() { return String; } } )) " |
| 149 | "=== Foo", |
| 150 | "boolean", "false"); |
| 151 | e.expectEval("function f(val) { return this == globalThis && val === 1; }; callFunction(f);", |
| 152 | "boolean", "true"); |
| 153 | } |
| 154 | |
| 155 | } // namespace |
| 156 | } // namespace workerd::jsg::test |