blob: 2aa9eee9ca2c2e4b9b197fb475fbddcd2b618cf0 [file] [log] [blame]
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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.
goog.provide('goog.net.ImageLoaderTest');
goog.setTestOnly('goog.net.ImageLoaderTest');
goog.require('goog.Timer');
goog.require('goog.array');
goog.require('goog.dispose');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.net.EventType');
goog.require('goog.net.ImageLoader');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.testing.AsyncTestCase');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(document.title);
// Set the AsyncTestCase timeout to larger value to allow more time
// for images to load.
asyncTestCase.stepTimeout = 5000;
var TEST_EVENT_TYPES = [
goog.events.EventType.LOAD,
goog.net.EventType.COMPLETE,
goog.net.EventType.ERROR
];
/**
* Mapping from test image file name to:
* [expected width, expected height, expected event to be fired].
*/
var TEST_IMAGES = {
'imageloader_testimg1.gif': [20, 20, goog.events.EventType.LOAD],
'imageloader_testimg2.gif': [20, 20, goog.events.EventType.LOAD],
'imageloader_testimg3.gif': [32, 32, goog.events.EventType.LOAD],
'this-is-not-image-1.gif': [0, 0, goog.net.EventType.ERROR],
'this-is-not-image-2.gif': [0, 0, goog.net.EventType.ERROR]
};
var startTime;
var loader;
function setUp() {
startTime = goog.now();
loader = new goog.net.ImageLoader();
// Adds test images to the loader.
var i = 0;
for (var key in TEST_IMAGES) {
var imageId = 'img_' + i++;
loader.addImage(imageId, key);
}
}
function tearDown() {
goog.dispose(loader);
}
/**
* Tests loading image and disposing before loading completes.
*/
function testDisposeInTheMiddleOfLoadingWorks() {
goog.events.listen(loader, TEST_EVENT_TYPES,
goog.partial(handleDisposalImageLoaderEvent, loader));
// waitForAsync before starting loader just in case
// handleDisposalImageLoaderEvent is called from within loader.start
// (before we yield control). This may happen in IE7/IE8.
asyncTestCase.waitForAsync('Waiting for loader handler to fire.');
loader.start();
}
function handleDisposalImageLoaderEvent(loader, e) {
assertFalse('Handler is still invoked after loader is disposed.',
loader.isDisposed());
switch (e.type) {
case goog.net.EventType.COMPLETE:
fail('This test should never get COMPLETE event.');
return;
case goog.events.EventType.LOAD:
case goog.net.EventType.ERROR:
loader.dispose();
break;
}
// Make sure that handler is never called again after disposal before
// marking test as successful.
asyncTestCase.waitForAsync('Wait to ensure that COMPLETE is never fired');
goog.Timer.callOnce(function() {
asyncTestCase.continueTesting();
}, 500);
}
/**
* Tests loading of images until completion.
*/
function testLoadingUntilCompletion() {
var results = {};
goog.events.listen(loader, TEST_EVENT_TYPES,
function(e) {
switch (e.type) {
case goog.events.EventType.LOAD:
var image = e.target;
results[image.src.substring(image.src.lastIndexOf('/') + 1)] =
[image.naturalWidth, image.naturalHeight, e.type];
return;
case goog.net.EventType.ERROR:
var image = e.target;
results[image.src.substring(image.src.lastIndexOf('/') + 1)] =
[image.naturalWidth, image.naturalHeight, e.type];
return;
case goog.net.EventType.COMPLETE:
// Test completes successfully.
asyncTestCase.continueTesting();
assertImagesAreCorrect(results);
return;
}
});
// waitForAsync before starting loader just in case handleImageLoaderEvent
// is called from within loader.start (before we yield control).
// This may happen in IE7/IE8.
asyncTestCase.waitForAsync('Waiting for loader handler to fire.');
loader.start();
}
function assertImagesAreCorrect(results) {
assertEquals(
goog.object.getCount(TEST_IMAGES), goog.object.getCount(results));
goog.object.forEach(TEST_IMAGES, function(value, key) {
// Check if fires the COMPLETE event.
assertTrue('Image is not loaded completely.', key in results);
var image = results[key];
// Check image size.
assertEquals('Image width is not correct', value[0], image[0]);
assertEquals('Image length is not correct', value[1], image[1]);
// Check if fired the correct event.
assertEquals('Event *' + value[2] + '* must be fired', value[2], image[2]);
});
}
/**
* Overrides the loader's loadImage_ method so that it dispatches an image
* loaded event immediately, causing any event listners to receive them
* synchronously. This allows tests to assume synchronous execution.
*/
function makeLoaderSynchronous(loader) {
var originalLoadImage = loader.loadImage_;
loader.loadImage_ = function(request, id) {
originalLoadImage.call(this, request, id);
var event = new goog.events.Event(goog.events.EventType.LOAD);
event.currentTarget = this.imageIdToImageMap_[id];
loader.onNetworkEvent_(event);
};
// Make listen() a no-op.
loader.handler_.listen = goog.nullFunction;
}
/**
* Verifies that if an additional image is added after start() was called, but
* before COMPLETE was dispatched, no COMPLETE event is sent. Verifies COMPLETE
* is finally sent when .start() is called again and all images have now
* completed loading.
*/
function testImagesAddedAfterStart() {
// Use synchronous image loading.
makeLoaderSynchronous(loader);
// Add another image once the first images finishes loading.
goog.events.listenOnce(loader, goog.events.EventType.LOAD, function() {
loader.addImage('extra_image', 'extra_image.gif');
});
// Keep track of the total # of image loads.
var loadRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.events.EventType.LOAD, loadRecordFn);
// Keep track of how many times COMPLETE was dispatched.
var completeRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.net.EventType.COMPLETE, completeRecordFn);
// Start testing.
loader.start();
assertEquals(
'COMPLETE event should not have been dispatched yet: An image was ' +
'added after the initial batch was started.',
0, completeRecordFn.getCallCount());
assertEquals('Just the test images should have loaded',
goog.object.getCount(TEST_IMAGES), loadRecordFn.getCallCount());
loader.start();
assertEquals('COMPLETE should have been dispatched once.',
1, completeRecordFn.getCallCount());
assertEquals('All images should have been loaded',
goog.object.getCount(TEST_IMAGES) + 1, loadRecordFn.getCallCount());
}
/**
* Verifies that more images can be added after an upload starts, and start()
* can be called for them, resulting in just one COMPLETE event once all the
* images have completed.
*/
function testImagesAddedAndStartedAfterStart() {
// Use synchronous image loading.
makeLoaderSynchronous(loader);
// Keep track of the total # of image loads.
var loadRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.events.EventType.LOAD, loadRecordFn);
// Add more images once the first images finishes loading, and call start()
// to get them going.
goog.events.listenOnce(loader, goog.events.EventType.LOAD, function(e) {
loader.addImage('extra_image', 'extra_image.gif');
loader.addImage('extra_image2', 'extra_image2.gif');
loader.start();
});
// Keep track of how many times COMPLETE was dispatched.
var completeRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.net.EventType.COMPLETE, completeRecordFn);
// Start testing. Make sure all 7 images loaded.
loader.start();
assertEquals('COMPLETE should have been dispatched once.',
1, completeRecordFn.getCallCount());
assertEquals('All images should have been loaded',
goog.object.getCount(TEST_IMAGES) + 2, loadRecordFn.getCallCount());
}
/**
* Verifies that if images are removed after loading has started, COMPLETE
* is dispatched once the remaining images have finished.
*/
function testImagesRemovedAfterStart() {
// Use synchronous image loading.
makeLoaderSynchronous(loader);
// Remove 2 images once the first image finishes loading.
goog.events.listenOnce(loader, goog.events.EventType.LOAD, function(e) {
loader.removeImage(
goog.array.peek(goog.object.getKeys(this.imageIdToRequestMap_)));
loader.removeImage(
goog.array.peek(goog.object.getKeys(this.imageIdToRequestMap_)));
});
// Keep track of the total # of image loads.
var loadRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.events.EventType.LOAD, loadRecordFn);
// Keep track of how many times COMPLETE was dispatched.
var completeRecordFn = goog.testing.recordFunction();
goog.events.listen(loader, goog.net.EventType.COMPLETE, completeRecordFn);
// Start testing. Make sure only the 3 images remaining loaded.
loader.start();
assertEquals('COMPLETE should have been dispatched once.',
1, completeRecordFn.getCallCount());
assertEquals('All images should have been loaded',
goog.object.getCount(TEST_IMAGES) - 2, loadRecordFn.getCallCount());
}
/**
* Verifies that the correct image attribute is set when using CORS requests.
*/
function testSetsCorsAttribute() {
// Use synchronous image loading.
makeLoaderSynchronous(loader);
// Verify the crossOrigin attribute of the requested images.
goog.events.listen(loader, goog.events.EventType.LOAD, function(e) {
var image = e.target;
if (image.id == 'cors_request') {
assertEquals(
'CORS requested image should have a crossOrigin attribute set',
'anonymous', image.crossOrigin);
} else {
assertTrue(
'Non-CORS requested images should not have a crossOrigin attribute',
goog.string.isEmptyOrWhitespace(goog.string.makeSafe(image.crossOrigin)));
}
});
// Make a new request for one of the images, this time using CORS.
var srcs = goog.object.getKeys(TEST_IMAGES);
loader.addImage(
'cors_request', srcs[0], goog.net.ImageLoader.CorsRequestType.ANONYMOUS);
loader.start();
}