11package at .tomtasche .reader .test ;
22
3- import static androidx .test .espresso .Espresso .onView ;
4- import static androidx .test .espresso .action .ViewActions .clearText ;
5- import static androidx .test .espresso .action .ViewActions .click ;
6- import static androidx .test .espresso .action .ViewActions .typeText ;
7- import static androidx .test .espresso .assertion .ViewAssertions .matches ;
8- import static androidx .test .espresso .intent .matcher .IntentMatchers .hasAction ;
9- import static androidx .test .espresso .matcher .ViewMatchers .isDisplayed ;
10- import static androidx .test .espresso .matcher .ViewMatchers .isEnabled ;
11- import static androidx .test .espresso .matcher .ViewMatchers .withClassName ;
12- import static androidx .test .espresso .matcher .ViewMatchers .withContentDescription ;
13- import static androidx .test .espresso .matcher .ViewMatchers .withId ;
14- import static androidx .test .espresso .matcher .ViewMatchers .withText ;
15- import static org .hamcrest .Matchers .allOf ;
16- import static org .hamcrest .Matchers .anyOf ;
17- import static org .hamcrest .Matchers .equalTo ;
18-
19- import android .app .Activity ;
203import android .app .Instrumentation ;
214import android .content .Context ;
225import android .content .Intent ;
2912import androidx .core .content .FileProvider ;
3013import androidx .test .espresso .IdlingRegistry ;
3114import androidx .test .espresso .IdlingResource ;
32- import androidx .test .espresso .intent .Intents ;
3315import androidx .test .ext .junit .runners .AndroidJUnit4 ;
3416import androidx .test .filters .LargeTest ;
3517import androidx .test .platform .app .InstrumentationRegistry ;
4931import java .io .IOException ;
5032import java .io .InputStream ;
5133import java .io .OutputStream ;
34+ import java .lang .reflect .Field ;
5235import java .util .Map ;
5336import java .util .concurrent .CountDownLatch ;
5437import java .util .concurrent .TimeUnit ;
5538import java .util .concurrent .atomic .AtomicReference ;
5639
57- import at .tomtasche .reader .R ;
40+ import at .tomtasche .reader .background . FileLoader ;
5841import at .tomtasche .reader .ui .EditActionModeCallback ;
5942import at .tomtasche .reader .ui .activity .MainActivity ;
6043import at .tomtasche .reader .ui .activity .DocumentFragment ;
@@ -83,8 +66,6 @@ public void setUp() {
8366 // Close system dialogs which may cover our Activity.
8467 // Happens frequently on slow emulators.
8568 mainActivity .sendBroadcast (new Intent (Intent .ACTION_CLOSE_SYSTEM_DIALOGS ));
86-
87- Intents .init ();
8869
8970 // Log test setup for debugging
9071 Log .d ("MainActivityTests" , "setUp() called for test: " + getClass ().getName ());
@@ -93,8 +74,6 @@ public void setUp() {
9374 @ After
9475 public void tearDown () {
9576 Log .d ("MainActivityTests" , "tearDown() called" );
96-
97- Intents .release ();
9877
9978 if (null != m_idlingResource ) {
10079 IdlingRegistry .getInstance ().unregister (m_idlingResource );
@@ -150,78 +129,39 @@ public static void cleanupTestFiles() {
150129 }
151130
152131 @ Test
153- public void testODT () {
132+ public void testODT () throws InterruptedException {
154133 File testFile = s_testFiles .get ("test.odt" );
155134 Assert .assertNotNull (testFile );
156- Context appCtx = InstrumentationRegistry .getInstrumentation ().getTargetContext ();
157- Uri testFileUri = FileProvider .getUriForFile (appCtx , appCtx .getPackageName () + ".provider" , testFile );
158- Intents .intending (hasAction (Intent .ACTION_OPEN_DOCUMENT )).respondWith (
159- new Instrumentation .ActivityResult (Activity .RESULT_OK ,
160- new Intent ()
161- .setData (testFileUri )
162- .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION )
163- )
164- );
165-
166- onView (allOf (withId (R .id .menu_open ), withContentDescription ("Open document" ), isDisplayed ()))
167- .perform (click ());
168-
169- // The menu item could be either Documents or Files.
170- onView (allOf (withId (android .R .id .text1 ), anyOf (withText ("Documents" ), withText ("Files" )), isDisplayed ()))
171- .perform (click ());
135+ MainActivity activity = mainActivityActivityTestRule .getActivity ();
136+ DocumentFragment documentFragment = loadDocument (activity , testFile );
172137
173- // next onView will be blocked until m_idlingResource is idle.
174- onView (allOf (withId (R .id .menu_edit ), withContentDescription ("Edit document" ), isEnabled ()))
175- .withFailureHandler ((error , viewMatcher ) -> {
176- // fails on small screens, try again with overflow menu
177- onView (allOf (withContentDescription ("More options" ), isDisplayed ())).perform (click ());
138+ PageView pageView = documentFragment .getPageView ();
139+ Assert .assertNotNull (pageView );
140+ Assert .assertTrue ("ODT should load" , waitForPageLoaded (pageView , 10000 ));
178141
179- onView ( allOf ( withId ( R . id . menu_edit ), withContentDescription ( "Edit document" ), isDisplayed ()))
180- . perform ( click () );
181- } );
142+ String fileType = documentFragment . getLastFileType ();
143+ Assert . assertNotNull ( fileType );
144+ Assert . assertTrue ( "Expected ODT file type" , fileType . startsWith ( "application/vnd.oasis.opendocument" ) );
182145 }
183146
184147 @ Test
185- public void testPDF () {
148+ public void testPDF () throws InterruptedException {
186149 File testFile = s_testFiles .get ("dummy.pdf" );
187150 Assert .assertNotNull (testFile );
188- Context appCtx = InstrumentationRegistry .getInstrumentation ().getTargetContext ();
189- Uri testFileUri = FileProvider .getUriForFile (appCtx , appCtx .getPackageName () + ".provider" , testFile );
190- Intents .intending (hasAction (Intent .ACTION_OPEN_DOCUMENT )).respondWith (
191- new Instrumentation .ActivityResult (Activity .RESULT_OK ,
192- new Intent ()
193- .setData (testFileUri )
194- .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION )
195- )
196- );
197-
198- onView (allOf (withId (R .id .menu_open ), withContentDescription ("Open document" ), isDisplayed ()))
199- .perform (click ());
200-
201- // The menu item could be either Documents or Files.
202- onView (allOf (withId (android .R .id .text1 ), anyOf (withText ("Documents" ), withText ("Files" )), isDisplayed ()))
203- .perform (click ());
204-
205- // next onView will be blocked until m_idlingResource is idle.
206-
207- onView (allOf (withId (R .id .menu_edit ), withContentDescription ("Edit document" ), isEnabled ()))
208- .withFailureHandler ((error , viewMatcher ) -> {
209- // fails on small screens, try again with overflow menu
210- onView (allOf (withContentDescription ("More options" ), isDisplayed ())).perform (click ());
151+ MainActivity activity = mainActivityActivityTestRule .getActivity ();
152+ DocumentFragment documentFragment = loadDocument (activity , testFile );
211153
212- onView ( allOf ( withId ( R . id . menu_edit ), withContentDescription ( "Edit document" ), isDisplayed ()))
213- . perform ( click () );
214- } );
154+ PageView pageView = documentFragment . getPageView ();
155+ Assert . assertNotNull ( pageView );
156+ Assert . assertTrue ( "PDF should load" , waitForPageLoaded ( pageView , 10000 ) );
215157
216- try {
217- Thread .sleep (10000 );
218- } catch (InterruptedException e ) {
219- throw new RuntimeException (e );
220- }
158+ String fileType = documentFragment .getLastFileType ();
159+ Assert .assertNotNull (fileType );
160+ Assert .assertTrue ("Expected PDF file type" , fileType .startsWith ("application/pdf" ));
221161 }
222162
223163 @ Test
224- public void testPasswordProtectedODT () {
164+ public void testPasswordProtectedODT () throws InterruptedException {
225165 File testFile = s_testFiles .get ("password-test.odt" );
226166 Assert .assertNotNull (testFile );
227167
@@ -237,52 +177,15 @@ public void testPasswordProtectedODT() {
237177 // Double-check we're using the right file
238178 Assert .assertEquals ("password-test.odt file size mismatch" , 12671L , testFile .length ());
239179
240- Context appCtx = InstrumentationRegistry .getInstrumentation ().getTargetContext ();
241- Uri testFileUri = FileProvider .getUriForFile (appCtx , appCtx .getPackageName () + ".provider" , testFile );
242- Intents .intending (hasAction (Intent .ACTION_OPEN_DOCUMENT )).respondWith (
243- new Instrumentation .ActivityResult (Activity .RESULT_OK ,
244- new Intent ()
245- .setData (testFileUri )
246- .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION )
247- )
248- );
249-
250- onView (allOf (withId (R .id .menu_open ), withContentDescription ("Open document" ), isDisplayed ()))
251- .perform (click ());
252-
253- onView (allOf (withId (android .R .id .text1 ), anyOf (withText ("Documents" ), withText ("Files" )), isDisplayed ()))
254- .perform (click ());
255-
256- // Wait for the password dialog to appear
257- onView (withText ("This document is password-protected" ))
258- .check (matches (isDisplayed ()));
259-
260- // Enter wrong password first
261- onView (withClassName (equalTo ("android.widget.EditText" )))
262- .perform (typeText ("wrongpassword" ));
263-
264- onView (withId (android .R .id .button1 ))
265- .perform (click ());
266-
267- // Should show password dialog again for wrong password
268- onView (withText ("This document is password-protected" ))
269- .check (matches (isDisplayed ()));
270-
271- // Clear the text field and enter correct password
272- onView (withClassName (equalTo ("android.widget.EditText" )))
273- .perform (clearText (), typeText ("passwort" ));
274-
275- onView (withId (android .R .id .button1 ))
276- .perform (click ());
180+ MainActivity activity = mainActivityActivityTestRule .getActivity ();
181+ DocumentFragment documentFragment = loadDocument (activity , testFile );
277182
278- // Check if edit button becomes available (indicating successful load)
279- onView (allOf (withId (R .id .menu_edit ), withContentDescription ("Edit document" ), isEnabled ()))
280- .withFailureHandler ((error , viewMatcher ) -> {
281- onView (allOf (withContentDescription ("More options" ), isDisplayed ())).perform (click ());
183+ setPasswordAndReload (documentFragment , "passwort" );
282184
283- onView (allOf (withId (R .id .menu_edit ), withContentDescription ("Edit document" ), isDisplayed ()))
284- .perform (click ());
285- });
185+ PageView pageView = documentFragment .getPageView ();
186+ Assert .assertNotNull (pageView );
187+ Assert .assertTrue ("Password-protected ODT should load with correct password" ,
188+ waitForPageLoaded (pageView , 10000 ));
286189 }
287190
288191 @ Test
@@ -355,6 +258,25 @@ private boolean waitForLastResult(DocumentFragment fragment, long timeoutMs) thr
355258 return false ;
356259 }
357260
261+ private void setPasswordAndReload (DocumentFragment documentFragment , String password ) {
262+ FileLoader .Result result = getLastResult (documentFragment );
263+ Assert .assertNotNull (result );
264+ Assert .assertNotNull (result .options );
265+ result .options .password = password ;
266+ InstrumentationRegistry .getInstrumentation ().runOnMainSync (() -> documentFragment .reloadUri (false ));
267+ }
268+
269+ private FileLoader .Result getLastResult (DocumentFragment documentFragment ) {
270+ try {
271+ Field field = DocumentFragment .class .getDeclaredField ("lastResult" );
272+ field .setAccessible (true );
273+ return (FileLoader .Result ) field .get (documentFragment );
274+ } catch (NoSuchFieldException | IllegalAccessException e ) {
275+ Assert .fail ("Failed to access lastResult: " + e .getMessage ());
276+ return null ;
277+ }
278+ }
279+
358280 private void enterEditMode (MainActivity activity , DocumentFragment documentFragment ) {
359281 AtomicReference <Boolean > started = new AtomicReference <>(false );
360282 InstrumentationRegistry .getInstrumentation ().runOnMainSync (() -> {
@@ -364,27 +286,41 @@ private void enterEditMode(MainActivity activity, DocumentFragment documentFragm
364286 Assert .assertTrue ("Failed to enter edit mode" , started .get ());
365287 }
366288
367- private boolean waitForEditableState (PageView pageView , boolean expected , long timeoutMs )
368- throws InterruptedException {
289+ private boolean waitForPageLoaded (PageView pageView , long timeoutMs ) throws InterruptedException {
369290 long startMs = SystemClock .elapsedRealtime ();
370291 while (SystemClock .elapsedRealtime () - startMs < timeoutMs ) {
371- if (expected == isEditableDom (pageView )) {
292+ String url = getPageViewUrl (pageView );
293+ if (url != null && !url .isEmpty () && !"about:blank" .equals (url )) {
372294 return true ;
373295 }
374296 SystemClock .sleep (250 );
375297 }
376298 return false ;
377299 }
378300
379- private boolean waitForNonEditableState (PageView pageView , long timeoutMs ) throws InterruptedException {
301+ private String getPageViewUrl (PageView pageView ) throws InterruptedException {
302+ AtomicReference <String > url = new AtomicReference <>();
303+ CountDownLatch latch = new CountDownLatch (1 );
304+ InstrumentationRegistry .getInstrumentation ().runOnMainSync (() -> {
305+ url .set (pageView .getUrl ());
306+ latch .countDown ();
307+ });
308+ if (!latch .await (5 , TimeUnit .SECONDS )) {
309+ Assert .fail ("Timed out waiting for WebView URL" );
310+ }
311+ return url .get ();
312+ }
313+
314+ private boolean waitForEditableState (PageView pageView , boolean expected , long timeoutMs )
315+ throws InterruptedException {
380316 long startMs = SystemClock .elapsedRealtime ();
381317 while (SystemClock .elapsedRealtime () - startMs < timeoutMs ) {
382- if (isEditableDom (pageView )) {
383- return false ;
318+ if (expected == isEditableDom (pageView )) {
319+ return true ;
384320 }
385321 SystemClock .sleep (250 );
386322 }
387- return true ;
323+ return false ;
388324 }
389325
390326 private boolean isEditableDom (PageView pageView ) throws InterruptedException {
0 commit comments