-- subscriptions_format_gate_test.lua — 订阅列表页「选择格式」下拉的启用条件 -- 用法:lua5.1 tests/subscriptions_format_gate_test.lua -- -- 需求:刚添加、还没点「更新」的订阅(以及更新失败 / 解析结果为空的订阅), -- node_count 为 0,此时「订阅链接转换」列的格式下拉必须是灰色不可选的 —— -- 否则用户会选出一个必然为空的订阅链接,等于引导他生成无效内容。 -- -- .htm 需要 LuCI 运行时才能渲染,本机没有。这里的做法是把模板按 LuCI 的方式 -- 重建成 Lua chunk(<% %> 里的代码原样,文本变成 __w(...)),用一套替身环境 -- **真的渲染一遍**,再对生成的 HTML 断言。比「源码里有没有某个字符串」强得多: -- 它同时验证了模板语法、分支条件与最终输出。 package.path = "./root/usr/share/?.lua;" .. package.path local core = require("substore.core") local passed, failed = 0, 0 local function check(name, cond) if cond then passed = passed + 1; print("PASS " .. name) else failed = failed + 1; print("FAIL " .. name) end end -- ---------- 数据目录改到 /tmp,绝不碰真实的 /etc/substore ---------- -- LIST_FILE / NODES_DIR 是 require 时算好的常量,必须一并改掉。 local TMPDIR = "/tmp/substore_fmtgate_" .. tostring(os.time()) .. "_" .. tostring(math.random(1e6)) os.execute("rm -rf " .. TMPDIR) core.DATA_DIR = TMPDIR core.LIST_FILE = TMPDIR .. "/subscriptions.json" core.NODES_DIR = TMPDIR .. "/nodes" core.ensure_dirs() -- ---------- 模板 → 可执行 chunk ---------- local VIEW = "root/usr/lib/lua/luci/view/substore/subscriptions.htm" -- 与 tests/template_escape_test.lua 的 build_chunk 同构,有两处不同: -- 1) 首行从环境取 __w 的实现(那边是 `local __w = function() end`,只为过语法检查) -- 2) **字面 HTML 也要 __w 出来**。那边只关心 <% %> 里的代码,字面文本全部丢弃; -- 这里要断言渲染结果,丢了字面文本就等于丢了全部标签属性 —— 实测会漏掉 -- `disabled="disabled"`(它是字面 HTML,不是表达式),断言恒假。 local function build_chunk(src) local out = { "local __w = __substore_w" } local i, n = 1, #src while i <= n do local s = src:find("<%", i, true) if not s then out[#out + 1] = "__w(" .. string.format("%q", src:sub(i)) .. ")" break end if s > i then out[#out + 1] = "__w(" .. string.format("%q", src:sub(i, s - 1)) .. ")" end local e = src:find("%>", s + 2, true) if not e then return nil, "unclosed <% at " .. s end local tag = src:sub(s + 2, e - 1) local kind = tag:sub(1, 1) local body = tag:sub(2) if kind == "=" then out[#out + 1] = "__w(" .. body .. ")" elseif kind == ":" then out[#out + 1] = "__w(" .. string.format("%q", body) .. ")" elseif kind == "+" then out[#out + 1] = "__w()" -- header / footer 不参与断言 else out[#out + 1] = tag end out[#out + 1] = "\n" i = e + 2 end return table.concat(out, "\n") end -- ---------- LuCI 替身 ---------- local luci_stub = { util = { pcdata = function(s) return tostring(s or "") end, urlencode = function(s) return tostring(s or "") end, }, i18n = { translate = function(s) return tostring(s or "") end }, dispatcher = { context = { query = {} }, build_url = function(...) return "/cgi-bin/luci/" .. table.concat({ ... }, "/") end, }, } local http_stub = { formvalue = function() return nil end, getenv = function() return nil end, } local real_require = require local function fake_require(name) if name == "luci.http" then return http_stub end if name == "luci.util" then return luci_stub.util end if name == "luci.i18n" then return luci_stub.i18n end return real_require(name) end local function render() local f = assert(io.open(VIEW, "rb")) local src = f:read("*a") f:close() local chunk, cerr = build_chunk(src) if not chunk then error(cerr) end local fn, lerr = loadstring(chunk, "subscriptions.htm") if not fn then error(lerr) end local buf = {} local env = setmetatable({ __substore_w = function(s) if s ~= nil then buf[#buf + 1] = tostring(s) end end, require = fake_require, luci = luci_stub, token = "TESTTOKEN", -- LuCI 注入的 CSRF token }, { __index = _G }) setfenv(fn, env) fn() return table.concat(buf) end -- 取出某个订阅那一行的 开标签:select_tag_of 只取到开标签的 ">" 为止, -- 选项在它之后,拿开标签找