diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt index 4cec5ce0b03..b4304c4e59b 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt @@ -4,7 +4,7 @@ import org.jetbrains.compose.resources.plural.PluralCategory import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi -private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""") +private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""") internal fun String.replaceWithArgs(args: List) = SimpleStringFormatRegex.replace(this) { matchResult -> args[matchResult.groupValues[1].toInt() - 1] } diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt new file mode 100644 index 00000000000..75765ac4cae --- /dev/null +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt @@ -0,0 +1,181 @@ +package org.jetbrains.compose.resources + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class StringFormatTest { + + @Test + fun `replaceWithArgs replaces placeholders with corresponding arguments`() { + val template = "Hello %1\$s, you have %2\$d new messages!" + val args = listOf("Alice", "5") + + val result = template.replaceWithArgs(args) + + assertEquals("Hello Alice, you have 5 new messages!", result) + } + + @Test + fun `replaceWithArgs works with multiple placeholders referring to the same argument`() { + val template = "%1\$s and %1\$s are best friends!" + val args = listOf("Alice") + + val result = template.replaceWithArgs(args) + + assertEquals("Alice and Alice are best friends!", result) + } + + @Test + fun `replaceWithArgs works when placeholders are out of order`() { + val template = "Order: %2\$s comes after %1\$s" + val args = listOf("Alice", "Bob") + + val result = template.replaceWithArgs(args) + + assertEquals("Order: Bob comes after Alice", result) + } + + @Test + fun `replaceWithArgs works when there are no placeholders`() { + val template = "No placeholders here!" + val args = emptyList() + + val result = template.replaceWithArgs(args) + + assertEquals("No placeholders here!", result) + } + + @Test + fun `replaceWithArgs throws exception when placeholders index is out of bounds`() { + val template = "Hello %1\$s, %2\$s!" + val args = listOf("Alice") + + assertFailsWith { + template.replaceWithArgs(args) + } + } + + @Test + fun `replaceWithArgs handles empty string template`() { + val template = "" + val args = listOf("Alice", "5") + + val result = template.replaceWithArgs(args) + + assertEquals("", result) + } + + @Test + fun `replaceWithArgs handles templates with no matching args`() { + val template = "Hello %1\$s, you have %3\$s messages" + val args = listOf("Alice") + + assertFailsWith { + template.replaceWithArgs(args) + } + } + + @Test + fun `replaceWithArgs replaces multiple placeholders of the same index`() { + val template = "Repeat: %1\$s, %1\$s, and again %1\$s!" + val args = listOf("Echo") + + val result = template.replaceWithArgs(args) + + assertEquals("Repeat: Echo, Echo, and again Echo!", result) + } + + @Test + fun `replaceWithArgs ensures %d and %s placeholders behave identically`() { + val template = "%1\$d, %1\$s, %2\$d, %2\$s" + val args = listOf("42", "hello") + + val result = template.replaceWithArgs(args) + + assertEquals("42, 42, hello, hello", result) + } + + @Test + fun `replaceWithArgs handles 15 arguments correctly`() { + val template = "%1\$s, %2\$s, %3\$s, %4\$s, %5\$s, %6\$s, %7\$s, %8\$s, %9\$s, %10\$s, %11\$s, %12\$s, %13\$s, %14\$s, %15\$s!" + val args = listOf( + "arg1", "arg2", "arg3", "arg4", "arg5", + "arg6", "arg7", "arg8", "arg9", "arg10", + "arg11", "arg12", "arg13", "arg14", "arg15" + ) + + val result = template.replaceWithArgs(args) + + assertEquals( + "arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15!", + result + ) + } + + @Test + fun `replaceWithArgs throws exception for template with missing argument index`() { + val template = "Hello %${'$'}s, how are you?" + val args = listOf("Alice") + + val result = template.replaceWithArgs(args) + + // Since the template doesn't properly specify a valid index (e.g., %1$s), it will replace nothing. + assertEquals("Hello %${'$'}s, how are you?", result) + } + + @Test + fun `replaceWithArgs does not replace invalid placeholders`() { + val template = "Hello %1\$x, how are you?" + val args = listOf("Alice") + + val result = template.replaceWithArgs(args) + + // %1$x is not a valid placeholder, so the template should remain unchanged + assertEquals("Hello %1\$x, how are you?", result) + } + + @Test + fun `replaceWithArgs throws exception for missing arguments`() { + val template = "Hello %1\$s, you have %2\$d messages!" + val args = listOf("Alice") + + // An exception should be thrown because the second argument (%2$d) is missing + assertFailsWith { + template.replaceWithArgs(args) + } + } + + @Test + fun `replaceWithArgs throws exception for unmatched placeholders`() { + val template = "Hello %1\$s, your rank is %3\$s" + val args = listOf("Alice", "1") + + // The template has a %3$s placeholder, but there is no third argument + assertFailsWith { + template.replaceWithArgs(args) + } + } + + @Test + fun `replaceWithArgs handles templates with invalid format`() { + val template = "This is %1\$" + val args = listOf("test") + + val result = template.replaceWithArgs(args) + + // The incomplete placeholder %1$ will not be replaced + assertEquals("This is %1\$", result) + } + + @Test + fun `replaceWithArgs ignores extra arguments`() { + val template = "Hello %1\$s!" + val args = listOf("Alice", "ExtraData1", "ExtraData2") + + val result = template.replaceWithArgs(args) + + // Only the first argument should be used, ignoring the rest + assertEquals("Hello Alice!", result) + } +} \ No newline at end of file