Testing, Documentation and Cleanup.

This commit is contained in:
Adith 2024-10-24 16:59:14 +11:00
parent 8fe958e3c6
commit ea94ea782b
5 changed files with 230 additions and 46 deletions

View file

@ -400,35 +400,43 @@ public class OkHttpJsonApiClient {
/**
* Retrieves a list of places based on the provided list of places and language.
*
* @param placeList A list of Place objects for which to fetch information.
* @param language The language code to use for the query.
* @param placeList A list of Place objects for which to fetch information.
* @param language The language code to use for the query.
* @param secondaryLanguages The serialized secondary language code(s) to use for fallback queries.
* @return A list of Place objects with additional information retrieved from Wikidata, or null
* if an error occurs.
* if an error occurs.
* @throws IOException If there is an issue with reading the resource file or executing the HTTP
* request.
*/
@Nullable
public List<Place> getPlaces(
final List<Place> placeList, final String language, final String secondaryLanguages) throws IOException {
final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq");
final String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*"); // could be used to generate backup SparQL Queries
// Read the SparQL query template from the resource file
final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq");
// Split the secondary languages string into an array to use in fallback queries
final String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*");
// Prepare the Wikidata entity IDs (QIDs) for each place in the list
String qids = "";
for (final Place place : placeList) {
qids += "\n" + ("wd:" + place.getWikiDataEntityId());
}
// Build fallback descriptions for secondary languages in case the primary language is unavailable
StringBuilder fallBackDescription = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallBackDescription.append("OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_")
.append(i + 1)
.append(i + 1) // Unique identifier for each fallback
.append(". FILTER (lang(?itemDescriptionPreferredLanguage_")
.append(i + 1)
.append(") = \"")
.append(secondaryLanguageArray[i])
.append(secondaryLanguageArray[i]) // Use the secondary language code
.append("\")}\n");
}
// Build fallback labels for secondary languages
StringBuilder fallbackLabel = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallbackLabel.append("OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_")
@ -440,6 +448,7 @@ public class OkHttpJsonApiClient {
.append("\")}\n");
}
// Build fallback class labels for secondary languages
StringBuilder fallbackClassLabel = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallbackClassLabel.append("OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_")
@ -451,6 +460,7 @@ public class OkHttpJsonApiClient {
.append("\")}\n");
}
// Replace placeholders in the query with actual data: QIDs, language codes, and fallback options
final String query = wikidataQuery
.replace("${ENTITY}", qids)
.replace("${LANG}", language)
@ -458,33 +468,41 @@ public class OkHttpJsonApiClient {
.replace("${SECONDARYLABEL}", fallbackLabel.toString())
.replace("${SECONDARYCLASSLABEL}", fallbackClassLabel.toString());
// Build the URL for the SparQL query with the formatted query string
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
.addQueryParameter("format", "json"); // Ensure JSON response
// Create and send the HTTP request
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
// Execute the request and handle the response
try (Response response = okHttpClient.newCall(request).execute()) {
if (response.isSuccessful()) {
// Parse the JSON response and convert it to a list of NearbyResultItems
final String json = response.body().string();
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
// Convert each NearbyResultItem into a Place object and return the list of places
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place placeFromNearbyItem = Place.from(item);
places.add(placeFromNearbyItem);
}
return places;
return places; // Return the list of places with additional information
} else {
// Handle unsuccessful HTTP response codes
throw new IOException("Unexpected response code: " + response.code());
}
}
}
/**
* Make API Call to get Places
*

View file

@ -127,6 +127,7 @@ public class NearbyPlaces {
*
* @param placeList A list of Place objects for which to fetch information.
* @param lang The language code to use for the query.
* @param lang2 The serialised secondary language code to use for the query.
* @return A list of Place objects obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process.
*/

View file

