blob: 6e2415ac47089085bca97485ba12e5b1dd663d17 [file] [log] [blame]
// 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';
/** @type {!MockVolumeManager} */
let volumeManager;
/** @type {!DeviceHandler} */
let deviceHandler;
/** Mock chrome APIs. */
let mockChrome;
// Set up the test components.
function setUp() {
// Set up string assets.
window.loadTimeData.data = {
DEVICE_UNSUPPORTED_MESSAGE: 'DEVICE_UNSUPPORTED: $1',
DEVICE_UNKNOWN_MESSAGE: 'DEVICE_UNKNOWN: $1',
MULTIPART_DEVICE_UNSUPPORTED_MESSAGE: 'MULTIPART_DEVICE_UNSUPPORTED: $1',
};
window.loadTimeData.getString = id => {
return window.loadTimeData.data_[id] || id;
};
setupChromeApis();
volumeManager = new MockVolumeManager();
MockVolumeManager.installMockSingleton(volumeManager);
deviceHandler = new DeviceHandler();
}
function setUpInIncognitoContext() {
mockChrome.extension.inIncognitoContext = true;
}
function testGoodDevice(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
const options = notifications['deviceNavigation:/device/path'];
assertEquals('REMOVABLE_DEVICE_NAVIGATION_MESSAGE', options.message);
assertTrue(options.isClickable);
}),
callback);
}
function testRemovableMediaDeviceWithImportEnabled(callback) {
const storage = new MockChromeStorageAPI();
setupFileSystem(VolumeManagerCommon.VolumeType.REMOVABLE, 'blabbity', [
'/DCIM/',
'/DCIM/grandma.jpg',
]);
const resolver = new importer.Resolver();
// Handle media device navigation requests.
deviceHandler.addEventListener(
DeviceHandler.VOLUME_NAVIGATION_REQUESTED, event => {
resolver.resolve(event);
});
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {volumeId: 'blabbity', deviceType: 'usb'},
shouldNotify: true
});
reportPromise(
resolver.promise.then(event => {
assertEquals('blabbity', event.volumeId);
}),
callback);
}
function testMtpMediaDeviceWithImportEnabled(callback) {
const storage = new MockChromeStorageAPI();
setupFileSystem(VolumeManagerCommon.VolumeType.MTP, 'blabbity', [
'/dcim/',
'/dcim/grandpa.jpg',
]);
const resolver = new importer.Resolver();
// Handle media device navigation requests.
deviceHandler.addEventListener(
DeviceHandler.VOLUME_NAVIGATION_REQUESTED, event => {
resolver.resolve(event);
});
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {volumeId: 'blabbity', deviceType: 'mtp'},
shouldNotify: true
});
reportPromise(
resolver.promise.then(event => {
assertEquals('blabbity', event.volumeId);
}),
callback);
}
function testMediaDeviceWithImportDisabled(callback) {
mockChrome.commandLinePrivate.cloudImportDisabled = true;
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label',
hasMedia: true
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
assertEquals(
'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
notifications['deviceNavigation:/device/path'].message,
'Device notification did not have the right message.');
}),
callback);
}
function testGoodDeviceNotNavigated() {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: false
});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
assertFalse(mockChrome.notifications.resolver.settled);
}
function testGoodDeviceWithBadParent(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertFalse(!!notifications['device:/device/path']);
assertEquals(
'DEVICE_UNKNOWN: label',
notifications['deviceFail:/device/path'].message);
}),
callback);
}
function testGoodDeviceWithBadParent_DuplicateMount(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
// Mounting the same device repeatedly should produce only
// a single notification.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
assertEquals(
'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
notifications['deviceNavigation:/device/path'].message);
}),
callback);
}
function testUnsupportedDevice(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unsupported_filesystem',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertFalse(!!mockChrome.notifications.items['device:/device/path']);
assertEquals(
'DEVICE_UNSUPPORTED: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
}),
callback);
}
function testUnknownDevice(callback) {
// Emulate adding a device which has unknown filesystem.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unknown_filesystem',
volumeMetadata: {
isParentDevice: false,
isReadOnly: false,
deviceType: 'usb',
devicePath: '/device/path',
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertFalse(!!mockChrome.notifications.items['device:/device/path']);
const item = mockChrome.notifications.items['deviceFail:/device/path'];
assertEquals('DEVICE_UNKNOWN_DEFAULT_MESSAGE', item.message);
// "Format device" button should appear.
assertEquals('DEVICE_UNKNOWN_BUTTON_LABEL', item.buttons[0].title);
}),
callback);
}
function testUnknownReadonlyDevice(callback) {
// Emulate adding a device which has unknown filesystem but is read-only.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unknown_filesystem',
volumeMetadata: {
isParentDevice: true,
isReadOnly: true,
deviceType: 'sd',
devicePath: '/device/path',
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertFalse(!!mockChrome.notifications.items['device:/device/path']);
const item = mockChrome.notifications.items['deviceFail:/device/path'];
assertEquals('DEVICE_UNKNOWN_DEFAULT_MESSAGE', item.message);
// "Format device" button should not appear.
assertFalse(!!item.buttons);
}),
callback);
}
function testUnsupportedWithUnknownParentReplacesNotification() {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(
'DEVICE_UNKNOWN: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unsupported_filesystem',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'DEVICE_UNSUPPORTED: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
}
function testMountPartialSuccess(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise
.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
assertEquals(
'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
notifications['deviceNavigation:/device/path'].message);
})
.then(() => {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unsupported_filesystem',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
})
.then(() => {
const notifications = mockChrome.notifications.items;
assertEquals(2, Object.keys(notifications).length);
assertEquals(
'MULTIPART_DEVICE_UNSUPPORTED: label',
notifications['deviceFail:/device/path'].message);
}),
callback);
}
function testUnknown(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_unknown',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
assertEquals(
'DEVICE_UNKNOWN: label',
notifications['deviceFail:/device/path'].message);
}),
callback);
}
function testNonASCIILabel(callback) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
// "RA (U+30E9) BE (U+30D9) RU (U+30EB)" in Katakana letters.
deviceLabel: '\u30E9\u30D9\u30EB'
},
shouldNotify: true
});
reportPromise(
mockChrome.notifications.resolver.promise.then(notifications => {
assertEquals(1, Object.keys(notifications).length);
assertEquals(
'DEVICE_UNKNOWN: \u30E9\u30D9\u30EB',
notifications['deviceFail:/device/path'].message);
}),
callback);
}
function testMulitpleFail() {
// The first parent error.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: true,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'DEVICE_UNKNOWN: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
// The first child error that replaces the parent error.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'DEVICE_UNKNOWN: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
// The second child error that turns to a multi-partition error.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'MULTIPART_DEVICE_UNSUPPORTED: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
// The third child error that should be ignored because the error message does
// not changed.
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'error_internal',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'MULTIPART_DEVICE_UNSUPPORTED: label',
mockChrome.notifications.items['deviceFail:/device/path'].message);
}
function testDisabledDevice() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'disabled', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'EXTERNAL_STORAGE_DISABLED_MESSAGE',
mockChrome.notifications.items['deviceFail:/device/path'].message);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'removed', devicePath: '/device/path'});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
}
function testFormatSucceeded() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'FORMATTING_OF_DEVICE_PENDING_MESSAGE',
mockChrome.notifications.items['formatStart:/device/path'].message);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_success', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'FORMATTING_FINISHED_SUCCESS_MESSAGE',
mockChrome.notifications.items['formatSuccess:/device/path'].message);
}
function testFormatFailed() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'FORMATTING_OF_DEVICE_PENDING_MESSAGE',
mockChrome.notifications.items['formatStart:/device/path'].message);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_fail', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'FORMATTING_FINISHED_FAILURE_MESSAGE',
mockChrome.notifications.items['formatFail:/device/path'].message);
}
function testRenameSucceeded() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'rename_start', devicePath: '/device/path'});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'rename_success', devicePath: '/device/path'});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
}
function testRenameFailed() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'rename_start', devicePath: '/device/path'});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'rename_fail', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'RENAMING_OF_DEVICE_FINISHED_FAILURE_MESSAGE',
mockChrome.notifications.items['renameFail:/device/path'].message);
}
function testDeviceHardUnplugged() {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'hard_unplugged', devicePath: '/device/path'});
assertEquals(1, Object.keys(mockChrome.notifications.items).length);
assertEquals(
'DEVICE_HARD_UNPLUGGED_MESSAGE',
mockChrome.notifications.items['hardUnplugged:/device/path'].message);
}
function testNotificationClicked(callback) {
const devicePath = '/device/path';
const notificationId = 'deviceNavigation:' + devicePath;
// Add a listener for navigation-requested events.
const resolver = new importer.Resolver();
deviceHandler.addEventListener(
DeviceHandler.VOLUME_NAVIGATION_REQUESTED, event => {
resolver.resolve(event);
});
// Call the notification-body-clicked handler and check that the
// navigation-requested event is dispatched.
mockChrome.notifications.onClicked.dispatch(notificationId);
reportPromise(
resolver.promise.then(event => {
assertEquals(null, event.volumeId);
assertEquals(devicePath, event.devicePath);
assertEquals(null, event.filePath);
}),
callback);
}
function testMiscMessagesInIncognito() {
setUpInIncognitoContext();
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
{type: 'format_start', devicePath: '/device/path'});
// No notification sent by this instance in incognito context.
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
assertFalse(mockChrome.notifications.resolver.settled);
}
function testMountCompleteInIncognito() {
setUpInIncognitoContext();
mockChrome.fileManagerPrivate.onMountCompleted.dispatch({
eventType: 'mount',
status: 'success',
volumeMetadata: {
isParentDevice: false,
deviceType: 'usb',
devicePath: '/device/path',
deviceLabel: 'label'
},
shouldNotify: true
});
assertEquals(0, Object.keys(mockChrome.notifications.items).length);
// TODO(yamaguchi): I think this test is incomplete.
// This looks as if notification is not generated yet because the promise
// is not settled yet. Same for testGoodDeviceNotNavigated.
assertFalse(mockChrome.notifications.resolver.settled);
}
/**
* @param {!VolumeManagerCommon.VolumeType} volumeType
* @param {string} volumeId
* @param {!Array<string>} fileNames
* @return {!VolumeInfo}
*/
function setupFileSystem(volumeType, volumeId, fileNames) {
/** @type {!MockVolumeManager}*/
const mockVolumeManager = volumeManager;
const volumeInfo = mockVolumeManager.createVolumeInfo(
volumeType, volumeId, 'A volume known as ' + volumeId);
assertTrue(!!volumeInfo);
const mockFileSystem = /** @type {!MockFileSystem} */
(volumeInfo.fileSystem);
mockFileSystem.populate(fileNames);
return volumeInfo;
}
function setupChromeApis() {
// Mock chrome APIs.
mockChrome = {
commandLinePrivate: {
hasSwitch: function(switchName, callback) {
if (switchName === 'disable-cloud-import') {
callback(mockChrome.commandLinePrivate.cloudImportDisabled);
}
},
cloudImportDisabled: false,
},
extension: {
inIncognitoContext: false,
},
fileManagerPrivate: {
onDeviceChanged: {
dispatch: null,
addListener: function(listener) {
mockChrome.fileManagerPrivate.onDeviceChanged.dispatch = listener;
}
},
onMountCompleted: {
dispatch: null,
addListener: function(listener) {
mockChrome.fileManagerPrivate.onMountCompleted.dispatch = listener;
}
},
getProfiles: function(callback) {
callback([{profileId: 'userid@xyz.domain.org'}]);
}
},
i18n: {
getUILanguage: function() {
return 'en-US';
}
},
notifications: {
resolver: new importer.Resolver(),
promise: null,
create: function(id, params, callback) {
mockChrome.notifications.promise =
mockChrome.notifications.resolver.promise;
mockChrome.notifications.items[id] = params;
if (!mockChrome.notifications.resolver.settled) {
mockChrome.notifications.resolver.resolve(
mockChrome.notifications.items);
}
callback();
},
clear: function(id, callback) {
delete mockChrome.notifications.items[id];
callback();
},
items: {},
onButtonClicked: {
dispatch: null,
addListener: function(listener) {
mockChrome.notifications.onButtonClicked.dispatch = listener;
}
},
onClicked: {
dispatch: null,
addListener: function(listener) {
mockChrome.notifications.onClicked.dispatch = listener;
}
},
getAll: function(callback) {
callback([]);
}
},
runtime: {
getURL: function(path) {
return path;
},
onStartup: {addListener: function() {}}
}
};
installMockChrome(mockChrome);
}