/*
 * Copyright 2008 Google Inc.
 *
 * 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.google.gwt.user.client.ui;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.junit.client.WithProperties;
import com.google.gwt.junit.client.WithProperties.Property;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.impl.ImageResourcePrototype;
import com.google.gwt.user.client.Timer;

/**
 * Tests for the Image widget. Images in both clipped mode and unclipped mode
 * are tested, along with the transitions between the two modes.
 */
public class ImageTest extends GWTTestCase {
  interface Bundle extends ClientBundle {
    ImageResource prettyPiccy();

    @Source("prettyPiccy.png")
    @ImageOptions(preventInlining = true)
    ImageResource prettyPiccyStandalone();
  }

  private static class TestErrorHandler implements ErrorHandler {
    private Image image;

    public TestErrorHandler(Image image) {
      this.image = image;
    }

    @Override
    public void onError(ErrorEvent event) {
      fail("The image " + image.getUrl() + " failed to load.");
    }
  }

  private static class TestImage extends Image {
    public TestImage(Element element) {
      super(element);
    }

    public static TestImage wrap(Element element) {
      // Assert that the element is attached.
      assert Document.get().getBody().isOrHasChild(element);

      TestImage image = new TestImage(element);

      // Mark it attached and remember it for cleanup.
      image.onAttach();
      RootPanel.detachOnWindowClose(image);

      return image;
    }
  }
  
  private abstract static class TestLoadHandler implements LoadHandler {
    private boolean finished = false;

    /**
     * Mark the test as finished.
     */
    public void finish() {
      finished = true;
    }

    /**
     * Returns true if the test has finished.
     */
    public boolean isFinished() {
      return finished;
    }
  }

  @Deprecated
  private abstract static class TestLoadListener implements LoadListener {
    private boolean finished = false;
    private Image image;

    public TestLoadListener(Image image) {
      this.image = image;
    }

    /**
     * Mark the test as finished.
     */
    public void finish() {
      finished = true;
    }

    /**
     * Returns true if the test has finished.
     */
    public boolean isFinished() {
      return finished;
    }

    @Override
    public void onError(Widget sender) {
      fail("The image " + image.getUrl() + " failed to load.");
    }
  }

  /**
   * The default timeout of asynchronous tests. This should be larger than
   * LOAD_EVENT_TIMEOUT and SYNTHETIC_LOAD_EVENT_TIMEOUT.
   */
  private static final int DEFAULT_TEST_TIMEOUT = 10000;

  /**
   * The amount of time to wait for a load event to fire in milliseconds.
   */
  private static final int LOAD_EVENT_TIMEOUT = 7000;

  /**
   * The amount of time to wait for a clipped image to fire a synthetic load
   * event in milliseconds.
   */
  private static final int SYNTHETIC_LOAD_EVENT_TIMEOUT = 1000;

  /**
   * Helper method that allows us to 'peek' at the private <code>state</code>
   * field in the Image object, and call the <code>state.getStateName()</code>
   * method.
   *
   * @param image The image instance
   * @return "unclipped" if image is in the unclipped state, or "clipped" if the
   *         image is in the clipped state
   */
  public static native String getCurrentImageStateName(Image image) /*-{
    var imgState = image.@com.google.gwt.user.client.ui.Image::state;
    return imgState.@com.google.gwt.user.client.ui.Image.State::getStateName() ();
  }-*/;

  private int firedError;

  private int firedLoad;

  @Override
  public String getModuleName() {
    return "com.google.gwt.user.UserTest";
  }

  public void testAltText() {
    final String altText = "this is an image";
    final Image image = new Image("image.png", 12, 12, 12, 12);

    assertEquals("", image.getAltText());
    image.setAltText(altText);
    assertEquals(altText, image.getAltText());
    image.setAltText("");
    assertEquals("", image.getAltText());
  }

