* Enhance Cloud TTS with navigation controls and shared UI overlay in desktop app * Refactor Cloud TTS voice settings and remove standalone settings overlay on desktop app * Persist reader window state and improve slider interaction in desktop app * Improve PDF page transitions and refine focus management in desktop app * Refactor scrollbar interaction and adjust desktop modal focus handling * Improve PDF sidecar synchronization and cross-platform metadata compatibility * Refactor PDF annotation comment logic to shared module and implement Desktop UI * Refactor reader screen to use tap-to-toggle and full-width styling in desktop app * Refactor reader workspace layout and chrome-panel interactions * Implement global search keyboard shortcuts and focusable chrome layers * Implement flavor-specific legal links and update the About UI * Refactor reader UI controls on desktop app * Enhance desktop folder sync with background metadata extraction and improved error handling * Refactor Library UI and remove redundant Home tab in desktop app * Add custom tooltips to reader icon buttons in desktop app * Integrate app theme controls into reader interfaces on desktop * Add right-to-left pagination support and improve focus restoration on desktop app * Improve EPUB pagination geometry and diagnostic logging for layout cutoffs on desktop app * Update desktop reader defaults and implement settings migration * Implement block-based position tracking in ReaderLocator * Enhance EPUB highlighting reliability in desktop app * Add support for custom reader themes and update highlight palette logic in desktop app * Replace the Tools panel with a "More" dropdown menu and refactor account UI * Implement account profile header in desktop sidebar * Implement cloud sync reliability improvements and sidebar toggle on desktop app * Improve EPUB annotation synchronization and highlight mapping accuracy in desktop app * Integrate WebView2 for EPUB vertical rendering on Windows * Refactor reader layout logic and enhance WebView2 diagnostics * Improve vertical reading layout and WebView2 resizing on Desktop * Refine vertical reading mode layout and margin handling * Enhance reader locator precision and Desktop mode-switching reliability * Implement chapter-level caching and warm-start pagination in desktop app * Replace bundled KCEF with native system webviews via SWT * Refactor EPUB page info bar visibility and layout logic * Improve PDF toolbar persistence and fix tab reactivation logic * Enable multi-selection and bulk operations for custom fonts * Refactor instrumentation tests * Add EPUB UI test fixture and initial instrumentation tests * Expand EpubReader UI tests and improve accessibility * Add instrumentation tests and test tags for library and reader screens * Enhance OPDS parser logic and catalog integration * Add support for toggling local synchronization on a per-folder basis. * Implement tri-state sizing for the TTS overlay * Persist TTS overlay size across sessions * Refactor reader brightness control and add incremental step buttons * Improve CSS support, pagination control, and style-aware semantic caching * Improve link handling, interaction, and diagnostics in the paginated reader * crash fixes * Implement persistent pending removal for external files * Implement book-specific word replacements * Add native vertical reading mode with custom renderer * Implement text selection and navigation improvements for the native vertical reader * Implement locator-based navigation and improved vertical scrolling in native vertical mode in epub * Implement lazy loading and chapter prefetching for native vertical reader * Improve window lifecycle and disposal handling on Desktop * Optimize vertical reading performance in desktop app * Enhance TTS start accuracy and diagnostic logging on desktop * Refactor AI settings visibility on desktop * Improve pagination height measurement and enhance cutoff diagnostics * Implement lifecycle management and improve justified text splitting for pagination * Refine AI usage tracking and force AI feature visibility on Desktop * Add descriptive context comments and usage examples to string and plural resources. * Optimize performance and memory usage in search and state mapping * Replace reader page sliders with minimal slider and navigation controls * Add support for CBT comic archives * Harden file path validation and XML parsing to prevent security vulnerabilities * Implement local account profile caching and optimize desktop performance * Improve desktop persistence reliability and add Linux secure storage support * Improved PDF zoom stability and layout prediction during zoom commits * Improved PDF spread layout prediction, reader focus restoration, and account profile caching * Enhance highlight precision and scoping using block-local offsets and CFIs * Enhance cloud book content synchronization and background downloads * Implement granular timestamp tracking for reading positions and PDF annotations * Restrict diagnostic logging and stack traces to debug builds * Refine PDF page gaps and reader chrome interaction logic * Refactor PDF highlight rendering and overhaul Desktop sidebar UI * Implement a new interaction dock and undo/redo history for PDF annotations in desktop * Enhance PDF color picker and improve navigation scroll restoration * Add highlight palette customization and improve selection menu UI in desktop app epub reader * Enhance desktop shelf management and library organization
415 lines
17 KiB
Kotlin
415 lines
17 KiB
Kotlin
// CssParserTest.kt
|
|
package com.aryan.reader.paginatedreader
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.isSpecified
|
|
import androidx.compose.ui.text.font.FontStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
import androidx.compose.ui.text.style.TextDecoration
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.em
|
|
import com.google.common.truth.Truth.assertThat
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
class CssParserTest {
|
|
|
|
private val dummyConstraints = androidx.compose.ui.unit.Constraints()
|
|
private val baseFontSize = 16f
|
|
private val density = 1f
|
|
|
|
private fun parseTextColor(value: String): Color? {
|
|
val result = CssParser.parse(
|
|
cssContent = "p { color: $value; }",
|
|
cssPath = null,
|
|
baseFontSizeSp = baseFontSize,
|
|
density = density,
|
|
constraints = dummyConstraints,
|
|
isDarkTheme = false
|
|
)
|
|
return result.rules.byTag["p"]
|
|
?.firstOrNull()
|
|
?.style
|
|
?.spanStyle
|
|
?.color
|
|
?.takeIf { it.isSpecified }
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handlesNamedColorsCorrectly() {
|
|
assertThat(parseTextColor("red")).isEqualTo(Color.Red)
|
|
assertThat(parseTextColor("black")).isEqualTo(Color.Black)
|
|
assertThat(parseTextColor("transparent")).isEqualTo(Color.Transparent)
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handles3DigitHexCodes() {
|
|
assertThat(parseTextColor("#F0C")).isEqualTo(Color(0xFFFF00CC))
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handles6DigitHexCodes() {
|
|
assertThat(parseTextColor("#FF00CC")).isEqualTo(Color(0xFFFF00CC))
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handles8DigitHexCodes() {
|
|
assertThat(parseTextColor("#80FF00CC")).isEqualTo(Color(128, 255, 0, 204))
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handlesRgbFunction() {
|
|
assertThat(parseTextColor("rgb(255, 0, 204)")).isEqualTo(Color(255, 0, 204))
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_handlesRgbaFunction() {
|
|
assertThat(parseTextColor("rgba(255, 0, 204, 0.5)")).isEqualTo(Color(255, 0, 204, 128))
|
|
}
|
|
|
|
@Test
|
|
fun parseColor_returnsNullForInvalidInput() {
|
|
assertThat(parseTextColor("not a color")).isNull()
|
|
assertThat(parseTextColor("#12345")).isNull()
|
|
assertThat(parseTextColor("rgb(1,2)")).isNull()
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesSimpleRule() {
|
|
val css = "p { color: red; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val rules = result.rules.byTag["p"]
|
|
assertThat(rules).hasSize(1)
|
|
assertThat(rules?.first()?.style?.spanStyle?.color).isEqualTo(Color.Red)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesMultipleSelectors() {
|
|
val css = "h1, h2, h3 { font-weight: bold; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.rules.byTag["h1"]).hasSize(1)
|
|
assertThat(result.rules.byTag["h2"]).hasSize(1)
|
|
assertThat(result.rules.byTag["h3"]).hasSize(1)
|
|
assertThat(result.rules.byTag["h1"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
|
|
assertThat(result.rules.byTag["h2"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
|
|
assertThat(result.rules.byTag["h3"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesImportantRules() {
|
|
val css = "p { color: red !important; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val importantRule = result.rules.byTag["p"]?.find { it.selector.specificity >= 10000 }
|
|
assertThat(importantRule).isNotNull()
|
|
assertThat(importantRule!!.style.spanStyle.color).isEqualTo(Color.Red)
|
|
val normalRule = result.rules.byTag["p"]?.find { it.selector.specificity < 10000 }
|
|
assertThat(normalRule).isNull()
|
|
}
|
|
|
|
@Test
|
|
fun parse_createsBothNormalAndImportantRulesWhenMixed() {
|
|
val css = "p { color: blue; background-color: white !important; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val rules = result.rules.byTag["p"]
|
|
assertThat(rules).hasSize(2)
|
|
|
|
val importantRule = rules?.find { it.selector.specificity >= 10000 }
|
|
assertThat(importantRule).isNotNull()
|
|
assertThat(importantRule!!.style.blockStyle.backgroundColor).isEqualTo(Color.White)
|
|
assertThat(importantRule.style.spanStyle.color.isSpecified).isFalse()
|
|
|
|
val normalRule = rules.find { it.selector.specificity < 10000 }
|
|
assertThat(normalRule).isNotNull()
|
|
assertThat(normalRule!!.style.spanStyle.color).isEqualTo(Color.Blue)
|
|
assertThat(normalRule.style.blockStyle.backgroundColor.isSpecified).isFalse()
|
|
}
|
|
|
|
@Test
|
|
fun parse_extractsFontFaceRulesAndResolvesPath() {
|
|
val css = """
|
|
@font-face {
|
|
font-family: "MyCustomFont";
|
|
src: url("../fonts/myfont.ttf");
|
|
font-weight: bold;
|
|
}
|
|
p { color: black; }
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, "OEBPS/styles/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.rules.byTag).containsKey("p")
|
|
assertThat(result.fontFaces).hasSize(1)
|
|
val fontFace = result.fontFaces.first()
|
|
assertThat(fontFace.fontFamily).isEqualTo("mycustomfont")
|
|
assertThat(fontFace.src).isEqualTo("OEBPS/fonts/myfont.ttf")
|
|
assertThat(fontFace.fontWeight).isEqualTo(FontWeight.Bold)
|
|
assertThat(fontFace.fontStyle).isEqualTo(FontStyle.Normal)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesFontFaceWithDataUri() {
|
|
val dataUri = "data:font/truetype;base64,AAEAAA..."
|
|
val css = """
|
|
@font-face {
|
|
font-family: 'MyDataFont';
|
|
src: url('$dataUri');
|
|
}
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, "/css/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.fontFaces).hasSize(1)
|
|
assertThat(result.fontFaces.first().src).isEqualTo(dataUri)
|
|
}
|
|
|
|
@Test
|
|
fun parse_sanitizesPseudoClassesFromSelectors() {
|
|
val css = "a:hover, p::first-line, button:focus { color: red; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.rules.byTag.keys).containsExactly("a", "p", "button")
|
|
}
|
|
|
|
@Test
|
|
fun parse_calculatesSpecificityCorrectly() {
|
|
val css = """
|
|
#myId { color: red; } /* 100 */
|
|
p.myClass { color: green; } /* 11 */
|
|
p { color: blue; } /* 1 */
|
|
div p { color: yellow; } /* 2 */
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val idRule = result.rules.byId["myId"]?.first()
|
|
val classRule = result.rules.otherComplex.find { it.selector.selector == "p.myClass" }
|
|
val elementRule = result.rules.byTag["p"]?.first()
|
|
val descendantRule = result.rules.otherComplex.find { it.selector.selector == "div p" }
|
|
|
|
assertThat(idRule?.selector?.specificity).isEqualTo(100)
|
|
assertThat(classRule?.selector?.specificity).isEqualTo(11)
|
|
assertThat(elementRule?.selector?.specificity).isEqualTo(1)
|
|
assertThat(descendantRule?.selector?.specificity).isEqualTo(2)
|
|
}
|
|
|
|
@Test
|
|
fun parse_ignoresComments() {
|
|
val css = """
|
|
/* This is a comment */
|
|
p {
|
|
color: /* another comment */ blue; /* block comment */
|
|
}
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val rules = result.rules.byTag["p"]
|
|
assertThat(rules).hasSize(1)
|
|
assertThat(rules?.first()?.style?.spanStyle?.color).isEqualTo(Color.Blue)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesBorderShorthand() {
|
|
val css = "div { border: 2px solid red; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["div"]?.first()?.style?.blockStyle
|
|
val expectedBorder = BorderStyle(width = 2.dp, color = Color.Red, style = "solid")
|
|
assertThat(style?.borderTop).isEqualTo(expectedBorder)
|
|
assertThat(style?.borderRight).isEqualTo(expectedBorder)
|
|
assertThat(style?.borderBottom).isEqualTo(expectedBorder)
|
|
assertThat(style?.borderLeft).isEqualTo(expectedBorder)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesMarginAndPaddingShorthand() {
|
|
val css = "p { margin: 10px 20px; padding: 1em 2em 3em 4em; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["p"]?.first()?.style?.blockStyle
|
|
assertThat(style?.margin?.top).isEqualTo(10.dp)
|
|
assertThat(style?.margin?.right).isEqualTo(20.dp)
|
|
assertThat(style?.margin?.bottom).isEqualTo(10.dp)
|
|
assertThat(style?.margin?.left).isEqualTo(20.dp)
|
|
|
|
assertThat(style?.padding?.top).isEqualTo(16.dp) // 1em
|
|
assertThat(style?.padding?.right).isEqualTo(32.dp) // 2em
|
|
assertThat(style?.padding?.bottom).isEqualTo(48.dp) // 3em
|
|
assertThat(style?.padding?.left).isEqualTo(64.dp) // 4em
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesFontSizeWithEmUnits() {
|
|
val css = "p { font-size: 1.2em; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["p"]?.first()?.style
|
|
assertThat(style?.fontSize?.isEm).isTrue()
|
|
assertThat(style?.fontSize?.value).isEqualTo(1.2f)
|
|
}
|
|
|
|
@Test
|
|
fun parse_optimizationCategorizesRulesCorrectly() {
|
|
val css = """
|
|
p { color: blue; }
|
|
.myClass { color: green; }
|
|
#myId { color: red; }
|
|
div > p { color: yellow; }
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.rules.byTag).containsKey("p")
|
|
assertThat(result.rules.byClass).containsKey("myClass")
|
|
assertThat(result.rules.byId).containsKey("myId")
|
|
assertThat(result.rules.otherComplex).hasSize(1)
|
|
assertThat(result.rules.otherComplex.first().selector.selector).isEqualTo("div > p")
|
|
}
|
|
|
|
@Test
|
|
fun parse_mediaQueryAppliesDarkThemeRules() {
|
|
val css = """
|
|
p { color: black; }
|
|
@media (prefers-color-scheme: dark) {
|
|
p { color: white; }
|
|
}
|
|
""".trimIndent()
|
|
val lightResult = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(lightResult.rules.byTag["p"]?.first()?.style?.spanStyle?.color).isEqualTo(Color.Black)
|
|
|
|
val darkResult = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = true)
|
|
assertThat(darkResult.rules.byTag["p"]?.last()?.style?.spanStyle?.color).isEqualTo(Color.White)
|
|
}
|
|
|
|
@Test
|
|
fun parse_fontFaceSelectsPreferredSourceFormat() {
|
|
val css = """
|
|
@font-face {
|
|
font-family: "MyFont";
|
|
src: url("font.woff2") format("woff2"),
|
|
url("font.otf") format("opentype"),
|
|
url("font.ttf") format("truetype");
|
|
}
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, "OEBPS/css/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.fontFaces).hasSize(1)
|
|
assertThat(result.fontFaces.first().src).isEqualTo("OEBPS/css/font.otf")
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesNestedMediaAndCalcVariables() {
|
|
val css = """
|
|
:root { --gap: 12px; }
|
|
@media screen and (min-width: 300px) {
|
|
p { margin-left: calc(var(--gap) + 8px); color: hsl(120 100% 25%); }
|
|
}
|
|
""".trimIndent()
|
|
|
|
val result = CssParser.parse(
|
|
css,
|
|
null,
|
|
baseFontSize,
|
|
density,
|
|
androidx.compose.ui.unit.Constraints(maxWidth = 500),
|
|
isDarkTheme = false
|
|
)
|
|
|
|
val style = result.rules.byTag["p"]!!.first().style
|
|
assertThat(style.blockStyle.margin.left).isEqualTo(20.dp)
|
|
assertThat(style.spanStyle.color).isEqualTo(Color(0, 128, 0))
|
|
}
|
|
|
|
@Test
|
|
fun parse_preservesBeforeAfterPseudoElementRules() {
|
|
val css = "p::before { content: 'Note: '; color: red; } p { color: blue; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
|
|
val pseudoRule = result.rules.otherComplex.single { it.pseudoElement == "before" }
|
|
assertThat(pseudoRule.selector.selector).isEqualTo("p")
|
|
assertThat(pseudoRule.style.content).isEqualTo("'Note: '")
|
|
assertThat(pseudoRule.style.spanStyle.color).isEqualTo(Color.Red)
|
|
assertThat(result.rules.byTag["p"]!!.single().style.spanStyle.color).isEqualTo(Color.Blue)
|
|
}
|
|
|
|
@Test
|
|
fun parse_handlesModernRgbSlashAlphaAndCssHexAlpha() {
|
|
assertThat(parseTextColor("rgb(255 0 204 / 50%)")).isEqualTo(Color(255, 0, 204, 128))
|
|
assertThat(parseTextColor("#ff00cc80")).isEqualTo(Color(255, 0, 204, 128))
|
|
}
|
|
|
|
@Test
|
|
fun parse_backgroundShorthandExtractsColorAndImage() {
|
|
val css = "section { background: #ffeecc url('../images/paper.png') repeat; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["section"]!!.first().style.blockStyle
|
|
|
|
assertThat(style.backgroundColor).isEqualTo(Color(255, 238, 204))
|
|
assertThat(style.backgroundImage).isEqualTo("../images/paper.png")
|
|
}
|
|
|
|
@Test
|
|
fun parse_listStyleShorthandExtractsMarkerTypeAndImage() {
|
|
val css = "ul { list-style: square url('../images/bullet.png') outside; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["ul"]!!.first().style.blockStyle
|
|
|
|
assertThat(style.listStyleType).isEqualTo("square")
|
|
assertThat(style.listStyleImage).isEqualTo("../images/bullet.png")
|
|
}
|
|
|
|
@Test
|
|
fun parse_propertiesHandlesVariousUnitsAndValues() {
|
|
val css = """
|
|
p {
|
|
font-size: 150%;
|
|
text-transform: uppercase;
|
|
text-decoration: underline;
|
|
text-align: center;
|
|
page-break-inside: avoid;
|
|
margin: 0 auto;
|
|
}
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["p"]?.first()?.style
|
|
assertThat(style?.fontSize).isEqualTo(1.5.em)
|
|
assertThat(style?.textTransform).isEqualTo("uppercase")
|
|
assertThat(style?.spanStyle?.textDecoration).isEqualTo(TextDecoration.Underline)
|
|
assertThat(style?.paragraphStyle?.textAlign).isEqualTo(TextAlign.Center)
|
|
assertThat(style?.blockStyle?.pageBreakInsideAvoid).isTrue()
|
|
assertThat(style?.blockStyle?.horizontalAlign).isEqualTo("center")
|
|
}
|
|
|
|
@Test
|
|
fun parse_themeAdaptationAdaptsColorsCorrectlyForDarkTheme() {
|
|
val css = "p { color: #111; background-color: #EEE; }" // very dark text, very light bg
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = true)
|
|
val style = result.rules.byTag["p"]?.first()?.style
|
|
|
|
assertThat(style?.spanStyle?.color).isEqualTo(Color.White.copy(alpha = 0.87f))
|
|
assertThat(style?.blockStyle?.backgroundColor).isEqualTo(Color.Transparent)
|
|
}
|
|
|
|
@Test
|
|
fun parse_dataUriWithSemicolonParsesCorrectly() {
|
|
val dataUri = "data:font/opentype;base64,d09GMgABAAAAAAPs...;something=else"
|
|
val css = """
|
|
@font-face {
|
|
font-family: 'MyDataFont';
|
|
src: url('$dataUri');
|
|
}
|
|
p { color: red; }
|
|
""".trimIndent()
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
assertThat(result.fontFaces).hasSize(1)
|
|
assertThat(result.fontFaces.first().src).isEqualTo(dataUri)
|
|
assertThat(result.rules.byTag).containsKey("p")
|
|
}
|
|
|
|
@Test
|
|
fun parse_textEmphasisParsesCorrectly() {
|
|
val css = "p { -epub-text-emphasis-style: filled dot; -epub-text-emphasis-color: red; }"
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val emphasis = result.rules.byTag["p"]?.first()?.style?.textEmphasis
|
|
assertThat(emphasis).isNotNull()
|
|
assertThat(emphasis?.style).isEqualTo("dot")
|
|
assertThat(emphasis?.fill).isEqualTo("filled")
|
|
assertThat(emphasis?.color).isEqualTo(Color.Red)
|
|
}
|
|
|
|
@Test
|
|
fun parse_lineHeightPreservesUnitlessMultiplier() {
|
|
val css = "p { line-height: 1.1; }" // This is treated as 1.1em
|
|
val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
|
val style = result.rules.byTag["p"]?.first()?.style
|
|
assertThat(style?.paragraphStyle?.lineHeight).isEqualTo(1.1.em)
|
|
}
|
|
}
|