Skip to content

Commit dad7821

Browse files
fix(typst): anchor color-token search in border parsing
CSS `border` and `border-color` declarations lost any tokens that preceded an `rgb()` / `rgba()` color because `s:find('%w+%b()', start)` in `translate_border` and `consume_color` is unanchored — it would skip past width/style (or earlier color) tokens to match the parens-bearing form. With `border: 0px solid rgb(255, 0, 0)` the width and style were never seen, leaving the default `medium` (2.25pt) thickness. Adding `^` anchors the search at `start` so the leading token is consumed first, matching how named/hex colors already worked. Also adds a luaunit-based unit-test convention: - tests/unit-lua/*.test.lua (luaunit suites; ends in os.exit(lu...)) - tests/smoke/lua-unit/lua-unit.test.ts runs each via `quarto run` with LUA_PATH set so `require('luaunit')` and filter-module requires work. Fixes #14460
1 parent ff77c43 commit dad7821

5 files changed

Lines changed: 3690 additions & 2 deletions

File tree

news/changelog-1.10.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ All changes included in 1.10:
1616
### `typst`
1717

1818
- ([#14261](https://github.com/quarto-dev/quarto-cli/issues/14261)): Fix theorem/example block titles containing inline code producing invalid Typst markup when syntax highlighting is applied.
19+
- ([#14460](https://github.com/quarto-dev/quarto-cli/issues/14460)): Fix CSS `border` and `border-color` declarations losing tokens that precede an `rgb()`/`rgba()` color (e.g. `border: 0px solid rgb(255, 0, 0)` rendering as a 2.25pt border instead of being suppressed).
1920

2021
### `revealjs`
2122

src/resources/filters/modules/typst_css.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,9 @@ local function translate_border(v, warnings)
679679
local style = 'solid' -- css specifies none
680680
local paint = 'black' -- css specifies currentcolor
681681
parse_multiple(v, 3, function(s, start)
682-
local fbeg, fend = s:find('%w+%b()', start)
682+
-- '^' anchors the match at `start` so a width/style token (e.g. "0px",
683+
-- "solid") preceding an rgb()/rgba() color is not skipped. See #14460.
684+
local fbeg, fend = s:find('^%w+%b()', start)
683685
if fbeg then
684686
local paint2 = translate_border_color(s:sub(fbeg, fend), warnings)
685687
if paint2 then
@@ -728,7 +730,9 @@ local function consume_style(s, start, warnings)
728730
end
729731

730732
local function consume_color(s, start, warnings)
731-
local fbeg, fend = s:find('%w+%b()', start)
733+
-- '^' anchors at `start` so we consume the token AT start instead of
734+
-- skipping ahead to a later rgb()/rgba(). See #14460.
735+
local fbeg, fend = s:find('^%w+%b()', start)
732736
if not fbeg then
733737
fbeg, fend = s:find('%S+', start)
734738
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* lua-unit.test.ts
3+
*
4+
* Runs the luaunit-based Lua unit tests in tests/unit-lua/ via `quarto run`.
5+
*
6+
* Convention for a Lua unit test file:
7+
* - Lives under tests/unit-lua/ and ends in `.test.lua`.
8+
* - `local lu = require('luaunit')` (resolved via LUA_PATH set below).
9+
* - May `require()` any module under src/resources/filters/ or
10+
* src/resources/filters/modules/ (also on LUA_PATH).
11+
* - Mocks any filter-runtime globals it needs (param, tcontains,
12+
* format_typst_float, quarto.log.*, _quarto.*).
13+
* - Ends with `os.exit(lu.LuaUnit.run())` so failures surface as a
14+
* non-zero exit code, which fails this Deno test.
15+
*
16+
* To add a new Lua unit test, drop a file into tests/unit-lua/ and add its
17+
* relative path to LUA_TESTS below.
18+
*/
19+
20+
import { fromFileUrl, join } from "../../../src/deno_ral/path.ts";
21+
import { assert } from "testing/asserts";
22+
import { execProcess } from "../../../src/core/process.ts";
23+
import { quartoDevCmd } from "../../utils.ts";
24+
import { unitTest } from "../../test.ts";
25+
26+
// Explicit list, relative to tests/unit-lua/. Keep alphabetized.
27+
const LUA_TESTS: string[] = [
28+
"typst-css.test.lua",
29+
];
30+
31+
const testsDir = fromFileUrl(new URL("../../", import.meta.url));
32+
const repoRoot = fromFileUrl(new URL("../../../", import.meta.url));
33+
const unitLuaDir = join(testsDir, "unit-lua");
34+
const filtersDir = join(repoRoot, "src", "resources", "filters");
35+
const filterModulesDir = join(filtersDir, "modules");
36+
37+
// Pandoc honors LUA_PATH for `require()` resolution in lua filters.
38+
// The `;;` at the end preserves the default search path.
39+
const LUA_PATH = [
40+
join(unitLuaDir, "?.lua"),
41+
join(filterModulesDir, "?.lua"),
42+
join(filtersDir, "?.lua"),
43+
"",
44+
].join(";") + ";";
45+
46+
for (const relPath of LUA_TESTS) {
47+
const luaScript = join(unitLuaDir, relPath);
48+
unitTest(`lua-unit > ${relPath}`, async () => {
49+
const result = await execProcess(
50+
{
51+
cmd: quartoDevCmd(),
52+
args: ["run", luaScript],
53+
env: { LUA_PATH },
54+
},
55+
undefined,
56+
undefined,
57+
undefined,
58+
true, // capture stdout/stderr
59+
);
60+
assert(
61+
result.success,
62+
`Lua unit test failed (exit ${result.code}): ${relPath}\n` +
63+
`--- stdout ---\n${result.stdout ?? ""}\n` +
64+
`--- stderr ---\n${result.stderr ?? ""}`,
65+
);
66+
});
67+
}

0 commit comments

Comments
 (0)