diff --git a/.gitignore b/.gitignore index c2db49228..b5365d8c1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ build/ *.iml art/ +*.actual.png diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java new file mode 100644 index 000000000..c308fe596 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.unit.views; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.helpers.ColorHelper; +import org.isoron.uhabits.views.RingView; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RingViewTest extends ViewTest +{ + @Test + public void renderTest1() throws IOException + { + RingView view = new RingView(targetContext); + view.setLabel("Hello world"); + view.setPercentage(0.6f); + view.setColor(ColorHelper.palette[0]); + view.setMaxDiameter(dpToPixels(100)); + measureView(dpToPixels(100), dpToPixels(100), view); + + assertRenders(view, "Views/RingView/renderTest1.png"); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java new file mode 100644 index 000000000..45344a560 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.unit.views; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.test.InstrumentationRegistry; +import android.view.View; + +import org.isoron.uhabits.helpers.DialogHelper; +import org.junit.Before; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static junit.framework.Assert.fail; + +public class ViewTest +{ + protected static final int SIMILARITY_CUTOFF = 6000; + + protected Context testContext; + protected Context targetContext; + + @Before + public void setup() + { + targetContext = InstrumentationRegistry.getTargetContext(); + testContext = InstrumentationRegistry.getContext(); + } + + protected void measureView(int width, int height, View view) + { + int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + + view.measure(specWidth, specHeight); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + } + + protected void assertRenders(View view, String expectedImagePath) throws IOException + { + StringBuilder errorMessage = new StringBuilder(); + + view.setDrawingCacheEnabled(true); + view.buildDrawingCache(); + + Bitmap actualBitmap = view.getDrawingCache(); + Bitmap expectedBitmap = getBitmapFromAssets(expectedImagePath); + Bitmap scaledExpectedBitmap = Bitmap.createScaledBitmap(expectedBitmap, + actualBitmap.getWidth(), actualBitmap.getHeight(), false); + + boolean similarEnough = true; + long distance; + + if ((distance = compareHistograms(getHistogram(actualBitmap), getHistogram( + scaledExpectedBitmap))) > SIMILARITY_CUTOFF) + { + similarEnough = false; + errorMessage.append(String.format( + "Rendered image has wrong histogram (distance=%d). ", + distance)); + } + + if(!similarEnough) + { + String path = saveBitmap(expectedImagePath, actualBitmap); + errorMessage.append(String.format("Actual rendered image " + "saved to %s", path)); + fail(errorMessage.toString()); + } + + actualBitmap.recycle(); + expectedBitmap.recycle(); + scaledExpectedBitmap.recycle(); + } + + private Bitmap getBitmapFromAssets(String path) throws IOException + { + InputStream stream = testContext.getAssets().open(path); + return BitmapFactory.decodeStream(stream); + } + + private String saveBitmap(String filename, Bitmap bitmap) + throws IOException + { + String absolutePath = String.format("%s/Failed/%s", targetContext.getExternalCacheDir(), + filename.replaceAll("\\.png$", ".actual.png")); + new File(absolutePath).getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(absolutePath); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + + return absolutePath; + } + + private int[][] getHistogram(Bitmap bitmap) + { + int histogram[][] = new int[4][256]; + + for(int x = 0; x < bitmap.getWidth(); x++) + { + for(int y = 0; y < bitmap.getHeight(); y++) + { + int color = bitmap.getPixel(x, y); + int[] argb = new int[]{ + (color >> 24) & 0xff, //alpha + (color >> 16) & 0xff, //red + (color >> 8) & 0xff, //green + (color ) & 0xff //blue + }; + + histogram[0][argb[0]]++; + histogram[1][argb[1]]++; + histogram[2][argb[2]]++; + histogram[3][argb[3]]++; + } + } + + return histogram; + } + + private long compareHistograms(int[][] actualHistogram, int[][] expectedHistogram) + { + long distance = 0; + + for(int i = 0; i < 255; i ++) + { + distance += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]); + distance += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]); + distance += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]); + distance += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]); + } + + return distance; + } + + protected int dpToPixels(int dp) + { + return (int) DialogHelper.dpToPixels(targetContext, dp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index a8314a453..ce02ed5f1 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -29,11 +29,11 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; +import android.util.Log; import android.view.View; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DialogHelper; import org.isoron.uhabits.R; +import org.isoron.uhabits.helpers.DialogHelper; public class RingView extends View { @@ -48,10 +48,16 @@ public class RingView extends View private int width; private int height; private float diameter; + private float maxDiameter; private float textSize; private int fadedTextColor; + public RingView(Context context) + { + super(context); + init(); + } public RingView(Context context, AttributeSet attrs) { @@ -59,25 +65,28 @@ public class RingView extends View this.label = DialogHelper.getAttribute(context, attrs, "label"); this.maxDiameter = DialogHelper.getFloatAttribute(context, attrs, "maxDiameter"); - this.maxDiameter = DialogHelper.dpToPixels(context, maxDiameter); - this.textSize = getResources().getDimension(R.dimen.smallTextSize); - this.color = ColorHelper.palette[7]; - this.percentage = 0.75f; init(); } public void setColor(int color) { this.color = color; - pRing.setColor(color); - postInvalidate(); + } + + public void setMaxDiameter(float maxDiameter) + { + this.maxDiameter = maxDiameter; + } + + public void setLabel(String label) + { + this.label = label; } public void setPercentage(float percentage) { this.percentage = percentage; - postInvalidate(); } private void init() @@ -88,6 +97,8 @@ public class RingView extends View pRing.setTextAlign(Paint.Align.CENTER); fadedTextColor = getResources().getColor(R.color.fadedTextColor); + textSize = getResources().getDimension(R.dimen.smallTextSize); + Log.d("RingView", String.format("textSize=%f", textSize)); rect = new RectF(); }