blob: a50ba297c69b4e9ab8d12e2fa81888a73d054e84 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.tests.gui.framework.fixture.layout;
import com.android.tools.idea.configurations.ConfigurationToolBar;
import com.android.tools.idea.rendering.*;
import com.android.tools.idea.rendering.multi.RenderPreview;
import com.android.tools.idea.rendering.multi.RenderPreviewManager;
import com.android.tools.idea.tests.gui.framework.GuiTests;
import com.android.tools.idea.tests.gui.framework.fixture.ToolWindowFixture;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import org.fest.swing.core.Robot;
import org.fest.swing.edt.GuiTask;
import org.fest.swing.timing.Condition;
import org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowForm;
import org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.android.tools.idea.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.timing.Pause.pause;
import static org.junit.Assert.*;
/**
* Fixture wrapping the the layout preview window
*/
public class LayoutPreviewFixture extends ToolWindowFixture implements LayoutFixture {
private final Project myProject;
public LayoutPreviewFixture(@NotNull Robot robot, @NotNull Project project) {
super("Preview", project, robot);
myProject = project;
}
@Override
@NotNull
public RenderErrorPanelFixture getRenderErrors() {
return new RenderErrorPanelFixture(myRobot, this, getContent());
}
@Override
@NotNull
public ConfigurationToolbarFixture getToolbar() {
AndroidLayoutPreviewToolWindowForm form = getContent();
ConfigurationToolBar toolbar = myRobot.finder().findByType(form.getContentPanel(), ConfigurationToolBar.class, true);
assertNotNull(toolbar);
return new ConfigurationToolbarFixture(myRobot, this, form, toolbar);
}
@NotNull
private AndroidLayoutPreviewToolWindowForm getContent() {
activate();
AndroidLayoutPreviewToolWindowForm form = getManager().getToolWindowForm();
assertNotNull(form); // because we opened the window with activate() above
return form;
}
@NotNull
private AndroidLayoutPreviewToolWindowManager getManager() {
return AndroidLayoutPreviewToolWindowManager.getInstance(myProject);
}
@NotNull
@Override
public Object waitForRenderToFinish() {
return waitForNextRenderToFinish(null);
}
/** Rendering token used by {@link #waitForRenderToFinish()} */
private Object myPreviousRender;
@Override
public void waitForNextRenderToFinish() {
myPreviousRender = waitForNextRenderToFinish(myPreviousRender);
}
@NotNull
@Override
public Object waitForNextRenderToFinish(@Nullable final Object previous) {
myRobot.waitForIdle();
pause(new Condition("Render is pending") {
@Override
public boolean test() {
AndroidLayoutPreviewToolWindowManager manager = getManager();
return !manager.isRenderPending() &&
manager.getToolWindowForm() != null &&
manager.getToolWindowForm().getLastResult() != null &&
manager.getToolWindowForm().getLastResult() != previous;
}
}, SHORT_TIMEOUT);
myRobot.waitForIdle();
Object token = getManager().getToolWindowForm().getLastResult();
assertNotNull(token);
return token;
}
@Override
public void requireRenderSuccessful() {
waitForRenderToFinish();
requireRenderSuccessful(false, false);
}
@Override
public void requireRenderSuccessful(boolean allowErrors, boolean allowWarnings) {
getRenderErrors().requireRenderSuccessful(allowErrors, allowWarnings);
}
/**
* Require that the title of the previews showing match the list of titles passed. The number of previews should match the size of list.
*/
public void requirePreviewTitles(@NotNull List<String> titles) {
RenderPreviewManager previewManager = getContent().getPreviewManager(false);
if (titles.isEmpty()) {
if (previewManager == null) {
return;
}
assertFalse(previewManager.hasPreviews());
}
assertNotNull(previewManager);
List<RenderPreview> previews = previewManager.getPreviews();
assertNotNull(previews);
List<String> previewTitles = new ArrayList<String>(titles.size());
for (RenderPreview preview : previews) {
previewTitles.add(preview.getDisplayName());
}
assertEquals(titles, previewTitles);
}
public void switchToPreview(@NotNull String displayName) {
final RenderPreviewManager previewManager = getContent().getPreviewManager(false);
assertNotNull(previewManager);
List<RenderPreview> previews = previewManager.getPreviews();
if (previews != null) {
for (final RenderPreview renderPreview : previews) {
if (displayName.equals(renderPreview.getDisplayName())) {
execute(new GuiTask() {
@Override
public void executeInEDT() {
previewManager.switchTo(renderPreview);
}
});
return;
}
}
}
fail("No preview titled " + displayName + " exists.");
}
/**
* Like {@link #requireThumbnailMatch(ImageFixture, String)} but using a default image fixture
* @param relativePath path relative to the GUI test data directory where the thumbnail should
* be located (allowed to use either / or File.separator)
* @throws IOException if there is a problem reading/writing thumbnails
*/
public void requireThumbnailMatch(@NotNull String relativePath) throws IOException {
requireThumbnailMatch(new ImageFixture(), relativePath, true);
}
/**
* Checks that the current rendering matches a given pre-recorded thumbnail. When you are writing
* a text you don't typically have this thumbnail; run the test once, which will then generate
* the thumbnail you can then copy into the data directory. (If you set the environment variable
* pointed to by {@link GuiTests#AOSP_SOURCE_PATH} the file will be created directly into your
* source tree).
* <p>
* NOTE: This will test the full painting of the result (e.g. the scaled view with device frame
* overlays etc; it's painting the actual scaled {@link com.android.tools.idea.rendering.RenderedImage},
* not just the layoutlib {@link java.awt.image.BufferedImage}. This helps test what the user would
* actually see. But the actual size of that window may depend on the display size on the test machine,
* so if layout zooming is turned off, there could be varying amounts of whitespace around the rendering
* which could lead to thumbnail differences.
* <p>
* If the thumbnails do not match, a "diff" image will be created which shows the two thumbnails and
* the delta between them.
*
* @param imageFixture the specific image fixture to use
* @param relativePath path relative to the GUI test data directory where the thumbnail should
* be located (allowed to use either / or File.separator)
* @param includeOverlays whether overlays (selection highlighting, include masks etc) should be painted as well
* @throws IOException if there is a problem reading/writing thumbnails
*/
public void requireThumbnailMatch(@NotNull ImageFixture imageFixture, @NotNull String relativePath, boolean includeOverlays)
throws IOException {
waitForRenderToFinish();
RenderResult lastResult = getContent().getLastResult();
assertNotNull("No render result available", lastResult);
RenderedImage image = lastResult.getImage();
assertNotNull("No rendered image available", image);
// Paint the image to the same scaled size that it is currently used
Dimension scaledSize = image.getScaledSize();
@SuppressWarnings("UndesirableClassUsage")
BufferedImage painted = new BufferedImage(scaledSize.width, scaledSize.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = painted.createGraphics();
image.paint(g2, 0, 0);
if (includeOverlays) {
getContent().getPreviewPanel().paintOverlays(g2);
}
g2.dispose();
// Strip out whitespace so we get the maximum number of useful pixels in the scaled bitmap
BufferedImage cropped = ImageUtils.cropBlank(painted, null);
if (cropped == null) {
cropped = painted; // blank image to begin with
}
imageFixture.requireSimilar(relativePath, cropped);
}
@NotNull
public LayoutWidgetFixture find(@NotNull TagMatcher matcher) {
List<LayoutWidgetFixture> all = findAll(matcher);
assertTrue("Expected to find exactly one match, but found " + all.size() + ": " + all, all.size() == 1);
return all.get(0);
}
@NotNull
public List<LayoutWidgetFixture> findAll(@NotNull TagMatcher matcher) {
waitForRenderToFinish();
RenderResult lastResult = getContent().getLastResult();
assertNotNull("No render result available", lastResult);
List<LayoutWidgetFixture> result = Lists.newArrayList();
RenderedViewHierarchy hierarchy = lastResult.getHierarchy();
assertNotNull("No view hierarchy", hierarchy);
for (RenderedView view : hierarchy.getRoots()) {
addMatching(view, matcher, result);
}
return result;
}
private void addMatching(@NotNull RenderedView view, @NotNull TagMatcher matcher, @NotNull List<LayoutWidgetFixture> result) {
if (view.tag != null && matcher.isMatching(view.tag)) {
result.add(new LayoutWidgetFixture(this, view));
}
for (RenderedView child : view.getChildren()) {
addMatching(child, matcher, result);
}
}
}