Initial commit
This commit is contained in:
commit
6072b2ba29
844 changed files with 220532 additions and 0 deletions
|
|
@ -0,0 +1,336 @@
|
|||
// 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 {
|
||||
|
||||
@Test
|
||||
fun parseColor_handlesNamedColorsCorrectly() {
|
||||
assertThat(CssParser.parseColor("red")).isEqualTo(Color.Red)
|
||||
assertThat(CssParser.parseColor("black")).isEqualTo(Color.Black)
|
||||
assertThat(CssParser.parseColor("transparent")).isEqualTo(Color.Transparent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_handles3DigitHexCodes() {
|
||||
assertThat(CssParser.parseColor("#F0C")).isEqualTo(Color(0xFFFF00CC))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_handles6DigitHexCodes() {
|
||||
assertThat(CssParser.parseColor("#FF00CC")).isEqualTo(Color(0xFFFF00CC))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_handles8DigitHexCodes() {
|
||||
assertThat(CssParser.parseColor("#80FF00CC")).isEqualTo(Color(0x80FF00CC))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_handlesRgbFunction() {
|
||||
assertThat(CssParser.parseColor("rgb(255, 0, 204)")).isEqualTo(Color(255, 0, 204))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_handlesRgbaFunction() {
|
||||
assertThat(CssParser.parseColor("rgba(255, 0, 204, 0.5)")).isEqualTo(Color(255, 0, 204, 128))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseColor_returnsNullForInvalidInput() {
|
||||
assertThat(CssParser.parseColor("not a color")).isNull()
|
||||
assertThat(CssParser.parseColor("#12345")).isNull()
|
||||
assertThat(CssParser.parseColor("rgb(1,2)")).isNull()
|
||||
}
|
||||
|
||||
private val dummyConstraints = androidx.compose.ui.unit.Constraints()
|
||||
private val baseFontSize = 16f
|
||||
private val density = 1f
|
||||
|
||||
@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, "/some/path/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("/some/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
|
||||
assertThat(style?.border).isNotNull()
|
||||
assertThat(style?.border?.width).isEqualTo(2.dp)
|
||||
assertThat(style?.border?.style).isEqualTo("solid")
|
||||
assertThat(style?.border?.color).isEqualTo(Color.Red)
|
||||
}
|
||||
|
||||
@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, "/css/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
|
||||
assertThat(result.fontFaces).hasSize(1)
|
||||
assertThat(result.fontFaces.first().src).isEqualTo("/css/font.otf")
|
||||
}
|
||||
|
||||
@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_lineHeightClampsSmallEmValues() {
|
||||
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(2.0.em)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue