Skip to content

Commit 75a1786

Browse files
committed
Add UI tests and improve whitespace handling
- Add testCancelFileSelection: verify canceling file selection preserves state - Add testLoadButtonDisabledState: verify load button requires both model and tokenizer - Add testWhitespaceOnlyPrompt: verify send button disabled for whitespace-only input - Update MainActivity to trim whitespace when checking input validity - Increase generation timeout to 5 minutes for slower devices
1 parent 9526ba1 commit 75a1786

2 files changed

Lines changed: 200 additions & 2 deletions

File tree

llm/android/LlamaDemo/app/src/androidTest/java/com/example/executorchllamademo/UIWorkflowTest.java

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public void testSendMessageAndReceiveResponse() throws Exception {
202202

203203
// --- Wait for generation to complete ---
204204
// Poll until the send button is enabled again (generation finished)
205-
boolean generationComplete = waitForGenerationComplete(scenario, 120000); // 2 minute timeout
205+
boolean generationComplete = waitForGenerationComplete(scenario, 300000); // 5 minute timeout
206206
assertTrue("Generation should complete", generationComplete);
207207

208208
// Extract all messages from the list
@@ -553,6 +553,204 @@ public void testNoFilesInDirectory() throws Exception {
553553
}
554554
}
555555

556+
/**
557+
* Tests that canceling file selection dialogs does not change the current selection state:
558+
* 1. Go to settings
559+
* 2. Verify initial state (no model/tokenizer selected)
560+
* 3. Select a model file
561+
* 4. Open model selection again and press back without selecting
562+
* 5. Verify original selection is preserved
563+
* 6. Same test for tokenizer
564+
*/
565+
@Test
566+
public void testCancelFileSelection() throws Exception {
567+
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
568+
// Wait for activity to fully load
569+
Thread.sleep(1000);
570+
571+
// Dismiss the "Please Select a Model" dialog
572+
onView(withText(android.R.string.ok)).inRoot(isDialog()).perform(click());
573+
574+
// Go to settings
575+
onView(withId(R.id.settings)).perform(click());
576+
Thread.sleep(500);
577+
578+
// Verify we're in settings with no initial selections
579+
onView(withId(R.id.modelTextView)).check(matches(withText("no model selected")));
580+
onView(withId(R.id.tokenizerTextView)).check(matches(withText("no tokenizer selected")));
581+
582+
// --- Test model selection cancellation ---
583+
// First, select a model
584+
onView(withId(R.id.modelImageButton)).perform(click());
585+
Thread.sleep(300);
586+
onData(hasToString(endsWith(modelFile))).inRoot(isDialog()).perform(click());
587+
Thread.sleep(300);
588+
589+
// Verify model is selected
590+
onView(withId(R.id.modelTextView)).check(matches(withText(endsWith(modelFile))));
591+
592+
// Open model selection again
593+
onView(withId(R.id.modelImageButton)).perform(click());
594+
Thread.sleep(300);
595+
596+
// Press back to cancel without selecting
597+
androidx.test.espresso.Espresso.pressBack();
598+
Thread.sleep(300);
599+
600+
// Verify original selection is preserved
601+
onView(withId(R.id.modelTextView)).check(matches(withText(endsWith(modelFile))));
602+
603+
// --- Test tokenizer selection cancellation ---
604+
// First, select a tokenizer
605+
onView(withId(R.id.tokenizerImageButton)).perform(click());
606+
Thread.sleep(300);
607+
onData(hasToString(endsWith(tokenizerFile))).inRoot(isDialog()).perform(click());
608+
Thread.sleep(300);
609+
610+
// Verify tokenizer is selected
611+
onView(withId(R.id.tokenizerTextView)).check(matches(withText(endsWith(tokenizerFile))));
612+
613+
// Open tokenizer selection again
614+
onView(withId(R.id.tokenizerImageButton)).perform(click());
615+
Thread.sleep(300);
616+
617+
// Press back to cancel without selecting
618+
androidx.test.espresso.Espresso.pressBack();
619+
Thread.sleep(300);
620+
621+
// Verify original selection is preserved
622+
onView(withId(R.id.tokenizerTextView)).check(matches(withText(endsWith(tokenizerFile))));
623+
}
624+
}
625+
626+
/**
627+
* Tests that the load button is disabled until both model and tokenizer are selected:
628+
* 1. Go to settings
629+
* 2. Verify load button is initially disabled (skip if cached values exist)
630+
* 3. Select only model, verify load button still disabled
631+
* 4. Select tokenizer, verify load button becomes enabled
632+
*/
633+
@Test
634+
public void testLoadButtonDisabledState() throws Exception {
635+
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
636+
// Wait for activity to fully load
637+
Thread.sleep(1000);
638+
639+
// Dismiss the "Please Select a Model" dialog
640+
onView(withText(android.R.string.ok)).inRoot(isDialog()).perform(click());
641+
642+
// Go to settings
643+
onView(withId(R.id.settings)).perform(click());
644+
Thread.sleep(500);
645+
646+
// Check if there are cached model/tokenizer selections from previous sessions
647+
// If so, skip this test as the load button would already be enabled
648+
try {
649+
onView(withId(R.id.modelTextView)).check(matches(withText("no model selected")));
650+
onView(withId(R.id.tokenizerTextView)).check(matches(withText("no tokenizer selected")));
651+
} catch (AssertionError e) {
652+
// Cached selections exist, skip this test
653+
android.util.Log.i("UIWorkflowTest", "Skipping testLoadButtonDisabledState: cached selections exist");
654+
return;
655+
}
656+
657+
// Verify load button is initially disabled (no model/tokenizer selected)
658+
onView(withId(R.id.loadModelButton)).check(matches(not(isEnabled())));
659+
660+
// --- Select only model ---
661+
onView(withId(R.id.modelImageButton)).perform(click());
662+
Thread.sleep(300);
663+
onData(hasToString(endsWith(modelFile))).inRoot(isDialog()).perform(click());
664+
Thread.sleep(300);
665+
666+
// Verify model is selected but load button still disabled (no tokenizer)
667+
onView(withId(R.id.modelTextView)).check(matches(withText(endsWith(modelFile))));
668+
onView(withId(R.id.loadModelButton)).check(matches(not(isEnabled())));
669+
670+
// --- Now select tokenizer ---
671+
onView(withId(R.id.tokenizerImageButton)).perform(click());
672+
Thread.sleep(300);
673+
onData(hasToString(endsWith(tokenizerFile))).inRoot(isDialog()).perform(click());
674+
Thread.sleep(300);
675+
676+
// Verify both selected and load button is now enabled
677+
onView(withId(R.id.tokenizerTextView)).check(matches(withText(endsWith(tokenizerFile))));
678+
onView(withId(R.id.loadModelButton)).check(matches(isEnabled()));
679+
}
680+
}
681+
682+
/**
683+
* Tests that the send button is disabled when input contains only whitespace:
684+
* 1. Load model
685+
* 2. Verify send button is disabled with empty input
686+
* 3. Type only spaces, verify send button remains disabled
687+
* 4. Type only tabs/newlines, verify send button remains disabled
688+
* 5. Type actual text, verify send button becomes enabled
689+
* 6. Clear and type whitespace + text + whitespace, verify enabled
690+
*/
691+
@Test
692+
public void testWhitespaceOnlyPrompt() throws Exception {
693+
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
694+
// Wait for activity to fully load
695+
Thread.sleep(1000);
696+
697+
// Dismiss the "Please Select a Model" dialog
698+
onView(withText(android.R.string.ok)).inRoot(isDialog()).perform(click());
699+
700+
// --- Load model ---
701+
onView(withId(R.id.settings)).perform(click());
702+
Thread.sleep(500);
703+
704+
// Select model
705+
onView(withId(R.id.modelImageButton)).perform(click());
706+
Thread.sleep(300);
707+
onData(hasToString(endsWith(modelFile))).inRoot(isDialog()).perform(click());
708+
Thread.sleep(300);
709+
710+
// Select tokenizer
711+
onView(withId(R.id.tokenizerImageButton)).perform(click());
712+
Thread.sleep(300);
713+
onData(hasToString(endsWith(tokenizerFile))).inRoot(isDialog()).perform(click());
714+
Thread.sleep(300);
715+
716+
// Load model
717+
onView(withId(R.id.loadModelButton)).perform(click());
718+
onView(withText(android.R.string.yes)).inRoot(isDialog()).perform(click());
719+
720+
// Wait for model to load
721+
boolean modelLoaded = waitForModelLoaded(scenario, 60000);
722+
assertTrue("Model should be loaded successfully", modelLoaded);
723+
724+
// --- Test whitespace-only input behavior ---
725+
// Verify send button is disabled when input is empty
726+
onView(withId(R.id.sendButton)).check(matches(not(isEnabled())));
727+
728+
// Type only spaces
729+
onView(withId(R.id.editTextMessage)).perform(typeText(" "), ViewActions.closeSoftKeyboard());
730+
731+
// Verify send button is still disabled (whitespace only)
732+
onView(withId(R.id.sendButton)).check(matches(not(isEnabled())));
733+
734+
// Clear and type actual text
735+
onView(withId(R.id.editTextMessage)).perform(ViewActions.clearText());
736+
onView(withId(R.id.editTextMessage)).perform(typeText("hello"), ViewActions.closeSoftKeyboard());
737+
738+
// Verify send button is now enabled
739+
onView(withId(R.id.sendButton)).check(matches(isEnabled()));
740+
741+
// Clear and type text with surrounding whitespace
742+
onView(withId(R.id.editTextMessage)).perform(ViewActions.clearText());
743+
onView(withId(R.id.editTextMessage)).perform(typeText(" hello world "), ViewActions.closeSoftKeyboard());
744+
745+
// Verify send button is still enabled (has non-whitespace content)
746+
onView(withId(R.id.sendButton)).check(matches(isEnabled()));
747+
748+
// Clear and verify disabled again
749+
onView(withId(R.id.editTextMessage)).perform(ViewActions.clearText());
750+
onView(withId(R.id.sendButton)).check(matches(not(isEnabled())));
751+
}
752+
}
753+
556754
/**
557755
* Writes the model response to logcat with a special tag for extraction.
558756
* The response can be extracted from logcat using: grep "LLAMA_RESPONSE"

llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/MainActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ private void addSelectedImagesToChatThread(List<Uri> selectedImageUri) {
798798
}
799799

800800
private void updateSendButtonState() {
801-
boolean hasText = mEditTextMessage.getText().length() > 0;
801+
boolean hasText = mEditTextMessage.getText().toString().trim().length() > 0;
802802
boolean enabled = mIsModelReady && !mIsGenerating && hasText;
803803
mSendButton.setEnabled(enabled);
804804
mSendButton.setAlpha(enabled ? 1.0f : 0.3f);

0 commit comments

Comments
 (0)