Files
MeBox/internal/service/reader/rule/js_scope_test.go
T
2026-10-01 21:57:25 +08:00

99 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rule
import (
"strings"
"testing"
)
// 本文件:规则 JS 作用域隔离的回归测试。
//
// 同一个 goja Runtime 会被一个书源的所有规则 JS 复用,而顶层 let/const 会留在
// 全局词法环境里。若不做隔离,「前一个脚本声明过的名字,后一个脚本再声明」就会
// 报 `SyntaxError: Identifier 'x' has already been declared`。
//
// 光遇聚合正好踩中:搜索列表规则是 `const { key, tab, ... } = res`,
// 单本书的 bookUrl 规则是 `let tab = result.tab || '小说'`。失败发生在「逐条取
// 字段」阶段且被 continue 跳过,于是表现为「搜索有结果但一条都读不出来」。
// legado 用的 Rhino 对顶层 let 更宽松,所以同一书源在阅读 App 里正常。
// TestRuleJSScopeIsIsolated 先后执行的规则 JS 不应因顶层 let/const 重名而失败。
func TestRuleJSScopeIsIsolated(t *testing.T) {
r := NewJSRunner(JSConfig{})
// 第一个脚本:解构声明(搜索列表规则的写法)
v, err := r.Run(nil, `const { tab, key } = {tab:'A', key:'K'}; tab + key`, nil, "")
if err != nil {
t.Fatalf("第一个脚本失败: %v", err)
}
if got := anyToString(v); got != "AK" {
t.Fatalf("第一个脚本返回 %q", got)
}
// 第二个脚本重名声明(bookUrl 规则的写法):不得报「已声明」
for i := 0; i < 3; i++ {
v, err = r.Run(nil, `let tab = 'B'; let book_id = 'x' + tab; book_id`, nil, "")
if err != nil {
t.Fatalf("第 %d 次重名声明冲突: %v", i+2, err)
}
if got := anyToString(v); got != "xB" {
t.Fatalf("第 %d 次返回 %q", i+2, got)
}
}
}
// TestRuleJSKeepsCompletionValue 包块不能吃掉「最后一条语句的值」——
// 书源的 URL 规则几乎都靠这个完成值返回结果。
func TestRuleJSKeepsCompletionValue(t *testing.T) {
r := NewJSRunner(JSConfig{})
v, err := r.Run(nil, "`data:;base64,AAAA,{\"type\":\"gysearch\"}`", nil, "")
if err != nil {
t.Fatal(err)
}
if got := anyToString(v); !strings.HasPrefix(got, "data:;base64,") {
t.Fatalf("模板字面量的完成值丢了: %q", got)
}
// 末尾带行注释也不能把收尾的块注释掉
v, err = r.Run(nil, "let a = 'ok'; a // 末尾注释", nil, "")
if err != nil {
t.Fatalf("带末尾注释的脚本失败: %v", err)
}
if got := anyToString(v); got != "ok" {
t.Fatalf("返回 %q", got)
}
}
// TestRuleJSThisStaysGlobal 包块后 this 仍是全局对象,
// 书源的 this.getVariable / this.BaseUrl / this.request 才照常可用。
func TestRuleJSThisStaysGlobal(t *testing.T) {
r := NewJSRunner(JSConfig{})
v, err := r.Run(nil, `String(this === globalThis) + '|' + typeof java.ajax`, nil, "")
if err != nil {
t.Fatal(err)
}
if got := anyToString(v); got != "true|function" {
t.Fatalf("this 语义被破坏: %q", got)
}
}
// TestRuleJSCanReadGlobalsFromJSLib 规则 JS 仍能读到 jsLib 定义的全局函数
// 与顶层 lexical 绑定(hosts 这类)。
func TestRuleJSCanReadGlobalsFromJSLib(t *testing.T) {
r := NewJSRunner(JSConfig{
JSLib: `let hosts = ['https://v1.example.com'];
function BaseUrl(){ return hosts[0] }
function pick(k){ return k + '@' + BaseUrl() }`,
})
if err := r.JSLibErr(); err != nil {
t.Fatalf("jsLib 失败: %v", err)
}
v, err := r.Run(nil, `{ let x = 'A'; pick(x) }`, nil, "")
if err != nil {
t.Fatal(err)
}
if got := anyToString(v); got != "A@https://v1.example.com" {
t.Fatalf("规则读不到 jsLib 的全局: %q", got)
}
}