mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
80244ea4e2
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.
465 lines
24 KiB
Lua
465 lines
24 KiB
Lua
-- Proves the widget board's layout rules: the grid each screen width gets,
|
|
-- dense packing, that every starter page fits every supported screen, how
|
|
-- boards are stored, and that editing can never keep a page that does not fit.
|
|
|
|
package.loaded["gettext"] = function(text)
|
|
return text
|
|
end
|
|
package.loaded["ffi/util"] = {
|
|
template = function(text, ...)
|
|
local values = { ... }
|
|
return (text:gsub("%%(%d+)", function(index)
|
|
return tostring(values[tonumber(index)])
|
|
end))
|
|
end,
|
|
}
|
|
local shown = {}
|
|
package.loaded["ui/uimanager"] = {
|
|
show = function(_, widget)
|
|
table.insert(shown, widget)
|
|
end,
|
|
}
|
|
package.loaded["ui/widget/infomessage"] = { new = function(_, opts) return opts end }
|
|
-- Refusals are told in BookOrbit's pill; record them like a shown widget.
|
|
package.loaded["bookorbit_toast"] = {
|
|
show = function(text) table.insert(shown, { text = text }) end,
|
|
error = function(text) table.insert(shown, { text = text }) end,
|
|
}
|
|
|
|
-- The real metrics module over a stubbed screen, so each device below is
|
|
-- measured in board units exactly as it is on the device.
|
|
local screen = { w = 600, h = 800, dpi = 167 }
|
|
package.loaded["ffi/blitbuffer"] = setmetatable({}, { __index = function() return 0 end })
|
|
local last_face_size
|
|
package.loaded["ui/font"] = { getFace = function(_, _, size) last_face_size = size return {} end }
|
|
package.loaded["device"] = {
|
|
screen = {
|
|
getWidth = function() return screen.w end,
|
|
getHeight = function() return screen.h end,
|
|
getDPI = function() return screen.dpi end,
|
|
scaleBySize = function(_, value)
|
|
return math.ceil(value * math.min(screen.w, screen.h) / 600)
|
|
end,
|
|
},
|
|
}
|
|
|
|
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
|
|
|
|
local BoardMetrics = require("bookorbit_board_metrics")
|
|
local BoardLayout = require("bookorbit_board_layout")
|
|
local BoardStore = require("bookorbit_board_store")
|
|
local BoardEdit = require("bookorbit_board_edit")
|
|
local DashboardSections = require("bookorbit_dashboard_sections")
|
|
|
|
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 function px(value) return value end
|
|
|
|
assertEqual(BoardMetrics.get().scale, 1.05, "metrics start at the default size before settings load")
|
|
|
|
-- Size codes adapt to the column count: on a two-column board W is M and XL
|
|
-- is L.
|
|
local cw, rh = BoardLayout.sizeDims("W", 2)
|
|
assertEqual(cw .. "x" .. rh, "2x1", "W on two columns is M")
|
|
cw, rh = BoardLayout.sizeDims("XL", 6)
|
|
assertEqual(cw .. "x" .. rh, "6x2", "XL spans six columns")
|
|
cw, rh = BoardLayout.sizeDims("L", 4)
|
|
assertEqual(cw .. "x" .. rh, "2x2", "L is two by two")
|
|
|
|
assertEqual(BoardLayout.columnsFor(559), 2, "a phone-width screen gets two columns")
|
|
assertEqual(BoardLayout.columnsFor(560), 4, "an e-reader width gets four columns")
|
|
assertEqual(BoardLayout.columnsFor(899), 4, "four columns up to tablet width")
|
|
assertEqual(BoardLayout.columnsFor(900), 6, "a landscape tablet gets six columns")
|
|
|
|
-- The last column absorbs the rounding remainder, so a row of tiles always
|
|
-- ends exactly on the right margin.
|
|
for _, width in ipairs({ 600, 601, 637, 1001 }) do
|
|
local g = BoardLayout.geometry(px, width, 900, width)
|
|
local x, total = 0, 0
|
|
while x < g.cols do
|
|
total = total + BoardLayout.spanWidth(g, x, 1) + (x + 1 < g.cols and g.gap or 0)
|
|
x = x + 1
|
|
end
|
|
assertEqual(total, g.w, "single tiles across a " .. width .. "px row fill it")
|
|
assertEqual(BoardLayout.spanWidth(g, 0, g.cols), g.w, "a full-width tile spans the grid at " .. width .. "px")
|
|
end
|
|
|
|
-- Packing is first-fit and dense: a later small widget fills a hole left
|
|
-- beside an earlier wide one.
|
|
local packed = BoardLayout.pack({
|
|
{ type = "a", cw = 3, rh = 1 },
|
|
{ type = "b", cw = 2, rh = 1 },
|
|
{ type = "c", cw = 1, rh = 1 },
|
|
}, 4)
|
|
assertEqual(packed.placed[3].x .. "," .. packed.placed[3].y, "3,0", "the small widget fills the first-row hole")
|
|
assertEqual(packed.rows, 2, "two rows are used")
|
|
assertEqual(packed.holes, 2, "the half-empty last row counts as two holes")
|
|
assertEqual(BoardLayout.pack({ { cw = 9, rh = 1 } }, 4).placed[1].cw, 4, "a widget wider than the grid is clamped")
|
|
|
|
-- A page with more rows than fit drops widgets from its end; a page with room
|
|
-- takes extras that leave no new hole.
|
|
local g = BoardLayout.geometry(px, 600, 800, 600)
|
|
local base = {}
|
|
for _ = 1, 12 do table.insert(base, { type = "s", cw = 4, rh = 1 }) end
|
|
local fitted = BoardLayout.fitPage(g, base, {})
|
|
assertEqual(fitted.row_h >= g.min_row, true, "a crowded page keeps rows at least the minimum height")
|
|
assertEqual(#fitted.placed < 12, true, "a crowded page drops widgets from its end")
|
|
fitted = BoardLayout.fitPage(g, { { type = "a", cw = 4, rh = 1 } }, { { type = "extra", cw = 4, rh = 1 } })
|
|
assertEqual(#fitted.placed, 2, "a roomy page takes an extra")
|
|
assertEqual(fitted.row_h <= g.max_row, true, "rows never grow past the maximum height")
|
|
|
|
-- Every starter page of every layout fits every class of screen KOReader runs
|
|
-- on, upright and turned, with and without a frontlight: rows stay between
|
|
-- the minimum and maximum height and the grid never runs past its space.
|
|
local DEVICES = {
|
|
{ "Kindle 4", 600, 800, 167 },
|
|
{ "Kobo Mini", 600, 800, 200 },
|
|
{ "Kobo Clara", 1072, 1448, 300 },
|
|
{ "Kobo Libra", 1264, 1680, 300 },
|
|
{ "Palma", 824, 1648, 300 },
|
|
{ "Kobo Elipsa", 1404, 1872, 227 },
|
|
{ "Kindle Scribe", 1860, 2480, 300 },
|
|
{ "phone", 1080, 2400, 420 },
|
|
{ "small phone", 720, 1280, 320 },
|
|
{ "tablet", 1600, 2560, 320 },
|
|
}
|
|
local checked = 0
|
|
for _, device in ipairs(DEVICES) do
|
|
for _, turned in ipairs({ false, true }) do
|
|
screen.w, screen.h, screen.dpi = device[2], device[3], device[4]
|
|
if turned then screen.w, screen.h = screen.h, screen.w end
|
|
for _, ui_scale in ipairs(BoardMetrics.UI_SCALES) do
|
|
BoardMetrics.setUserScale(ui_scale)
|
|
local m = BoardMetrics.refresh()
|
|
local sg = BoardLayout.geometry(BoardMetrics.px, m.w, m.h, m.W)
|
|
for _, preset in ipairs(BoardStore.PRESETS) do
|
|
for _, has_light in ipairs({ true, false }) do
|
|
for page_index, page in ipairs(BoardStore.starterPages(sg.cols, preset, has_light, m.w > m.h)) do
|
|
local label = string.format("%s page %d on %s%s at %.1fx",
|
|
preset, page_index, device[1], turned and " turned" or "", ui_scale)
|
|
local result = BoardLayout.fitPage(sg, page.base, page.extras)
|
|
assertEqual(result.row_h >= sg.min_row, true, label .. " keeps rows tall enough")
|
|
assertEqual(result.grid_h <= sg.avail, true, label .. " fits the screen")
|
|
for _, item in ipairs(result.placed) do
|
|
assertEqual(item.x + item.cw <= sg.cols, true,
|
|
label .. " keeps " .. item.type .. " inside the grid")
|
|
end
|
|
checked = checked + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
assertEqual(checked, #DEVICES * 2 * #BoardMetrics.UI_SCALES * 3 * 2 * BoardStore.PAGE_COUNT,
|
|
"every starter page was checked at every interface size")
|
|
screen.w, screen.h, screen.dpi = 600, 800, 167
|
|
BoardMetrics.setUserScale(1)
|
|
BoardMetrics.refresh()
|
|
|
|
BoardMetrics.setUserScale(1.2)
|
|
local scaled = BoardMetrics.refresh()
|
|
assertEqual(scaled.scale, 1.2, "the selected interface size is retained")
|
|
BoardMetrics.face(10)
|
|
assertEqual(last_face_size, 10 * scaled.adj * scaled.scale, "the selected interface size scales fonts")
|
|
BoardMetrics.namedFace("cfont", 11)
|
|
assertEqual(last_face_size, 11 * scaled.adj * scaled.scale, "named KOReader fonts use the same interface scale")
|
|
assertEqual(BoardMetrics.normalizeUserScale(1.149999), 1.15, "minor numeric drift is normalized")
|
|
assertEqual(BoardMetrics.normalizeUserScale(1.25), 1.2, "the retired interface size migrates to the supported maximum")
|
|
assertEqual(BoardMetrics.normalizeUserScale("1.25"), 1.2, "the retired persisted string size migrates")
|
|
assertEqual(BoardMetrics.DEFAULT_UI_SCALE, 1.05, "the default interface size is 1.05x")
|
|
assertEqual(BoardMetrics.normalizeUserScale(nil), 1.05, "missing preferences use the default size")
|
|
assertEqual(BoardMetrics.normalizeUserScale("invalid"), 1.05, "invalid preferences use the default size")
|
|
assertEqual(BoardMetrics.normalizeUserScale(1.17), 1.05, "unsupported interface sizes fall back safely")
|
|
assertEqual(BoardMetrics.normalizeUserScale(1), 1, "an explicit saved 1.0x preference is preserved")
|
|
BoardMetrics.setUserScale(1)
|
|
BoardMetrics.refresh()
|
|
|
|
-- Each page has one job: no widget appears on two pages, on any screen
|
|
-- layout, with or without a frontlight.
|
|
for _, cols in ipairs({ 2, 4, 6 }) do
|
|
for _, landscape in ipairs({ false, true }) do
|
|
for _, has_light in ipairs({ true, false }) do
|
|
local seen = {}
|
|
for _, page in ipairs(BoardStore.starterPages(cols, "reader", has_light, landscape)) do
|
|
for _, list in ipairs({ page.base, page.extras }) do
|
|
for _, item in ipairs(list) do
|
|
local key = item.type .. ":" .. tostring(item.props and (item.props.source or item.props.link) or "")
|
|
assertEqual(seen[key] == nil or seen[key] == page.id, true,
|
|
key .. " appears on both " .. tostring(seen[key]) .. " and " .. page.id .. " (" .. cols .. " columns)")
|
|
seen[key] = page.id
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The Stats and Library starters keep the same pages and only change which of
|
|
-- the first three comes first; Decor is always last.
|
|
local function ids(pages)
|
|
local out = {}
|
|
for i, page in ipairs(pages) do out[i] = page.id end
|
|
return table.concat(out, ",")
|
|
end
|
|
assertEqual(ids(BoardStore.starterPages(4, "reader", true)), "home,insights,library,decor", "Reader opens on Home")
|
|
assertEqual(ids(BoardStore.starterPages(4, "stats", true)), "insights,home,library,decor", "Stats opens on Insights")
|
|
assertEqual(ids(BoardStore.starterPages(4, "library", true)), "library,home,insights,decor", "Library opens on Library")
|
|
assertEqual(table.concat(BoardStore.pageNames(BoardStore.starterPages(4, "stats", true)), ","), "Insights,Home,Library,Decor",
|
|
"page names follow the pages")
|
|
|
|
-- A device without a frontlight never gets the Light widget.
|
|
for _, cols in ipairs({ 2, 4, 6 }) do
|
|
for _, preset in ipairs(BoardStore.PRESETS) do
|
|
for _, page in ipairs(BoardStore.starterPages(cols, preset, false)) do
|
|
for _, list in ipairs({ page.base, page.extras }) do
|
|
for _, item in ipairs(list) do
|
|
assertEqual(item.type ~= "light", true, preset .. " on " .. cols .. " columns has no Light without a frontlight")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- A shelf is never one row tall, in a starter layout or as a size the editor
|
|
-- offers: a single row leaves covers too small to recognise.
|
|
for _, cols in ipairs({ 2, 4, 6 }) do
|
|
for _, preset in ipairs(BoardStore.PRESETS) do
|
|
for _, page in ipairs(BoardStore.starterPages(cols, preset, true)) do
|
|
for _, list in ipairs({ page.base, page.extras }) do
|
|
for _, item in ipairs(list) do
|
|
if item.type == "shelf" then
|
|
assertEqual(item.rh >= 2, true, preset .. " on " .. cols .. " columns keeps its shelves two rows tall")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for _, entry in ipairs(BoardEdit.LIBRARY) do
|
|
if entry.type == "shelf" then
|
|
for _, code in ipairs(entry.sizes) do
|
|
local _cw, size_rh = BoardLayout.sizeDims(code, 4)
|
|
assertEqual(size_rh >= 2, true, entry.name() .. " offers no one-row size")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Storage: anything unusable reads as the default Reader board.
|
|
local state = BoardStore.read({})
|
|
assertEqual(state.preset .. "/" .. state.tiles .. "/" .. tostring(state.started), "reader/outline/false", "no setting reads as the default")
|
|
state = BoardStore.read({ [BoardStore.SETTING_KEY] = { version = 1, preset = "nope", tiles = "neon", started = true } })
|
|
assertEqual(state.preset .. "/" .. state.tiles, "reader/outline", "unknown values fall back")
|
|
assertEqual(state.started, true, "a valid started flag survives")
|
|
state = BoardStore.read({ [BoardStore.SETTING_KEY] = { version = 99, preset = "stats" } })
|
|
assertEqual(state.preset, "reader", "a setting from another version is not trusted")
|
|
|
|
-- Custom pages are kept per column count; other counts still get the starter.
|
|
local settings = {}
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withPreset(settings, "stats")
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, {
|
|
{ items = { { type = "clock", cw = 1, rh = 1 } } },
|
|
})
|
|
local pages, custom = BoardStore.pages(settings, 4, true)
|
|
assertEqual(custom, true, "four columns use the reader's pages")
|
|
assertEqual(pages[1].base[1].type, "clock", "the stored widget is drawn")
|
|
assertEqual(#pages[2].base, 0, "a page the reader emptied stays empty")
|
|
pages, custom = BoardStore.pages(settings, 2, true)
|
|
assertEqual(custom, false, "two columns still use the starter layout")
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withPreset(settings, "library")
|
|
assertEqual(next(BoardStore.read(settings).custom), nil, "a new layout replaces the reader's pages")
|
|
|
|
-- Portrait and landscape can share a column count but not a height, so each
|
|
-- keeps its own pages.
|
|
settings = {}
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, {
|
|
{ items = { { type = "clock", cw = 1, rh = 1 } } },
|
|
}, true)
|
|
pages, custom = BoardStore.pages(settings, 4, true, true)
|
|
assertEqual(custom and pages[1].base[1].type, "clock", "landscape reads its own pages")
|
|
pages, custom = BoardStore.pages(settings, 4, true, false)
|
|
assertEqual(custom, false, "portrait keeps the starter layout")
|
|
|
|
-- A saved page keeps which page it is, so a Stats board stays named right.
|
|
settings = {}
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, {
|
|
{ id = "insights", items = {} }, { id = "home", items = {} }, { id = "library", items = {} },
|
|
})
|
|
pages = BoardStore.pages(settings, 4, true)
|
|
assertEqual(ids(pages), "insights,home,library,decor", "saved pages keep their ids")
|
|
-- A board saved before the Decor page existed gets it as it starts, and a
|
|
-- Decor page the reader emptied stays empty.
|
|
assertEqual(#pages[4].base > 0, true, "a three-page board gains the starter Decor page")
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, {
|
|
{ id = "insights", items = {} }, { id = "home", items = {} }, { id = "library", items = {} }, { id = "decor", items = {} },
|
|
})
|
|
pages = BoardStore.pages(settings, 4, true)
|
|
assertEqual(#pages[4].base, 0, "an emptied Decor page stays empty")
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, { { items = {} } })
|
|
assertEqual(BoardStore.pages(settings, 4, true)[1].id, "home", "pages saved before ids default by position")
|
|
|
|
-- Pages saved before shelves had a two-row minimum read back with two-row shelves.
|
|
settings = {}
|
|
settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(settings, 4, {
|
|
{ items = { { type = "shelf", cw = 4, rh = 1, props = { source = "random" } } } },
|
|
})
|
|
pages = BoardStore.pages(settings, 4, true)
|
|
assertEqual(pages[1].base[1].rh, 2, "a stored one-row shelf is drawn two rows tall")
|
|
assertEqual(BoardStore.read(settings).custom["4"].pages[1].items[1].rh, 1, "the stored setting itself is left alone")
|
|
|
|
-- A shelf's key is its source plus its filter, in a stable order.
|
|
assertEqual(BoardStore.shelfKey({ source = "random" }), "random", "a plain shelf keys by source")
|
|
assertEqual(BoardStore.shelfKey({ source = "authors", params = { sort = "title", author = "Le Guin" } }),
|
|
"authors:author=Le Guin:sort=title", "filters are keyed in sorted order")
|
|
assertEqual(BoardStore.shelfKey(nil), "random", "a shelf without props is Discover")
|
|
|
|
-- Editing, on a four-column board.
|
|
local function newCatalog(opts)
|
|
opts = opts or {}
|
|
local catalog = {
|
|
settings = opts.settings or {},
|
|
inner_dimen = { w = opts.w or 600, h = opts.h or 800 },
|
|
current_context = { kind = "dashboard", board_page = 1, dashboard = { boardShelves = {}, boardNames = {} } },
|
|
redraws = 0,
|
|
fetches = 0,
|
|
}
|
|
for name, fn in pairs(BoardEdit) do
|
|
if type(fn) == "function" and name:match("^board") then catalog[name] = fn end
|
|
end
|
|
function catalog:menuChromeHeight() return 0, 0 end
|
|
function catalog:dashboardMode() return true end
|
|
function catalog:updateItems() self.redraws = self.redraws + 1 end
|
|
function catalog:persistSetting(key, value) self.settings[key] = value end
|
|
function catalog:boardHasLight() return true end
|
|
function catalog:boardPages(cols) return BoardStore.pages(self.settings, cols, true) end
|
|
function catalog:boardPage() return self.current_context.board_page end
|
|
function catalog:boardSources() return {}, {} end
|
|
function catalog:loadDashboardRoot() self.fetches = self.fetches + 1 end
|
|
return catalog
|
|
end
|
|
|
|
local catalog = newCatalog()
|
|
catalog:boardEnterEdit()
|
|
local edit = catalog.board_edit
|
|
local eg = catalog:boardGeometry()
|
|
local items = edit.pages[1].items
|
|
local fitted_home = BoardLayout.fitPage(eg, BoardStore.starterPages(eg.cols, "reader", true)[1].base,
|
|
BoardStore.starterPages(eg.cols, "reader", true)[1].extras)
|
|
assertEqual(#items, #fitted_home.placed, "editing starts from the starter page as drawn")
|
|
|
|
-- Earlier and Later swap neighbours.
|
|
local first, second = items[1].type, items[2].type
|
|
catalog:boardEditRun({ kind = "edit-select", index = 1 })
|
|
catalog:boardEditRun({ kind = "edit-move", delta = 1 })
|
|
assertEqual(items[1].type .. "," .. items[2].type, second .. "," .. first, "Later swaps with the next widget")
|
|
assertEqual(edit.selected, 2, "the selection follows the moved widget")
|
|
|
|
-- A size that would overflow the page is refused and the widget keeps its size.
|
|
shown = {}
|
|
local before = items[2].cw .. "x" .. items[2].rh
|
|
while catalog:boardPageFits(eg, items) do
|
|
table.insert(items, { type = "week", cw = 4, rh = 1 })
|
|
end
|
|
table.remove(items)
|
|
assertEqual(catalog:boardPageFits(eg, items), true, "the page is filled to its limit")
|
|
catalog:boardEditRun({ kind = "edit-size", code = "XL" })
|
|
assertEqual(items[2].cw .. "x" .. items[2].rh, before, "an overflowing size is refused")
|
|
assertEqual(#shown, 1, "the reader is told why")
|
|
assertEqual(catalog:boardPageFits(eg, items), true, "the page still fits")
|
|
|
|
-- Adding to a full page is refused rather than squeezing the page.
|
|
shown = {}
|
|
local count = #items
|
|
catalog:boardEditRun({ kind = "sheet-add", entry = BoardEdit.entryFor({ type = "heatmap" }) })
|
|
assertEqual(#items, count, "a widget that cannot fit is not added")
|
|
assertEqual(#shown, 1, "the reader is told the page is full")
|
|
|
|
-- Moving to another page lands on the next page and leaves this one.
|
|
catalog:boardEditRun({ kind = "edit-select", index = 1 })
|
|
local moved = items[1]
|
|
local target_count = #edit.pages[2].items
|
|
catalog:boardEditRun({ kind = "edit-page" })
|
|
local dest = edit.pages[2].items
|
|
if dest[#dest] == moved then
|
|
assertEqual(#dest, target_count + 1, "the widget lands on the next page")
|
|
assertEqual(items[1] ~= moved, true, "and leaves this one")
|
|
else
|
|
assertEqual(#dest, target_count, "a full next page refuses the widget")
|
|
end
|
|
|
|
-- Removing frees the room, and leaving edit mode stores the pages for this
|
|
-- column count and redraws once.
|
|
catalog:boardEditRun({ kind = "edit-remove", index = 1 })
|
|
assertEqual(edit.changed, true, "the board knows it was changed")
|
|
catalog:boardEditRun({ kind = "edit-done" })
|
|
assertEqual(catalog.board_edit, nil, "edit mode ends")
|
|
local stored = BoardStore.read(catalog.settings).custom[tostring(eg.cols)]
|
|
assertEqual(type(stored) == "table" and #stored.pages, BoardStore.PAGE_COUNT, "every page is stored for this column count")
|
|
assertEqual(catalog.fetches, 0, "nothing is fetched when the cache already holds what the board shows")
|
|
|
|
-- A saved page that no longer fits this screen opens in the editor as it is
|
|
-- drawn, so every change can still be checked against the page.
|
|
local crowded = {}
|
|
for _ = 1, 12 do table.insert(crowded, { type = "week", cw = 4, rh = 1 }) end
|
|
catalog = newCatalog()
|
|
catalog.settings[BoardStore.SETTING_KEY] = BoardStore.withCustomPages(catalog.settings, eg.cols, { { items = crowded } })
|
|
catalog:boardEnterEdit()
|
|
assertEqual(#catalog.board_edit.pages[1].items < 12, true, "the editor drops what the screen cannot show")
|
|
assertEqual(catalog:boardPageFits(eg, catalog.board_edit.pages[1].items), true, "the edited page fits")
|
|
|
|
-- Leaving without a change stores nothing.
|
|
catalog = newCatalog()
|
|
catalog:boardEnterEdit()
|
|
catalog:boardEditRun({ kind = "edit-done" })
|
|
assertEqual(next(BoardStore.read(catalog.settings).custom), nil, "an untouched board keeps the starter layout")
|
|
|
|
-- A shelf the board now shows but the cache lacks is fetched after the redraw.
|
|
catalog = newCatalog()
|
|
function catalog:boardSources() return { ["want-to-read"] = { source = "want-to-read" } }, {} end
|
|
catalog:boardRefreshIfMissing()
|
|
assertEqual(catalog.redraws, 1, "the board redraws first")
|
|
assertEqual(catalog.fetches, 1, "the missing shelf is fetched")
|
|
catalog.current_context.dashboard.boardShelves["want-to-read"] = {}
|
|
catalog:boardRefreshIfMissing()
|
|
assertEqual(catalog.fetches, 1, "a shelf the cache holds, even empty, is not fetched again")
|
|
|
|
-- Shelves picked on the old four-slot dashboard join the board once it has
|
|
-- started, and the old setting is dropped so this happens once.
|
|
local legacy = {
|
|
{ type = "stats" },
|
|
{ type = "continue-reading" },
|
|
{ type = "authors", sourceName = "Le Guin", params = { author = "Le Guin" } },
|
|
{ type = "browse" },
|
|
schemaVersion = DashboardSections.SCHEMA_VERSION,
|
|
}
|
|
catalog = newCatalog({ settings = { [DashboardSections.SETTING_KEY] = legacy } })
|
|
assertEqual(catalog:boardMigrateLegacyShelves(), false, "nothing moves before the board has started")
|
|
assertEqual(catalog.settings[DashboardSections.SETTING_KEY] ~= nil, true, "the old setting waits for the start")
|
|
catalog.settings[BoardStore.SETTING_KEY] = BoardStore.withPreset(catalog.settings, "reader")
|
|
assertEqual(catalog:boardMigrateLegacyShelves(), true, "the migration runs once the board has started")
|
|
assertEqual(catalog.settings[DashboardSections.SETTING_KEY], nil, "the old setting is dropped")
|
|
local found
|
|
for _, page in ipairs(BoardStore.read(catalog.settings).custom[tostring(eg.cols)].pages) do
|
|
for _, item in ipairs(page.items) do
|
|
if item.type == "shelf" and item.props.source == "authors" then found = item end
|
|
end
|
|
end
|
|
assertEqual(found and found.props.title, "Le Guin", "the picked author became a shelf")
|
|
assertEqual(found and found.props.params.author, "Le Guin", "the shelf keeps its filter")
|
|
assertEqual(catalog:boardMigrateLegacyShelves(), false, "the migration does not run twice")
|
|
|
|
assertEqual(BoardLayout.editBarTop(0, 300, 104, 24, 1300), 324, "sparse page toolbar stays below its first tile")
|
|
assertEqual(BoardLayout.editBarTop(1000, 250, 104, 24, 1300), 872, "bottom tile toolbar fits above")
|
|
assertEqual(BoardLayout.editBarTop(0, 250, 104, 24, 300), 0, "toolbar cannot escape above the grid")
|
|
for _, source in ipairs({ "libraries", "collections", "smart-scopes", "authors", "series" }) do
|
|
local picked_entry = BoardEdit.entryFor({ type = "shelf", props = { source = source, params = { id = 1 } } })
|
|
assertEqual(picked_entry and picked_entry.pick, true, "picked " .. source .. " shelf retains its library entry")
|
|
assertEqual(table.concat(picked_entry.sizes, ","), "XL,L", "picked shelf retains both advertised sizes")
|
|
end
|
|
|
|
print("bookorbit_dashboard_layout_test.lua: ok")
|