mirror of https://github.com/tasks/tasks
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.
340 lines
12 KiB
Java
340 lines
12 KiB
Java
/**
|
|
* Copyright (c) 2012 Todoroo Inc
|
|
*
|
|
* See the file "LICENSE" for the full license governing this code.
|
|
*/
|
|
package com.todoroo.andlib.test;
|
|
|
|
|
|
import android.content.res.Configuration;
|
|
import android.content.res.Resources;
|
|
import android.test.AndroidTestCase;
|
|
import android.util.DisplayMetrics;
|
|
|
|
import org.tasks.R;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
import static org.tasks.date.DateTimeUtils.newDate;
|
|
|
|
/**
|
|
* 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>
|
|
*
|
|
*/
|
|
abstract public class TranslationTests extends AndroidTestCase {
|
|
|
|
// --- abstract methods
|
|
|
|
/**
|
|
* @return R.string.class
|
|
*/
|
|
public abstract Class<?> getStringResources();
|
|
|
|
/**
|
|
* @return R.array.class
|
|
*/
|
|
public abstract Class<?> getArrayResources();
|
|
|
|
/**
|
|
* @return array of fields that are parsed by SimpleDateFormat
|
|
*/
|
|
public abstract int[] getDateFormatStrings();
|
|
|
|
// --- tests
|
|
|
|
/**
|
|
* Sets locale
|
|
*/
|
|
private void setLocale(Locale locale) {
|
|
Locale.setDefault(locale);
|
|
Configuration config = new Configuration();
|
|
config.locale = locale;
|
|
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
|
|
getContext().getResources().updateConfiguration(config, metrics);
|
|
}
|
|
|
|
/**
|
|
* Loop through each locale and call runnable
|
|
*/
|
|
protected void forEachLocale(Runnable r) {
|
|
Locale[] locales = Locale.getAvailableLocales();
|
|
for(Locale locale : locales) {
|
|
setLocale(locale);
|
|
|
|
r.run();
|
|
}
|
|
}
|
|
|
|
private static final class FormatStringData {
|
|
private static final char[] scratch = new char[10];
|
|
|
|
/** format characters */
|
|
public final char[] characters;
|
|
|
|
/** the original string */
|
|
public 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 */
|
|
public 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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal test of format string parser
|
|
*/
|
|
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
|
|
*/
|
|
public void testFormatStringsMatch() throws Exception {
|
|
final Resources r = getContext().getResources();
|
|
final int[] strings = getResourceIds(getStringResources());
|
|
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
|
|
|
|
final StringBuilder failures = new StringBuilder();
|
|
|
|
for(int i = 0; i < strings.length; i++) {
|
|
try {
|
|
String string = r.getString(strings[i]);
|
|
formatStrings[i] = new FormatStringData(string);
|
|
} catch (Exception e) {
|
|
String name = r.getResourceName(strings[i]);
|
|
failures.append(String.format("error opening %s: %s\n",
|
|
name, e.getMessage()));
|
|
}
|
|
}
|
|
|
|
|
|
forEachLocale(new Runnable() {
|
|
public void run() {
|
|
Locale locale = r.getConfiguration().locale;
|
|
for(int i = 0; i < strings.length; i++) {
|
|
try {
|
|
switch(strings[i]) {
|
|
case R.string.premium_speech_bubble_2: // Special exception--this string contains a % character
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Test that date formatters parse correctly
|
|
*/
|
|
public void testDateFormats() throws Exception {
|
|
final Resources r = getContext().getResources();
|
|
|
|
final StringBuilder failures = new StringBuilder();
|
|
final int[] dateStrings = getDateFormatStrings();
|
|
final Date date = newDate();
|
|
|
|
forEachLocale(new Runnable() {
|
|
public void run() {
|
|
Locale locale = r.getConfiguration().locale;
|
|
for (int dateString : dateStrings) {
|
|
try {
|
|
String string = r.getString(dateString);
|
|
try {
|
|
new SimpleDateFormat(string).format(date);
|
|
} catch (Exception e) {
|
|
String name = r.getResourceName(dateString);
|
|
failures.append(String.format("%s: invalid format string '%s': %s\n",
|
|
locale.toString(), name, e.getMessage()));
|
|
}
|
|
} catch (Exception e) {
|
|
String name = r.getResourceName(dateString);
|
|
failures.append(String.format("%s: error opening %s: %s\n",
|
|
locale.toString(), name, e.getMessage()));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
assertEquals(failures.toString(), 0, errorCount(failures));
|
|
|
|
}
|
|
|
|
/**
|
|
* Test that there are the same number of array entries in each locale
|
|
*/
|
|
public void testArraySizesMatch() throws Exception {
|
|
final Resources r = getContext().getResources();
|
|
final int[] arrays = getResourceIds(getArrayResources());
|
|
final int[] sizes = new int[arrays.length];
|
|
final StringBuilder failures = new StringBuilder();
|
|
|
|
for(int i = 0; i < arrays.length; i++) {
|
|
try {
|
|
sizes[i] = r.getStringArray(arrays[i]).length;
|
|
} catch (Resources.NotFoundException e) {
|
|
String name = r.getResourceName(arrays[i]);
|
|
failures.append(String.format("error opening %s: %s\n",
|
|
name, e.getMessage()));
|
|
sizes[i] = -1;
|
|
}
|
|
}
|
|
|
|
forEachLocale(new Runnable() {
|
|
public void run() {
|
|
for(int i = 0; i < arrays.length; i++) {
|
|
if(sizes[i] == -1)
|
|
continue;
|
|
int size;
|
|
try {
|
|
size = r.getStringArray(arrays[i]).length;
|
|
} catch (Resources.NotFoundException e) {
|
|
String name = r.getResourceName(arrays[i]);
|
|
Locale locale = r.getConfiguration().locale;
|
|
failures.append(String.format("%s: error opening %s: %s\n",
|
|
locale, name, e.getMessage()));
|
|
continue;
|
|
}
|
|
|
|
if(size != sizes[i]) {
|
|
String name = r.getResourceName(arrays[i]);
|
|
Locale locale = r.getConfiguration().locale;
|
|
failures.append(String.format("%s (%s): size %d != %d\n",
|
|
name, locale.toString(), size, sizes[i]));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
assertEquals(failures.toString(), 0, errorCount(failures));
|
|
}
|
|
|
|
// --- helper methods
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
}
|