Commit 1ec41961 authored by hirono@chromium.org's avatar hirono@chromium.org

Add unit tests for utility functions of FileOperationManager.

BUG=315439
TEST=run the js tests.
R=yoshiki@chromium.org

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288015 0039d316-1c4b-4281-b951-d872f2087c98
parent a4453a44
......@@ -55,3 +55,8 @@ IN_PROC_BROWSER_TEST_F(
FileManagerJsTest, MetadataCacheTest) {
RunTest(base::FilePath(FILE_PATH_LITERAL("metadata_cache_unittest.html")));
}
IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileOperationManagerTest) {
RunTest(base::FilePath(
FILE_PATH_LITERAL("file_operation_manager_unittest.html")));
}
<!DOCTYPE html>
<!-- Copyright 2014 The Chromium Authors. All rights reserved.
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.
-->
<html>
<body>
<script src="../../../../../ui/webui/resources/js/cr.js"></script>
<script src="../../../../../ui/webui/resources/js/cr/event_target.js"></script>
<script src="../../../../../ui/file_manager/file_manager/common/js/util.js"></script>
<script src="../../../../../ui/file_manager/file_manager/common/js/async_util.js"></script>
<script src="../../../../../ui/file_manager/file_manager/background/js/file_operation_manager.js"></script>
<script src="mocks/mock_entry.js"></script>
<script src="file_operation_manager_unittest.js"></script>
</body>
</html>
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Reports the result of promise to the test system.
* @param {Promise} promise Promise to be fulfilled or rejected.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* error.
*/
function reportPromise(promise, callback) {
promise.then(
callback.bind(null, false),
function(error) {
if (error instanceof FileOperationManager.Error) {
console.log('FileOperationManager.Error: code=' + error.code);
} else {
console.log(error.stack || error.name || error);
}
callback(true);
});
}
/**
* Tests the fileOperationUtil.resolvePath function.
* @param {function(boolean:hasError)} callback Callback to be passed true on
* 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 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);
});
reportPromise(Promise.all([
rootPromise,
filePromise,
directoryPromise,
errorPromise
]).then(function(results) {
assertArrayEquals([
root,
fileEntry,
directoryEntry,
undefined
], results);
}), callback);
}
/**
* Tests the fileOperationUtil.deduplicatePath
* @param {function(boolean:hasError)} callback Callback to be passed true on
* 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 nonExistingPromise =
fileOperationUtil.deduplicatePath(directoryEntry1, 'file.txt').
then(function(path) {
assertEquals('file.txt', path);
});
var existingPathPromise =
fileOperationUtil.deduplicatePath(directoryEntry2, 'file.txt').
then(function(path) {
assertEquals('file (1).txt', path);
});
var failedPromise =
fileOperationUtil.deduplicatePath(directoryEntry3, 'file.txt').
then(function() {
assertTrue(false, 'FileOperationManager.Error is not reported.');
}, function(error) {
assertTrue(error instanceof FileOperationManager.Error);
assertEquals(util.FileOperationErrorType.TARGET_EXISTS, error.code);
});
var testPromise = Promise.all([
nonExistingPromise,
existingPathPromise,
failedPromise
]);
reportPromise(testPromise, callback);
}
......@@ -10,40 +10,102 @@
var fileOperationUtil = {};
/**
* Simple wrapper for util.deduplicatePath. On error, this method translates
* the FileError to FileOperationManager.Error object.
* Resolves a path to either a DirectoryEntry or a FileEntry, regardless of
* whether the path is a directory or file.
*
* @param {DirectoryEntry} root The root of the filesystem to search.
* @param {string} path The path to be resolved.
* @return {Promise} Promise fulfilled with the resolved entry, or rejected with
* FileError.
*/
fileOperationUtil.resolvePath = function(root, path) {
if (path === '' || path === '/')
return Promise.resolve(root);
return new Promise(root.getFile.bind(root, path, {create: false})).
catch(function(error) {
if (error.name === util.FileError.TYPE_MISMATCH_ERR) {
// Bah. It's a directory, ask again.
return new Promise(
root.getDirectory.bind(root, path, {create: false}));
} else {
return Promise.reject(error);
}
});
};
/**
* Checks if an entry exists at |relativePath| in |dirEntry|.
* If exists, tries to deduplicate the path by inserting parenthesized number,
* such as " (1)", before the extension. If it still exists, tries the
* deduplication again by increasing the number up to 10 times.
* For example, suppose "file.txt" is given, "file.txt", "file (1).txt",
* "file (2).txt", ..., "file (9).txt" will be tried.
*
* @param {DirectoryEntry} dirEntry The target directory entry.
* @param {string} relativePath The path to be deduplicated.
* @param {function(string)} successCallback Callback run with the deduplicated
* path on success.
* @param {function(FileOperationManager.Error)} errorCallback Callback run on
* error.
* @param {function(string)=} opt_successCallback Callback run with the
* deduplicated path on success.
* @param {function(FileOperationManager.Error)=} opt_errorCallback Callback run
* on error.
*/
fileOperationUtil.deduplicatePath = function(
dirEntry, relativePath, successCallback, errorCallback) {
util.deduplicatePath(
dirEntry, relativePath, successCallback,
function(err) {
var onFileSystemError = function(error) {
errorCallback(new FileOperationManager.Error(
util.FileOperationErrorType.FILESYSTEM_ERROR, error));
};
if (err.name == util.FileError.PATH_EXISTS_ERR) {
// Failed to uniquify the file path. There should be an existing
// entry, so return the error with it.
util.resolvePath(
dirEntry, relativePath,
function(entry) {
errorCallback(new FileOperationManager.Error(
util.FileOperationErrorType.TARGET_EXISTS, entry));
},
onFileSystemError);
return;
}
onFileSystemError(err);
});
dirEntry, relativePath, opt_successCallback, opt_errorCallback) {
// The trial is up to 10.
var MAX_RETRY = 10;
// Crack the path into three part. The parenthesized number (if exists) will
// be replaced by incremented number for retry. For example, suppose
// |relativePath| is "file (10).txt", the second check path will be
// "file (11).txt".
var match = /^(.*?)(?: \((\d+)\))?(\.[^.]*?)?$/.exec(relativePath);
var prefix = match[1];
var ext = match[3] || '';
// Check to see if the target exists.
var resolvePath = function(trialPath, numRetry, copyNumber) {
return fileOperationUtil.resolvePath(dirEntry, trialPath).then(function() {
if (numRetry <= 1) {
// Hit the limit of the number of retrial.
// Note that we cannot create FileError object directly, so here we
// use Object.create instead.
return Promise.reject(
util.createDOMError(util.FileError.PATH_EXISTS_ERR));
}
var newTrialPath = prefix + ' (' + copyNumber + ')' + ext;
return resolvePath(newTrialPath, numRetry - 1, copyNumber + 1);
}, function(error) {
// We expect to be unable to resolve the target file, since we're
// going to create it during the copy. However, if the resolve fails
// with anything other than NOT_FOUND, that's trouble.
if (error.name === util.FileError.NOT_FOUND_ERR)
return trialPath;
else
return Promise.reject(error);
});
};
var promise = resolvePath(relativePath, MAX_RETRY, 1).catch(function(error) {
var targetPromise;
if (error.name === util.FileError.PATH_EXISTS_ERR) {
// Failed to uniquify the file path. There should be an existing
// entry, so return the error with it.
targetPromise = fileOperationUtil.resolvePath(dirEntry, relativePath);
} else {
targetPromise = Promise.reject(error);
}
return targetPromise.then(function(entry) {
return Promise.reject(new FileOperationManager.Error(
util.FileOperationErrorType.TARGET_EXISTS, entry));
}, function(inError) {
if (inError instanceof Error)
return Promise.reject(inError);
return Promise.reject(new FileOperationManager.Error(
util.FileOperationErrorType.FILESYSTEM_ERROR, inError));
});
});
if (opt_successCallback)
promise.then(opt_successCallback, opt_errorCallback);
return promise;
};
/**
......@@ -530,13 +592,13 @@ FileOperationManager.CopyTask = function(sourceEntries,
targetDirEntry);
this.deleteAfterCopy = deleteAfterCopy;
/*
/**
* Rate limiter which is used to avoid sending update request for progress bar
* too frequently.
* @type {AsyncUtil.RateLimiter}
* @private
*/
this.updateProgressRateLimiter_ = null
this.updateProgressRateLimiter_ = null;
};
/**
......
......@@ -282,40 +282,6 @@ util.getFiles = function(dirEntry, params, paths, successCallback,
getNextFile();
};
/**
* Resolve a path to either a DirectoryEntry or a FileEntry, regardless of
* whether the path is a directory or file.
*
* @param {DirectoryEntry} root The root of the filesystem to search.
* @param {string} path The path to be resolved.
* @param {function(Entry)} resultCallback Called back when a path is
* successfully resolved. Entry will be either a DirectoryEntry or
* a FileEntry.
* @param {function(FileError)} errorCallback Called back if an unexpected
* error occurs while resolving the path.
*/
util.resolvePath = function(root, path, resultCallback, errorCallback) {
if (path == '' || path == '/') {
resultCallback(root);
return;
}
root.getFile(
path, {create: false},
resultCallback,
function(err) {
if (err.name == util.FileError.TYPE_MISMATCH_ERR) {
// Bah. It's a directory, ask again.
root.getDirectory(
path, {create: false},
resultCallback,
errorCallback);
} else {
errorCallback(err);
}
});
};
/**
* Renames the entry to newName.
* @param {Entry} entry The entry to be renamed.
......@@ -365,68 +331,6 @@ util.removeFileOrDirectory = function(entry, onSuccess, onError) {
entry.remove(onSuccess, onError);
};
/**
* Checks if an entry exists at |relativePath| in |dirEntry|.
* If exists, tries to deduplicate the path by inserting parenthesized number,
* such as " (1)", before the extension. If it still exists, tries the
* deduplication again by increasing the number up to 10 times.
* For example, suppose "file.txt" is given, "file.txt", "file (1).txt",
* "file (2).txt", ..., "file (9).txt" will be tried.
*
* @param {DirectoryEntry} dirEntry The target directory entry.
* @param {string} relativePath The path to be deduplicated.
* @param {function(string)} onSuccess Called with the deduplicated path on
* success.
* @param {function(FileError)} onError Called on error.
*/
util.deduplicatePath = function(dirEntry, relativePath, onSuccess, onError) {
// The trial is up to 10.
var MAX_RETRY = 10;
// Crack the path into three part. The parenthesized number (if exists) will
// be replaced by incremented number for retry. For example, suppose
// |relativePath| is "file (10).txt", the second check path will be
// "file (11).txt".
var match = /^(.*?)(?: \((\d+)\))?(\.[^.]*?)?$/.exec(relativePath);
var prefix = match[1];
var copyNumber = match[2] ? parseInt(match[2], 10) : 0;
var ext = match[3] ? match[3] : '';
// The path currently checking the existence.
var trialPath = relativePath;
var onNotResolved = function(err) {
// We expect to be unable to resolve the target file, since we're going
// to create it during the copy. However, if the resolve fails with
// anything other than NOT_FOUND, that's trouble.
if (err.name != util.FileError.NOT_FOUND_ERR) {
onError(err);
return;
}
// Found a path that doesn't exist.
onSuccess(trialPath);
};
var numRetry = MAX_RETRY;
var onResolved = function(entry) {
if (--numRetry == 0) {
// Hit the limit of the number of retrial.
// Note that we cannot create FileError object directly, so here we use
// Object.create instead.
onError(util.createDOMError(util.FileError.PATH_EXISTS_ERR));
return;
}
++copyNumber;
trialPath = prefix + ' (' + copyNumber + ')' + ext;
util.resolvePath(dirEntry, trialPath, onResolved, onNotResolved);
};
// Check to see if the target exists.
util.resolvePath(dirEntry, trialPath, onResolved, onNotResolved);
};
/**
* Convert a number of bytes into a human friendly format, using the correct
* number separators.
......
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