blob: d7572b71dc766e1e76891d43a438d8807b19283c [file] [log] [blame]
// Copyright 2018 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';
function notreached(error) {
assertTrue(false, 'NOTREACHED(): ' + (error.stack || error));
}
/**
* Creates a new volume with a single, mock VolumeEntry.
* @param {?VolumeManagerCommon.VolumeType} volumeType
* @param {DirectoryEntry=} displayRoot
* @param {Object=} additionalProperties
* @return {!VolumeEntry}
*/
function fakeVolumeEntry(volumeType, displayRoot, additionalProperties) {
const kLabel = 'Fake Filesystem';
if (displayRoot === undefined) {
displayRoot = createFakeDisplayRoot();
}
let fakeVolumeInfo = {
displayRoot: displayRoot,
label: kLabel,
volumeType: volumeType
};
Object.assign(fakeVolumeInfo, additionalProperties || {});
// Create the VolumeEntry via casting (duck typing).
return new VolumeEntry(/** @type{!VolumeInfo} */ (fakeVolumeInfo));
}
/** Test constructor and default public attributes. */
function testEntryList(testReportCallback) {
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
assertEquals('My files', entryList.label);
assertEquals('entry-list://my_files', entryList.toURL());
assertEquals('my_files', entryList.rootType);
assertFalse(entryList.isNativeType);
assertEquals(null, entryList.getNativeEntry());
assertEquals(0, entryList.getUIChildren().length);
assertTrue(entryList.isDirectory);
assertFalse(entryList.isFile);
entryList.addEntry(
new EntryList('Child Entry', VolumeManagerCommon.RootType.MY_FILES));
assertEquals(1, entryList.getUIChildren().length);
const reader = entryList.createReader();
// How many times the reader callback |accumulateResults| has been called?
let callCounter = 0;
// How many times it was called with results?
let resultCouter = 0;
const accumulateResults = (readerResult) => {
// It's called with readerResult==[] a last time to indicate no more files.
callCounter++;
if (readerResult.length > 0) {
resultCouter++;
reader.readEntries(accumulateResults, () => {});
}
};
reader.readEntries(accumulateResults, () => {});
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
// accumulateResults should be called 2x in normal conditions;
return callCounter >= 2;
}).then(() => {
// Now we can check the final result.
assertEquals(2, callCounter);
assertEquals(1, resultCouter);
}),
testReportCallback);
}
/** Tests method EntryList.getParent. */
function testEntryListGetParent(testReportCallback) {
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
let callbackTriggered = false;
entryList.getParent(parentEntry => {
// EntryList should return itself since it's a root and that's what the web
// spec says.
callbackTriggered = true;
assertEquals(parentEntry, entryList);
}, notreached /* error */);
reportPromise(waitUntil(() => callbackTriggered), testReportCallback);
}
/** Tests method EntryList.addEntry. */
function testEntryListAddEntry() {
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
assertEquals(0, entryList.getUIChildren().length);
const childEntry = fakeVolumeEntry(VolumeManagerCommon.VolumeType.DOWNLOADS);
entryList.addEntry(childEntry);
assertEquals(1, entryList.getUIChildren().length);
assertEquals(childEntry, entryList.getUIChildren()[0]);
}
/**
* Tests EntryList's methods addEntry, findIndexByVolumeInfo,
* removeByVolumeType, removeByRootType, removeChildEntry.
*/
function testEntryFindIndex() {
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
const downloads = fakeVolumeEntry(VolumeManagerCommon.VolumeType.DOWNLOADS);
const crostini = fakeVolumeEntry(VolumeManagerCommon.VolumeType.CROSTINI);
const fakeEntry = /** @type{!Entry} */ ({
isDirectory: true,
rootType: VolumeManagerCommon.RootType.CROSTINI,
name: 'Linux files',
toURL: function() {
return 'fake-entry://linux-files';
}
});
entryList.addEntry(downloads);
entryList.addEntry(crostini);
// Test findIndexByVolumeInfo.
assertEquals(0, entryList.findIndexByVolumeInfo(downloads.volumeInfo));
assertEquals(1, entryList.findIndexByVolumeInfo(crostini.volumeInfo));
// Test removeByVolumeType.
assertTrue(
entryList.removeByVolumeType(VolumeManagerCommon.VolumeType.CROSTINI));
assertEquals(1, entryList.getUIChildren().length);
// Now crostini volume doesn't exist anymore, so should return False.
assertFalse(
entryList.removeByVolumeType(VolumeManagerCommon.VolumeType.CROSTINI));
// Test removeByRootType.
entryList.addEntry(fakeEntry);
assertTrue(entryList.removeByRootType(VolumeManagerCommon.RootType.CROSTINI));
assertEquals(1, entryList.getUIChildren().length);
// Test removeChildEntry.
assertTrue(entryList.removeChildEntry(entryList.getUIChildren()[0]));
assertEquals(0, entryList.getUIChildren().length);
// Nothing left to remove.
assertFalse(entryList.removeChildEntry(/** @type {Entry} */ ({})));
}
/**
* Tests VolumeEntry's methods findIndexByVolumeInfo, removeByVolumeType,
* removeByRootType, removeChildEntry.
* @suppress {accessControls} to be able to access private properties.
*/
function testVolumeEntryFindIndex() {
const fakeRootEntry = createFakeDisplayRoot();
const volumeEntry =
fakeVolumeEntry(VolumeManagerCommon.VolumeType.DOWNLOADS, fakeRootEntry);
const crostini = fakeVolumeEntry(VolumeManagerCommon.VolumeType.CROSTINI);
const android = fakeVolumeEntry(VolumeManagerCommon.VolumeType.ANDROID_FILES);
const fakeEntry = /** @type{!Entry} */ ({
isDirectory: true,
rootType: VolumeManagerCommon.RootType.CROSTINI,
name: 'Linux files',
toURL: function() {
return 'fake-entry://linux-files';
}
});
volumeEntry.addEntry(crostini);
volumeEntry.addEntry(android);
// Test findIndexByVolumeInfo.
assertEquals(0, volumeEntry.findIndexByVolumeInfo(crostini.volumeInfo));
assertEquals(1, volumeEntry.findIndexByVolumeInfo(android.volumeInfo));
assertEquals(2, volumeEntry.getUIChildren().length);
assertEquals(crostini, volumeEntry.getUIChildren()[0]);
assertEquals(android, volumeEntry.getUIChildren()[1]);
// Test removeByVolumeType.
assertTrue(
volumeEntry.removeByVolumeType(VolumeManagerCommon.VolumeType.CROSTINI));
assertEquals(1, volumeEntry.children_.length);
// Now crostini volume doesn't exist anymore, so should return False.
assertFalse(
volumeEntry.removeByVolumeType(VolumeManagerCommon.VolumeType.CROSTINI));
// Test removeByRootType.
volumeEntry.addEntry(fakeEntry);
assertTrue(
volumeEntry.removeByRootType(VolumeManagerCommon.RootType.CROSTINI));
assertEquals(1, volumeEntry.children_.length);
// Test removeChildEntry.
assertTrue(volumeEntry.removeChildEntry(volumeEntry.getUIChildren()[0]));
assertEquals(0, volumeEntry.getUIChildren().length);
// Nothing left to remove.
assertFalse(volumeEntry.removeChildEntry(/** @type {Entry} */ ({})));
}
/** Tests method EntryList.getMetadata. */
function testEntryListGetMetadata(testReportCallback) {
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
let modificationTime = null;
entryList.getMetadata(metadata => {
modificationTime = metadata.modificationTime;
}, notreached /* error */);
// getMetadata runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
return modificationTime !== null;
}).then(() => {
// Now we can check the final result, it returns "now", so let's just
// check the type and 1 attribute here.
assertTrue(modificationTime instanceof Date);
assertTrue(!!modificationTime.getUTCFullYear());
}),
testReportCallback);
}
/** Tests StaticReader.readEntries. */
function testStaticReader(testReportCallback) {
const reader = new StaticReader(['file1', 'file2']);
const testResults = [];
// How many times the reader callback |accumulateResults| has been called?
let callCounter = 0;
const accumulateResults = (readerResult) => {
callCounter++;
// merge on testResults.
readerResult.map(f => testResults.push(f));
if (readerResult.length > 0) {
reader.readEntries(accumulateResults, () => {});
}
};
reader.readEntries(accumulateResults, () => {});
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
// accumulateResults should be called 2x in normal conditions;
return callCounter >= 2;
}).then(() => {
// Now we can check the final result.
assertEquals(2, callCounter);
assertEquals(2, testResults.length);
assertEquals('file1', testResults[0]);
assertEquals('file2', testResults[1]);
}),
testReportCallback);
}
/** Tests CombinedReader.readEntries. */
function testCombinedReader(testReportCallback) {
const innerReaders = [
new StaticReader(['file1']),
new StaticReader(['file2']),
];
const reader = new CombinedReaders(innerReaders);
const testResults = [];
// How many times the reader callback |accumulateResults| has been called?
let callCounter = 0;
const accumulateResults = (readerResult) => {
callCounter++;
// merge on testResults.
readerResult.map(f => testResults.push(f));
if (readerResult.length > 0) {
reader.readEntries(accumulateResults, () => {});
}
};
reader.readEntries(accumulateResults, () => {});
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
// accumulateResults should be called 2x in normal conditions;
return callCounter >= 3;
}).then(() => {
// Now we can check the final result.
assertEquals(3, callCounter);
assertEquals(2, testResults.length);
assertEquals('file1', testResults[0]);
assertEquals('file2', testResults[1]);
}),
testReportCallback);
}
function testCombinedReaderError(testReportCallback) {
const expectedError = new Error('a fake error');
const alwaysFailReader = {
readEntries: (success, error) => {
error(expectedError);
},
};
const innerReaders = [
new StaticReader(['file1']),
alwaysFailReader,
];
const reader = new CombinedReaders(innerReaders);
const errors = [];
const accumulateFailures = (error) => {
errors.push(error);
};
let callCounter = 0;
const testResults = [];
const accumulateResults = (readerResult) => {
callCounter++;
// merge on testResults.
readerResult.map(f => testResults.push(f));
if (readerResult.length > 0) {
reader.readEntries(accumulateResults, accumulateFailures);
}
};
reader.readEntries(accumulateResults, accumulateFailures);
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
// accumulateResults should be called 2x in normal conditions;
return callCounter >= 1 && errors.length >= 1;
}).then(() => {
// Now we can check the final result.
assertEquals(1, callCounter);
assertEquals(1, testResults.length);
assertEquals('file1', testResults[0]);
assertEquals(1, errors.length);
assertEquals(expectedError, errors[0]);
}),
testReportCallback);
}
/**
* Returns an object that can be used as displayRoot on a FakeVolumeInfo.
* VolumeEntry delegates many attributes and methods to displayRoot.
*/
function createFakeDisplayRoot() {
const fs = new MockFileSystem('fake-fs');
return fs.root;
}
/**
* Tests VolumeEntry constructor and default public attributes/getter/methods.
*/
function testVolumeEntry() {
const fakeRootEntry = createFakeDisplayRoot();
const volumeEntry =
fakeVolumeEntry(VolumeManagerCommon.VolumeType.DOWNLOADS, fakeRootEntry);
assertEquals(fakeRootEntry, volumeEntry.getNativeEntry());
assertEquals(VolumeManagerCommon.VolumeType.DOWNLOADS, volumeEntry.iconName);
assertEquals('filesystem:fake-fs/', volumeEntry.filesystem.rootURL);
assertEquals('/', volumeEntry.fullPath);
assertEquals('filesystem:fake-fs/', volumeEntry.toURL());
assertEquals('Fake Filesystem', volumeEntry.name);
assertTrue(volumeEntry.isNativeType);
assertEquals(fakeRootEntry, volumeEntry.getNativeEntry());
assertTrue(volumeEntry.isDirectory);
assertFalse(volumeEntry.isFile);
}
function testVolumeEntryCreateReader(testReportCallback) {
const fakeRootEntry = createFakeDisplayRoot();
fakeRootEntry.createReader = () => new StaticReader(['file1']);
const volumeEntry =
fakeVolumeEntry(VolumeManagerCommon.VolumeType.DOWNLOADS, fakeRootEntry);
const crostini = fakeVolumeEntry(VolumeManagerCommon.VolumeType.CROSTINI);
const android = fakeVolumeEntry(VolumeManagerCommon.VolumeType.ANDROID_FILES);
volumeEntry.addEntry(crostini);
volumeEntry.addEntry(android);
const reader = volumeEntry.createReader();
const readFiles = [];
const accumulateResults = (readerResult) => {
readerResult.map((f) => readFiles.push(f));
if (readerResult.length > 0) {
reader.readEntries(accumulateResults);
}
};
reader.readEntries(accumulateResults);
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
return readFiles.length >= 3;
}).then(() => {
// Now we can check the final result.
assertEquals(3, readFiles.length);
assertEquals('file1', readFiles[0]);
assertEquals(crostini, readFiles[1]);
assertEquals(android, readFiles[2]);
}),
testReportCallback);
}
/** Tests VolumeEntry createReader when root entry isn't resolved yet. */
function testVolumeEntryCreateReaderUnresolved(testReportCallback) {
// A VolumeInfo that doesn't resolve the display root.
const fakeVolumeInfo = /** @type{!VolumeInfo} */ ({
displayRoot: null,
label: 'Fake Filesystem label',
volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS,
resolveDisplayRoot: (successCallback, errorCallback) => {
// Do nothing here.
},
});
const volumeEntry = new VolumeEntry(fakeVolumeInfo);
const crostini = fakeVolumeEntry(VolumeManagerCommon.VolumeType.CROSTINI);
const android = fakeVolumeEntry(VolumeManagerCommon.VolumeType.ANDROID_FILES);
volumeEntry.addEntry(crostini);
volumeEntry.addEntry(android);
const reader = volumeEntry.createReader();
const readFiles = [];
const accumulateResults = (readerResult) => {
readerResult.map((f) => readFiles.push(f));
if (readerResult.length > 0) {
reader.readEntries(accumulateResults);
}
};
reader.readEntries(accumulateResults);
// readEntries runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
return readFiles.length >= 2;
}).then(() => {
// Now we can check the final result.
assertEquals(2, readFiles.length);
assertEquals(crostini, readFiles[0]);
assertEquals(android, readFiles[1]);
}),
testReportCallback);
}
/**
* Tests VolumeEntry getFile and getDirectory methods.
*/
function testVolumeEntryGetDirectory(testReportCallback) {
const root = createFakeDisplayRoot();
root.filesystem.populate(['/bla/', '/bla.txt']);
const volumeEntry = fakeVolumeEntry(null, root);
let foundDir = null;
let foundFile = null;
volumeEntry.getDirectory('/bla', {create: false}, (entry) => {
foundDir = entry;
});
volumeEntry.getFile('/bla.txt', {create: false}, (entry) => {
foundFile = entry;
});
reportPromise(
waitUntil(() => {
return foundDir !== null && foundFile !== null;
}),
testReportCallback);
}
/**
* Tests VolumeEntry which initially doesn't have displayRoot.
*/
function testVolumeEntryDelayedDisplayRoot(testReportCallback) {
let callbackTriggered = false;
const fakeRootEntry = createFakeDisplayRoot();
// Create an entry using a VolumeInfo without displayRoot.
const volumeEntry = fakeVolumeEntry(null, null, {
resolveDisplayRoot: function(successCallback, errorCallback) {
setTimeout(() => {
successCallback(fakeRootEntry);
callbackTriggered = true;
}, 0);
}
});
// rootEntry_ starts as null.
assertEquals(null, volumeEntry.rootEntry_);
assertEquals(null, volumeEntry.getNativeEntry());
reportPromise(
waitUntil(() => callbackTriggered).then(() => {
// Eventually rootEntry_ gets the value.
assertEquals(fakeRootEntry, volumeEntry.getNativeEntry());
}),
testReportCallback);
}
/** Tests VolumeEntry.getParent */
function testVolumeEntryGetParent(testReportCallback) {
const volumeEntry = fakeVolumeEntry(null);
let callbackTriggered = false;
volumeEntry.getParent(parentEntry => {
callbackTriggered = true;
// VolumeEntry should return itself since it's a root and that's what the
// web spec says.
assertEquals(parentEntry, volumeEntry);
}, notreached /* error */);
reportPromise(waitUntil(() => callbackTriggered), testReportCallback);
}
/** Tests VolumeEntry.getMetadata */
function testVolumeEntryGetMetadata(testReportCallback) {
const volumeEntry = fakeVolumeEntry(null);
let modificationTime = null;
volumeEntry.getMetadata(metadata => {
modificationTime = metadata.modificationTime;
}, notreached /* error */);
// getMetadata runs asynchronously, so let's wait it to be called.
reportPromise(
waitUntil(() => {
return modificationTime !== null;
}),
testReportCallback);
}
/**
* Test EntryList.addEntry sets prefix on VolumeEntry.
*/
function testEntryListAddEntrySetsPrefix() {
const volumeEntry = fakeVolumeEntry(null);
const entryList =
new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES);
entryList.addEntry(volumeEntry);
assertEquals(1, entryList.getUIChildren().length);
// entryList is parent of volumeEntry so it should be its prefix.
assertEquals(entryList, volumeEntry.volumeInfo.prefixEntry);
}
/**
* Test FakeEntry, which is only static data.
*/
function testFakeEntry(testReportCallback) {
let fakeEntry = new FakeEntry('label', VolumeManagerCommon.RootType.CROSTINI);
assertEquals(undefined, fakeEntry.sourceRestriction);
assertEquals('FakeEntry', fakeEntry.type_name);
assertEquals('label', fakeEntry.label);
assertEquals('label', fakeEntry.name);
assertEquals('fake-entry://crostini', fakeEntry.toURL());
assertEquals('crostini', fakeEntry.iconName);
assertEquals(VolumeManagerCommon.RootType.CROSTINI, fakeEntry.rootType);
assertFalse(fakeEntry.isNativeType);
assertEquals(null, fakeEntry.getNativeEntry());
assertTrue(fakeEntry.isDirectory);
assertFalse(fakeEntry.isFile);
// Check sourceRestriction constructor args.
const kSourceRestriction =
/** @type{chrome.fileManagerPrivate.SourceRestriction} */ ('fake');
fakeEntry = new FakeEntry(
'label', VolumeManagerCommon.RootType.CROSTINI, kSourceRestriction);
assertEquals(kSourceRestriction, fakeEntry.sourceRestriction);
let callCounter = 0;
fakeEntry.getMetadata((metadata) => {
// Returns empty (but non-null) metadata {}.
assert(metadata);
assertEquals(0, Object.keys(metadata).length);
callCounter++;
}, notreached /* error */);
fakeEntry.getParent((parentEntry) => {
// Should return itself.
assertEquals(fakeEntry, parentEntry);
callCounter++;
}, notreached /* error */);
reportPromise(
waitUntil(() => {
// It should be called for getMetadata and for getParent.
return callCounter == 2;
}),
testReportCallback);
}