You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.java

261 lines
8.1 KiB
Java

/**
* Copyright (c) 2012 Todoroo Inc
*
* <p>See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.test;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.content.res.Configuration;
import android.content.res.Resources;
import androidx.test.runner.AndroidJUnit4;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.R;
/**
* Tests translations for consistency with the default values. You must extend this class and create
* it with your own values for strings and arrays.
*
* @author Tim Su <tim@todoroo.com>
*/
@RunWith(AndroidJUnit4.class)
public class TranslationTests {
/** Loop through each locale and call runnable */
private void forEachLocale(Callback<Resources> callback) {
Locale[] locales = Locale.getAvailableLocales();
for (Locale locale : locales) {
callback.apply(getResourcesForLocale(locale));
}
}
private Resources getResourcesForLocale(Locale locale) {
Resources resources = getTargetContext().getResources();
Configuration configuration = new Configuration(resources.getConfiguration());
configuration.locale = locale;
return new Resources(resources.getAssets(), resources.getDisplayMetrics(), configuration);
}
/** Internal test of format string parser */
@Test
public void testFormatStringParser() {
String s = "abc";
FormatStringData data = new FormatStringData(s);
assertEquals(s, data.string);
assertEquals(0, data.characters.length);
s = "abc %s def";
data = new FormatStringData(s);
assertEquals(1, data.characters.length);
assertEquals('s', data.characters[0]);
s = "abc %%s def %d";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals('%', data.characters[0]);
assertEquals('d', data.characters[1]);
assertTrue(data.toString(), data.toString().contains("[%"));
assertTrue(data.toString(), data.toString().contains("d]"));
assertTrue(data.toString(), data.toString().contains(s));
assertTrue(data.matches(new FormatStringData("espanol %% und %d si")));
assertFalse(data.matches(new FormatStringData("ingles %d ja %% pon")));
s = "% abc %";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals(' ', data.characters[0]);
assertEquals('\0', data.characters[1]);
}
/**
* Test that the format specifiers in translations match exactly the translations in the default
* text
*/
@Test
public void testFormatStringsMatch() {
final Resources resources = getTargetContext().getResources();
final int[] strings = getResourceIds(R.string.class);
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
final StringBuilder failures = new StringBuilder();
for (int i = 0; i < strings.length; i++) {
try {
String string = resources.getString(strings[i]);
formatStrings[i] = new FormatStringData(string);
} catch (Exception e) {
String name = resources.getResourceName(strings[i]);
failures.append(String.format("error opening %s: %s\n", name, e.getMessage()));
}
}
forEachLocale(
r -> {
Locale locale = r.getConfiguration().locale;
for (int i = 0; i < strings.length; i++) {
try {
switch (strings[i]) {
case R.string.abc_shareactionprovider_share_with_application:
continue;
}
String string = r.getString(strings[i]);
FormatStringData newFS = new FormatStringData(string);
if (!newFS.matches(formatStrings[i])) {
String name = r.getResourceName(strings[i]);
failures.append(
String.format(
"%s (%s): %s != %s\n", name, locale.toString(), newFS, formatStrings[i]));
}
} catch (Exception e) {
String name = r.getResourceName(strings[i]);
failures.append(
String.format(
"%s: error opening %s: %s\n", locale.toString(), name, e.getMessage()));
}
}
});
assertTrue(failures.toString(), errorCount(failures) == 0);
}
/** check if string contains contains substrings */
private void contains(Resources r, int resource, StringBuilder failures, String expected) {
String translation = r.getString(resource);
if (!translation.contains(expected)) {
Locale locale = r.getConfiguration().locale;
String name = r.getResourceName(resource);
failures.append(
String.format("%s: %s did not contain: %s\n", locale.toString(), name, expected));
}
}
/** Test dollar sign resources */
@Test
public void testSpecialStringsMatch() {
final StringBuilder failures = new StringBuilder();
forEachLocale(
r -> {
contains(r, R.string.CFC_tag_text, failures, "?");
contains(r, R.string.CFC_title_contains_text, failures, "?");
contains(r, R.string.CFC_dueBefore_text, failures, "?");
contains(r, R.string.CFC_tag_contains_text, failures, "?");
contains(r, R.string.CFC_gtasks_list_text, failures, "?");
});
assertEquals(failures.toString(), 0, failures.toString().replaceAll("[^\n]", "").length());
}
/** Count newlines */
private int errorCount(StringBuilder failures) {
int count = 0;
int pos = -1;
while (true) {
pos = failures.indexOf("\n", pos + 1);
if (pos == -1) {
return count;
}
count++;
}
}
/** @return an array of all string resource id's */
private int[] getResourceIds(Class<?> resources) {
Field[] fields = resources.getDeclaredFields();
List<Integer> ids = new ArrayList<>(fields.length);
for (Field field : fields) {
try {
ids.add(field.getInt(null));
} catch (Exception e) {
// not a field we care about
}
}
int[] idsAsIntArray = new int[ids.size()];
for (int i = 0; i < ids.size(); i++) {
idsAsIntArray[i] = ids.get(i);
}
return idsAsIntArray;
}
public interface Callback<T> {
void apply(T entry);
}
private static final class FormatStringData {
private static final char[] scratch = new char[10];
/** format characters */
final char[] characters;
/** the original string */
final String string;
public FormatStringData(String string) {
this.string = string;
int pos = -1;
int count = 0;
while (true) {
pos = string.indexOf('%', ++pos);
if (pos++ == -1) {
break;
}
if (pos >= string.length()) {
scratch[count++] = '\0';
} else {
scratch[count++] = string.charAt(pos);
}
}
characters = new char[count];
for (int i = 0; i < count; i++) {
characters[i] = scratch[i];
}
}
/** test that the characters match */
boolean matches(FormatStringData other) {
if (characters.length != other.characters.length) {
return false;
}
outer:
for (int i = 0; i < characters.length; i++) {
if (Character.isDigit(characters[i])) {
for (int j = 0; j < other.characters.length; j++) {
if (characters[i] == other.characters[j]) {
break outer;
}
}
return false;
} else if (characters[i] != other.characters[i]) {
return false;
}
}
return true;
}
@Override
public String toString() {
StringBuilder value = new StringBuilder("[");
for (int i = 0; i < characters.length; i++) {
value.append(characters[i]);
if (i < characters.length - 1) {
value.append(',');
}
}
value.append("]: '").append(string).append('\'');
return value.toString();
}
}
}