Files
bookorbit/koreader-plugin/spec/bookorbit_download_folder_test.lua
neon 80244ea4e2 feat(koreader): rebuild the plugin interface and fix four-way annotation sync
Squash of feat/koreader-widget-board. KOReader plugin 2.0.0 gets the widget-board dashboard, redesigned
book details, menus, lists and dialogs, Decor page and reading companions. Highlights, notes and bookmarks
now stay in step across web, Kobo, KOReader and the phones (defects D-00 to D-20, migration 0121), and
reading progress and narration hand off cleanly between devices.
2026-10-20 16:05:27 -06:00

177 lines
7.3 KiB
Lua

-- Single and bulk downloads write to one destination, KOReader's own
-- download_dir, so both sheets have to offer the same chooser and see each
-- other's choice.
local shown = {}
local next_ticks = {}
package.loaded["ui/bidi"] = {
dirpath = function(value) return value end,
filepath = function(value) return value end,
}
package.loaded["datastorage"] = {
getSettingsDir = function() return "/tmp/bookorbit-spec" end,
getDataDir = function() return "/tmp/bookorbit-spec" end,
}
package.loaded["dump"] = function(value) return tostring(value) end
package.loaded["document/documentregistry"] = { hasProvider = function() return true end }
package.loaded["ui/widget/buttondialog"] = {
new = function(_, opts)
opts = opts or {}
function opts:setTitle(title) self.title = title end
return opts
end,
}
package.loaded["ui/widget/infomessage"] = { new = function(_, opts) return opts or {} end }
package.loaded["ui/widget/confirmbox"] = package.loaded["ui/widget/infomessage"]
package.loaded["ui/widget/inputdialog"] = package.loaded["ui/widget/infomessage"]
package.loaded["ui/widget/notification"] = package.loaded["ui/widget/infomessage"]
package.loaded["ui/uimanager"] = {
show = function(_, widget) table.insert(shown, widget) end,
close = function() end,
nextTick = function(_, callback) table.insert(next_ticks, callback) end,
scheduleIn = function(_, _, callback) table.insert(next_ticks, callback) end,
preventStandby = function() end,
allowStandby = function() end,
forceRePaint = function() end,
}
package.loaded["libs/libkoreader-lfs"] = {
attributes = function() return nil end,
dir = function() return function() return nil end end,
}
package.loaded["logger"] = { dbg = function() end, warn = function() end }
package.loaded["util"] = {
fixUtf8 = function(value) return value end,
getSafeFilename = function(value) return value end,
makePath = function() end,
trim = function(value) return value end,
}
package.loaded["ffi/util"] = {
template = function(value, ...)
local values = { ... }
return (tostring(value):gsub("%%(%d+)", function(index)
return tostring(values[tonumber(index)])
end))
end,
}
package.loaded["gettext"] = setmetatable({
ngettext = function(one, many, count) return count == 1 and one or many end,
}, { __call = function(_, text) return text end })
package.loaded["bookorbit_state_manager"] = {}
package.loaded["bookorbit_download_transfer"] = {}
package.loaded["bookorbit_bulk_checkpoint"] = { MAX_FAILURES = 8 }
package.loaded["bookorbit_capabilities"] = { has = function() return false end }
-- The chooser is KOReader's own directory browser; the test drives its confirm
-- callback directly rather than rendering it.
local chooser = { opened = 0, start_dir = nil, confirm = nil }
package.loaded["ui/downloadmgr"] = {
new = function(_, opts)
return {
chooseDir = function(_, start_dir)
chooser.opened = chooser.opened + 1
chooser.start_dir = start_dir
chooser.confirm = opts.onConfirm
end,
}
end,
}
-- The bulk sheet is drawn elsewhere; here it records what it was opened with
-- and offers its Folder row the way the sheet does.
local plans = {}
package.loaded["bookorbit_bulk_sheet"] = {
openPlan = function(catalog, plan)
table.insert(plans, plan)
return { catalog = catalog, plan = plan }
end,
}
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
local BulkDownload = require("bookorbit_catalog_bulk_download")
local CatalogDownload = require("bookorbit_catalog_download")
local function assertEqual(actual, expected, label)
if actual ~= expected then
error(string.format("%s: expected %s, got %s", label, tostring(expected), tostring(actual)))
end
end
local stored = { download_dir = "/mnt/onboard/Books" }
local saved = {}
local persisted_dir
G_reader_settings = {
readSetting = function(_, key) return stored[key] end,
saveSetting = function(_, key, value)
stored[key] = value
table.insert(saved, { key = key, value = value })
end,
flush = function() persisted_dir = stored.download_dir end,
}
local Catalog = {}
BulkDownload.install(Catalog)
CatalogDownload.install(Catalog)
Catalog.settings = {}
Catalog.client = { server_url = "http://localhost", username = "test" }
Catalog:initBulkDownloadState()
-- The bulk settings sheet offers the folder alongside the other knobs.
Catalog:showBulkDownloadSettings()
assertEqual(#plans, 1, "the settings sheet opened")
assertEqual(plans[1].settings_only, true, "it opens as settings, with nothing to download")
assertEqual(Catalog:getCurrentDownloadDir(), "/mnt/onboard/Books", "the sheet's Folder row reports the current folder")
-- Its Folder row runs the shared chooser and redraws the sheet once the
-- choice is saved.
local redrawn = 0
Catalog:chooseDownloadDir(function() redrawn = redrawn + 1 end)
assertEqual(chooser.opened, 1, "the chooser opened")
assertEqual(chooser.start_dir, "/mnt/onboard/Books", "the chooser starts at the current folder")
chooser.confirm("/mnt/onboard/Books/BookOrbit")
assertEqual(#saved, 1, "one setting was written")
assertEqual(saved[1].key, "download_dir", "the shared download_dir is what changed")
assertEqual(saved[1].value, "/mnt/onboard/Books/BookOrbit", "the chosen path was stored")
assertEqual(persisted_dir, stored.download_dir, "the chosen folder is durable before the sheet redraws")
assertEqual(#next_ticks, 1, "the sheet was scheduled to redraw")
next_ticks[1]()
assertEqual(redrawn, 1, "the sheet redraws with the new folder")
-- The same choice is what a bulk run then downloads into.
assertEqual(Catalog:getCurrentDownloadDir(), "/mnt/onboard/Books/BookOrbit", "bulk downloads follow the choice")
-- An over-long path keeps the end, which is the part that says where the files
-- actually land; truncating the other way would leave every folder looking the
-- same.
local CatalogUtil = require("bookorbit_catalog_util")
local deep = "/mnt/onboard/Books/Fiction/Science Fiction/Series/Long Name"
local short = CatalogUtil.shortPath(deep, 24)
assertEqual(#short, 24, "the label is bounded")
assertEqual(short:sub(1, 3), "...", "the truncation is marked")
assertEqual(deep:sub(-10), short:sub(-10), "the tail of the path survives")
assertEqual(CatalogUtil.shortPath("/short", 24), "/short", "a short path is left alone")
local file = assert(io.open("koreader-plugin/bookorbit.koplugin/bookorbit_main_menu.lua"))
local source = file:read("*a")
file:close()
local block = assert(source:match("(function MainMenu:chooseDownloadFolder.-)\n%-%- %-%-+ shortcut"))
local MainMenu = {}
local chunk = assert(loadstring(block))
setfenv(chunk, setmetatable({ MainMenu = MainMenu, UIManager = package.loaded["ui/uimanager"] }, { __index = _G }))
chunk()
local returned = false
MainMenu:chooseDownloadFolder(nil, function()
assertEqual(persisted_dir, "/path/to/library/downloads", "direct Settings chooser persists before returning")
returned = true
end)
chooser.confirm("/path/to/library/downloads")
assertEqual(persisted_dir, stored.download_dir, "the fallback chooser also persists immediately")
assertEqual(returned, false, "the fallback waits until the chooser closes before returning")
next_ticks[#next_ticks]()
assertEqual(returned, true, "direct Settings receives the saved folder")
print("bookorbit_download_folder_test.lua: ok")