[go: nahoru, domu]

blob: 77464dd86ddcfd1330e208860a4ba0e410b486de [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.text
import androidx.ui.graphics.Color
import androidx.ui.text.font.FontStyle
import androidx.ui.text.font.FontWeight
import androidx.ui.text.style.TextAlign
import androidx.ui.unit.TextUnit
import androidx.ui.unit.em
import androidx.ui.unit.sp
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class AnnotatedStringBuilderTest {
@Test
fun defaultConstructor() {
val annotatedString = AnnotatedString.Builder().toAnnotatedString()
assertThat(annotatedString.text).isEmpty()
assertThat(annotatedString.spanStyles).isEmpty()
assertThat(annotatedString.paragraphStyles).isEmpty()
}
@Test
fun constructorWithString() {
val text = "a"
val annotatedString = AnnotatedString.Builder(text).toAnnotatedString()
assertThat(annotatedString.text).isEqualTo(text)
assertThat(annotatedString.spanStyles).isEmpty()
assertThat(annotatedString.paragraphStyles).isEmpty()
}
@Test
fun constructorWithAnnotatedString_hasSameAnnotatedStringAttributes() {
val text = createAnnotatedString(text = "a")
val annotatedString = AnnotatedString.Builder(text).toAnnotatedString()
assertThat(annotatedString.text).isEqualTo(text.text)
assertThat(annotatedString.spanStyles).isEqualTo(text.spanStyles)
assertThat(annotatedString.paragraphStyles).isEqualTo(text.paragraphStyles)
}
@Test
fun addStyle_withSpanStyle_addsStyle() {
val style = SpanStyle(color = Color.Red)
val range = TextRange(0, 1)
val annotatedString = with(AnnotatedString.Builder("ab")) {
addStyle(style, range.start, range.end)
toAnnotatedString()
}
val expectedSpanStyles = listOf(
AnnotatedString.Range(style, range.start, range.end)
)
assertThat(annotatedString.paragraphStyles).isEmpty()
assertThat(annotatedString.spanStyles).isEqualTo(expectedSpanStyles)
}
@Test
fun addStyle_withParagraphStyle_addsStyle() {
val style = ParagraphStyle(lineHeight = 30.sp)
val range = TextRange(0, 1)
val annotatedString = with(AnnotatedString.Builder("ab")) {
addStyle(style, range.start, range.end)
toAnnotatedString()
}
val expectedParagraphStyles = listOf(
AnnotatedString.Range(style, range.start, range.end)
)
assertThat(annotatedString.spanStyles).isEmpty()
assertThat(annotatedString.paragraphStyles).isEqualTo(expectedParagraphStyles)
}
@Test
fun append_withString_appendsTheText() {
val text = "a"
val appendedText = "b"
val annotatedString = with(AnnotatedString.Builder(text)) {
append(appendedText)
toAnnotatedString()
}
val expectedString = "$text$appendedText"
assertThat(annotatedString.text).isEqualTo(expectedString)
assertThat(annotatedString.spanStyles).isEmpty()
assertThat(annotatedString.paragraphStyles).isEmpty()
}
@Test
fun append_withString_andMultipleCalls_appendsAllOfTheText() {
val annotatedString = with(AnnotatedString.Builder("a")) {
append("b")
append("c")
toAnnotatedString()
}
assertThat(annotatedString.text).isEqualTo("abc")
}
@Test
fun append_withAnnotatedString_appendsTheText() {
val color = Color.Red
val text = "a"
val lineHeight = 20.sp
val annotatedString = createAnnotatedString(
text = text,
color = color,
lineHeight = lineHeight
)
val appendedColor = Color.Blue
val appendedText = "b"
val appendedLineHeight = 30.sp
val appendedAnnotatedString = createAnnotatedString(
text = appendedText,
color = appendedColor,
lineHeight = appendedLineHeight
)
val buildResult = with(AnnotatedString.Builder(annotatedString)) {
append(appendedAnnotatedString)
toAnnotatedString()
}
val expectedString = "$text$appendedText"
val expectedSpanStyles = listOf(
SpanStyleRange(
item = SpanStyle(color),
start = 0,
end = text.length
),
SpanStyleRange(
item = SpanStyle(appendedColor),
start = text.length,
end = expectedString.length
)
)
val expectedParagraphStyles = listOf(
ParagraphStyleRange(
item = ParagraphStyle(lineHeight = lineHeight),
start = 0,
end = text.length
),
ParagraphStyleRange(
item = ParagraphStyle(lineHeight = appendedLineHeight),
start = text.length,
end = expectedString.length
)
)
assertThat(buildResult.text).isEqualTo(expectedString)
assertThat(buildResult.spanStyles).isEqualTo(expectedSpanStyles)
assertThat(buildResult.paragraphStyles).isEqualTo(expectedParagraphStyles)
}
@Test
fun pushStyle() {
val text = "Test"
val style = SpanStyle(color = Color.Red)
val buildResult = AnnotatedString.Builder().apply {
pushStyle(style)
append(text)
pop()
}.toAnnotatedString()
assertThat(buildResult.text).isEqualTo(text)
assertThat(buildResult.spanStyles).hasSize(1)
assertThat(buildResult.spanStyles[0].item).isEqualTo(style)
assertThat(buildResult.spanStyles[0].start).isEqualTo(0)
assertThat(buildResult.spanStyles[0].end).isEqualTo(buildResult.length)
}
@Test
fun pushStyle_without_pop() {
val styles = arrayOf(
SpanStyle(color = Color.Red),
SpanStyle(fontStyle = FontStyle.Italic),
SpanStyle(fontWeight = FontWeight.Bold)
)
val buildResult = with(AnnotatedString.Builder()) {
styles.forEachIndexed { index, spanStyle ->
// pop is intentionally not called here
pushStyle(spanStyle)
append("Style$index")
}
toAnnotatedString()
}
assertThat(buildResult.text).isEqualTo("Style0Style1Style2")
assertThat(buildResult.spanStyles).hasSize(3)
styles.forEachIndexed { index, spanStyle ->
assertThat(buildResult.spanStyles[index].item).isEqualTo(spanStyle)
assertThat(buildResult.spanStyles[index].end).isEqualTo(buildResult.length)
}
assertThat(buildResult.spanStyles[0].start).isEqualTo(0)
assertThat(buildResult.spanStyles[1].start).isEqualTo("Style0".length)
assertThat(buildResult.spanStyles[2].start).isEqualTo("Style0Style1".length)
}
@Test
fun pushStyle_with_multiple_styles() {
val spanStyle1 = SpanStyle(color = Color.Red)
val spanStyle2 = SpanStyle(fontStyle = FontStyle.Italic)
val buildResult = with(AnnotatedString.Builder()) {
pushStyle(spanStyle1)
append("Test")
pushStyle(spanStyle2)
append(" me")
pop()
pop()
toAnnotatedString()
}
assertThat(buildResult.text).isEqualTo("Test me")
assertThat(buildResult.spanStyles).hasSize(2)
assertThat(buildResult.spanStyles[0].item).isEqualTo(spanStyle1)
assertThat(buildResult.spanStyles[0].start).isEqualTo(0)
assertThat(buildResult.spanStyles[0].end).isEqualTo(buildResult.length)
assertThat(buildResult.spanStyles[1].item).isEqualTo(spanStyle2)
assertThat(buildResult.spanStyles[1].start).isEqualTo("Test".length)
assertThat(buildResult.spanStyles[1].end).isEqualTo(buildResult.length)
}
@Test
fun pushStyle_with_multiple_styles_on_top_of_each_other() {
val styles = arrayOf(
SpanStyle(color = Color.Red),
SpanStyle(fontStyle = FontStyle.Italic),
SpanStyle(fontWeight = FontWeight.Bold)
)
val buildResult = with(AnnotatedString.Builder()) {
styles.forEach { spanStyle ->
// pop is intentionally not called here
pushStyle(spanStyle)
}
toAnnotatedString()
}
assertThat(buildResult.text).isEmpty()
assertThat(buildResult.spanStyles).hasSize(3)
styles.forEachIndexed { index, spanStyle ->
assertThat(buildResult.spanStyles[index].item).isEqualTo(spanStyle)
assertThat(buildResult.spanStyles[index].start).isEqualTo(buildResult.length)
assertThat(buildResult.spanStyles[index].end).isEqualTo(buildResult.length)
}
}
@Test
fun pushStyle_with_multiple_stacks_should_construct_styles_in_the_same_order() {
val styles = arrayOf(
SpanStyle(color = Color.Red),
SpanStyle(fontStyle = FontStyle.Italic),
SpanStyle(fontWeight = FontWeight.Bold),
SpanStyle(letterSpacing = 1.2.em)
)
val buildResult = with(AnnotatedString.Builder()) {
pushStyle(styles[0])
append("layer1-1")
pushStyle(styles[1])
append("layer2-1")
pushStyle(styles[2])
append("layer3-1")
pop()
pushStyle(styles[3])
append("layer3-2")
pop()
append("layer2-2")
pop()
append("layer1-2")
toAnnotatedString()
}
assertThat(buildResult.spanStyles).hasSize(4)
styles.forEachIndexed { index, spanStyle ->
assertThat(buildResult.spanStyles[index].item).isEqualTo(spanStyle)
}
}
@Test
fun pushStyle_with_multiple_nested_styles_should_return_styles_in_same_order() {
val styles = arrayOf(
SpanStyle(color = Color.Red),
SpanStyle(fontStyle = FontStyle.Italic),
SpanStyle(fontWeight = FontWeight.Bold),
SpanStyle(letterSpacing = 1.2.em)
)
val buildResult = with(AnnotatedString.Builder()) {
pushStyle(styles[0])
append("layer1-1")
pushStyle(styles[1])
append("layer2-1")
pop()
pushStyle(styles[2])
append("layer2-2")
pushStyle(styles[3])
append("layer3-1")
pop()
append("layer2-3")
pop()
append("layer1-2")
pop()
toAnnotatedString()
}
assertThat(buildResult.spanStyles).hasSize(4)
styles.forEachIndexed { index, spanStyle ->
assertThat(buildResult.spanStyles[index].item).isEqualTo(spanStyle)
}
}
@Test(expected = IllegalStateException::class)
fun pop_when_empty_does_not_throw_exception() {
AnnotatedString.Builder().pop()
}
@Test
fun pop_in_the_middle() {
val spanStyle1 = SpanStyle(color = Color.Red)
val spanStyle2 = SpanStyle(fontStyle = FontStyle.Italic)
val buildResult = with(AnnotatedString.Builder()) {
append("Style0")
pushStyle(spanStyle1)
append("Style1")
pop()
pushStyle(spanStyle2)
append("Style2")
pop()
append("Style3")
toAnnotatedString()
}
assertThat(buildResult.text).isEqualTo("Style0Style1Style2Style3")
assertThat(buildResult.spanStyles).hasSize(2)
// the order is first applied is in the second
assertThat(buildResult.spanStyles[0].item).isEqualTo((spanStyle1))
assertThat(buildResult.spanStyles[0].start).isEqualTo(("Style0".length))
assertThat(buildResult.spanStyles[0].end).isEqualTo(("Style0Style1".length))
assertThat(buildResult.spanStyles[1].item).isEqualTo((spanStyle2))
assertThat(buildResult.spanStyles[1].start).isEqualTo(("Style0Style1".length))
assertThat(buildResult.spanStyles[1].end).isEqualTo(("Style0Style1Style2".length))
}
@Test
fun push_increments_the_style_index() {
val style = SpanStyle(color = Color.Red)
with(AnnotatedString.Builder()) {
val styleIndex0 = pushStyle(style)
val styleIndex1 = pushStyle(style)
val styleIndex2 = pushStyle(style)
assertThat(styleIndex0).isEqualTo(0)
assertThat(styleIndex1).isEqualTo(1)
assertThat(styleIndex2).isEqualTo(2)
}
}
@Test
fun push_reduces_the_style_index_after_pop() {
val spanStyle = SpanStyle(color = Color.Red)
val paragraphStyle = ParagraphStyle(lineHeight = 18.sp)
with(AnnotatedString.Builder()) {
val styleIndex0 = pushStyle(spanStyle)
val styleIndex1 = pushStyle(spanStyle)
assertThat(styleIndex0).isEqualTo(0)
assertThat(styleIndex1).isEqualTo(1)
// a pop should reduce the next index to one
pop()
val paragraphStyleIndex = pushStyle(paragraphStyle)
assertThat(paragraphStyleIndex).isEqualTo(1)
}
}
@Test(expected = IllegalStateException::class)
fun pop_until_throws_exception_for_invalid_index() {
val style = SpanStyle(color = Color.Red)
with(AnnotatedString.Builder()) {
val styleIndex = pushStyle(style)
// should throw exception
pop(styleIndex + 1)
}
}
@Test
fun pop_until_index_pops_correctly() {
val style = SpanStyle(color = Color.Red)
with(AnnotatedString.Builder()) {
pushStyle(style)
// store the index of second push
val styleIndex = pushStyle(style)
pushStyle(style)
// pop up to and including styleIndex
pop(styleIndex)
// push again to get a new index to compare
val newStyleIndex = pushStyle(style)
assertThat(newStyleIndex).isEqualTo(styleIndex)
}
}
@Test
fun withStyle_applies_style_to_block() {
val style = SpanStyle(color = Color.Red)
val buildResult = with(AnnotatedString.Builder()) {
withStyle(style) {
append("Style")
}
toAnnotatedString()
}
assertThat(buildResult.paragraphStyles).isEmpty()
assertThat(buildResult.spanStyles).isEqualTo(
listOf(SpanStyleRange(style, 0, buildResult.length))
)
}
@Test
fun withStyle_with_paragraphStyle_applies_style_to_block() {
val style = ParagraphStyle(lineHeight = 18.sp)
val buildResult = with(AnnotatedString.Builder()) {
withStyle(style) {
append("Style")
}
toAnnotatedString()
}
assertThat(buildResult.spanStyles).isEmpty()
assertThat(buildResult.paragraphStyles).isEqualTo(
listOf(ParagraphStyleRange(style, 0, buildResult.length))
)
}
@Test
fun append_char_appends() {
val buildResult = with(AnnotatedString.Builder("a")) {
append('b')
append('c')
toAnnotatedString()
}
assertThat(buildResult).isEqualTo(AnnotatedString("abc"))
}
@Test
fun builderLambda() {
val text1 = "Hello"
val text2 = "World"
val spanStyle1 = SpanStyle(color = Color.Red)
val spanStyle2 = SpanStyle(color = Color.Blue)
val paragraphStyle1 = ParagraphStyle(textAlign = TextAlign.Right)
val paragraphStyle2 = ParagraphStyle(textAlign = TextAlign.Right)
val buildResult = annotatedString {
withStyle(paragraphStyle1) {
withStyle(spanStyle1) {
append(text1)
}
}
append(" ")
pushStyle(paragraphStyle2)
pushStyle(spanStyle2)
append(text2)
pop()
}
val expectedString = "$text1 $text2"
val expectedSpanStyles = listOf(
SpanStyleRange(spanStyle1, 0, text1.length),
SpanStyleRange(spanStyle2, text1.length + 1, expectedString.length)
)
val expectedParagraphStyles = listOf(
ParagraphStyleRange(paragraphStyle1, 0, text1.length),
ParagraphStyleRange(paragraphStyle2, text1.length + 1, expectedString.length)
)
assertThat(buildResult.text).isEqualTo(expectedString)
assertThat(buildResult.spanStyles).isEqualTo(expectedSpanStyles)
assertThat(buildResult.paragraphStyles).isEqualTo(expectedParagraphStyles)
}
@Test
fun toAnnotatedString_calling_twice_creates_equal_annotated_strings() {
val builder = AnnotatedString.Builder().apply {
// pushed styles not popped on purpose
pushStyle(SpanStyle(color = Color.Red))
append("Hello")
pushStyle(SpanStyle(color = Color.Blue))
append("World")
}
assertThat(builder.toAnnotatedString()).isEqualTo(builder.toAnnotatedString())
}
@Test
fun can_call_other_functions_after_toAnnotatedString() {
val builder = AnnotatedString.Builder().apply {
// pushed styles not popped on purpose
pushStyle(SpanStyle(fontSize = 12.sp))
append("Hello")
pushStyle(SpanStyle(fontSize = 16.sp))
append("World")
}
val buildResult1 = builder.toAnnotatedString()
val buildResult2 = with(builder) {
pop()
pop()
pushStyle(SpanStyle(fontSize = 18.sp))
append("!")
toAnnotatedString()
}
// buildResult2 should be the same as creating a new AnnotatedString based on the first
// result and appending the same values
val expectedResult = with(AnnotatedString.Builder(buildResult1)) {
withStyle(SpanStyle(fontSize = 18.sp)) {
append("!")
}
toAnnotatedString()
}
assertThat(buildResult2).isEqualTo(expectedResult)
}
@Test
fun pushAnnotation() {
val text = "Test"
val annotation = "Annotation"
val tag = "tag"
val buildResult = AnnotatedString.Builder().apply {
pushStringAnnotation(tag, annotation)
append(text)
pop()
}.toAnnotatedString()
assertThat(buildResult.text).isEqualTo(text)
assertThat(buildResult.getStringAnnotations(tag, 0, text.length)).hasSize(1)
}
@Test
fun pushAnnotation_multiple_nested() {
val annotation1 = "Annotation1"
val annotation2 = "Annotation2"
val tag = "tag"
val buildResult = AnnotatedString.Builder().apply {
pushStringAnnotation(tag, annotation1)
append("Hello")
pushStringAnnotation(tag, annotation2)
append("world")
pop()
append("!")
pop()
}.toAnnotatedString()
// The final result is Helloworld!
// [ ]
// [ ]
assertThat(buildResult.text).isEqualTo("Helloworld!")
assertThat(buildResult.getStringAnnotations(tag, 0, 11)).hasSize(2)
assertThat(buildResult.getStringAnnotations(tag, 0, 5)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag, 5, 10)).hasSize(2)
assertThat(buildResult.getStringAnnotations(tag, 10, 11)).hasSize(1)
val annotations = buildResult.getStringAnnotations(tag, 0, 11)
assertThat(annotations[0]).isEqualTo(
AnnotatedString.Range(annotation1, 0, 11, tag)
)
assertThat(annotations[1]).isEqualTo(
AnnotatedString.Range(annotation2, 5, 10, tag)
)
}
@Test
fun pushAnnotation_multiple_differentTag() {
val annotation1 = "Annotation1"
val annotation2 = "Annotation2"
val tag1 = "tag1"
val tag2 = "tag2"
val buildResult = AnnotatedString.Builder().apply {
pushStringAnnotation(tag1, annotation1)
append("Hello")
pushStringAnnotation(tag2, annotation2)
append("world")
pop()
append("!")
pop()
}.toAnnotatedString()
// The final result is Helloworld!
// [ ]
// [ ]
assertThat(buildResult.text).isEqualTo("Helloworld!")
assertThat(buildResult.getStringAnnotations(tag1, 0, 11)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag1, 0, 5)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag1, 5, 10)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag1, 5, 10).first())
.isEqualTo(AnnotatedString.Range(annotation1, 0, 11, tag1))
assertThat(buildResult.getStringAnnotations(tag2, 5, 10)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag2, 5, 10).first())
.isEqualTo(AnnotatedString.Range(annotation2, 5, 10, tag2))
assertThat(buildResult.getStringAnnotations(tag2, 10, 11)).hasSize(0)
}
@Test
fun getAnnotation() {
val annotation = "Annotation"
val tag = "tag"
val buildResult = AnnotatedString.Builder().apply {
append("Hello")
pushStringAnnotation(tag, annotation)
append("World")
pop()
append("Hello")
pushStringAnnotation(tag, annotation)
pop()
append("World")
}.toAnnotatedString()
// The final result is: HelloWorldHelloWorld
// [ ]
// []
assertThat(buildResult.getStringAnnotations(tag, 0, 5)).hasSize(0)
assertThat(buildResult.getStringAnnotations(tag, 0, 6)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag, 6, 6)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag, 10, 10)).hasSize(0)
assertThat(buildResult.getStringAnnotations(tag, 8, 13)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag, 15, 15)).hasSize(1)
assertThat(buildResult.getStringAnnotations(tag, 10, 15)).hasSize(0)
assertThat(buildResult.getStringAnnotations(tag, 15, 20)).hasSize(1)
}
@Test
fun getAnnotation_withOutTag_multipleAnnotations() {
val annotation1 = "Annotation1"
val annotation2 = "Annotation2"
val tag1 = "tag1"
val tag2 = "tag2"
val buildResult = AnnotatedString.Builder().apply {
pushStringAnnotation(tag1, annotation1)
append("Hello")
pushStringAnnotation(tag2, annotation2)
append("world")
pop()
append("!")
pop()
}.toAnnotatedString()
// The final result is Helloworld!
// [ ]
// [ ]
assertThat(buildResult.getStringAnnotations(0, 5)).isEqualTo(
listOf(AnnotatedString.Range(annotation1, 0, 11, tag1))
)
assertThat(buildResult.getStringAnnotations(5, 6)).isEqualTo(
listOf(
AnnotatedString.Range(annotation1, 0, 11, tag1),
AnnotatedString.Range(annotation2, 5, 10, tag2)
)
)
assertThat(buildResult.getStringAnnotations(10, 11)).isEqualTo(
listOf(AnnotatedString.Range(annotation1, 0, 11, tag1))
)
}
private fun createAnnotatedString(
text: String,
color: Color = Color.Red,
lineHeight: TextUnit = 20.sp
): AnnotatedString {
return AnnotatedString(
text = text,
spanStyles = listOf(
SpanStyleRange(
item = SpanStyle(color),
start = 0,
end = text.length
)
),
paragraphStyles = listOf(
ParagraphStyleRange(
item = ParagraphStyle(lineHeight = lineHeight),
start = 0,
end = text.length
)
)
)
}
}