  /**
   * Test that attaching and immediately detaching an element does not cause an
   * error.
   */
  public void testAttachDetach() {

    final Image image = new Image("counting-forwards.png");
    RootPanel.get().add(image);
    RootPanel.get().remove(image);

    // Wait for the synthetic event to attempt to fire.
    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        // The synthetic event did not cause an error.
        finishTest();
      }
    }.schedule(SYNTHETIC_LOAD_EVENT_TIMEOUT);
  }

  /**
   * Tests the transition from the clipped state to the unclipped state.
   */
  @DoNotRunWith(Platform.HtmlUnitUnknown)
  public void testChangeClippedImageToUnclipped() {
    final Image image = new Image("counting-forwards.png", 12, 13, 8, 8);
    assertEquals("clipped", getCurrentImageStateName(image));

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        ++onLoadEventCount;
        if (onLoadEventCount == 1) { // Set the url after the first image loads
          image.setUrl("counting-forwards.png");
        } else if (onLoadEventCount == 2) {
          assertEquals(0, image.getOriginLeft());
          assertEquals(0, image.getOriginTop());
          assertEquals(32, image.getWidth());
          assertEquals(32, image.getHeight());
          assertEquals("unclipped", getCurrentImageStateName(image));
          finishTest();
        }
      }
    });

    RootPanel.get().add(image);
  }

  /**
   * Tests the transition from the unclipped state to the clipped state.
   */
  @DoNotRunWith(Platform.HtmlUnitUnknown)
  public void testChangeImageToClipped() {
    final Image image = new Image("counting-forwards.png");
    assertEquals("unclipped", getCurrentImageStateName(image));

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (getCurrentImageStateName(image).equals("unclipped")) {
          image.setVisibleRect(12, 13, 8, 8);
        }

        if (++onLoadEventCount == 2) {
          assertEquals(12, image.getOriginLeft());
          assertEquals(13, image.getOriginTop());
          assertEquals(8, image.getWidth());
          assertEquals(8, image.getHeight());
          assertEquals("clipped", getCurrentImageStateName(image));
          finishTest();
        }
      }
    });

    RootPanel.get().add(image);
  }

  /**
   * Tests the transition from the unclipped state to the clipped state
   * before a load event fires.
   */
  @DoNotRunWith(Platform.HtmlUnitUnknown)
  public void testChangeImageToClippedSynchronously() {

    final Image image = new Image("counting-forwards.png");
    assertEquals("unclipped", getCurrentImageStateName(image));

    image.addErrorHandler(new TestErrorHandler(image));
    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired twice. Expected it to fire only once.");
        }

        assertEquals("clipped", getCurrentImageStateName(image));
        assertEquals(12, image.getOriginLeft());
        assertEquals(13, image.getOriginTop());
        assertEquals(8, image.getWidth());
        assertEquals(8, image.getHeight());
        finish();
      }
    };
    image.addLoadHandler(loadHandler);

    /*
     * Change the image to a clipped image before a load event fires. We only
     * expect one asynchronous load event to fire for the final state. This is
     * consistent with the expected behavior of changing the source URL multiple
     * times.
     */
    RootPanel.get().add(image);
    image.setVisibleRect(12, 13, 8, 8);
    assertEquals("clipped", getCurrentImageStateName(image));

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(SYNTHETIC_LOAD_EVENT_TIMEOUT);
  }

  /**
   * Tests the creation of an image in unclipped mode.
   */
  public void testCreateImage() {
    final Image image = new Image("counting-forwards.png");

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (++onLoadEventCount == 1) {
          assertEquals(32, image.getWidth());
          assertEquals(32, image.getHeight());
          finishTest();
        }
      }
    });

    RootPanel.get().add(image);
    assertEquals(0, image.getOriginLeft());
    assertEquals(0, image.getOriginTop());
    assertEquals("unclipped", getCurrentImageStateName(image));
  }

  /**
   * Tests the creation of an image that does not exist.
   */
  @DoNotRunWith(Platform.HtmlUnitUnknown)
  public void testCreateImageWithError() {
    final Image image = new Image("imageDoesNotExist.png");

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new ErrorHandler() {
      @Override
      public void onError(ErrorEvent event) {
        finishTest();
      }
    });
    image.addLoadHandler(new LoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        fail("The image " + image.getUrl() + " should have failed to load.");
      }
    });

    RootPanel.get().add(image);
  }

  /**
   * Tests the firing of onload events when
   * {@link com.google.gwt.user.client.ui.Image#setUrl(String)} is called on an
   * unclipped image.
   */
  public void testSetUrlAndLoadEventsOnUnclippedImage() {
    final Image image = new Image();

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (++onLoadEventCount == 2) {
          finishTest();
        } else {
          image.setUrl("counting-forwards.png");
        }
      }
    });

    RootPanel.get().add(image);
    image.setUrl("counting-backwards.png");
  }

  /**
   * Tests the behavior of
   * <code>setUrlAndVisibleRect(String, int, int, int, int)</code> method on an
   * unclipped image, which causes a state transition to the clipped state.
   */
  @DoNotRunWith(Platform.HtmlUnitUnknown)
  public void testSetUrlAndVisibleRectOnUnclippedImage() {
    final Image image = new Image("counting-backwards.png");
    assertEquals("unclipped", getCurrentImageStateName(image));

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (getCurrentImageStateName(image).equals("unclipped")) {
          image.setUrlAndVisibleRect("counting-forwards.png", 0, 16, 16, 16);
        }

        if (++onLoadEventCount == 2) {
          assertEquals(0, image.getOriginLeft());
          assertEquals(16, image.getOriginTop());
          assertEquals(16, image.getWidth());
          assertEquals(16, image.getHeight());
          assertEquals("clipped", getCurrentImageStateName(image));
          finishTest();
        }
      }
    });

    RootPanel.get().add(image);
  }

  /**
   * Tests the creation of an image in clipped mode.
   */
  @SuppressWarnings("deprecation")
  public void testCreateClippedImage() {
    final Image image = new Image("counting-forwards.png", 16, 16, 16, 16);

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    final TestLoadListener listener = new TestLoadListener(image) {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(Widget sender) {
        if (++onLoadEventCount == 1) {
          assertEquals(16, image.getWidth());
          assertEquals(16, image.getHeight());
          finish();
        }
      }
    };
    image.addLoadListener(listener);

    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (++onLoadEventCount == 1) {
          assertEquals(16, image.getWidth());
          assertEquals(16, image.getHeight());
          if (listener.isFinished()) {
            finishTest();
          } else {
            fail("Listener did not fire first");
          }
        }
      }
    });
    image.addErrorHandler(new TestErrorHandler(image));

    RootPanel.get().add(image);
    assertEquals(16, image.getOriginLeft());
    assertEquals(16, image.getOriginTop());
    assertEquals("clipped", getCurrentImageStateName(image));
  }

  @SuppressWarnings("deprecation")
  public void testLoadListenerWiring() {
    Image im = new Image();

    im.addLoadListener(new LoadListener() {

      @Override
      public void onError(Widget sender) {
        ++firedError;
      }

      @Override
      public void onLoad(Widget sender) {
        ++firedLoad;
      }
    });
    im.fireEvent(new LoadEvent() {
      // Replaced by Joel's event firing when possible.
    });
    assertEquals(1, firedLoad);
    assertEquals(0, firedError);
    im.fireEvent(new ErrorEvent() {
      // Replaced by Joel's event firing when possible.
    });
    assertEquals(1, firedLoad);
    assertEquals(1, firedError);
  }

  /**
   * Test that attaching an image multiple times results in only one load event.
   */
  public void testMultipleAttach() {
    final Image image = new Image("counting-forwards.png");

    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired multiple times.");
        }
        finish();
      }
    };
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(loadHandler);

    RootPanel.get().add(image);
    RootPanel.get().remove(image);
    RootPanel.get().add(image);
    RootPanel.get().remove(image);
    RootPanel.get().add(image);
    RootPanel.get().remove(image);
    RootPanel.get().add(image);

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(LOAD_EVENT_TIMEOUT);
  }

  /**
   * Verify that detaching and reattaching an image in a handler does not fire a
   * second onload event.
   */
  public void testNoEventOnReattachInHandler() {
    final Image image = new Image("counting-forwards.png");

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(new LoadHandler() {
      private int onLoadEventCount = 0;

      @Override
      public void onLoad(LoadEvent event) {
        if (++onLoadEventCount == 1) {
          RootPanel.get().remove(image);
          RootPanel.get().add(image);
          // The extra onLoad would will fire synchronously before finishTest().
          finishTest();
        } else {
          fail("onLoad fired on reattach.");
        }
      }
    });

    RootPanel.get().add(image);
  }

  public void testOneEventOnly() {
    final Image image = new Image("counting-forwards.png");

    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired multiple times.");
        }
        finish();
      }
    };
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(loadHandler);

    RootPanel.get().add(image);
    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(LOAD_EVENT_TIMEOUT);
  }

  public void testOneEventOnlyClippedImage() {
    final Image image = new Image("counting-forwards.png", 12, 13, 8, 8);

    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired multiple times.");
        }
        finish();
      }
    };
    image.addErrorHandler(new TestErrorHandler(image));
    image.addLoadHandler(loadHandler);

    RootPanel.get().add(image);
    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(LOAD_EVENT_TIMEOUT);
   }

  @WithProperties({
    @Property(name = "ClientBundle.enableInlining", value = "false")
  })
  public void testResourceConstructor() {
    Bundle b = GWT.create(Bundle.class);
    Image image = new Image(b.prettyPiccy());
    assertResourceWorked(image, b.prettyPiccy());
    assertTrue(b.prettyPiccy() instanceof ImageResourcePrototype.Bundle);
    assertEquals("clipped", getCurrentImageStateName(image));
  }

  @WithProperties({
    @Property(name = "ClientBundle.enableInlining", value = "false")
  })
  public void testSetResource() {
    Bundle b = GWT.create(Bundle.class);
    Image image = new Image();
    image.setResource(b.prettyPiccy());
    assertResourceWorked(image, b.prettyPiccy());

    assertTrue(b.prettyPiccy() instanceof ImageResourcePrototype.Bundle);
    assertEquals("clipped", getCurrentImageStateName(image));
  }

  public void testStandaloneResourceConstructor() {
    Bundle b = GWT.create(Bundle.class);
    Image image = new Image(b.prettyPiccyStandalone());
    assertResourceWorked(image, b.prettyPiccyStandalone());

    assertFalse(b.prettyPiccyStandalone() instanceof ImageResourcePrototype.Bundle);
    assertEquals("unclipped", getCurrentImageStateName(image));
  }

  public void testStandaloneSetResource() {
    Bundle b = GWT.create(Bundle.class);
    Image image = new Image();
    image.setResource(b.prettyPiccyStandalone());
    assertResourceWorked(image, b.prettyPiccyStandalone());

    assertFalse(b.prettyPiccyStandalone() instanceof ImageResourcePrototype.Bundle);
    assertEquals("unclipped", getCurrentImageStateName(image));
  }

  /**
   * Tests the behavior of
   * {@link com.google.gwt.user.client.ui.Image#setUrlAndVisibleRect(String,int,int,int,int)}
   * on a clipped image.
   */
  @SuppressWarnings("deprecation")
  public void testSetUrlAndVisibleRectOnClippedImage() {
    final Image image = new Image("counting-backwards.png", 12, 12, 12, 12);

    final TestLoadListener listener = new TestLoadListener(image) {
      @Override
      public void onLoad(Widget sender) {
        if (isFinished()) {
          fail("LoadListener fired twice. Expected it to fire only once.");
        }

        assertEquals(0, image.getOriginLeft());
        assertEquals(16, image.getOriginTop());
        assertEquals(16, image.getWidth());
        assertEquals(16, image.getHeight());
        assertEquals("clipped", getCurrentImageStateName(image));
        finish();
      }
    };
    image.addLoadListener(listener);

    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired twice. Expected it to fire only once.");
        }
        if (listener.isFinished()) {
          finish();
        } else {
          fail("Listener did not fire first");
        }
      }
    };
    image.addLoadHandler(loadHandler);
    image.addErrorHandler(new TestErrorHandler(image));

    RootPanel.get().add(image);
    assertEquals("clipped", getCurrentImageStateName(image));

    /*
     * Change the url and visible rect, but we only expect one asynchronous load
     * event to fire. This is consistent with the expected behavior of changing
     * the source URL in the same event loop.
     */
    image.setUrlAndVisibleRect("counting-forwards.png", 0, 16, 16, 16);

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(SYNTHETIC_LOAD_EVENT_TIMEOUT);
  }

  /**
   * Tests the firing of onload events when calling
   * {@link com.google.gwt.user.client.ui.Image#setVisibleRect(int,int,int,int)}
   * on a clipped image.
   */
  @SuppressWarnings("deprecation")
  public void testSetVisibleRectAndLoadEventsOnClippedImage() {
    final Image image = new Image("counting-backwards.png", 16, 16, 16, 16);

    final TestLoadListener listener = new TestLoadListener(image) {
      @Override
      public void onLoad(Widget sender) {
        if (isFinished()) {
          fail("LoadListener fired twice. Expected it to fire only once.");
        }
        finish();
      }
    };
    image.addLoadListener(listener);

    final TestLoadHandler loadHandler = new TestLoadHandler() {
      @Override
      public void onLoad(LoadEvent event) {
        if (isFinished()) {
          fail("LoadHandler fired twice. Expected it to fire only once.");
        }
        if (listener.isFinished()) {
          finish();
        } else {
          fail("Listener did not fire first");
        }
      }
    };
    image.addLoadHandler(loadHandler);
    image.addErrorHandler(new TestErrorHandler(image));

    RootPanel.get().add(image);

    /*
     * Change the visible rect multiple times, but we only expect one
     * asynchronous load event to fire after the final change. This is consistent
     * with the expected behavior of changing the source URL multiple times.
     */
    image.setVisibleRect(0, 0, 16, 16);
    image.setVisibleRect(0, 0, 16, 16);
    image.setVisibleRect(16, 0, 16, 16);
    image.setVisibleRect(16, 8, 8, 8);

    delayTestFinish(DEFAULT_TEST_TIMEOUT);
    new Timer() {
      @Override
      public void run() {
        assertTrue(loadHandler.isFinished());
        finishTest();
      }
    }.schedule(SYNTHETIC_LOAD_EVENT_TIMEOUT);
  }

  /**
   * Tests that it is possible to make a subclass of Image that can be wrapped.
   */
  public void testWrapOfSubclass() {
    String uid = Document.get().createUniqueId();
    DivElement div = Document.get().createDivElement();
    div.setInnerHTML("<img id='" + uid + "' src='counting-forwards.png'>");
    Document.get().getBody().appendChild(div);

    final TestImage image = TestImage.wrap(Document.get().getElementById(uid));
    assertNotNull(image);

    // Cleanup.
    Document.get().getBody().removeChild(div);
    RootPanel.detachNow(image);
  }

  /**
   * Same test, but don't remove from the dom first
   */
  public void testWrapOfSubclassWithoutRemove() {
    String uid = Document.get().createUniqueId();
    DivElement div = Document.get().createDivElement();
    div.setInnerHTML("<img id='" + uid + "' src='counting-forwards.png'>");
    Document.get().getBody().appendChild(div);

    final TestImage image = TestImage.wrap(Document.get().getElementById(uid));
    assertNotNull(image);

    // Cleanup.
    RootPanel.detachNow(image);
  }

  /**
   * Same test, but with removeFromParent instead of detachNow
   */
  public void testRemoveFromParent() {
    String uid = Document.get().createUniqueId();
    DivElement div = Document.get().createDivElement();
    div.setInnerHTML("<img id='" + uid + "' src='counting-forwards.png'>");
    Document.get().getBody().appendChild(div);

    final TestImage image = TestImage.wrap(Document.get().getElementById(uid));
    assertNotNull(image);

    // Cleanup.
    Document.get().getBody().removeChild(div);
    image.removeFromParent();
  }

  /**
   * Same test, but with removeFromParent isntead of detachNow, and don't remove the dom manually
   * first.
   */
  public void testRemoveFromParentWithoutRemove() {
    String uid = Document.get().createUniqueId();
    DivElement div = Document.get().createDivElement();
    div.setInnerHTML("<img id='" + uid + "' src='counting-forwards.png'>");
    Document.get().getBody().appendChild(div);

    final TestImage image = TestImage.wrap(Document.get().getElementById(uid));
    assertNotNull(image);

    // Cleanup.
    image.removeFromParent();
  }

  /**
   * Tests that wrapping an existing DOM element works if you call
   * setUrlAndVisibleRect() on it.
   */
  public void testWrapThenSetUrlAndVisibleRect() {
    String uid = Document.get().createUniqueId();
    DivElement div = Document.get().createDivElement();
    div.setInnerHTML("<img id='" + uid
        + "' src='counting-backwards.png' width='16' height='16'>");
    Document.get().getBody().appendChild(div);
    final Image image = Image.wrap(Document.get().getElementById(uid));

    assertEquals(0, image.getOriginLeft());
    assertEquals(0, image.getOriginTop());
    assertEquals(16, image.getWidth());
    assertEquals(16, image.getHeight());
    assertEquals("unclipped", getCurrentImageStateName(image));

    image.setUrlAndVisibleRect("counting-forwards.png", 0, 16, 16, 16);
    assertEquals(0, image.getOriginLeft());
    assertEquals(16, image.getOriginTop());
    assertEquals(16, image.getWidth());
    assertEquals(16, image.getHeight());
    assertEquals("clipped", getCurrentImageStateName(image));
  }

  private void assertResourceWorked(Image image, ImageResource prettyPiccy) {
    assertEquals(prettyPiccy.getSafeUri().asString(), image.getUrl());
    assertEquals(prettyPiccy.getTop(), image.getOriginTop());
    assertEquals(prettyPiccy.getHeight(), image.getHeight());
    assertEquals(prettyPiccy.getLeft(), image.getOriginLeft());
    assertEquals(prettyPiccy.getWidth(), image.getWidth());
  }
}
