From 6fab9b1c6f0b28fcbaa99bba62aa358c7d996cef Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 20 Jun 2021 19:59:28 +0200 Subject: [PATCH 1/4] Fix #891 - When editing transactions, new lines are replaced with spaces. When the description TextView loses focus and before the transaction is saved, all new lines are replaced with spaces. --- .../transaction/TransactionFormFragment.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/java/org/gnucash/android/ui/transaction/TransactionFormFragment.java b/app/src/main/java/org/gnucash/android/ui/transaction/TransactionFormFragment.java index ad9f36e03..550f7c174 100644 --- a/app/src/main/java/org/gnucash/android/ui/transaction/TransactionFormFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/transaction/TransactionFormFragment.java @@ -454,9 +454,27 @@ public void onItemClick(AdapterView adapterView, View view, int position, lon } }); + mDescriptionEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (!hasFocus && view instanceof AutoCompleteTextView) { + TransactionFormFragment.this.sanitizeDescription(); + } + } + }); + mDescriptionEditText.setAdapter(adapter); } + /** + * Removes new line characters from the description text field and replaces them with spaces. + */ + private void sanitizeDescription() { + String original = mDescriptionEditText.getText().toString(); + mDescriptionEditText.setText(original.replaceAll("\\r?\\n", " ")); + } + + /** * Initialize views in the fragment with information from a transaction. * This method is called if the fragment is used for editing a transaction @@ -845,6 +863,8 @@ private void saveNewTransaction() { return; } + sanitizeDescription(); + Transaction transaction = extractTransactionFromView(); if (mEditMode) { //if editing an existing transaction transaction.setUID(mTransaction.getUID()); From 00f5b8f0ed30ece293cbc4fc517a310c35b6715a Mon Sep 17 00:00:00 2001 From: thomas Date: Mon, 21 Jun 2021 12:23:37 +0200 Subject: [PATCH 2/4] UI tests for the fix of #891. --- .../test/ui/TransactionsActivityTest.java | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java b/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java index 4395e7386..dc1f91f98 100644 --- a/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java +++ b/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java @@ -65,6 +65,7 @@ import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.clearText; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.RootMatchers.withDecorView; @@ -147,7 +148,7 @@ public static void prepareTestCase(){ } @Before - public void setUp() throws Exception { + public void setUp() { mAccountsDbAdapter.deleteAllRecords(); mAccountsDbAdapter.addRecord(mBaseAccount, DatabaseAdapter.UpdateMethod.insert); mAccountsDbAdapter.addRecord(mTransferAccount, DatabaseAdapter.UpdateMethod.insert); @@ -645,7 +646,7 @@ public void testLegacyIntentTransactionRecording(){ transactionIntent.setType(Transaction.MIME_TYPE); transactionIntent.putExtra(Intent.EXTRA_TITLE, "Power intents"); transactionIntent.putExtra(Intent.EXTRA_TEXT, "Intents for sale"); - transactionIntent.putExtra(Transaction.EXTRA_AMOUNT, new BigDecimal(4.99)); + transactionIntent.putExtra(Transaction.EXTRA_AMOUNT, new BigDecimal("4.99")); transactionIntent.putExtra(Transaction.EXTRA_ACCOUNT_UID, TRANSACTIONS_ACCOUNT_UID); transactionIntent.putExtra(Transaction.EXTRA_TRANSACTION_TYPE, TransactionType.DEBIT.name()); transactionIntent.putExtra(Account.EXTRA_CURRENCY_CODE, "USD"); @@ -868,6 +869,63 @@ public void editingTransferAccount_shouldKeepSplitAmountsConsistent() { } + /** + * In this test we check that new lines in the transaction description are replaced + * with spaces after focus change. + */ + @Test + public void noNewLinesinDescriptionAfterFocusChange() { + setDoubleEntryEnabled(true); + setDefaultTransactionType(TransactionType.DEBIT); + validateTransactionListDisplayed(); + + onView(withId(R.id.fab_create_transaction)).perform(click()); + + final String description = "\r\nText with\r\n new lines\n"; + clickOnView(R.id.input_transaction_name); + onView(withId(R.id.input_transaction_name)).perform(ViewActions.replaceText(description)); + + Espresso.closeSoftKeyboard(); + clickOnView(R.id.input_transaction_amount); + onView(withId(R.id.input_transaction_amount)).perform(typeText("899")); + Espresso.closeSoftKeyboard(); + + final String expectedDescription = " Text with new lines "; + onView(withId(R.id.input_transaction_name)).check(matches(withText(expectedDescription))); + } + + /** + * In this test we check that editing new lines in the transaction description are replaced + * with spaces when saving. + */ + @Test + public void noNewLinesinDescriptionAfterSave() { + setDoubleEntryEnabled(true); + setDefaultTransactionType(TransactionType.DEBIT); + validateTransactionListDisplayed(); + + onView(withId(R.id.fab_create_transaction)).perform(click()); + + final String description = "\r\nText with\r\n new lines\n"; + onView(withId(R.id.input_transaction_name)).perform(replaceText(description)); + + onView(withId(R.id.input_transaction_amount)).perform(typeText("899")); + Espresso.closeSoftKeyboard(); + + onView(withId(R.id.menu_save)).perform(click()); + + validateTransactionListDisplayed(); + + List transactions = mTransactionsDbAdapter.getAllTransactionsForAccount(TRANSACTIONS_ACCOUNT_UID); + assertThat(transactions).hasSize(2); + Transaction transaction = transactions.get(0); + + // during save the description is also trimmed + final String expectedDescription = "Text with new lines "; + assertThat(transaction.getDescription().equals(expectedDescription)); + } + + /** * Simple wrapper for clicking on views with espresso * @param viewId View resource ID @@ -893,7 +951,7 @@ public void run() { } @After - public void tearDown() throws Exception { + public void tearDown() { if (mTransactionsActivity != null) mTransactionsActivity.finish(); } From 8393afbed101daf5e2dee30535033b1232b9a762 Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 27 Jun 2021 14:47:34 +0200 Subject: [PATCH 3/4] Fix of #891 - New lines are replaced with spaces for description and memo lines of the QIF export. Empty memos are no longer exported (field is optional in QIF). Also fixed export test and added new test for no new lines export. Increased Robolectric version from 4.3.1 to 4.5.1 because of failing build (see https://github.com/robolectric/robolectric/issues/5456). --- app/build.gradle | 4 +-- .../android/export/qif/QifExporter.java | 19 +++++----- .../gnucash/android/export/qif/QifHelper.java | 7 ++++ .../test/unit/export/QifExporterTest.java | 35 ++++++++++++++++--- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b4483fbfe..fe038326e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,14 +229,14 @@ dependencies { } - testImplementation 'org.robolectric:robolectric:4.3.1' + testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation( 'junit:junit:4.12', 'joda-time:joda-time:2.9.4', 'org.assertj:assertj-core:3.14.0' ) - testImplementation 'org.robolectric:shadows-multidex:4.3.1' + testImplementation 'org.robolectric:shadows-multidex:4.5.1' androidTestImplementation ( 'com.android.support:support-annotations:' + androidSupportVersion, diff --git a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java index 9f1478df8..3ce8597eb 100644 --- a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java +++ b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java @@ -145,7 +145,7 @@ public List generateExport() throws ExporterException { currentAccountUID = accountUID; writer.append(QifHelper.ACCOUNT_HEADER).append(newLine); writer.append(QifHelper.ACCOUNT_NAME_PREFIX) - .append(cursor.getString(cursor.getColumnIndexOrThrow("acct1_full_name"))) + .append(QifHelper.sanitizeQifLine(cursor.getString(cursor.getColumnIndexOrThrow("acct1_full_name")))) .append(newLine); writer.append(QifHelper.ENTRY_TERMINATOR).append(newLine); writer.append(QifHelper.getQifHeader(cursor.getString(cursor.getColumnIndexOrThrow("acct1_type")))) @@ -158,12 +158,15 @@ public List generateExport() throws ExporterException { .append(newLine); // Payee / description writer.append(QifHelper.PAYEE_PREFIX) - .append(cursor.getString(cursor.getColumnIndexOrThrow("trans_desc"))) + .append(QifHelper.sanitizeQifLine(cursor.getString(cursor.getColumnIndexOrThrow("trans_desc")))) .append(newLine); // Notes, memo - writer.append(QifHelper.MEMO_PREFIX) - .append(cursor.getString(cursor.getColumnIndexOrThrow("trans_notes"))) - .append(newLine); + String memo = QifHelper.sanitizeQifLine(cursor.getString(cursor.getColumnIndexOrThrow("trans_notes"))); + if (!memo.isEmpty()) { + writer.append(QifHelper.MEMO_PREFIX) + .append(memo) + .append(newLine); + } // deal with imbalance first double imbalance = cursor.getDouble(cursor.getColumnIndexOrThrow("trans_acct_balance")); BigDecimal decimalImbalance = BigDecimal.valueOf(imbalance).setScale(2, BigDecimal.ROUND_HALF_UP); @@ -186,10 +189,10 @@ public List generateExport() throws ExporterException { // amount associated with the header account will not be exported. // It can be auto balanced when importing to GnuCash writer.append(QifHelper.SPLIT_CATEGORY_PREFIX) - .append(cursor.getString(cursor.getColumnIndexOrThrow("acct2_full_name"))) + .append(QifHelper.sanitizeQifLine(cursor.getString(cursor.getColumnIndexOrThrow("acct2_full_name")))) .append(newLine); - String splitMemo = cursor.getString(cursor.getColumnIndexOrThrow("split_memo")); - if (splitMemo != null && splitMemo.length() > 0) { + String splitMemo = QifHelper.sanitizeQifLine(cursor.getString(cursor.getColumnIndexOrThrow("split_memo"))); + if (!splitMemo.isEmpty()) { writer.append(QifHelper.SPLIT_MEMO_PREFIX) .append(splitMemo) .append(newLine); diff --git a/app/src/main/java/org/gnucash/android/export/qif/QifHelper.java b/app/src/main/java/org/gnucash/android/export/qif/QifHelper.java index 0f9450be8..96275296e 100644 --- a/app/src/main/java/org/gnucash/android/export/qif/QifHelper.java +++ b/app/src/main/java/org/gnucash/android/export/qif/QifHelper.java @@ -83,4 +83,11 @@ public static String getQifHeader(AccountType accountType){ public static String getQifHeader(String accountType) { return getQifHeader(AccountType.valueOf(accountType)); } + + static String sanitizeQifLine(String line) { + if (line == null) { + return ""; + } + return line.replaceAll("\\r?\\n", " "); + } } diff --git a/app/src/test/java/org/gnucash/android/test/unit/export/QifExporterTest.java b/app/src/test/java/org/gnucash/android/test/unit/export/QifExporterTest.java index 689e69b33..2e84349cd 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/export/QifExporterTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/export/QifExporterTest.java @@ -26,6 +26,7 @@ import org.gnucash.android.export.ExportFormat; import org.gnucash.android.export.ExportParams; import org.gnucash.android.export.qif.QifExporter; +import org.gnucash.android.export.qif.QifHelper; import org.gnucash.android.model.Account; import org.gnucash.android.model.Book; import org.gnucash.android.model.Commodity; @@ -156,13 +157,39 @@ public void memoAndDescription_shouldBeExported() throws IOException { String expectedDescription = "my description"; String expectedMemo = "my memo"; + checkGivenMemoAndDescription(expectedDescription,expectedDescription,expectedMemo,expectedMemo); + } + + /** + * Test that new lines in the memo and description fields of transactions are not exported. + */ + @Test + public void memoAndDescription_doNotExportNewLines() throws IOException { + final String lineBreak = "\r\n"; + final String expectedLineBreakReplacement = " "; + + final String descriptionFirstLine = "Test description"; + final String descriptionSecondLine = "with 2 lines"; + final String originalDescription = descriptionFirstLine + lineBreak + descriptionSecondLine; + final String expectedDescription = descriptionFirstLine + expectedLineBreakReplacement + descriptionSecondLine; + + final String memoFirstLine = "My memo has multiply lines"; + final String memoSecondLine = "This is the second line"; + final String memoThirdLine = "This is the third line"; + final String originalMemo = memoFirstLine + lineBreak + memoSecondLine + lineBreak + memoThirdLine; + final String expectedMemo = memoFirstLine + expectedLineBreakReplacement + memoSecondLine + expectedLineBreakReplacement + memoThirdLine; + + checkGivenMemoAndDescription(originalDescription,expectedDescription,originalMemo,expectedMemo); + } + + private void checkGivenMemoAndDescription(final String originalDescription, final String expectedDescription, final String originalMemo, final String expectedMemo) throws IOException { AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(mDb); Account account = new Account("Basic Account"); Transaction transaction = new Transaction("One transaction"); transaction.addSplit(new Split(Money.createZeroInstance("EUR"), account.getUID())); - transaction.setDescription(expectedDescription); - transaction.setNote(expectedMemo); + transaction.setDescription(originalDescription); + transaction.setNote(originalMemo); account.addTransaction(transaction); accountsDbAdapter.addRecord(account); @@ -179,8 +206,8 @@ public void memoAndDescription_shouldBeExported() throws IOException { File file = new File(exportedFiles.get(0)); String fileContent = readFileContent(file); assertThat(file).exists().hasExtension("qif"); - assertThat(fileContent.contains(expectedDescription)); - assertThat(fileContent.contains(expectedMemo)); + assertThat(fileContent).contains(QifHelper.PAYEE_PREFIX + expectedDescription); + assertThat(fileContent).contains(QifHelper.MEMO_PREFIX + expectedMemo); } @NonNull From 7b0d6dff9aedfbe1edf79b923ee224d4949270ff Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 27 Jun 2021 15:18:16 +0200 Subject: [PATCH 4/4] Fixed error in new UI test. --- .../org/gnucash/android/test/ui/TransactionsActivityTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java b/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java index dc1f91f98..445ba9ea6 100644 --- a/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java +++ b/app/src/androidTest/java/org/gnucash/android/test/ui/TransactionsActivityTest.java @@ -883,7 +883,7 @@ public void noNewLinesinDescriptionAfterFocusChange() { final String description = "\r\nText with\r\n new lines\n"; clickOnView(R.id.input_transaction_name); - onView(withId(R.id.input_transaction_name)).perform(ViewActions.replaceText(description)); + onView(withId(R.id.input_transaction_name)).perform(replaceText(description)); Espresso.closeSoftKeyboard(); clickOnView(R.id.input_transaction_amount);