Commit 8a07e58b authored by hirono@chromium.org's avatar hirono@chromium.org

Files.app: Add more unit tests for FileOperationManager class.

The CL adds tests for 

 * Copy
 * Delete
 * Zip

operations of the class.

BUG=315439
TEST=run the test

Review URL: https://codereview.chromium.org/460963002

Cr-Commit-Position: refs/heads/master@{#289622}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289622 0039d316-1c4b-4281-b951-d872f2087c98
parent b5ed0496
......@@ -41,33 +41,6 @@ chrome.fileBrowserPrivate = {
chrome.fileBrowserPrivate.onCopyProgress.listener_ = null;
},
listener_: null
},
startCopy: function(source, destination, newName, callback) {
var id = 1;
var events = [
'begin_copy_entry',
'progress',
'end_copy_entry',
'success'
].map(function(type) {
return [id, {type: type, sourceUrl: source, destinationUrl: destination}];
});
var sendEvent = function(index) {
// Call the function asynchronously.
return Promise.resolve().then(function() {
chrome.fileBrowserPrivate.onCopyProgress.listener_.apply(
null, events[index]);
if (index + 1 < events.length)
return sendEvent(index + 1);
else
return null;
}.bind(this));
}.bind(this);
callback(id);
sendEvent(0).catch(function(error) {
console.log(error.stack || error);
window.onerror();
});
}
};
......@@ -82,14 +55,82 @@ function reportPromise(promise, callback) {
callback.bind(null, false),
function(error) {
if (error instanceof FileOperationManager.Error) {
console.log('FileOperationManager.Error: code=' + error.code);
console.error('FileOperationManager.Error: code=' + error.code);
} else {
console.log(error.stack || error.name || error);
console.error(error.stack || error.name || error);
}
callback(true);
});
}
/**
* Size of directory.
* @type {number}
* @const
*/
var DIRECTORY_SIZE = -1;
/**
* Creates test file system.
* @param {string} id File system ID.
* @param {Object.<string, number>} entries Map of entries' paths and their
* size. If the size is equals to DIRECTORY_SIZE, the entry is derectory.
*/
function createTestFileSystem(id, entries) {
var fileSystem = new TestFileSystem(id);
for (var path in entries) {
if (entries[path] === DIRECTORY_SIZE) {
fileSystem.entries[path] = new MockDirectoryEntry(fileSystem, path);
} else {
fileSystem.entries[path] =
new MockFileEntry(fileSystem, path, {size: entries[path]});
}
}
return fileSystem;
}
/**
* Resolves URL on the file system.
* @param {FakeFileSystem} fileSystem Fake file system.
* @param {string} url URL.
* @param {function(MockEntry)} success Success callback.
* @param {function()} failure Failure callback.
*/
function resolveTestFileSystemURL(fileSystem, url, success, failure) {
for (var name in fileSystem.entries) {
var entry = fileSystem.entries[name];
if (entry.toURL() == url) {
success(entry);
return;
}
}
failure();
}
/**
* Waits for events until 'success'.
* @param {FileOperationManager} fileOperationManager File operation manager.
* @return {Promise} Promise to be fulfilled with an event list.
*/
function waitForEvents(fileOperationManager) {
return new Promise(function(fulfill) {
var events = [];
fileOperationManager.addEventListener('copy-progress', function(event) {
events.push(event);
if (event.reason === 'SUCCESS')
fulfill(events);
});
fileOperationManager.addEventListener('entry-changed', function(event) {
events.push(event);
});
fileOperationManager.addEventListener('delete', function(event) {
events.push(event);
if (event.reason === 'SUCCESS')
fulfill(events);
});
});
}
/**
* Test target.
* @type {FileOperationManager}
......@@ -109,22 +150,18 @@ function setUp() {
* error.
*/
function testResolvePath(callback) {
var fileEntry = new MockFileEntry('testVolume', '/file', {});
var directoryEntry = new MockDirectoryEntry('testVolume', '/directory', {});
var root = new MockDirectoryEntry('testVolume', '/', {
'/file': fileEntry,
'/directory': directoryEntry
var fileSystem = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/file': 10,
'/directory': DIRECTORY_SIZE
});
var root = fileSystem.root;
var rootPromise = fileOperationUtil.resolvePath(root, '/');
var filePromise = fileOperationUtil.resolvePath(root, '/file');
var directoryPromise = fileOperationUtil.resolvePath(root, '/directory');
var errorPromise = fileOperationUtil.resolvePath(root, '/not_found').then(
function() {
assertTrue(false, 'The NOT_FOUND error is not reported.');
},
function(error) {
assertEquals('NotFoundError', error.name);
});
function() { assertTrue(false, 'The NOT_FOUND error is not reported.'); },
function(error) { return error.name; });
reportPromise(Promise.all([
rootPromise,
filePromise,
......@@ -132,10 +169,10 @@ function testResolvePath(callback) {
errorPromise
]).then(function(results) {
assertArrayEquals([
root,
fileEntry,
directoryEntry,
undefined
fileSystem.entries['/'],
fileSystem.entries['/file'],
fileSystem.entries['/directory'],
'NotFoundError'
], results);
}), callback);
}
......@@ -146,39 +183,37 @@ function testResolvePath(callback) {
* error.
*/
function testDeduplicatePath(callback) {
var directoryEntry1 = new MockDirectoryEntry('testVolume', '/directory', {});
var directoryEntry2 = new MockDirectoryEntry(
'testVolume',
'/directory',
{'file.txt': new MockFileEntry('testVolume', '/file.txt', {})});
var directoryEntry3 = new MockDirectoryEntry(
'testVolume',
'/directory',
{
'file.txt': new MockFileEntry('testVolume', '/file.txt', {}),
'file (1).txt': new MockFileEntry('testVolume', '/file (1).txt', {}),
'file (2).txt': new MockFileEntry('testVolume', '/file (2).txt', {}),
'file (3).txt': new MockFileEntry('testVolume', '/file (3).txt', {}),
'file (4).txt': new MockFileEntry('testVolume', '/file (4).txt', {}),
'file (5).txt': new MockFileEntry('testVolume', '/file (5).txt', {}),
'file (6).txt': new MockFileEntry('testVolume', '/file (6).txt', {}),
'file (7).txt': new MockFileEntry('testVolume', '/file (7).txt', {}),
'file (8).txt': new MockFileEntry('testVolume', '/file (8).txt', {}),
'file (9).txt': new MockFileEntry('testVolume', '/file (9).txt', {})
});
var fileSystem1 = createTestFileSystem('testVolume', {'/': DIRECTORY_SIZE});
var fileSystem2 = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/file.txt': 10
});
var fileSystem3 = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/file.txt': 10,
'/file (1).txt': 10,
'/file (2).txt': 10,
'/file (3).txt': 10,
'/file (4).txt': 10,
'/file (5).txt': 10,
'/file (6).txt': 10,
'/file (7).txt': 10,
'/file (8).txt': 10,
'/file (9).txt': 10,
});
var nonExistingPromise =
fileOperationUtil.deduplicatePath(directoryEntry1, 'file.txt').
fileOperationUtil.deduplicatePath(fileSystem1.root, 'file.txt').
then(function(path) {
assertEquals('file.txt', path);
});
var existingPathPromise =
fileOperationUtil.deduplicatePath(directoryEntry2, 'file.txt').
fileOperationUtil.deduplicatePath(fileSystem2.root, 'file.txt').
then(function(path) {
assertEquals('file (1).txt', path);
});
var failedPromise =
fileOperationUtil.deduplicatePath(directoryEntry3, 'file.txt').
fileOperationUtil.deduplicatePath(fileSystem3.root, 'file.txt').
then(function() {
assertTrue(false, 'FileOperationManager.Error is not reported.');
}, function(error) {
......@@ -196,33 +231,36 @@ function testDeduplicatePath(callback) {
/**
* Tests the fileOperationUtil.paste.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* error.
*/
function testCopy(callback) {
// Prepare entries and their resolver.
var sourceEntries =
[new MockFileEntry('testVolume', '/test.txt', {size: 10})];
var targetEntry = new MockDirectoryEntry('testVolume', '/', {});
window.webkitResolveLocalFileSystemURL = function(url, success, failure) {
if (url === sourceEntries[0].toURL())
success(sourceEntries[0]);
else if (url === targetEntry.toURL())
success(targetEntry);
else
failure();
};
var fileSystem = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/test.txt': 10,
});
window.webkitResolveLocalFileSystemURL =
resolveTestFileSystemURL.bind(null, fileSystem);
chrome.fileBrowserPrivate.startCopy =
function(source, destination, newName, callback) {
var makeStatus = function(type) {
return {type: type, sourceUrl: source, destinationUrl: destination};
};
callback(1);
var listener = chrome.fileBrowserPrivate.onCopyProgress.listener_;
listener(1, makeStatus('begin_copy_entry'));
listener(1, makeStatus('progress'));
var newPath = joinPath('/', newName);
fileSystem.entries[newPath] =
fileSystem.entries['/test.txt'].clone(newPath);
listener(1, makeStatus('end_copy_entry'));
listener(1, makeStatus('success'));
};
// Observing manager's events.
var eventsPromise = new Promise(function(fulfill) {
var events = [];
fileOperationManager.addEventListener('copy-progress', function(event) {
events.push(event);
if (event.reason === 'SUCCESS')
fulfill(events);
});
fileOperationManager.addEventListener('entry-changed', function(event) {
events.push(event);
});
});
var eventsPromise = waitForEvents(fileOperationManager);
// Verify the events.
reportPromise(eventsPromise.then(function(events) {
......@@ -237,7 +275,159 @@ function testCopy(callback) {
assertEquals(0, lastEvent.status.numRemainingItems);
assertEquals(10, lastEvent.status.processedBytes);
assertEquals(10, lastEvent.status.totalBytes);
assertTrue(events.some(function(event) {
return event.type === 'entry-changed' &&
event.kind === util.EntryChangedKind.CREATED &&
event.entry.fullPath === '/test (1).txt';
}));
assertFalse(events.some(function(event) {
return event.type === 'delete';
}));
}), callback);
fileOperationManager.paste(
[fileSystem.entries['/test.txt']],
fileSystem.entries['/'],
false);
}
/**
* Tests the fileOperationUtil.paste for move.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* error.
*/
function testMove(callback) {
// Prepare entries and their resolver.
var fileSystem = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/directory': DIRECTORY_SIZE,
'/test.txt': 10,
});
window.webkitResolveLocalFileSystemURL =
resolveTestFileSystemURL.bind(null, fileSystem);
// Observing manager's events.
var eventsPromise = waitForEvents(fileOperationManager);
// Verify the events.
reportPromise(eventsPromise.then(function(events) {
var firstEvent = events[0];
assertEquals('BEGIN', firstEvent.reason);
assertEquals(1, firstEvent.status.numRemainingItems);
assertEquals(0, firstEvent.status.processedBytes);
assertEquals(1, firstEvent.status.totalBytes);
var lastEvent = events[events.length - 1];
assertEquals('SUCCESS', lastEvent.reason);
assertEquals(0, lastEvent.status.numRemainingItems);
assertEquals(1, lastEvent.status.processedBytes);
assertEquals(1, lastEvent.status.totalBytes);
assertTrue(events.some(function(event) {
return event.type === 'entry-changed' &&
event.kind === util.EntryChangedKind.DELETED &&
event.entry.fullPath === '/test.txt';
}));
assertTrue(events.some(function(event) {
return event.type === 'entry-changed' &&
event.kind === util.EntryChangedKind.CREATED &&
event.entry.fullPath === '/directory/test.txt';
}));
assertFalse(events.some(function(event) {
return event.type === 'delete';
}));
}), callback);
fileOperationManager.paste(
[fileSystem.entries['/test.txt']],
fileSystem.entries['/directory'],
true);
}
/**
* Tests the fileOperationUtil.deleteEntries.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* error.
*/
function testDelete(callback) {
// Prepare entries and their resolver.
var fileSystem = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/test.txt': 10,
});
window.webkitResolveLocalFileSystemURL =
resolveTestFileSystemURL.bind(null, fileSystem);
// Observing manager's events.
reportPromise(waitForEvents(fileOperationManager).then(function(events) {
assertEquals('delete', events[0].type);
assertEquals('BEGIN', events[0].reason);
assertEquals(10, events[0].totalBytes);
assertEquals(0, events[0].processedBytes);
var lastEvent = events[events.length - 1];
assertEquals('delete', lastEvent.type);
assertEquals('SUCCESS', lastEvent.reason);
assertEquals(10, lastEvent.totalBytes);
assertEquals(10, lastEvent.processedBytes);
assertFalse(events.some(function(event) {
return event.type === 'copy-progress';
}));
}), callback);
fileOperationManager.deleteEntries([fileSystem.entries['/test.txt']]);
}
/**
* Tests the fileOperationUtil.zipSelection.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* error.
*/
function testZip(callback) {
// Prepare entries and their resolver.
var fileSystem = createTestFileSystem('testVolume', {
'/': DIRECTORY_SIZE,
'/test.txt': 10,
});
window.webkitResolveLocalFileSystemURL =
resolveTestFileSystemURL.bind(null, fileSystem);
chrome.fileBrowserPrivate.zipSelection =
function(parentURL, sources, newName, success, error) {
var newPath = joinPath('/', newName);
var newEntry = new MockFileEntry(fileSystem, newPath, {size: 10});
fileSystem.entries[newPath] = newEntry;
success(newEntry);
};
// Observing manager's events.
reportPromise(waitForEvents(fileOperationManager).then(function(events) {
assertEquals('copy-progress', events[0].type);
assertEquals('BEGIN', events[0].reason);
assertEquals(1, events[0].status.totalBytes);
assertEquals(0, events[0].status.processedBytes);
var lastEvent = events[events.length - 1];
assertEquals('copy-progress', lastEvent.type);
assertEquals('SUCCESS', lastEvent.reason);
assertEquals(10, lastEvent.status.totalBytes);
assertEquals(10, lastEvent.status.processedBytes);
assertFalse(events.some(function(event) {
return event.type === 'delete';
}));
assertTrue(events.some(function(event) {
return event.type === 'entry-changed' &&
event.entry.fullPath === '/test.zip';
}));
}), callback);
fileOperationManager.paste(sourceEntries, targetEntry, false);
fileOperationManager.zipSelection(
fileSystem.entries['/'],
[fileSystem.entries['/test.txt']]);
}
......@@ -2,40 +2,130 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Joins paths so that the two paths are connected by only 1 '/'.
* @param {string} a Path.
* @param {string} b Path.
* @return {string} Joined path.
*/
function joinPath(a, b) {
return a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
};
/**
* Test file system.
*
* @param {string} fileSystemId File system ID.
* @constructor
*/
function TestFileSystem(fileSystemId) {
this.fileSystemId = fileSystemId;
this.entries = {};
};
TestFileSystem.prototype = {
get root() { return this.entries['/']; }
};
/**
* Base class of mock entries.
*
* @param {string} volumeId ID of the volume that contains the entry.
* @param {TestFileSystem} filesystem File system where the entry is localed.
* @param {string} fullpath Full path of the entry.
* @constructor
*/
function MockEntry(volumeId, fullPath) {
this.volumeId = volumeId;
function MockEntry(filesystem, fullPath) {
this.filesystem = filesystem;
this.fullPath = fullPath;
}
MockEntry.prototype = {
/**
* @return {string} Name of the entry.
*/
get name() {
return this.fullPath.replace(/^.*\//, '');
}
};
/**
* Returns fake URL.
*
* @return {string} Fake URL.
*/
MockEntry.prototype.toURL = function() {
return 'filesystem:' + this.volumeId + this.fullPath;
return 'filesystem:' + this.filesystem.fileSystemId + this.fullPath;
};
/**
* Obtains parent directory.
*
* @param {function(MockDirectoryEntry)} onSuccess Callback invoked with
* the parent directory.
* @param {function(Object)} onError Callback invoked with an error
* object.
*/
MockEntry.prototype.getParent = function(
onSuccess, onError) {
var path = this.fullPath.replace(/\/[^\/]+$/, '') || '/';
if (this.filesystem.entries[path])
onSuccess(this.filesystem.entries[path]);
else
onError({name: util.FileError.NOT_FOUND_ERR});
};
/**
* Moves the entry to the directory.
*
* @param {MockDirectoryEntry} parent Destination directory.
* @param {string=} opt_newName New name.
* @param {function(MockDirectoryEntry)} onSuccess Callback invoked with the
* moved entry.
* @param {function(Object)} onError Callback invoked with an error object.
*/
MockEntry.prototype.moveTo = function(parent, opt_newName, onSuccess, onError) {
Promise.resolve().then(function() {
this.filesystem.entries[this.fullPath] = null;
return this.clone(joinPath(parent.fullPath, opt_newName || this.name));
}.bind(this)).then(onSuccess, onError);
};
/**
* Removes the entry.
*
* @param {function()} onSuccess Success callback.
* @param {function(Object)} onError Callback invoked with an error object.
*/
MockEntry.prototype.remove = function(onSuccess, onError) {
Promise.resolve().then(function() {
this.filesystem.entries[this.fullPath] = null;
}.bind(this)).then(onSuccess, onError);
};
/**
* Clones the entry with the new fullpath.
*
* @param {string} fullpath New fullpath.
* @return {MockEntry} Cloned entry.
*/
MockEntry.prototype.clone = function(fullpath) {
throw new Error('Not implemented.');
};
/**
* Mock class for FileEntry.
*
* @param {string} volumeId Id of the volume containing the entry.
* @param {FileSystem} filesystem File system where the entry is localed.
* @param {string} fullPath Full path for the entry.
* @param {Object} metadata Metadata.
* @extends {MockEntry}
* @constructor
*/
function MockFileEntry(volumeId, fullPath, metadata) {
MockEntry.call(this, volumeId, fullPath);
this.volumeId = volumeId;
this.fullPath = fullPath;
function MockFileEntry(filesystem, fullPath, metadata) {
MockEntry.call(this, filesystem, fullPath);
this.metadata_ = metadata;
this.isFile = true;
this.isDirectory = false;
}
MockFileEntry.prototype = {
......@@ -53,41 +143,55 @@ MockFileEntry.prototype.getMetadata = function(callback) {
});
};
/**
* @override
*/
MockFileEntry.prototype.clone = function(path) {
return new MockFileEntry(this.filesystem, path, this.metadata);
};
/**
* Mock class for DirectoryEntry.
*
* @param {string} volumeId Id of the volume containing the entry.
* @param {FileSystem} filesystem File system where the entry is localed.
* @param {string} fullPath Full path for the entry.
* @param {Object.<String, MockFileEntry|MockDirectoryEntry>} contents Map of
* path and MockEntry contained in the directory.
* @extends {MockEntry}
* @constructor
*/
function MockDirectoryEntry(volumeId, fullPath, contents) {
MockEntry.call(this, volumeId, fullPath);
this.contents_ = contents;
function MockDirectoryEntry(filesystem, fullPath) {
MockEntry.call(this, filesystem, fullPath);
this.isFile = false;
this.isDirectory = true;
}
MockDirectoryEntry.prototype = {
__proto__: MockEntry.prototype
};
/**
* @override
*/
MockDirectoryEntry.prototype.clone = function(path) {
return new MockDirectoryEntry(this.filesystem, path);
};
/**
* Returns a file under the directory.
*
* @param {string} path Path.
* @param {Object} option Option.
* @param {callback(MockFileEntry)} successCallback Success callback.
* @param {callback(Object)} failureCallback Failure callback;
* @param {callback(MockFileEntry)} onSuccess Success callback.
* @param {callback(Object)} onError Failure callback;
*/
MockDirectoryEntry.prototype.getFile = function(
path, option, successCallback, failureCallback) {
if (!this.contents_[path])
failureCallback({name: util.FileError.NOT_FOUND_ERR});
else if (!(this.contents_[path] instanceof MockFileEntry))
failureCallback({name: util.FileError.TYPE_MISMATCH_ERR});
path, option, onSuccess, onError) {
var fullPath = path[0] === '/' ? path : joinPath(this.fullPath, path);
if (!this.filesystem.entries[fullPath])
onError({name: util.FileError.NOT_FOUND_ERR});
else if (!(this.filesystem.entries[fullPath] instanceof MockFileEntry))
onError({name: util.FileError.TYPE_MISMATCH_ERR});
else
successCallback(this.contents_[path]);
onSuccess(this.filesystem.entries[fullPath]);
};
/**
......@@ -95,15 +199,16 @@ MockDirectoryEntry.prototype.getFile = function(
*
* @param {string} path Path.
* @param {Object} option Option.
* @param {callback(MockDirectoryEntry)} successCallback Success callback.
* @param {callback(Object)} failureCallback Failure callback;
* @param {callback(MockDirectoryEntry)} onSuccess Success callback.
* @param {callback(Object)} onError Failure callback;
*/
MockDirectoryEntry.prototype.getDirectory =
function(path, option, successCallback, failureCallback) {
if (!this.contents_[path])
failureCallback({name: util.FileError.NOT_FOUND_ERR});
else if (!(this.contents_[path] instanceof MockDirectoryEntry))
failureCallback({name: util.FileError.TYPE_MISMATCH_ERR});
function(path, option, onSuccess, onError) {
var fullPath = path[0] === '/' ? path : joinPath(this.fullPath, path);
if (!this.filesystem.entries[fullPath])
onError({name: util.FileError.NOT_FOUND_ERR});
else if (!(this.filesystem.entries[fullPath] instanceof MockDirectoryEntry))
onError({name: util.FileError.TYPE_MISMATCH_ERR});
else
successCallback(this.contents_[path]);
onSuccess(this.filesystem.entries[fullPath]);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment