/ internal / tools / browser_jswrap_test.go
browser_jswrap_test.go
 1  package tools
 2  
 3  import "testing"
 4  
 5  func TestWrapJSForEvaluate(t *testing.T) {
 6  	cases := []struct {
 7  		name        string
 8  		in          string
 9  		wantWrapped bool
10  	}{
11  		{"plain expression", "JSON.stringify(document.title)", false},
12  		{"arrow call", "[...document.querySelectorAll('a')].length", false},
13  		{"empty", "", false},
14  		{"whitespace only", "   ", false},
15  		// Semicolon-terminated expressions and multi-line expression-only
16  		// scripts must pass through: wrapping them in an IIFE without an
17  		// explicit `return` would silently change the return value to
18  		// `undefined`. Only top-level statement keywords (which are syntax
19  		// errors in expression context) trigger wrapping.
20  		{"expression with trailing semicolon", "JSON.stringify(document.title);", false},
21  		{"two expressions semicolon-separated", "foo(); bar()", false},
22  		{"two expressions newline-separated", "foo()\nbar()", false},
23  		{"return statement", "return 1", true},
24  		{"const + return", "const x = 1; return x", true},
25  		{"let", "let y = 2", true},
26  		{"var", "var z = 3", true},
27  		{"function declaration", "function f() { return 1 }", true},
28  		{"async function", "async function g() { return 1 }", true},
29  		{"async function with extra whitespace", "async  function g() { return 1 }", true},
30  		// `async` alone must NOT trigger wrap — `async () => expr` is a
31  		// valid expression; wrapping it without an explicit `return` in the
32  		// outer IIFE would silently yield `undefined`.
33  		{"async arrow expression", "async () => fetch('/x').then(r => r.json())", false},
34  		{"async arrow without space", "async()=>1", false},
35  		{"asyncFoo identifier", "asyncFoo", false},
36  		{"multiline const", "const a = 1\na", true},
37  		{"leading if", "if (true) { return 1 }", true},
38  		{"leading try", "try { return 1 } catch (e) {}", true},
39  		// Identifiers that happen to start with a keyword must NOT be wrapped.
40  		{"returnValue identifier", "returnValue", false},
41  		{"constexpr identifier", "constexpr + 1", false},
42  		// Already wrapped IIFEs pass through unchanged.
43  		{"user IIFE", "(() => { return 1 })()", false},
44  		{"user async IIFE", "(async () => { return 1 })()", false},
45  		{"user function IIFE", "(function() { return 1 })()", false},
46  	}
47  	for _, tc := range cases {
48  		t.Run(tc.name, func(t *testing.T) {
49  			out := wrapJSForEvaluate(tc.in)
50  			wrapped := out != tc.in
51  			if wrapped != tc.wantWrapped {
52  				t.Errorf("wrapJSForEvaluate(%q) = %q (wrapped=%v), wantWrapped=%v", tc.in, out, wrapped, tc.wantWrapped)
53  			}
54  		})
55  	}
56  }