Merge branch '6.8-MAINT'
This commit is contained in:
commit
45d79a9a16
14 changed files with 340 additions and 110 deletions
|
@ -0,0 +1,7 @@
|
||||||
|
- Added DNSSEC support when looking for server settings during setup
|
||||||
|
- Made a change to prevent some software keyboards from capitalizing/auto-correcting email addresses in account setup
|
||||||
|
- Fixed a crash when a very long subject was used
|
||||||
|
- Fixed displaying OAuth 2.0 error messages
|
||||||
|
- Fixed rare crash when downloading an attachment
|
||||||
|
- Added code to disallow line breaks in single line text inputs
|
||||||
|
- Updated translations
|
2
app-k9mail/proguard-rules.pro
vendored
2
app-k9mail/proguard-rules.pro
vendored
|
@ -39,6 +39,8 @@
|
||||||
public <init>(android.content.Context);
|
public <init>(android.content.Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-keep class com.fsck.k9.mail.oauth.XOAuth2Response { *; }
|
||||||
|
|
||||||
# okhttp rules
|
# okhttp rules
|
||||||
# see: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
# see: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ public class AttachmentDownloadDialogFragment extends DialogFragment {
|
||||||
private static final String ARG_MESSAGE = "message";
|
private static final String ARG_MESSAGE = "message";
|
||||||
|
|
||||||
|
|
||||||
private ProgressDialog dialog;
|
|
||||||
private MessagingListener messagingListener;
|
private MessagingListener messagingListener;
|
||||||
private MessagingController messagingController;
|
private MessagingController messagingController;
|
||||||
|
|
||||||
|
@ -42,6 +41,14 @@ public class AttachmentDownloadDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
final SizeUnit sizeUnit = SizeUnit.getAppropriateFor(size);
|
final SizeUnit sizeUnit = SizeUnit.getAppropriateFor(size);
|
||||||
|
|
||||||
|
ProgressDialog dialog = new ProgressDialog(getActivity());
|
||||||
|
dialog.setMessage(message);
|
||||||
|
dialog.setMax(sizeUnit.valueInSizeUnit(size));
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
dialog.setProgress(0);
|
||||||
|
dialog.setProgressNumberFormat("%1d/%2d " + sizeUnit.shortName);
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
messagingListener = new SimpleMessagingListener() {
|
messagingListener = new SimpleMessagingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void updateProgress(int progress) {
|
public void updateProgress(int progress) {
|
||||||
|
@ -52,14 +59,6 @@ public class AttachmentDownloadDialogFragment extends DialogFragment {
|
||||||
messagingController = MessagingController.getInstance(getActivity());
|
messagingController = MessagingController.getInstance(getActivity());
|
||||||
messagingController.addListener(messagingListener);
|
messagingController.addListener(messagingListener);
|
||||||
|
|
||||||
dialog = new ProgressDialog(getActivity());
|
|
||||||
dialog.setMessage(message);
|
|
||||||
dialog.setMax(sizeUnit.valueInSizeUnit(size));
|
|
||||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
||||||
dialog.setProgress(0);
|
|
||||||
dialog.setProgressNumberFormat("%1d/%2d " + sizeUnit.shortName);
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,15 @@
|
||||||
Locale-specific versions are kept in res/raw-<locale qualifier>/changelog.xml.
|
Locale-specific versions are kept in res/raw-<locale qualifier>/changelog.xml.
|
||||||
-->
|
-->
|
||||||
<changelog>
|
<changelog>
|
||||||
|
<release version="6.801" versioncode="38001" date="2024-03-11">
|
||||||
|
<change>Added DNSSEC support when looking for server settings during setup</change>
|
||||||
|
<change>Made a change to prevent some software keyboards from capitalizing/auto-correcting email addresses in account setup</change>
|
||||||
|
<change>Fixed a crash when a very long subject was used</change>
|
||||||
|
<change>Fixed displaying OAuth 2.0 error messages</change>
|
||||||
|
<change>Fixed rare crash when downloading an attachment</change>
|
||||||
|
<change>Added code to disallow line breaks in single line text inputs</change>
|
||||||
|
<change>Updated translations</change>
|
||||||
|
</release>
|
||||||
<release version="6.800" versioncode="38000" date="2024-02-29">
|
<release version="6.800" versioncode="38000" date="2024-02-29">
|
||||||
<change>New and improved account setup</change>
|
<change>New and improved account setup</change>
|
||||||
<change>Added option to return to the message list after marking a message as unread in the message view</change>
|
<change>Added option to return to the message list after marking a message as unread in the message view</change>
|
||||||
|
|
|
@ -5,6 +5,12 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||||
|
|
||||||
|
private val LINE_BREAK = "[\\r\\n]".toRegex()
|
||||||
|
|
||||||
|
internal fun stripLineBreaks(onValueChange: (String) -> Unit): (String) -> Unit = { value ->
|
||||||
|
onValueChange(value.replace(LINE_BREAK, replacement = ""))
|
||||||
|
}
|
||||||
|
|
||||||
internal fun selectLabel(
|
internal fun selectLabel(
|
||||||
label: String?,
|
label: String?,
|
||||||
isRequired: Boolean,
|
isRequired: Boolean,
|
||||||
|
|
|
@ -26,7 +26,7 @@ fun TextFieldOutlined(
|
||||||
) {
|
) {
|
||||||
MaterialOutlinedTextField(
|
MaterialOutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = if (isSingleLine) stripLineBreaks(onValueChange) else onValueChange,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
label = selectLabel(label, isRequired),
|
label = selectLabel(label, isRequired),
|
||||||
|
|
|
@ -22,13 +22,14 @@ fun TextFieldOutlinedEmailAddress(
|
||||||
) {
|
) {
|
||||||
MaterialOutlinedTextField(
|
MaterialOutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = stripLineBreaks(onValueChange),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
label = selectLabel(label, isRequired),
|
label = selectLabel(label, isRequired),
|
||||||
readOnly = isReadOnly,
|
readOnly = isReadOnly,
|
||||||
isError = hasError,
|
isError = hasError,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
|
autoCorrect = false,
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardType = KeyboardType.Email,
|
||||||
),
|
),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
|
|
@ -35,7 +35,7 @@ fun TextFieldOutlinedPassword(
|
||||||
|
|
||||||
MaterialOutlinedTextField(
|
MaterialOutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = stripLineBreaks(onValueChange),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
label = selectLabel(label, isRequired),
|
label = selectLabel(label, isRequired),
|
||||||
|
@ -70,7 +70,7 @@ fun TextFieldOutlinedPassword(
|
||||||
) {
|
) {
|
||||||
MaterialOutlinedTextField(
|
MaterialOutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = stripLineBreaks(onValueChange),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
label = selectLabel(label, isRequired),
|
label = selectLabel(label, isRequired),
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val TEST_TAG = "TextFieldOutlinedEmailAddress"
|
||||||
|
|
||||||
|
class TextFieldOutlinedEmailAddressKtTest : ComposeTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should call onValueChange when value changes`() = runComposeTest {
|
||||||
|
var value = "initial"
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedEmailAddress(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("initial + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should strip line breaks before onValueChange is called`() = runComposeTest {
|
||||||
|
var value = ""
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedEmailAddress(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput("one\n two")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("one two")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val TEST_TAG = "TextFieldOutlined"
|
||||||
|
|
||||||
|
class TextFieldOutlinedKtTest : ComposeTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should call onValueChange when value changes with isSingleLine = false`() = runComposeTest {
|
||||||
|
var value = "initial"
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isSingleLine = false,
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("initial + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should call onValueChange when value changes with isSingleLine = true`() = runComposeTest {
|
||||||
|
var value = "initial"
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isSingleLine = true,
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("initial + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should allow line breaks when isSingleLine = false`() = runComposeTest {
|
||||||
|
var value = ""
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isSingleLine = false,
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput("one\ntwo")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("one\ntwo")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should strip line breaks before onValueChange is called when isSingleLine = true`() = runComposeTest {
|
||||||
|
var value = ""
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isSingleLine = true,
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput("one\n two")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("one two")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,24 @@
|
||||||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import androidx.compose.ui.test.assertIsDisplayed
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
import app.k9mail.core.ui.compose.designsystem.R
|
import app.k9mail.core.ui.compose.designsystem.R
|
||||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
import assertk.assertThat
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
import assertk.assertions.isTrue
|
import assertk.assertions.isTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
private const val PASSWORD = "Password input"
|
private const val PASSWORD = "Password input"
|
||||||
|
private const val TEST_TAG = "TextFieldOutlinedPassword"
|
||||||
|
|
||||||
class TextFieldOutlinedPasswordKtTest : ComposeTest() {
|
class TextFieldOutlinedPasswordKtTest : ComposeTest() {
|
||||||
|
|
||||||
|
@ -131,6 +137,78 @@ class TextFieldOutlinedPasswordKtTest : ComposeTest() {
|
||||||
onNodeWithText(PASSWORD).assertDoesNotExist()
|
onNodeWithText(PASSWORD).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variant 1 should call onValueChange when value changes`() = runComposeTest {
|
||||||
|
var value = "initial"
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedPassword(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("initial + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variant 2 should call onValueChange when value changes`() = runComposeTest {
|
||||||
|
var value = "initial"
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedPassword(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isPasswordVisible = false,
|
||||||
|
onPasswordVisibilityToggleClicked = {},
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("initial + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variant 1 should strip line breaks before onValueChange is called`() = runComposeTest {
|
||||||
|
var value = ""
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedPassword(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput("one\n two")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("one two")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variant 2 should strip line breaks before onValueChange is called`() = runComposeTest {
|
||||||
|
var value = ""
|
||||||
|
setContent {
|
||||||
|
TextFieldOutlinedPassword(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
isPasswordVisible = false,
|
||||||
|
onPasswordVisibilityToggleClicked = {},
|
||||||
|
modifier = Modifier.testTag(TEST_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(TEST_TAG).performClick()
|
||||||
|
onNodeWithTag(TEST_TAG).performTextInput("one\n two")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("one two")
|
||||||
|
}
|
||||||
|
|
||||||
private fun SemanticsNodeInteractionsProvider.onShowPasswordNode(): SemanticsNodeInteraction {
|
private fun SemanticsNodeInteractionsProvider.onShowPasswordNode(): SemanticsNodeInteraction {
|
||||||
return onNodeWithContentDescription(
|
return onNodeWithContentDescription(
|
||||||
getString(R.string.designsystem_atom_password_textfield_show_password),
|
getString(R.string.designsystem_atom_password_textfield_show_password),
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
|
||||||
import androidx.compose.ui.test.performClick
|
|
||||||
import androidx.compose.ui.test.performTextInput
|
|
||||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
|
||||||
|
|
||||||
data class TextInputTextFieldTestData(
|
|
||||||
val name: String,
|
|
||||||
val input: String,
|
|
||||||
val content: @Composable (
|
|
||||||
value: String,
|
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
modifier: Modifier,
|
|
||||||
) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
|
||||||
class TextInputTextFieldTest(
|
|
||||||
data: TextInputTextFieldTestData,
|
|
||||||
) : ComposeTest() {
|
|
||||||
|
|
||||||
private val testSubjectName = data.name
|
|
||||||
private val testSubject = data.content
|
|
||||||
private val testInput = data.input
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `should call onValueChange when value changes`() = runComposeTest {
|
|
||||||
var value = testInput
|
|
||||||
setContent {
|
|
||||||
testSubject(
|
|
||||||
value,
|
|
||||||
{ value = it },
|
|
||||||
Modifier.testTag(testSubjectName),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onNodeWithTag(testSubjectName).performClick()
|
|
||||||
onNodeWithTag(testSubjectName).performTextInput(" + added text")
|
|
||||||
|
|
||||||
assertThat(value).isEqualTo("$testInput + added text")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
|
|
||||||
fun data(): List<TextInputTextFieldTestData> = listOf(
|
|
||||||
TextInputTextFieldTestData(
|
|
||||||
name = "TextFieldOutlined",
|
|
||||||
input = "value",
|
|
||||||
content = { value, onValueChange: (String) -> Unit, modifier ->
|
|
||||||
TextFieldOutlined(
|
|
||||||
value = value,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextInputTextFieldTestData(
|
|
||||||
name = "TextFieldOutlinedPassword",
|
|
||||||
input = "value",
|
|
||||||
content = { value, onValueChange: (String) -> Unit, modifier ->
|
|
||||||
TextFieldOutlinedPassword(
|
|
||||||
value = value,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextInputTextFieldTestData(
|
|
||||||
name = "TextFieldOutlinedEmail",
|
|
||||||
input = "value",
|
|
||||||
content = { value, onValueChange: (String) -> Unit, modifier ->
|
|
||||||
TextFieldOutlinedEmailAddress(
|
|
||||||
value = value,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,29 @@
|
||||||
package com.fsck.k9.mail.internet
|
package com.fsck.k9.mail.internet
|
||||||
|
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil
|
||||||
|
|
||||||
object MimeHeaderEncoder {
|
object MimeHeaderEncoder {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encode(name: String, value: String): String {
|
fun encode(name: String, value: String): String {
|
||||||
// TODO: Fold long text that provides enough opportunities for folding and doesn't contain any characters that
|
// TODO: Fold long text that provides enough opportunities for folding and doesn't contain any characters that
|
||||||
// need to be encoded.
|
// need to be encoded.
|
||||||
return if (hasToBeEncoded(name, value)) {
|
|
||||||
EncoderUtil.encodeEncodedWord(value)
|
// Number of characters already used up on the first line (header field name + colon + space)
|
||||||
|
val usedCharacters = name.length + COLON_PLUS_SPACE_LENGTH
|
||||||
|
|
||||||
|
return if (hasToBeEncoded(value, usedCharacters)) {
|
||||||
|
MimeUtil.fold(EncoderUtil.encodeEncodedWord(value), usedCharacters)
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasToBeEncoded(name: String, value: String): Boolean {
|
private fun hasToBeEncoded(value: String, usedCharacters: Int): Boolean {
|
||||||
return exceedsRecommendedLineLength(name, value) || charactersNeedEncoding(value)
|
return exceedsRecommendedLineLength(value, usedCharacters) || charactersNeedEncoding(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exceedsRecommendedLineLength(name: String, value: String): Boolean {
|
private fun exceedsRecommendedLineLength(value: String, usedCharacters: Int): Boolean {
|
||||||
return name.length + COLON_PLUS_SPACE_LENGTH + value.length > RECOMMENDED_MAX_LINE_LENGTH
|
return usedCharacters + value.length > RECOMMENDED_MAX_LINE_LENGTH
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun charactersNeedEncoding(text: String): Boolean {
|
private fun charactersNeedEncoding(text: String): Boolean {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.fsck.k9.mail.internet
|
||||||
|
|
||||||
|
import assertk.Assert
|
||||||
|
import assertk.all
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.each
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isLessThanOrEqualTo
|
||||||
|
import assertk.assertions.length
|
||||||
|
import assertk.fail
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class MimeHeaderEncoderTest {
|
||||||
|
@Test
|
||||||
|
fun `short subject containing only ASCII characters should not be encoded`() {
|
||||||
|
val result = MimeHeaderEncoder.encode(name = "Subject", value = "Hello World!")
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("Hello World!")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `short subject containing non-ASCII characters should be encoded`() {
|
||||||
|
val result = MimeHeaderEncoder.encode(name = "Subject", value = "Gänseblümchen")
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("=?UTF-8?Q?G=C3=A4nsebl=C3=BCmchen?=")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `subject with recommended line length should not be folded`() {
|
||||||
|
val subject = "a".repeat(RECOMMENDED_MAX_LINE_LENGTH - "Subject: ".length)
|
||||||
|
|
||||||
|
val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `subject exceeding recommended line length should be folded`() {
|
||||||
|
val subject = "a".repeat(34) + " " + "a".repeat(35)
|
||||||
|
|
||||||
|
val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)
|
||||||
|
|
||||||
|
assertThat(result).all {
|
||||||
|
transform { it.lines() }.each {
|
||||||
|
it.length().isLessThanOrEqualTo(RECOMMENDED_MAX_LINE_LENGTH)
|
||||||
|
}
|
||||||
|
isValidHeader(name = "Subject")
|
||||||
|
decodesTo(subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `subject exceeding maximum line length should be encoded`() {
|
||||||
|
val subject = "a".repeat(999)
|
||||||
|
|
||||||
|
val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)
|
||||||
|
|
||||||
|
assertThat(result).all {
|
||||||
|
isValidHeader(name = "Subject")
|
||||||
|
decodesTo(subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Assert<String>.isValidHeader(name: String) = given { value ->
|
||||||
|
try {
|
||||||
|
MimeHeaderChecker.checkHeader(name, value)
|
||||||
|
} catch (e: MimeHeaderParserException) {
|
||||||
|
fail(AssertionError("Not a valid RFC5322 header", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Assert<String>.decodesTo(expected: String) = given { value ->
|
||||||
|
assertThat(MimeUtility.unfoldAndDecode(value)).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue