/******************************************************************************* * Copyright (c) 2013 Torkild U. Resheim. * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Torkild U. Resheim - initial API and implementation *******************************************************************************/ package no.resheim.elibrarium.epub.ui.tests; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import no.resheim.elibrarium.epub.ui.reader.EpubUiUtility; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.browser.ProgressListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; /** * This type is for testing the EPUB reader viewer, or more specifically the * Javascript code used in the browser along with how the browser widget renders * the test chapters. * <p> * Screenshots are created for each test and placed in the "screenshots" * sub-folder so that they can be inspected visually. * </p> * * @author Torkild U. Resheim */ public class EpubReaderViewerTest { /** * Action to perform when the browser is done loading content. */ private abstract class ContentLoadedAction { /** * Implement to handle activities when the page has been loaded. */ public abstract void onLoad(); public void verifyImage(Image image) { assertImageBasics(image); } } private class ContentLoader implements ProgressListener { private ContentLoadedAction action; @Override public void changed(ProgressEvent event) { // ignore } /** * This event is triggered every time a full HTML file has been loaded * into the browser component. */ @Override public void completed(ProgressEvent event) { browser.removeProgressListener(this); // Execute the action to perform when page has been loaded boolean ok; try { ok = utility.injectJavaScript(browser); Assert.assertTrue("Javascript could not be injected", ok); action.onLoad(); // Finish all UI work while (display.readAndDispatch()) { display.sleep(); } // Save the screenshot Image image = saveScreenshot(); // Verify that the basic image is OK action.verifyImage(image); shell.dispose(); } catch (IOException e) { Assert.fail(e.getMessage()); } } public void load(String url, ContentLoadedAction action) { this.action = action; browser.addProgressListener(ContentLoader.this); browser.setUrl(url); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * Save a screenshot (of the browser) for the running test. */ private Image saveScreenshot() { File dir = new File("screenshots"); if (!dir.exists()) { dir.mkdir(); } GC gc = new GC(browser); final Image image = new Image(display, shell.getSize().x, shell.getSize().y); gc.copyArea(image, 0, 0); gc.dispose(); ImageLoader il = new ImageLoader(); il.data = new ImageData[] { image.getImageData() }; String methodName = name.getMethodName(); if (methodName == null) { methodName = "nulltestname"; } String imageName = dir.getAbsolutePath() + File.separator + methodName + (imageCount > 0 ? imageCount : "") + ".png"; File imageFile = new File(imageName); if (imageFile.exists()) { imageFile.delete(); } il.save(imageName, SWT.IMAGE_PNG); imageCount++; return image; } } private static int imageCount = 0; private static boolean deleteFolder(File folder) { if (folder.isDirectory()) { String[] children = folder.list(); for (String element : children) { boolean ok = deleteFolder(new File(folder, element)); if (!ok) { return false; } } } return folder.delete(); } @BeforeClass public static void initialize() { deleteFolder(new File("screenshots")); } private Browser browser; private final Display display = Display.getDefault(); private ContentLoader loader; @Rule public TestName name = new TestName(); private final int[] pages = new int[] { 0, 2, 2 }; private final String[] ranges = new String[] { "0/1/3:0,0/1/3:10", "2/9/3:150,0/11/3:6", "0/3/13/3:3,4/13/3:7" }; private Shell shell; private final String[] texts = new String[] { "First item", "lectus.\n\t\n\tSecond", "fringilla. Donec" }; private final EpubUiUtility utility = new EpubUiUtility(); /** The colour white */ private final int WHITE = 0xFFFFFF; /** * Assert that the screenshot (of the browser) does not only contain white * pixels at the left margin. If that is the case we have a offset skew as * described in <a * href="http://github.com/turesheim/elibrarium/issues/issue/15">bug 15: * Offset skew when browsing</a> * * @param image * the image to test * @see http://github.com/turesheim/elibrarium/issues/issue/15 */ private void assertImageBasics(Image image) { boolean notWhite = false; for (int y = 0; y < browser.getSize().y; y++) { int i = image.getImageData().getPixel(0, y); if (i < WHITE) { notWhite = true; } } Assert.assertTrue("Left side of image is only white pixels. Offset skew?", notWhite); } /** * Loads content into the browser component * * @param filename * @param action * @throws IOException */ private void loadContent(final String filename, final ContentLoadedAction action) throws IOException { final File file = new File(filename); if (!file.exists()) { throw new FileNotFoundException(file.toString()); } loader.load(file.toURI().toString(), action); } @Before public void setUp() { imageCount = 0; loader = new ContentLoader(); shell = new Shell(display); shell.setSize(300, 400); shell.setLayout(new FillLayout()); browser = new Browser(shell, SWT.NONE); shell.open(); } @Test public void testAnnotations() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { // Create ranges and assert that the text of these is correct // and that they are on the correct page. for (int i = 0; i < ranges.length; i++) { int page = (int) Math.round((Double) browser.evaluate("page=markRange('" + ranges[i] + "','" + i + "');return page;")); String text = (String) browser.evaluate("return markedText;"); Assert.assertEquals(texts[i], text); Assert.assertEquals(pages[i], page); // Page 3 is the most interesting in this case EpubUiUtility.navigateToPage(browser, 3); } } }); } @Test public void testPage_1() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { EpubUiUtility.navigateToPage(browser, 1); } }); } @Test public void testPage_2() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { EpubUiUtility.navigateToPage(browser, 2); } }); } @Test public void testPage_3() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { EpubUiUtility.navigateToPage(browser, 3); } }); } @Test public void testPage_4() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { EpubUiUtility.navigateToPage(browser, 4); } }); } /** * At the given resolution the test chapter should be rendered in four * pages. * * @throws Exception */ @Test public void testRenderingBasics() throws Exception { loadContent("testdata/plain-page.xhtml", new ContentLoadedAction() { @Override public void onLoad() { int count = (int) Math.round((Double) browser.evaluate("return pageCount")); // Must be four pages Assert.assertEquals(4, count); // Page must be 300 pixels wide Assert.assertEquals(300, EpubUiUtility.getPageWidth(browser)); } }); } }