commit 89a7513ad02534355cdc4b4cde7f010dfe513072 equwal <truex@equwal.com> 2026-09-22 14:05:27 -0700 Test the pure Lua modules with the text of five languages The plugin counts, cuts and finds the text of a cue in the book. Each step works on bytes, so it is sensitive to the script. Only English and Japanese had a test, so a defect in another language was not found by a test. Add spec/languages_spec.lua with one line of English, Portuguese, Spanish, Russian and Japanese. It checks the count of the characters, the cut of an anchor and a tail on a character boundary, the read of a subtitle file that mixes the scripts, and the search that finds the cue of a page. Add the new file to the fallback runner in spec/run.lua.
spec/languages_spec.lua | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ spec/run.lua | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/spec/languages_spec.lua b/spec/languages_spec.lua new file mode 100644 index 0000000..8c2c01b --- /dev/null +++ b/spec/languages_spec.lua @@ -0,0 +1,92 @@ +-- The languages SubRead is checked against: English, Portuguese, Spanish, +-- Russian and Japanese. The cue text of each has to survive the pure Lua +-- modules unchanged: the search in the book uses it byte for byte. + +local Text = require("subread.text") +local Srt = require("subread.srt") +local Cues = require("subread.cues") + +-- One line of a book in each language, with the marks that its books use. +local LINES = { + en = "It was a dark night; the rain fell on the yellow wallpaper.", + pt = "— Não me parece bonito — disse ela, à porta do coração.", + es = "¿Qué es esto? ¡Ñandú, señor Quijote, en la defensa de la dueña!", + ru = "«Ёлка, — сказал он, — и её огни горят в ожидании заказа».", + ja = "「女のいない男たち」東京で暮らしている。吾輩は猫である。", +} +-- Characters in each line, counted by hand. +local CHARS = { en = 59, pt = 55, es = 63, ru = 57, ja = 28 } +local ORDER = { "en", "pt", "es", "ru", "ja" } + +local function srtOf() + local blocks = {} + for i, code in ipairs(ORDER) do + blocks[#blocks + 1] = string.format("%d\n00:00:%02d,000 --> 00:00:%02d,900\n%s\n", + i, i, i, LINES[code]) + end + return table.concat(blocks, "\n") +end + +describe("SubRead languages", function() + it("counts the characters of each script", function() + for code, line in pairs(LINES) do + assert.equals(CHARS[code], Text.len(line)) + end + end) + + it("keeps a normalised line byte for byte", function() + for _, line in pairs(LINES) do + assert.equals(line, Text.normalize(line)) + assert.equals(line, Text.sub(line, 1, Text.len(line))) + end + end) + + it("turns the ideographic space of Japanese into one space", function() + assert.equals("a b", Text.normalize("a\227\128\128b")) + assert.equals("「女のいない男たち」 東京", Text.normalize("「女のいない男たち」 東京")) + end) + + it("cuts anchors and tails on character boundaries in each script", function() + for code, line in pairs(LINES) do + local anchors = Text.anchors(line) + assert.equals(2, #anchors, code) + assert.equals(24, Text.len(anchors[1]), code) + assert.equals(12, Text.len(anchors[2]), code) + assert.equals(anchors[1], Text.sub(line, 1, 24), code) + local tail = Text.tail(line) + assert.equals(12, Text.len(tail), code) + assert.equals(tail, Text.sub(line, Text.len(line) - 11, Text.len(line)), code) + end + end) + + it("reads a subtitle file that mixes the scripts", function() + local cues = Srt.parse(srtOf()) + assert.equals(#ORDER, #cues) + for i, code in ipairs(ORDER) do + assert.equals(LINES[code], cues[i].text) + assert.equals(LINES[code], cues[i].norm) + assert.is_false(cues[i].no_place) + assert.near(i, cues[i].start, 1e-6) + end + end) + + it("finds the cue of a page in each script", function() + local index = Cues.new(Srt.parse(srtOf())) + for i, code in ipairs(ORDER) do + -- The page starts a few characters into the line, as a page often does. + local page = Text.sub(LINES[code], 3, Text.len(LINES[code])) .. " and more text of the page" + assert.equals(i, index:findIndexForText(page), code) + -- A selection of a few words in the middle of the line. + local selected = Text.sub(LINES[code], 8, 19) + assert.equals(i, index:findIndexContaining(Text.normalize(selected)), code) + end + end) + + it("ellipsizes on a character boundary", function() + for _, line in pairs(LINES) do + local short = Text.ellipsize(line, 10) + assert.equals(11, Text.len(short)) + assert.equals(Text.sub(line, 1, 10), Text.sub(short, 1, 10)) + end + end) +end) diff --git a/spec/run.lua b/spec/run.lua index c77d230..78d7db8 100644 --- a/spec/run.lua +++ b/spec/run.lua @@ -38,7 +38,7 @@ function _G.it(name, body) end _G.setup, _G.teardown, _G.before_each, _G.after_each = function(f) f() end, function() end, nil, nil -for _, name in ipairs({ "text", "srt", "cues", "clock", "player_state" }) do +for _, name in ipairs({ "text", "srt", "cues", "clock", "player_state", "languages" }) do dofile(here .. name .. "_spec.lua") end print(string.format("%d passed, %d failed", passed, failed))