|
22 | 22 | import android.content.Intent; |
23 | 23 | import android.content.res.AssetManager; |
24 | 24 | import android.net.Uri; |
| 25 | +import android.os.SystemClock; |
25 | 26 | import android.util.ArrayMap; |
26 | 27 | import android.util.Log; |
27 | 28 |
|
|
49 | 50 | import java.io.InputStream; |
50 | 51 | import java.io.OutputStream; |
51 | 52 | import java.util.Map; |
| 53 | +import java.util.concurrent.CountDownLatch; |
| 54 | +import java.util.concurrent.TimeUnit; |
| 55 | +import java.util.concurrent.atomic.AtomicReference; |
52 | 56 |
|
53 | 57 | import at.tomtasche.reader.R; |
| 58 | +import at.tomtasche.reader.ui.EditActionModeCallback; |
54 | 59 | import at.tomtasche.reader.ui.activity.MainActivity; |
| 60 | +import at.tomtasche.reader.ui.activity.DocumentFragment; |
| 61 | +import at.tomtasche.reader.ui.widget.PageView; |
55 | 62 |
|
56 | 63 | @LargeTest |
57 | 64 | @RunWith(AndroidJUnit4.class) |
@@ -126,7 +133,7 @@ public static void extractTestFiles() throws IOException { |
126 | 133 |
|
127 | 134 | AssetManager testAssetManager = instrumentation.getContext().getAssets(); |
128 | 135 |
|
129 | | - for (String filename: new String[] {"test.odt", "dummy.pdf", "password-test.odt"}) { |
| 136 | + for (String filename: new String[] {"test.odt", "dummy.pdf", "password-test.odt", "style-various-1.docx"}) { |
130 | 137 | File targetFile = new File(testDocumentsDir, filename); |
131 | 138 | try (InputStream inputStream = testAssetManager.open(filename)) { |
132 | 139 | copy(inputStream, targetFile); |
@@ -277,4 +284,135 @@ public void testPasswordProtectedODT() { |
277 | 284 | .perform(click()); |
278 | 285 | }); |
279 | 286 | } |
| 287 | + |
| 288 | + @Test |
| 289 | + public void testODTEditMode() throws InterruptedException { |
| 290 | + File testFile = s_testFiles.get("test.odt"); |
| 291 | + Assert.assertNotNull(testFile); |
| 292 | + MainActivity activity = mainActivityActivityTestRule.getActivity(); |
| 293 | + DocumentFragment documentFragment = loadDocument(activity, testFile); |
| 294 | + enterEditMode(activity, documentFragment); |
| 295 | + |
| 296 | + PageView pageView = documentFragment.getPageView(); |
| 297 | + Assert.assertNotNull(pageView); |
| 298 | + Assert.assertTrue( |
| 299 | + "ODT should become editable after entering edit mode", |
| 300 | + waitForEditableState(pageView, true, 10000) |
| 301 | + ); |
| 302 | + } |
| 303 | + |
| 304 | + @Test |
| 305 | + public void testDOCXEditMode() throws InterruptedException { |
| 306 | + File testFile = s_testFiles.get("style-various-1.docx"); |
| 307 | + Assert.assertNotNull(testFile); |
| 308 | + MainActivity activity = mainActivityActivityTestRule.getActivity(); |
| 309 | + DocumentFragment documentFragment = loadDocument(activity, testFile); |
| 310 | + enterEditMode(activity, documentFragment); |
| 311 | + |
| 312 | + PageView pageView = documentFragment.getPageView(); |
| 313 | + Assert.assertNotNull(pageView); |
| 314 | + |
| 315 | + Assert.assertTrue( |
| 316 | + "DOCX should become editable after entering edit mode", |
| 317 | + waitForEditableState(pageView, true, 10000) |
| 318 | + ); |
| 319 | + } |
| 320 | + |
| 321 | + private DocumentFragment loadDocument(MainActivity activity, File testFile) throws InterruptedException { |
| 322 | + Context appCtx = InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| 323 | + Uri testFileUri = FileProvider.getUriForFile(appCtx, appCtx.getPackageName() + ".provider", testFile); |
| 324 | + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activity.loadUri(testFileUri)); |
| 325 | + |
| 326 | + DocumentFragment fragment = waitForDocumentFragment(activity, 10000); |
| 327 | + Assert.assertNotNull(fragment); |
| 328 | + Assert.assertTrue("Timed out waiting for document to load", waitForLastResult(fragment, 10000)); |
| 329 | + return fragment; |
| 330 | + } |
| 331 | + |
| 332 | + private DocumentFragment waitForDocumentFragment(MainActivity activity, long timeoutMs) |
| 333 | + throws InterruptedException { |
| 334 | + long startMs = SystemClock.elapsedRealtime(); |
| 335 | + DocumentFragment fragment; |
| 336 | + do { |
| 337 | + fragment = (DocumentFragment) activity.getSupportFragmentManager() |
| 338 | + .findFragmentByTag("document_fragment"); |
| 339 | + if (fragment != null) { |
| 340 | + return fragment; |
| 341 | + } |
| 342 | + SystemClock.sleep(100); |
| 343 | + } while (SystemClock.elapsedRealtime() - startMs < timeoutMs); |
| 344 | + return null; |
| 345 | + } |
| 346 | + |
| 347 | + private boolean waitForLastResult(DocumentFragment fragment, long timeoutMs) throws InterruptedException { |
| 348 | + long startMs = SystemClock.elapsedRealtime(); |
| 349 | + while (SystemClock.elapsedRealtime() - startMs < timeoutMs) { |
| 350 | + if (fragment.hasLastResult()) { |
| 351 | + return true; |
| 352 | + } |
| 353 | + SystemClock.sleep(100); |
| 354 | + } |
| 355 | + return false; |
| 356 | + } |
| 357 | + |
| 358 | + private void enterEditMode(MainActivity activity, DocumentFragment documentFragment) { |
| 359 | + AtomicReference<Boolean> started = new AtomicReference<>(false); |
| 360 | + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { |
| 361 | + started.set(activity.startSupportActionMode( |
| 362 | + new EditActionModeCallback(activity, documentFragment)) != null); |
| 363 | + }); |
| 364 | + Assert.assertTrue("Failed to enter edit mode", started.get()); |
| 365 | + } |
| 366 | + |
| 367 | + private boolean waitForEditableState(PageView pageView, boolean expected, long timeoutMs) |
| 368 | + throws InterruptedException { |
| 369 | + long startMs = SystemClock.elapsedRealtime(); |
| 370 | + while (SystemClock.elapsedRealtime() - startMs < timeoutMs) { |
| 371 | + if (expected == isEditableDom(pageView)) { |
| 372 | + return true; |
| 373 | + } |
| 374 | + SystemClock.sleep(250); |
| 375 | + } |
| 376 | + return false; |
| 377 | + } |
| 378 | + |
| 379 | + private boolean waitForNonEditableState(PageView pageView, long timeoutMs) throws InterruptedException { |
| 380 | + long startMs = SystemClock.elapsedRealtime(); |
| 381 | + while (SystemClock.elapsedRealtime() - startMs < timeoutMs) { |
| 382 | + if (isEditableDom(pageView)) { |
| 383 | + return false; |
| 384 | + } |
| 385 | + SystemClock.sleep(250); |
| 386 | + } |
| 387 | + return true; |
| 388 | + } |
| 389 | + |
| 390 | + private boolean isEditableDom(PageView pageView) throws InterruptedException { |
| 391 | + String result = evaluateJavascript(pageView, |
| 392 | + "(function(){" |
| 393 | + + "var bodyEditable = document.body && document.body.isContentEditable;" |
| 394 | + + "var editableNode = document.querySelector('[contenteditable=\"true\"], [contenteditable=\"plaintext-only\"]');" |
| 395 | + + "return !!(bodyEditable || editableNode);" |
| 396 | + + "})()"); |
| 397 | + if (result == null) { |
| 398 | + return false; |
| 399 | + } |
| 400 | + String normalized = result.replace("\"", ""); |
| 401 | + return "true".equalsIgnoreCase(normalized); |
| 402 | + } |
| 403 | + |
| 404 | + private String evaluateJavascript(PageView pageView, String script) throws InterruptedException { |
| 405 | + AtomicReference<String> result = new AtomicReference<>(); |
| 406 | + CountDownLatch latch = new CountDownLatch(1); |
| 407 | + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { |
| 408 | + pageView.evaluateJavascript(script, value -> { |
| 409 | + result.set(value); |
| 410 | + latch.countDown(); |
| 411 | + }); |
| 412 | + }); |
| 413 | + if (!latch.await(10, TimeUnit.SECONDS)) { |
| 414 | + Assert.fail("Timed out waiting for JS evaluation result"); |
| 415 | + } |
| 416 | + return result.get(); |
| 417 | + } |
280 | 418 | } |
0 commit comments