Skip to content
File

Blob: src/workerd/jsg/jsvalue-test.c++

5.4 KB
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"
7namespace workerd::jsg::test {
8namespace {
9 
10V8System v8System;
11class ContextGlobalObject: public Object, public ContextGlobal {};
12 
13struct 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};
114JSG_DECLARE_ISOLATE_TYPE(JsValueIsolate, JsValueContext, JsValueContext::Foo);
115 
116KJ_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