@ -290,6 +290,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
});
}
/**
* Updates the ListView to display saved languages using the SavedLanguagesAdapter.
*
* @param savedLanguageListView The ListView that will display the saved languages.
* @param savedLanguages A list of saved Language objects to be displayed.
* @param selectedLanguages A HashMap containing the selected language IDs and their corresponding names.
*/
private void updateSavedLanguages(ListView savedLanguageListView, List<Language> savedLanguages, HashMap<Integer, String> selectedLanguages) {
// Use SavedLanguagesAdapter to display saved languages
SavedLanguagesAdapter savedLanguagesAdapter = new SavedLanguagesAdapter(
@ -302,6 +309,12 @@ public class SettingsFragment extends PreferenceFragmentCompat {
savedLanguageListView.setAdapter(savedLanguagesAdapter);
}
/**
* Deserializes a comma-separated string of language codes into an ArrayList of strings.
*
* @param languageCodes A string containing language codes separated by commas.
* @return An ArrayList of language codes, or an empty ArrayList if the input is null or empty.
*/
private ArrayList<String> deSerialise(String languageCodes) {
// Check if the stored string is empty or null
if (languageCodes == null || languageCodes.isEmpty()) {
@ -313,21 +326,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return new ArrayList<>(Arrays.asList(languageArray)); // Convert array to ArrayList and return
}
/**
* Prepare and Show language selection dialog box
* Disable default/already selected language from dialog box
* Saves values chosen by user to shared preferences as a serialised string.
*/
private void prepareSecondaryLanguageDialog() {
final String languageCode = getCurrentLanguageCode("descriptionSecondaryLanguagePref");
HashMap<Integer, String> selectedLanguages = new HashMap<>();
assert languageCode != null;
selectedLanguages.put(0, Locale.getDefault().getLanguage());
System.out.println(Locale.getDefault().getLanguage());
System.out.println(languageCode);
// Deserializing saved language codes to Language objects
ArrayList<Language> savedLanguages = new ArrayList<>();
for (String code : deSerialise(languageCode)) {
System.out.println(code);
if(code.equals(Locale.getDefault().getLanguage())){
System.out.println("match");
continue;
}
Locale locale = new Locale(code);
@ -633,42 +646,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
separator.setVisibility(View.GONE);
}
private String reSerialise(ArrayList<String> languageCodes) {
// Join the elements of the list into a single string, separated by a comma and a space
return String.join(", ", languageCodes);
}
/**
* Changing the default app language with selected one and save it to SharedPreferences
*/
public void setLocale(final Activity activity, String userSelectedValue) {
// if (userSelectedValue.equals("")) {
// userSelectedValue = Locale.getDefault().getLanguage();
// }
//
// String current = Locale.getDefault().getLanguage();
// ArrayList<String> languageCodes = deSerialise(current);
// if(appUI) {
// languageCodes.set(0, userSelectedValue);
// userSelectedValue = reSerialise(languageCodes);
// }
// else{
// ArrayList<String> newLanguageCodes = new ArrayList<>();
// ArrayList<String> userSelctedCode = deSerialise(userSelectedValue);
//
// newLanguageCodes.add(languageCodes.get(0));
// for(String code : userSelctedCode){
// newLanguageCodes.add(code);
// }
// userSelectedValue = reSerialise(newLanguageCodes);
// }
//
// System.out.println("Final locale");
// System.out.println(userSelectedValue);
//
// System.out.println("vs");
// System.out.println(getCurrentLanguageCode("appUiDefaultLanguagePref"));
// System.out.println(getCurrentLanguageCode("descriptionSecondaryLanguagePref"));
final Locale locale = createLocale(userSelectedValue);
Locale.setDefault(locale);

View file

@ -0,0 +1,74 @@
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StringBuilderTest {
@Test
void testFallbackDescriptionBuilder() {
String secondaryLanguages = "en,fr,es"; // Example secondary languages
String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*");
StringBuilder fallBackDescription = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallBackDescription.append("OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_")
.append(i + 1) // Unique identifier for each fallback
.append(". FILTER (lang(?itemDescriptionPreferredLanguage_")
.append(i + 1)
.append(") = \"")
.append(secondaryLanguageArray[i]) // Use the secondary language code
.append("\")}\n");
}
String expected = "OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_1. FILTER (lang(?itemDescriptionPreferredLanguage_1) = \"en\")}\n" +
"OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_2. FILTER (lang(?itemDescriptionPreferredLanguage_2) = \"fr\")}\n" +
"OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_3. FILTER (lang(?itemDescriptionPreferredLanguage_3) = \"es\")}\n";
assertEquals(expected, fallBackDescription.toString());
}
@Test
void testFallbackLabelBuilder() {
String secondaryLanguages = "en,fr,es"; // Example secondary languages
String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*");
StringBuilder fallbackLabel = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallbackLabel.append("OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_")
.append(i + 1)
.append(". FILTER (lang(?itemLabelPreferredLanguage_")
.append(i + 1)
.append(") = \"")
.append(secondaryLanguageArray[i])
.append("\")}\n");
}
String expected = "OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_1. FILTER (lang(?itemLabelPreferredLanguage_1) = \"en\")}\n" +
"OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_2. FILTER (lang(?itemLabelPreferredLanguage_2) = \"fr\")}\n" +
"OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_3. FILTER (lang(?itemLabelPreferredLanguage_3) = \"es\")}\n";
assertEquals(expected, fallbackLabel.toString());
}
@Test
void testFallbackClassLabelBuilder() {
String secondaryLanguages = "en,fr,es"; // Example secondary languages
String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*");
StringBuilder fallbackClassLabel = new StringBuilder();
for (int i = 0; i < secondaryLanguageArray.length; i++) {
fallbackClassLabel.append("OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_")
.append(i + 1)
.append(". FILTER (lang(?classLabelPreferredLanguage_")
.append(i + 1)
.append(") = \"")
.append(secondaryLanguageArray[i])
.append("\")}\n");
}
String expected = "OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_1. FILTER (lang(?classLabelPreferredLanguage_1) = \"en\")}\n" +
"OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_2. FILTER (lang(?classLabelPreferredLanguage_2) = \"fr\")}\n" +
"OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_3. FILTER (lang(?classLabelPreferredLanguage_3) = \"es\")}\n";
assertEquals(expected, fallbackClassLabel.toString());
}
}

View file

@ -0,0 +1,110 @@
package fr.free.nrw.commons.settings
import android.app.Dialog
import android.os.Looper
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.recentlanguages.Language
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import java.lang.reflect.Method
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED)
class SettingsFragmentSecondaryLanguageTests {
private lateinit var fragment: SettingsFragment
private lateinit var recentLanguagesDao: RecentLanguagesDao
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
val activity = Robolectric.buildActivity(SettingsActivity::class.java).create().get()
fragment = SettingsFragment()
val fragmentManager = activity.supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(fragment, null)
fragmentTransaction.commitNowAllowingStateLoss()
// Mock RecentLanguagesDao
recentLanguagesDao = Mockito.mock(RecentLanguagesDao::class.java)
fragment.recentLanguagesDao = recentLanguagesDao
}
@Test
@Throws(Exception::class)
fun `Test prepareSecondaryLanguageDialog is invoked and dialog is created`() {
// Set up the main looper to idle, as necessary in Robolectric tests
Shadows.shadowOf(Looper.getMainLooper()).idle()
val method: Method = SettingsFragment::class.java.getDeclaredMethod("prepareSecondaryLanguageDialog")
method.isAccessible = true
method.invoke(fragment)
// Verify if the dialog was created and is not null
val dialogField = SettingsFragment::class.java.getDeclaredField("dialog")
dialogField.isAccessible = true
val dialog: Dialog? = dialogField.get(fragment) as Dialog?
Assert.assertNotNull(dialog)
}
@Test
@Throws(Exception::class)
fun `Test prepareSecondaryLanguageDialog adds a language and updates preferences`() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
// Mock recent languages and saved languages
val savedLanguages = mutableListOf(Language("English", "en"))
val method: Method = SettingsFragment::class.java.getDeclaredMethod("prepareSecondaryLanguageDialog")
method.isAccessible = true
method.invoke(fragment)
val dialogField = SettingsFragment::class.java.getDeclaredField("dialog")
dialogField.isAccessible = true
val dialog: Dialog? = dialogField.get(fragment) as Dialog?
val newLanguage = Language("German", "de")
savedLanguages.add(newLanguage)
// Verify if the saved languages now include the newly added language
Assert.assertTrue(savedLanguages.contains(newLanguage))
}
@Test
@Throws(Exception::class)
fun `Test prepareSecondaryLanguageDialog removes a language and updates preferences`() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
// Mock recent languages and saved languages
val savedLanguages = mutableListOf(Language("English", "en"), Language("French", "fr"))
val method: Method = SettingsFragment::class.java.getDeclaredMethod("prepareSecondaryLanguageDialog")
method.isAccessible = true
method.invoke(fragment)
val dialogField = SettingsFragment::class.java.getDeclaredField("dialog")
dialogField.isAccessible = true
val dialog: Dialog? = dialogField.get(fragment) as Dialog?
// Simulate removing a language from the saved list (e.g., removing "French")
val positionToRemove = 1
savedLanguages.removeAt(positionToRemove)
Assert.assertFalse(savedLanguages.any { it.languageCode == "fr" })
}
}