diff --git a/ui/file_manager/file_manager/background/js/mock_volume_manager.js b/ui/file_manager/file_manager/background/js/mock_volume_manager.js index eb352f5882bf48..01c4a472787445 100644 --- a/ui/file_manager/file_manager/background/js/mock_volume_manager.js +++ b/ui/file_manager/file_manager/background/js/mock_volume_manager.js @@ -120,7 +120,7 @@ export class MockVolumeManager { * Current implementation can handle only fake entries. * * @param {!Entry|!FilesAppEntry} entry A fake entry. - * @return {!EntryLocation} Location information. + * @return {!EntryLocation|null} Location information. */ getLocationInfo(entry) { if (util.isFakeEntry(entry)) { @@ -160,6 +160,11 @@ export class MockVolumeManager { } const volumeInfo = this.getVolumeInfo(entry); + // For filtered out volumes, its volume info won't exist in the volume info + // list. + if (!volumeInfo) { + return null; + } const rootType = VolumeManagerCommon.getRootTypeFromVolumeType( assert(volumeInfo.volumeType)); const isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root); diff --git a/ui/file_manager/file_manager/state/for_tests.ts b/ui/file_manager/file_manager/state/for_tests.ts index bcca8052f0fe26..da10e472bc304e 100644 --- a/ui/file_manager/file_manager/state/for_tests.ts +++ b/ui/file_manager/file_manager/state/for_tests.ts @@ -6,13 +6,11 @@ import {assertDeepEquals} from 'chrome://webui-test/chromeos/chai_assert.js'; import {MockVolumeManager} from '../background/js/mock_volume_manager.js'; import {DialogType} from '../common/js/dialog_type.js'; -import {VolumeManagerCommon} from '../common/js/volume_manager_types.js'; import {Crostini} from '../externs/background/crostini.js'; import {FilesAppDirEntry} from '../externs/files_app_entry_interfaces.js'; -import {FileData, FileKey, PropStatus, State, Volume} from '../externs/ts/state.js'; -import {constants} from '../foreground/js/constants.js'; +import {FileKey, PropStatus, State} from '../externs/ts/state.js'; +import {VolumeInfo} from '../externs/volume_info.js'; import {FileSelectionHandler} from '../foreground/js/file_selection.js'; -import {MetadataItem} from '../foreground/js/metadata/metadata_item.js'; import {MetadataModel} from '../foreground/js/metadata/metadata_model.js'; import {MockMetadataModel} from '../foreground/js/metadata/mock_metadata.js'; import {createFakeDirectoryModel} from '../foreground/js/mock_directory_model.js'; @@ -76,6 +74,21 @@ export function updateContent(store: Store, entries: Entry[]) { store.dispatch(updateDirectoryContent({entries})); } +/** + * Store state might include objects (e.g. Entry type) which can not stringified + * by JSON, here we implement a custom "replacer" to handle that. + */ +function jsonStringifyStoreState(state: any): string { + return JSON.stringify(state, (key, value) => { + // Currently only the key with "entry" (inside `FileData`) can't be + // stringified, we just return its URL. + if (key === 'entry') { + return value.toURL(); + } + return value; + }, 2); +} + /** * Waits for a part of the Store to be in the expected state. * @@ -92,7 +105,9 @@ export async function waitDeepEquals( let got: any; const timeout = new Promise((_, reject) => { setTimeout(() => { - reject(new Error(`waitDeepEquals timed out waiting for \n${want}`)); + reject(new Error(`waitDeepEquals timed out.\nWANT:\n${ + jsonStringifyStoreState( + want)}\nGOT:\n${jsonStringifyStoreState(got)}`)); }, 10000); }); @@ -105,8 +120,6 @@ export async function waitDeepEquals( if (error.constructor?.name === 'AssertionError') { return false; } - console.log(error.stack); - console.error(error); throw error; } }); @@ -115,9 +128,9 @@ export async function waitDeepEquals( } /** Setup store and initialize it with empty state. */ -export function setupStore(): Store { +export function setupStore(initialState: State = getEmptyState()): Store { const store = getStore(); - store.init(getEmptyState()); + store.init(initialState); return store; } @@ -139,98 +152,39 @@ export function setUpFileManagerOnWindow() { } /** - * Create a fake FileData with partial information. Only the fields listed are - * required, other fields are optional. + * Create a fake VolumeMetadata with VolumeInfo, VolumeInfo can be created by + * MockVolumeManager.createMockVolumeInfo. */ -export function createFakeFileData( - partialFileData: Pick& - Partial>, - ): FileData { - const defaultFileData = { - icon: constants.ICON_TYPES.FOLDER, - volumeType: null, - isDirectory: true, - metadata: {} as MetadataItem, - isRootEntry: false, - isEjectable: false, - shouldDelayLoadingChildren: false, - children: [], - expanded: false, - }; - return { - ...defaultFileData, - ...partialFileData, - }; -} - -/** Create a fake VolumeMetadata. */ export function createFakeVolumeMetadata( - partialMetadata: - Pick& - Partial>, + volumeInfo: VolumeInfo, ): chrome.fileManagerPrivate.VolumeMetadata { - const defaultMetadata = { + return { + volumeId: volumeInfo.volumeId, + volumeType: volumeInfo.volumeType, profile: { - displayName: 'foobar@chromium.org', - isCurrentProfile: true, + ...volumeInfo.profile, profileId: '', }, - configurable: false, - watchable: true, - source: VolumeManagerCommon.Source.SYSTEM, - volumeLabel: undefined, + configurable: volumeInfo.configurable, + watchable: volumeInfo.watchable, + source: volumeInfo.source, + volumeLabel: volumeInfo.label, fileSystemId: undefined, - providerId: undefined, + providerId: volumeInfo.providerId, sourcePath: undefined, - deviceType: undefined, - devicePath: undefined, + deviceType: volumeInfo.deviceType, + devicePath: volumeInfo.devicePath, isParentDevice: undefined, - isReadOnly: false, - isReadOnlyRemovableDevice: false, - hasMedia: false, + isReadOnly: volumeInfo.isReadOnly, + isReadOnlyRemovableDevice: volumeInfo.isReadOnlyRemovableDevice, + hasMedia: volumeInfo.hasMedia, mountCondition: undefined, mountContext: undefined, - diskFileSystemType: undefined, - iconSet: {icon16x16Url: '', icon32x32Url: ''}, - driveLabel: '', - remoteMountPath: undefined, + diskFileSystemType: volumeInfo.diskFileSystemType, + iconSet: volumeInfo.iconSet, + driveLabel: volumeInfo.driveLabel, + remoteMountPath: volumeInfo.remoteMountPath, hidden: false, - vmType: undefined, - }; - return { - ...defaultMetadata, - ...partialMetadata, - }; -} - -/** - * Create a fake Volume. Only the fields listed are required, other fields are - * optional. - */ -export function createFakeVolume( - partialVolume: Pick& - Partial>): Volume { - const defaultVolume = { - status: PropStatus.SUCCESS, - source: VolumeManagerCommon.Source.SYSTEM, - error: undefined, - deviceType: undefined, - devicePath: undefined, - isReadOnly: false, - isReadOnlyRemovableDevice: false, - providerId: undefined, - configurable: false, - watchable: true, - diskFileSystemType: '', - iconSet: {icon16x16Url: '', icon32x32Url: ''}, - driveLabel: '', - vmType: undefined, - isDisabled: false, - prefixKey: undefined, - }; - return { - ...defaultVolume, - ...partialVolume, + vmType: volumeInfo.vmType, }; } diff --git a/ui/file_manager/file_manager/state/reducers/all_entries.ts b/ui/file_manager/file_manager/state/reducers/all_entries.ts index b43cbec906e65a..607bc9b03e76fe 100644 --- a/ui/file_manager/file_manager/state/reducers/all_entries.ts +++ b/ui/file_manager/file_manager/state/reducers/all_entries.ts @@ -360,6 +360,7 @@ function getEntryType(entry: Entry|FilesAppEntry): EntryType { case VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT: return EntryType.ENTRY_LIST; case VolumeManagerCommon.RootType.CROSTINI: + case VolumeManagerCommon.RootType.ANDROID_FILES: return EntryType.PLACEHOLDER; case VolumeManagerCommon.RootType.DRIVE_OFFLINE: case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME: @@ -436,6 +437,7 @@ export function getMyFiles(state: State): myFilesEntryList = new EntryList( str('MY_FILES_ROOT_LABEL'), VolumeManagerCommon.RootType.MY_FILES); appendEntry(state, myFilesEntryList); + state.uiEntries = [...state.uiEntries, myFilesEntryList.toURL()]; } return { @@ -484,6 +486,9 @@ function volumeNestingEntries( // Also remove it from the children field. myFilesFileData.children = myFilesFileData.children.filter( childKey => childKey !== childEntry.toURL()); + // And remove it from the uiEntries if existed. + state.uiEntries = state.uiEntries.filter( + uiEntryKey => uiEntryKey !== childEntry.toURL()); } } appendChildIfNotExisted(myFilesEntry, newVolumeEntry); @@ -513,6 +518,9 @@ function volumeNestingEntries( appendChildIfNotExisted(myFilesVolumeEntry!, childEntry); myFilesEntryList.removeChildEntry(childEntry); } + // Remove MyFiles entry list from the uiEntries. + state.uiEntries = state.uiEntries.filter( + uiEntryKey => uiEntryKey !== myFilesEntryListKey); } } @@ -527,6 +535,7 @@ function volumeNestingEntries( str('DRIVE_DIRECTORY_LABEL'), VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT); appendEntry(state, googleDrive); + state.uiEntries = [...state.uiEntries, googleDrive.toURL()]; } appendChildIfNotExisted(googleDrive, myDrive!); @@ -559,7 +568,7 @@ function volumeNestingEntries( fakeEntries[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME]; if (fakeSharedWithMe) { appendEntry(state, fakeSharedWithMe); - state.uiEntries.push(fakeSharedWithMe.toURL()); + state.uiEntries = [...state.uiEntries, fakeSharedWithMe.toURL()]; appendChildIfNotExisted(googleDrive, fakeSharedWithMe); } @@ -567,7 +576,7 @@ function volumeNestingEntries( const fakeOffline = fakeEntries[VolumeManagerCommon.RootType.DRIVE_OFFLINE]; if (fakeOffline) { appendEntry(state, fakeOffline); - state.uiEntries.push(fakeOffline.toURL()); + state.uiEntries = [...state.uiEntries, fakeOffline.toURL()]; appendChildIfNotExisted(googleDrive, fakeOffline); } } @@ -591,6 +600,7 @@ function volumeNestingEntries( volumeMetadata.driveLabel || '', VolumeManagerCommon.RootType.REMOVABLE, volumeMetadata.devicePath); appendEntry(state, parentEntry); + state.uiEntries = [...state.uiEntries, parentEntry.toURL()]; // Removable devices with group, its parent should always be ejectable. state.allEntries[parentKey].isEjectable = true; } diff --git a/ui/file_manager/file_manager/state/reducers/all_entries_unittest.ts b/ui/file_manager/file_manager/state/reducers/all_entries_unittest.ts index 6364ebc6b70102..794ef5531602f1 100644 --- a/ui/file_manager/file_manager/state/reducers/all_entries_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/all_entries_unittest.ts @@ -2,26 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertArrayEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; +import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; import {MockVolumeManager} from '../../background/js/mock_volume_manager.js'; import {EntryList, FakeEntryImpl, VolumeEntry} from '../../common/js/files_app_entry_types.js'; import {MockFileSystem} from '../../common/js/mock_entry.js'; import {waitUntil} from '../../common/js/test_error_reporting.js'; import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; -import {EntryType, FileData} from '../../externs/ts/state.js'; +import {EntryType, FileData, State} from '../../externs/ts/state.js'; import {VolumeInfo} from '../../externs/volume_info.js'; -import {constants} from '../../foreground/js/constants.js'; import {MetadataItem} from '../../foreground/js/metadata/metadata_item.js'; import {MockMetadataModel} from '../../foreground/js/metadata/mock_metadata.js'; import {ActionType} from '../actions.js'; -import {addChildEntries as addChildEntriesAction, ClearStaleCachedEntriesAction} from '../actions/all_entries.js'; -import {addVolume} from '../actions/volumes.js'; -import {allEntriesSize, assertAllEntriesEqual, cd, changeSelection, createFakeFileData, createFakeVolume, createFakeVolumeMetadata, setUpFileManagerOnWindow, setupStore, updMetadata} from '../for_tests.js'; +import {addChildEntries, ClearStaleCachedEntriesAction} from '../actions/all_entries.js'; +import {allEntriesSize, assertAllEntriesEqual, cd, changeSelection, createFakeVolumeMetadata, setUpFileManagerOnWindow, setupStore, updMetadata, waitDeepEquals} from '../for_tests.js'; import {getEmptyState, Store} from '../store.js'; -import {addChildEntries, cacheEntries, clearCachedEntries, getMyFiles} from './all_entries.js'; -import {driveRootEntryListKey, makeRemovableParentKey, myFilesEntryListKey} from './volumes.js'; +import {clearCachedEntries, convertEntryToFileData, getMyFiles} from './all_entries.js'; +import {convertVolumeInfoAndMetadataToVolume, myFilesEntryListKey} from './volumes.js'; let store: Store; let fileSystem: MockFileSystem; @@ -47,25 +45,19 @@ export function setUp() { /** Generate MyFiles entry with fake entry list. */ function createMyFilesDataWithEntryList(): FileData { - return createFakeFileData({ - entry: new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES), - label: 'My files', - type: EntryType.ENTRY_LIST, - }); + const myFilesEntryList = + new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES); + return convertEntryToFileData(myFilesEntryList); } /** Generate MyFiles entry with real volume entry. */ function createMyFilesDataWithVolumeEntry(): {fileData: FileData, volumeInfo: VolumeInfo} { - const {volumeManager} = window.fileManager; + const volumeManager = new MockVolumeManager(); const downloadsVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( VolumeManagerCommon.VolumeType.DOWNLOADS)!; - const fileData = createFakeFileData({ - entry: new VolumeEntry(downloadsVolumeInfo), - volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS, - label: 'My files', - type: EntryType.VOLUME_ROOT, - }); + const myFilesVolumeEntry = new VolumeEntry(downloadsVolumeInfo); + const fileData = convertEntryToFileData(myFilesVolumeEntry); return {fileData, volumeInfo: downloadsVolumeInfo}; } @@ -248,12 +240,9 @@ export function testGetMyFilesWithVolumeEntry() { const currentState = getEmptyState(); // Add MyFiles volume to the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); - const volume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); + const volumeMetadata = createFakeVolumeMetadata(volumeInfo); + const volume = + convertVolumeInfoAndMetadataToVolume(volumeInfo, volumeMetadata); currentState.allEntries[fileData.entry.toURL()] = fileData; currentState.volumes[volumeInfo.volumeId] = volume; const {myFilesEntry, myFilesVolume} = getMyFiles(currentState); @@ -280,287 +269,76 @@ export function testGetMyFilesCreateEntryList() { assertEquals(null, myFilesVolume); } -/** Tests that MyFiles volume entry can be cached correctly. */ -export function testCacheEntriesForMyFilesVolume() { - const currentState = getEmptyState(); - const myFilesFileData = createMyFilesDataWithEntryList(); - const myFilesEntryList = myFilesFileData.entry as EntryList; - // Put MyFiles entry in the store and add ui entries as its children. - currentState.allEntries[myFilesEntryList.toURL()] = myFilesFileData; - const playFilesEntry = new FakeEntryImpl( - 'Play files', VolumeManagerCommon.RootType.ANDROID_FILES); - myFilesEntryList.addEntry(playFilesEntry); - const linuxFilesEntry = - new FakeEntryImpl('Linux files', VolumeManagerCommon.RootType.CROSTINI); - myFilesEntryList.addEntry(linuxFilesEntry); - - const {volumeManager} = window.fileManager; - const myFilesVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( - VolumeManagerCommon.VolumeType.DOWNLOADS)!; - const myFilesVolumeMetadata = createFakeVolumeMetadata({ - volumeId: myFilesVolumeInfo.volumeId, - volumeType: myFilesVolumeInfo.volumeType, - }); - cacheEntries(currentState, addVolume({ - volumeInfo: myFilesVolumeInfo, - volumeMetadata: myFilesVolumeMetadata, - })); - - // cacheEntries() updates state in place. - const newState = currentState; - // Expect all existing ui children will be added to the real MyFiles entry. - const myFilesVolumeEntry: VolumeEntry = - newState.allEntries[myFilesVolumeInfo.displayRoot!.toURL()].entry; - const uiChildren = myFilesVolumeEntry.getUIChildren(); - assertEquals(2, uiChildren.length); - assertEquals(playFilesEntry, uiChildren[0]); - assertEquals(linuxFilesEntry, uiChildren[1]); - assertEquals(0, myFilesEntryList.getUIChildren().length); -} - -/** Tests that volume nested in MyFiles volume can be cached correctly. */ -export function testCacheEntriesForNestedVolumeInMyFilesVolume() { - const currentState = getEmptyState(); - // Put MyFiles and play files ui entry in the store. - const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); - const myFilesVolumeEntry = fileData.entry as VolumeEntry; - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; - // Placeholder ui entry and the volume entry it represents have the same - // label. - const label = 'Play files'; - const playFilesUiEntry = - new FakeEntryImpl(label, VolumeManagerCommon.RootType.ANDROID_FILES); - myFilesVolumeEntry.addEntry(playFilesUiEntry); - fileData.children.push(playFilesUiEntry.toURL()); - - const {volumeManager} = window.fileManager; - const playFilesVolumeInfo = MockVolumeManager.createMockVolumeInfo( - VolumeManagerCommon.VolumeType.ANDROID_FILES, 'playFilesId', label); - volumeManager.volumeInfoList.add(playFilesVolumeInfo); - const playFilesVolumeMetadata = createFakeVolumeMetadata({ - volumeType: playFilesVolumeInfo.volumeType, - volumeId: playFilesVolumeInfo.volumeId, - }); - cacheEntries(currentState, addVolume({ - volumeInfo: playFilesVolumeInfo, - volumeMetadata: playFilesVolumeMetadata, - })); - // cacheEntries() updates state in place. - const newState = currentState; - // Expect the new play file volume will be nested inside MyFiles and the old - // placeholder will be removed. - const playFilesVolumeEntry = - newState.allEntries[playFilesVolumeInfo.displayRoot!.toURL()].entry; - const newMyFilesFileData: FileData = - newState.allEntries[myFilesVolumeEntry.toURL()]; - assertEquals(1, myFilesVolumeEntry.getUIChildren().length); - assertEquals(playFilesVolumeEntry, myFilesVolumeEntry.getUIChildren()[0]); - assertEquals(1, newMyFilesFileData.children.length); - assertEquals(playFilesVolumeEntry.toURL(), newMyFilesFileData.children[0]); -} - -/** Tests that drive volume can be cached correctly. */ -export function testAddDriveVolume(done: () => void) { - const currentState = getEmptyState(); - - const {volumeManager} = window.fileManager; - const driveVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( - VolumeManagerCommon.VolumeType.DRIVE)!; - const driveVolumeMetadata = createFakeVolumeMetadata({ - volumeType: driveVolumeInfo.volumeType, - volumeId: driveVolumeInfo.volumeId, - }); - // DriveFS takes time to resolve. - driveVolumeInfo.resolveDisplayRoot(() => { - cacheEntries(currentState, addVolume({ - volumeInfo: driveVolumeInfo, - volumeMetadata: driveVolumeMetadata, - })); - // cacheEntries() updates state in place. - const newState = currentState; - // Expect all fake entries inside Drive will be added as its children. - const driveFakeRootEntry: EntryList = - newState.allEntries[driveRootEntryListKey].entry; - assertEquals( - VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT, - driveFakeRootEntry.rootType); - const driveChildren = driveFakeRootEntry.getUIChildren(); - assertEquals(5, driveChildren.length); - // My Drive. - const myDriveEntry: VolumeEntry = - newState.allEntries[driveChildren[0]!.toURL()].entry; - assertEquals(myDriveEntry, driveChildren[0]); - // Shared drives root. - const sharedDrivesRootEntry: DirectoryEntry = - newState.allEntries[driveChildren[1]!.toURL()].entry; - assertEquals('/team_drives', sharedDrivesRootEntry.fullPath); - assertEquals(sharedDrivesRootEntry, driveChildren[1]); - // Computers root. - const computersRootEntry: DirectoryEntry = - newState.allEntries[driveChildren[2]!.toURL()].entry; - assertEquals('/Computers', computersRootEntry.fullPath); - assertEquals(computersRootEntry, driveChildren[2]); - // Shared with me. - const sharedWithMeEntry: FakeEntryImpl = - newState.allEntries[driveChildren[3]!.toURL()].entry; - assertEquals( - VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME, - sharedWithMeEntry.rootType); - assertEquals(sharedWithMeEntry, driveChildren[3]); - // Offline. - const offlineEntry: FakeEntryImpl = - newState.allEntries[driveChildren[4]!.toURL()].entry; - assertEquals(offlineEntry, driveChildren[4]); - assertEquals( - VolumeManagerCommon.RootType.DRIVE_OFFLINE, offlineEntry.rootType); - assertArrayEquals( - [sharedWithMeEntry.toURL(), offlineEntry.toURL()], newState.uiEntries); - - done(); - }); -} - -/** Tests that multiple partition volumes can be cached correctly. */ -export function testCacheEntriesForMultipleUsbPartitionsGrouping() { - const currentState = getEmptyState(); - // Add partition-1 into the store. - const {volumeManager} = window.fileManager; - const partition1VolumeInfo = MockVolumeManager.createMockVolumeInfo( - VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition1', - 'Partition 1', '/device/path/1'); - volumeManager.volumeInfoList.add(partition1VolumeInfo); - const partition1VolumeEntry = new VolumeEntry(partition1VolumeInfo); - const partition1FileData = createFakeFileData({ - entry: partition1VolumeEntry, - label: partition1VolumeInfo.label, - type: EntryType.VOLUME_ROOT, - }); - const partition1Volume = createFakeVolume({ - volumeId: partition1VolumeInfo.volumeId, - volumeType: VolumeManagerCommon.VolumeType.REMOVABLE, - rootKey: partition1VolumeInfo.displayRoot!.toURL(), - label: partition1VolumeInfo.label, - devicePath: partition1VolumeInfo.devicePath, - driveLabel: 'USB_Drive', - }); - currentState.volumes[partition1Volume.volumeId] = partition1Volume; - currentState.allEntries[partition1VolumeEntry.toURL()] = partition1FileData; - - const partition2VolumeInfo = MockVolumeManager.createMockVolumeInfo( - VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition2', - 'Partition 2', partition1Volume.devicePath); - volumeManager.volumeInfoList.add(partition2VolumeInfo); - const partition2VolumeMetadata = createFakeVolumeMetadata({ - volumeType: partition2VolumeInfo.volumeType, - volumeId: partition2VolumeInfo.volumeId, - devicePath: partition1Volume.devicePath, - driveLabel: partition1Volume.driveLabel, - }); - cacheEntries(currentState, addVolume({ - volumeInfo: partition2VolumeInfo, - volumeMetadata: partition2VolumeMetadata, - })); - // cacheEntries() updates state in place. - const newState = currentState; - // Expect a fake parent entry list will be created. - const parentEntryFileData: FileData = - newState.allEntries[makeRemovableParentKey(partition1Volume)]; - const parentEntry = parentEntryFileData.entry as EntryList; - assertEquals('USB_Drive', parentEntry.label); - assertEquals(VolumeManagerCommon.RootType.REMOVABLE, parentEntry.rootType); - assertTrue(parentEntryFileData.isEjectable); - // Expect both partition1 and partition2 will be added as children. - const partition2VolumeEntry: VolumeEntry = - newState.allEntries[partition2VolumeInfo.displayRoot!.toURL()].entry; - assertEquals(2, parentEntry.getUIChildren().length); - assertEquals(partition1VolumeEntry, parentEntry.getUIChildren()[0]); - assertEquals(partition2VolumeEntry, parentEntry.getUIChildren()[1]); - assertEquals( - constants.ICON_TYPES.UNKNOWN_REMOVABLE, - newState.allEntries[partition1VolumeEntry.toURL()].icon); - assertEquals( - constants.ICON_TYPES.UNKNOWN_REMOVABLE, - newState.allEntries[partition2VolumeEntry.toURL()].icon); -} - /** Tests that child entries can be added to the store correctly. */ -export function testAddChildEntries() { - const currentState = getEmptyState(); +export async function testAddChildEntries(done: () => void) { + const initialState = getEmptyState(); // Add parent/children entries to the store. - const fakeFs = new MockFileSystem('fake-fs'); - fakeFs.populate([ - '/aaa/', - '/aaa/1/', - '/aaa/2/', - '/aaa/2/123/', + fileSystem.populate([ + '/a/', + '/a/1/', + '/a/2/', + '/a/2/b/', ]); - currentState.allEntries[fakeFs.entries['/aaa'].toURL()] = createFakeFileData({ - entry: fakeFs.entries['/aaa'], - label: 'AAA', - type: EntryType.FS_API, - }); - currentState.allEntries[fakeFs.entries['/aaa/1'].toURL()] = - createFakeFileData({ - entry: fakeFs.entries['/aaa/1'], - label: 'AAA 1', - type: EntryType.FS_API, - }); - currentState.allEntries[fakeFs.entries['/aaa/2'].toURL()] = - createFakeFileData({ - entry: fakeFs.entries['/aaa/2'], - label: 'AAA 2', - type: EntryType.FS_API, - shouldDelayLoadingChildren: true, - }); - currentState.allEntries[fakeFs.entries['/aaa/2/123'].toURL()] = - createFakeFileData({ - entry: fakeFs.entries['/aaa/2/123'], - label: 'AAA 123', - type: EntryType.FS_API, - }); - - // Add child entries for /aaa/. - const newState1 = addChildEntries( - currentState, addChildEntriesAction({ - parentKey: fakeFs.entries['/aaa'].toURL(), - entries: [fakeFs.entries['/aaa/1'], fakeFs.entries['/aaa/2']], - })); - // Expect the children filed is updated. - const newChildren1 = - newState1.allEntries[fakeFs.entries['/aaa'].toURL()].children; - assertEquals(2, newChildren1.length); - assertEquals(fakeFs.entries['/aaa/1'].toURL(), newChildren1[0]); - assertEquals(fakeFs.entries['/aaa/2'].toURL(), newChildren1[1]); - - // Add child entries for /aaa/2 who has shouldDelayLoadingChildren. - assertFalse(currentState.allEntries[fakeFs.entries['/aaa/2/123'].toURL()] - .shouldDelayLoadingChildren); - const newState2 = - addChildEntries(currentState, addChildEntriesAction({ - parentKey: fakeFs.entries['/aaa/2'].toURL(), - entries: [fakeFs.entries['/aaa/2/123']], - })); - // Expect child entry also has shouldDelayLoadingChildren=true. - const newChildren2 = - newState2.allEntries[fakeFs.entries['/aaa/2'].toURL()].children; - assertEquals(1, newChildren2.length); - assertEquals(fakeFs.entries['/aaa/2/123'].toURL(), newChildren2[0]); - assertTrue(newState2.allEntries[fakeFs.entries['/aaa/2/123'].toURL()] - .shouldDelayLoadingChildren); - - // Add child entries for non-existed parent. - const newState3 = addChildEntries(currentState, addChildEntriesAction({ - parentKey: 'non-exist-key', - entries: [fakeFs.entries['/aaa/1']], - })); - // Expect nothing happens. - assertEquals(currentState, newState3); + const aEntry = fileSystem.entries['/a']; + initialState.allEntries[aEntry.toURL()] = convertEntryToFileData(aEntry); + // Make sure aEntry won't be cleared. + initialState.uiEntries.push(aEntry.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to add child entries for /aaa/. + const a1Entry = fileSystem.entries['/a/1']; + const a2Entry = fileSystem.entries['/a/2']; + store.dispatch(addChildEntries({ + parentKey: aEntry.toURL(), + entries: [a1Entry, a2Entry], + })); + + // Expect the children filed of /a is updated. + const want1: State['allEntries'] = { + [aEntry.toURL()]: { + ...convertEntryToFileData(aEntry), + children: [a1Entry.toURL(), a2Entry.toURL()], + }, + [a1Entry.toURL()]: convertEntryToFileData(a1Entry), + [a2Entry.toURL()]: convertEntryToFileData(a2Entry), + }; + await waitDeepEquals(store, want1, (state) => state.allEntries); + + // Set shouldDelayLoadingChildren=true for /a/2. + store.getState().allEntries[a2Entry.toURL()].shouldDelayLoadingChildren = + true; + // Dispatch an action to add child entries for /a/2. + const bEntry = fileSystem.entries['/a/2/b']; + store.dispatch(addChildEntries({ + parentKey: a2Entry.toURL(), + entries: [bEntry], + })); + + // Expect child entry /a/2/b also has shouldDelayLoadingChildren=true. + const want2: State['allEntries'] = { + ...want1, + [a2Entry.toURL()]: { + ...convertEntryToFileData(a2Entry), + shouldDelayLoadingChildren: true, + children: [bEntry.toURL()], + }, + [bEntry.toURL()]: { + ...convertEntryToFileData(bEntry), + shouldDelayLoadingChildren: true, + }, + }; + await waitDeepEquals(store, want2, (state) => state.allEntries); + + // Dispatch an action to add child entries for non-existed parent entry. + store.dispatch(addChildEntries({ + parentKey: 'non-exist-key', + entries: [a1Entry], + })); + + // Expect nothing changes in the store. + await waitDeepEquals(store, want2, (state) => state.allEntries); + + done(); } diff --git a/ui/file_manager/file_manager/state/reducers/android_apps_unittest.ts b/ui/file_manager/file_manager/state/reducers/android_apps_unittest.ts index 8b44457b6503c6..923fd4c546ccbb 100644 --- a/ui/file_manager/file_manager/state/reducers/android_apps_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/android_apps_unittest.ts @@ -2,16 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertEquals} from 'chrome://webui-test/chai_assert.js'; - -import {addAndroidApps as addAndroidAppsAction} from '../actions/android_apps.js'; -import {getEmptyState} from '../store.js'; - -import {addAndroidApps} from './android_apps.js'; +import {State} from '../../externs/ts/state.js'; +import {addAndroidApps} from '../actions/android_apps.js'; +import {setupStore, waitDeepEquals} from '../for_tests.js'; /** Tests that android apps can be added correctly to the store. */ -export function testAddAndroidApps() { - const currentState = getEmptyState(); +export async function testAddAndroidApps(done: () => void) { const androidApps: chrome.fileManagerPrivate.AndroidApp[] = [ { name: 'App 1', @@ -26,15 +22,17 @@ export function testAddAndroidApps() { iconSet: {icon16x16Url: 'url3', icon32x32Url: 'url4'}, }, ]; - const newState = - addAndroidApps(currentState, addAndroidAppsAction({apps: androidApps})); - const keys = Object.keys(newState.androidApps); - assertEquals(2, keys.length); - assertEquals('com.test.app1', keys[0]); - assertEquals('com.test.app2', keys[1]); - assertEquals('App 1', newState.androidApps[keys[0]!].name); - assertEquals('App 2', newState.androidApps[keys[1]!].name); - assertEquals('Activity1', newState.androidApps[keys[0]!].activityName); - assertEquals('url1', newState.androidApps[keys[0]!].iconSet.icon16x16Url); - assertEquals('url4', newState.androidApps[keys[1]!].iconSet.icon32x32Url); + + // Dispatch an action to add android apps. + const store = setupStore(); + store.dispatch(addAndroidApps({apps: androidApps})); + + // Expect both android apps are existed in the store. + const want: State['androidApps'] = { + 'com.test.app1': androidApps[0], + 'com.test.app2': androidApps[1], + }; + await waitDeepEquals(store, want, (state) => state.androidApps); + + done(); } diff --git a/ui/file_manager/file_manager/state/reducers/folder_shortcuts_unittest.ts b/ui/file_manager/file_manager/state/reducers/folder_shortcuts_unittest.ts index cc2c6527d3b26b..3573b72cc88712 100644 --- a/ui/file_manager/file_manager/state/reducers/folder_shortcuts_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/folder_shortcuts_unittest.ts @@ -2,16 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertEquals} from 'chrome://webui-test/chai_assert.js'; - import {MockFileSystem} from '../../common/js/mock_entry.js'; -import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; -import {EntryType} from '../../externs/ts/state.js'; -import {addFolderShortcut as addFolderShortcutAction, refreshFolderShortcut as refreshFolderShortcutAction, removeFolderShortcut as removeFolderShortcutAction} from '../actions/folder_shortcuts.js'; -import {createFakeFileData} from '../for_tests.js'; +import {State} from '../../externs/ts/state.js'; +import {addFolderShortcut, refreshFolderShortcut, removeFolderShortcut} from '../actions/folder_shortcuts.js'; +import {setupStore, waitDeepEquals} from '../for_tests.js'; import {getEmptyState} from '../store.js'; -import {addFolderShortcut, refreshFolderShortcut, removeFolderShortcut} from './folder_shortcuts.js'; +import {convertEntryToFileData} from './all_entries.js'; /** Generate a fake file system with fake file entries. */ function setupFileSystem(): MockFileSystem { @@ -25,92 +22,137 @@ function setupFileSystem(): MockFileSystem { return fileSystem; } - /** Tests folder shortcuts can be refreshed correctly. */ -export function testRefreshFolderShortcuts() { - const currentState = getEmptyState(); +export async function testRefreshFolderShortcuts(done: () => void) { + const initialState = getEmptyState(); // Add shortcut-1 to the store. const fileSystem = setupFileSystem(); const shortcutEntry1: DirectoryEntry = fileSystem.entries['/shortcut-1']; + initialState.allEntries[shortcutEntry1.toURL()] = + convertEntryToFileData(shortcutEntry1); + initialState.folderShortcuts.push(shortcutEntry1.toURL()); + + const store = setupStore(initialState); + + // Dispatch a refresh action with shortcut 2 and 3. const shortcutEntry2: DirectoryEntry = fileSystem.entries['/shortcut-2']; const shortcutEntry3: DirectoryEntry = fileSystem.entries['/shortcut-3']; - currentState.allEntries[shortcutEntry1.toURL()] = createFakeFileData({ - entry: shortcutEntry1, - volumeType: VolumeManagerCommon.VolumeType.DRIVE, - label: 'shortcut 1', - type: EntryType.FS_API, - }); - currentState.folderShortcuts.push(shortcutEntry1.toURL()); - - const newState = refreshFolderShortcut( - currentState, - refreshFolderShortcutAction({entries: [shortcutEntry2, shortcutEntry3]})); - assertEquals(2, newState.folderShortcuts.length); - assertEquals(shortcutEntry2.toURL(), newState.folderShortcuts[0]); - assertEquals(shortcutEntry3.toURL(), newState.folderShortcuts[1]); + store.dispatch( + refreshFolderShortcut({entries: [shortcutEntry2, shortcutEntry3]})); + + // Expect all shortcut entries are in allEntries, and only shortcut 2 and 3 + // are in the folderShortcuts. + const want: Partial = { + allEntries: { + [shortcutEntry2.toURL()]: convertEntryToFileData(shortcutEntry2), + [shortcutEntry3.toURL()]: convertEntryToFileData(shortcutEntry3), + }, + folderShortcuts: [shortcutEntry2.toURL(), shortcutEntry3.toURL()], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + folderShortcuts: state.folderShortcuts, + })); + + done(); } /** Tests folder shortcut can be added correctly. */ -export function testAddFolderShortcut() { - const currentState = getEmptyState(); +export async function testAddFolderShortcut(done: () => void) { + const initialState = getEmptyState(); // Add shortcut-1 and shortcut-3 to the store. const fileSystem = setupFileSystem(); const shortcutEntry1: DirectoryEntry = fileSystem.entries['/shortcut-1']; - const shortcutEntry2: DirectoryEntry = fileSystem.entries['/shortcut-2']; const shortcutEntry3: DirectoryEntry = fileSystem.entries['/shortcut-3']; + initialState.allEntries[shortcutEntry1.toURL()] = + convertEntryToFileData(shortcutEntry1); + initialState.allEntries[shortcutEntry3.toURL()] = + convertEntryToFileData(shortcutEntry3); + initialState.folderShortcuts.push(shortcutEntry1.toURL()); + initialState.folderShortcuts.push(shortcutEntry3.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to add shortcut 4. const shortcutEntry4: DirectoryEntry = fileSystem.entries['/shortcut-4']; - currentState.allEntries[shortcutEntry1.toURL()] = createFakeFileData({ - entry: shortcutEntry1, - volumeType: VolumeManagerCommon.VolumeType.DRIVE, - label: 'shortcut 1', - type: EntryType.FS_API, - }); - currentState.allEntries[shortcutEntry3.toURL()] = createFakeFileData({ - entry: shortcutEntry3, - volumeType: VolumeManagerCommon.VolumeType.DRIVE, - label: 'shortcut 3', - type: EntryType.FS_API, - }); - currentState.folderShortcuts.push(shortcutEntry1.toURL()); - currentState.folderShortcuts.push(shortcutEntry3.toURL()); - - // Add a new shortcut. - const newState1 = addFolderShortcut( - currentState, addFolderShortcutAction({entry: shortcutEntry2})); - assertEquals(3, newState1.folderShortcuts.length); - assertEquals(shortcutEntry2.toURL(), newState1.folderShortcuts[1]); - // Add an already existed shortcut. - const newState2 = addFolderShortcut( - currentState, addFolderShortcutAction({entry: shortcutEntry1})); - assertEquals(newState2.folderShortcuts, currentState.folderShortcuts); - // Add another entry to check sorting. - const newState3 = addFolderShortcut( - currentState, addFolderShortcutAction({entry: shortcutEntry4})); - assertEquals(3, newState3.folderShortcuts.length); - assertEquals(shortcutEntry4.toURL(), newState3.folderShortcuts[2]); + store.dispatch(addFolderShortcut({entry: shortcutEntry4})); + + // Expect the newly added shortcut 4 is in the store. + const want1: Partial = { + allEntries: { + [shortcutEntry1.toURL()]: convertEntryToFileData(shortcutEntry1), + [shortcutEntry3.toURL()]: convertEntryToFileData(shortcutEntry3), + [shortcutEntry4.toURL()]: convertEntryToFileData(shortcutEntry4), + }, + folderShortcuts: [ + shortcutEntry1.toURL(), + shortcutEntry3.toURL(), + shortcutEntry4.toURL(), + ], + }; + await waitDeepEquals(store, want1, (state) => ({ + allEntries: state.allEntries, + folderShortcuts: state.folderShortcuts, + })); + + // Dispatch another action to add already existed shortcut 1. + store.dispatch(addFolderShortcut({entry: shortcutEntry1})); + + // Expect no changes in the store. + await waitDeepEquals(store, want1, (state) => ({ + allEntries: state.allEntries, + folderShortcuts: state.folderShortcuts, + })); + + // Dispatch another action to add shortcut 2 to check sorting. + const shortcutEntry2: DirectoryEntry = fileSystem.entries['/shortcut-2']; + store.dispatch(addFolderShortcut({entry: shortcutEntry2})); + + // Expect shortcut 2 will be inserted in the middle. + const want2: Partial = { + allEntries: { + ...want1.allEntries, + [shortcutEntry2.toURL()]: convertEntryToFileData(shortcutEntry2), + }, + folderShortcuts: [ + shortcutEntry1.toURL(), + shortcutEntry2.toURL(), + shortcutEntry3.toURL(), + shortcutEntry4.toURL(), + ], + }; + await waitDeepEquals(store, want2, (state) => ({ + allEntries: state.allEntries, + folderShortcuts: state.folderShortcuts, + })); + + done(); } /** Tests folder shortcut can be removed correctly. */ -export function testRemoveFolderShortcut() { - const currentState = getEmptyState(); +export async function testRemoveFolderShortcut(done: () => void) { + const initialState = getEmptyState(); // Add shortcut-1 to the store. const fileSystem = setupFileSystem(); const shortcutEntry1: DirectoryEntry = fileSystem.entries['/shortcut-1']; + initialState.allEntries[shortcutEntry1.toURL()] = + convertEntryToFileData(shortcutEntry1); + initialState.folderShortcuts.push(shortcutEntry1.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to remove shortcut 1. + store.dispatch(removeFolderShortcut({key: shortcutEntry1.toURL()})); + + // Expect shortcut 1 is removed from the store. + await waitDeepEquals(store, [], (state) => state.folderShortcuts); + + // Dispatch another action to remove non-existed shortcut 2. const shortcutEntry2: DirectoryEntry = fileSystem.entries['/shortcut-2']; - currentState.allEntries[shortcutEntry1.toURL()] = createFakeFileData({ - entry: shortcutEntry1, - volumeType: VolumeManagerCommon.VolumeType.DRIVE, - label: 'shortcut 1', - type: EntryType.FS_API, - }); - currentState.folderShortcuts.push(shortcutEntry1.toURL()); - - // Remove a shortcut. - const newState1 = removeFolderShortcut( - currentState, removeFolderShortcutAction({key: shortcutEntry1.toURL()})); - assertEquals(0, newState1.folderShortcuts.length); - // Remove a non-exist shortcut. - const newState2 = removeFolderShortcut( - currentState, removeFolderShortcutAction({key: shortcutEntry2.toURL()})); - assertEquals(newState2.folderShortcuts, currentState.folderShortcuts); + store.dispatch(removeFolderShortcut({key: shortcutEntry2.toURL()})); + + // Expect no changes in the store. + await waitDeepEquals(store, [], (state) => state.folderShortcuts); + + done(); } diff --git a/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts b/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts index 6dbb32629089e5..21e32d231c18d9 100644 --- a/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts @@ -2,20 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; - import {MockVolumeManager} from '../../background/js/mock_volume_manager.js'; import {EntryList, FakeEntryImpl, VolumeEntry} from '../../common/js/files_app_entry_types.js'; import {MockFileEntry, MockFileSystem} from '../../common/js/mock_entry.js'; import {TrashRootEntry} from '../../common/js/trash.js'; import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; -import {EntryType, FileData, NavigationSection, NavigationType, Volume} from '../../externs/ts/state.js'; -import {refreshNavigationRoots as refreshNavigationRootsAction, updateNavigationEntry as updateNavigationEntryAction} from '../actions/navigation.js'; -import {createFakeFileData, createFakeVolume, setUpFileManagerOnWindow} from '../for_tests.js'; +import {FileData, NavigationSection, NavigationType, State, Volume} from '../../externs/ts/state.js'; +import {refreshNavigationRoots, updateNavigationEntry} from '../actions/navigation.js'; +import {createFakeVolumeMetadata, setUpFileManagerOnWindow, setupStore, waitDeepEquals} from '../for_tests.js'; import {getEmptyState} from '../store.js'; -import {refreshNavigationRoots, updateNavigationEntry} from './navigation.js'; -import {driveRootEntryListKey, myFilesEntryListKey, recentRootKey, trashRootKey} from './volumes.js'; +import {convertEntryToFileData} from './all_entries.js'; +import {convertVolumeInfoAndMetadataToVolume, driveRootEntryListKey, myFilesEntryListKey, recentRootKey, trashRootKey} from './volumes.js'; export function setUp() { setUpFileManagerOnWindow(); @@ -27,11 +25,7 @@ function createRecentFileData(): FileData { 'Recent', VolumeManagerCommon.RootType.RECENT, chrome.fileManagerPrivate.SourceRestriction.ANY_SOURCE, chrome.fileManagerPrivate.FileCategory.ALL); - return createFakeFileData({ - entry: recentEntry, - label: 'Recent', - type: EntryType.RECENT, - }); + return convertEntryToFileData(recentEntry); } /** Create FileData for shortcut entry. */ @@ -39,11 +33,10 @@ function createShortcutEntryFileData( fileSystemName: string, entryName: string, label: string): FileData { const fakeFs = new MockFileSystem(fileSystemName); const shortcutEntry = MockFileEntry.create(fakeFs, `/root/${entryName}`); - return createFakeFileData({ - entry: shortcutEntry, + return { + ...convertEntryToFileData(shortcutEntry), label, - type: EntryType.FS_API, - }); + }; } /** Create FileData for MyFiles entry. */ @@ -52,40 +45,23 @@ function createMyFilesEntryFileData(): {fileData: FileData, volume: Volume} { const downloadsVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( VolumeManagerCommon.VolumeType.DOWNLOADS)!; const myFilesEntry = new VolumeEntry(downloadsVolumeInfo); - const fileData = createFakeFileData({ - entry: myFilesEntry, - volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS, - label: 'My files', - type: EntryType.VOLUME_ROOT, - }); - const volume = createFakeVolume({ - volumeId: downloadsVolumeInfo.volumeId, - volumeType: fileData.volumeType!, - label: fileData.label, - rootKey: fileData.entry.toURL(), - }); + const fileData = convertEntryToFileData(myFilesEntry); + const volume = convertVolumeInfoAndMetadataToVolume( + downloadsVolumeInfo, createFakeVolumeMetadata(downloadsVolumeInfo)); return {fileData, volume}; } /** Create FileData for drive root entry. */ -function createDriveRootEntryFileData(): FileData { - const driveEntry = new EntryList( +function createDriveRootEntryListFileData(): FileData { + const driveRootEntryList = new EntryList( 'Google Drive', VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT); - return createFakeFileData({ - entry: driveEntry, - label: 'Google Drive', - type: EntryType.ENTRY_LIST, - }); + return convertEntryToFileData(driveRootEntryList); } /** Create FileData for trash entry. */ function createTrashEntryFileData(): FileData { const trashEntry = new TrashRootEntry(); - return createFakeFileData({ - entry: trashEntry, - label: 'Bin', - type: EntryType.TRASH, - }); + return convertEntryToFileData(trashEntry); } /** Create android apps. */ @@ -118,18 +94,9 @@ function createVolumeFileData( const {volumeManager} = window.fileManager; volumeManager.volumeInfoList.add(volumeInfo); const volumeEntry = new VolumeEntry(volumeInfo); - const fileData = createFakeFileData({ - entry: volumeEntry, - label, - type: EntryType.VOLUME_ROOT, - }); - const volume = createFakeVolume({ - volumeId: volumeInfo.volumeId, - volumeType: volumeInfo.volumeType, - label, - rootKey: volumeEntry.toURL(), - devicePath, - }); + const fileData = convertEntryToFileData(volumeEntry); + const volume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); return {fileData, volume}; } @@ -139,44 +106,44 @@ function createVolumeFileData( * 2. manages NavigationSection for the relevant volumes. * 3. keeps MTP/Archive/Removable volumes on the original order. */ -export function testNavigationRoots() { - const currentState = getEmptyState(); +export async function testNavigationRoots(done: () => void) { + const initialState = getEmptyState(); // Put recent entry in the store. const recentEntryFileData = createRecentFileData(); - currentState.allEntries[recentRootKey] = recentEntryFileData; + initialState.allEntries[recentRootKey] = recentEntryFileData; // Put 2 shortcut entries in the store. const shortcutEntryFileData1 = createShortcutEntryFileData('drive', 'shortcut1', 'Shortcut 1'); - currentState.allEntries[shortcutEntryFileData1.entry.toURL()] = + initialState.allEntries[shortcutEntryFileData1.entry.toURL()] = shortcutEntryFileData1; - currentState.folderShortcuts.push(shortcutEntryFileData1.entry.toURL()); + initialState.folderShortcuts.push(shortcutEntryFileData1.entry.toURL()); const shortcutEntryFileData2 = createShortcutEntryFileData('drive', 'shortcut2', 'Shortcut 2'); - currentState.allEntries[shortcutEntryFileData2.entry.toURL()] = + initialState.allEntries[shortcutEntryFileData2.entry.toURL()] = shortcutEntryFileData2; - currentState.folderShortcuts.push(shortcutEntryFileData2.entry.toURL()); + initialState.folderShortcuts.push(shortcutEntryFileData2.entry.toURL()); // Put MyFiles entry in the store. const myFilesVolume = createMyFilesEntryFileData(); - currentState.allEntries[myFilesVolume.fileData.entry.toURL()] = + initialState.allEntries[myFilesVolume.fileData.entry.toURL()] = myFilesVolume.fileData; - currentState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; + initialState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; // Put drive entry in the store. - const driveRootEntryFileData = createDriveRootEntryFileData(); - currentState.allEntries[driveRootEntryListKey] = driveRootEntryFileData; + const driveRootEntryFileData = createDriveRootEntryListFileData(); + initialState.allEntries[driveRootEntryListKey] = driveRootEntryFileData; // Put trash entry in the store. const trashEntryFileData = createTrashEntryFileData(); - currentState.allEntries[trashRootKey] = trashEntryFileData; + initialState.allEntries[trashRootKey] = trashEntryFileData; // Put the android apps in the store. const androidAppsData = createAndroidApps(); - currentState.androidApps[androidAppsData[0].packageName] = androidAppsData[0]; - currentState.androidApps[androidAppsData[1].packageName] = androidAppsData[1]; + initialState.androidApps[androidAppsData[0].packageName] = androidAppsData[0]; + initialState.androidApps[androidAppsData[1].packageName] = androidAppsData[1]; // Create different volumes. const providerVolume1 = createVolumeFileData( VolumeManagerCommon.VolumeType.PROVIDED, 'provided:prov1'); - currentState.allEntries[providerVolume1.fileData.entry.toURL()] = + initialState.allEntries[providerVolume1.fileData.entry.toURL()] = providerVolume1.fileData; - currentState.volumes[providerVolume1.volume.volumeId] = + initialState.volumes[providerVolume1.volume.volumeId] = providerVolume1.volume; // Set the device paths of the removable volumes to different strings to @@ -184,53 +151,56 @@ export function testNavigationRoots() { const hogeVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge', 'Hoge', 'device/path/1'); - currentState.allEntries[hogeVolume.fileData.entry.toURL()] = + initialState.allEntries[hogeVolume.fileData.entry.toURL()] = hogeVolume.fileData; - currentState.volumes[hogeVolume.volume.volumeId] = hogeVolume.volume; + initialState.volumes[hogeVolume.volume.volumeId] = hogeVolume.volume; const fugaVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga', 'Fuga', 'device/path/2'); - currentState.allEntries[fugaVolume.fileData.entry.toURL()] = + initialState.allEntries[fugaVolume.fileData.entry.toURL()] = fugaVolume.fileData; - currentState.volumes[fugaVolume.volume.volumeId] = fugaVolume.volume; + initialState.volumes[fugaVolume.volume.volumeId] = fugaVolume.volume; const archiveVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.ARCHIVE, 'archive:a-rar'); - currentState.allEntries[archiveVolume.fileData.entry.toURL()] = + initialState.allEntries[archiveVolume.fileData.entry.toURL()] = archiveVolume.fileData; - currentState.volumes[archiveVolume.volume.volumeId] = archiveVolume.volume; + initialState.volumes[archiveVolume.volume.volumeId] = archiveVolume.volume; const mtpVolume = createVolumeFileData(VolumeManagerCommon.VolumeType.MTP, 'mtp:a-phone'); - currentState.allEntries[mtpVolume.fileData.entry.toURL()] = + initialState.allEntries[mtpVolume.fileData.entry.toURL()] = mtpVolume.fileData; - currentState.volumes[mtpVolume.volume.volumeId] = mtpVolume.volume; + initialState.volumes[mtpVolume.volume.volumeId] = mtpVolume.volume; const providerVolume2 = createVolumeFileData( VolumeManagerCommon.VolumeType.PROVIDED, 'provided:prov2'); - currentState.allEntries[providerVolume2.fileData.entry.toURL()] = + initialState.allEntries[providerVolume2.fileData.entry.toURL()] = providerVolume2.fileData; - currentState.volumes[providerVolume2.volume.volumeId] = + initialState.volumes[providerVolume2.volume.volumeId] = providerVolume2.volume; const androidFilesVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.ANDROID_FILES, 'android_files:droid'); androidFilesVolume.volume.prefixKey = myFilesVolume.fileData.entry.toURL(); - currentState.allEntries[androidFilesVolume.fileData.entry.toURL()] = + initialState.allEntries[androidFilesVolume.fileData.entry.toURL()] = androidFilesVolume.fileData; - currentState.volumes[androidFilesVolume.volume.volumeId] = + initialState.volumes[androidFilesVolume.volume.volumeId] = androidFilesVolume.volume; const smbVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.SMB, 'smb:file-share'); - currentState.allEntries[smbVolume.fileData.entry.toURL()] = + initialState.allEntries[smbVolume.fileData.entry.toURL()] = smbVolume.fileData; - currentState.volumes[smbVolume.volume.volumeId] = smbVolume.volume; + initialState.volumes[smbVolume.volume.volumeId] = smbVolume.volume; - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); - // Navigation roots built above: + const store = setupStore(initialState); + + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); + + // Expect navigation roots being built in the store: // 1. fake-entry://recent // 2. /root/shortcut1 // 3. /root/shortcut2 @@ -253,357 +223,449 @@ export function testNavigationRoots() { // Check items order and that MTP/Archive/Removable respect the original // order. - const {roots} = newState.navigation; - assertEquals(15, roots.length); - - // recent. - assertEquals(recentEntryFileData.entry.toURL(), roots[0]!.key); - assertEquals(NavigationSection.TOP, roots[0]!.section); - assertEquals(false, roots[0]!.separator); - assertEquals(NavigationType.RECENT, roots[0]!.type); - // shortcut1. - assertEquals(shortcutEntryFileData1.entry.toURL(), roots[1]!.key); - assertEquals(NavigationSection.TOP, roots[1]!.section); - assertEquals(false, roots[1]!.separator); - assertEquals(NavigationType.SHORTCUT, roots[1]!.type); - // shortcut2. - assertEquals(shortcutEntryFileData2.entry.toURL(), roots[2]!.key); - assertEquals(NavigationSection.TOP, roots[2]!.section); - assertEquals(false, roots[2]!.separator); - assertEquals(NavigationType.SHORTCUT, roots[2]!.type); - - // My Files. - assertEquals(myFilesVolume.fileData.entry.toURL(), roots[3]!.key); - assertEquals(NavigationSection.MY_FILES, roots[3]!.section); - assertEquals(true, roots[3]!.separator); - assertEquals(NavigationType.VOLUME, roots[3]!.type); - - // Drive. - assertEquals(driveRootEntryFileData.entry.toURL(), roots[4]!.key); - assertEquals(NavigationSection.CLOUD, roots[4]!.section); - assertEquals(true, roots[4]!.separator); - assertEquals(NavigationType.DRIVE, roots[4]!.type); - - // Trash. - assertEquals(trashEntryFileData.entry.toURL(), roots[5]!.key); - assertEquals(NavigationSection.TRASH, roots[5]!.section); - assertEquals(true, roots[5]!.separator); - assertEquals(NavigationType.TRASH, roots[5]!.type); - - // FSP, and SMB are grouped together. - // smb:file-share. - assertEquals(smbVolume.fileData.entry.toURL(), roots[6]!.key); - assertEquals(NavigationSection.CLOUD, roots[6]!.section); - assertEquals(true, roots[6]!.separator); - assertEquals(NavigationType.VOLUME, roots[6]!.type); - // provided:prov1. - assertEquals(providerVolume1.fileData.entry.toURL(), roots[7]!.key); - assertEquals(NavigationSection.CLOUD, roots[7]!.section); - assertEquals(false, roots[7]!.separator); - assertEquals(NavigationType.VOLUME, roots[7]!.type); - // provided:prov2. - assertEquals(providerVolume2.fileData.entry.toURL(), roots[8]!.key); - assertEquals(NavigationSection.CLOUD, roots[8]!.section); - assertEquals(false, roots[8]!.separator); - assertEquals(NavigationType.VOLUME, roots[8]!.type); - - // MTP/Archive/Removable are grouped together. - // removable:hoge. - assertEquals(hogeVolume.fileData.entry.toURL(), roots[9]!.key); - assertEquals(NavigationSection.REMOVABLE, roots[9]!.section); - assertEquals(true, roots[9]!.separator); - assertEquals(NavigationType.VOLUME, roots[9]!.type); - // removable:fuga. - assertEquals(fugaVolume.fileData.entry.toURL(), roots[10]!.key); - assertEquals(NavigationSection.REMOVABLE, roots[10]!.section); - assertEquals(false, roots[10]!.separator); - assertEquals(NavigationType.VOLUME, roots[10]!.type); - // archive:a-rar. - assertEquals(archiveVolume.fileData.entry.toURL(), roots[11]!.key); - assertEquals(NavigationSection.REMOVABLE, roots[11]!.section); - assertEquals(false, roots[11]!.separator); - assertEquals(NavigationType.VOLUME, roots[11]!.type); - // mtp:a-phone. - assertEquals(mtpVolume.fileData.entry.toURL(), roots[12]!.key); - assertEquals(NavigationSection.REMOVABLE, roots[12]!.section); - assertEquals(false, roots[12]!.separator); - assertEquals(NavigationType.VOLUME, roots[12]!.type); - - // android:app1 - assertEquals(androidAppsData[0].packageName, roots[13]!.key); - assertEquals(NavigationSection.ANDROID_APPS, roots[13]!.section); - assertEquals(true, roots[13]!.separator); - assertEquals(NavigationType.ANDROID_APPS, roots[13]!.type); - // android:app2 - assertEquals(androidAppsData[1].packageName, roots[14]!.key); - assertEquals(NavigationSection.ANDROID_APPS, roots[14]!.section); - assertEquals(false, roots[14]!.separator); - assertEquals(NavigationType.ANDROID_APPS, roots[14]!.type); + const want: State['navigation']['roots'] = [ + // recent. + { + key: recentEntryFileData.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.RECENT, + }, + // shortcut1. + { + key: shortcutEntryFileData1.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.SHORTCUT, + }, + // shortcut2. + { + key: shortcutEntryFileData2.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.SHORTCUT, + }, + // My Files. + { + key: myFilesVolume.fileData.entry.toURL(), + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.VOLUME, + }, + // Drive. + { + key: driveRootEntryFileData.entry.toURL(), + section: NavigationSection.CLOUD, + separator: true, + type: NavigationType.DRIVE, + }, + // Trash. + { + key: trashEntryFileData.entry.toURL(), + section: NavigationSection.TRASH, + separator: true, + type: NavigationType.TRASH, + }, + // FSP, and SMB are grouped together. + // smb:file-share. + { + key: smbVolume.fileData.entry.toURL(), + section: NavigationSection.CLOUD, + separator: true, + type: NavigationType.VOLUME, + }, + // provided:prov1. + { + key: providerVolume1.fileData.entry.toURL(), + section: NavigationSection.CLOUD, + separator: false, + type: NavigationType.VOLUME, + }, + // provided:prov2. + { + key: providerVolume2.fileData.entry.toURL(), + section: NavigationSection.CLOUD, + separator: false, + type: NavigationType.VOLUME, + }, + // MTP/Archive/Removable are grouped together. + // removable:hoge. + { + key: hogeVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: true, + type: NavigationType.VOLUME, + }, + // removable:fuga. + { + key: fugaVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: false, + type: NavigationType.VOLUME, + }, + // archive:a-rar. + { + key: archiveVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: false, + type: NavigationType.VOLUME, + }, + // mtp:a-phone. + { + key: mtpVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: false, + type: NavigationType.VOLUME, + }, + // android:app1. + { + key: androidAppsData[0].packageName, + section: NavigationSection.ANDROID_APPS, + separator: true, + type: NavigationType.ANDROID_APPS, + }, + // android:app2. + { + key: androidAppsData[1].packageName, + section: NavigationSection.ANDROID_APPS, + separator: false, + type: NavigationType.ANDROID_APPS, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** * Tests navigation roots with no Recents. */ -export function testNavigationRootsWithoutRecents() { - const currentState = getEmptyState(); +export async function testNavigationRootsWithoutRecents(done: () => void) { + const initialState = getEmptyState(); // Put shortcut entry in the store. const shortcutEntryFileData = createShortcutEntryFileData('drive', 'shortcut', 'Shortcut'); - currentState.allEntries[shortcutEntryFileData.entry.toURL()] = + initialState.allEntries[shortcutEntryFileData.entry.toURL()] = shortcutEntryFileData; - currentState.folderShortcuts.push(shortcutEntryFileData.entry.toURL()); + initialState.folderShortcuts.push(shortcutEntryFileData.entry.toURL()); // Put MyFiles entry in the store. const myFilesVolume = createMyFilesEntryFileData(); - currentState.allEntries[myFilesVolume.fileData.entry.toURL()] = + initialState.allEntries[myFilesVolume.fileData.entry.toURL()] = myFilesVolume.fileData; - currentState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; + initialState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; + + const store = setupStore(initialState); - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); // Expect 2 navigation roots. - const {roots} = newState.navigation; - assertEquals(2, roots.length); - assertDeepEquals( - { - key: shortcutEntryFileData.entry.toURL(), - section: NavigationSection.TOP, - separator: false, - type: NavigationType.SHORTCUT, - }, - roots[0]); - assertEquals(myFilesVolume.fileData.entry.toURL(), roots[1]!.key); + const want: State['navigation']['roots'] = [ + // shortcut. + { + key: shortcutEntryFileData.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.SHORTCUT, + }, + // My Files volume. + { + key: myFilesVolume.fileData.entry.toURL(), + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.VOLUME, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** * Tests navigation roots with fake MyFiles. */ -export function testNavigationRootsWithFakeMyFiles() { - const currentState = getEmptyState(); +export async function testNavigationRootsWithFakeMyFiles(done: () => void) { + const initialState = getEmptyState(); // Put recent entry in the store. const recentEntryFileData = createRecentFileData(); - currentState.allEntries[recentRootKey] = recentEntryFileData; + initialState.allEntries[recentRootKey] = recentEntryFileData; // Put MyFiles entry in the store. const myFilesEntryList = new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES); - currentState.allEntries[myFilesEntryList.toURL()] = createFakeFileData({ - entry: myFilesEntryList, - label: 'My files', - type: EntryType.ENTRY_LIST, - }); + initialState.allEntries[myFilesEntryList.toURL()] = + convertEntryToFileData(myFilesEntryList); + + const store = setupStore(initialState); - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); // Expect 2 navigation roots. - const {roots} = newState.navigation; - assertEquals(2, roots.length); - // The type of MyFiles navigation root should be ENTRY_LIST. - assertDeepEquals( - { - key: myFilesEntryList.toURL(), - section: NavigationSection.MY_FILES, - separator: true, - type: NavigationType.ENTRY_LIST, - }, - roots[1]); + const want: State['navigation']['roots'] = [ + // recent. + { + key: recentEntryFileData.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.RECENT, + }, + // My Files entry list. + { + key: myFilesEntryList.toURL(), + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.ENTRY_LIST, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** * Tests navigation roots with volumes. */ -export function testNavigationRootsWithVolumes() { - const currentState = getEmptyState(); +export async function testNavigationRootsWithVolumes(done: () => void) { + const initialState = getEmptyState(); // Put recent entry in the store. const recentEntryFileData = createRecentFileData(); - currentState.allEntries[recentRootKey] = recentEntryFileData; + initialState.allEntries[recentRootKey] = recentEntryFileData; // Put MyFiles entry in the store. const myFilesVolume = createMyFilesEntryFileData(); - currentState.allEntries[myFilesVolume.fileData.entry.toURL()] = + initialState.allEntries[myFilesVolume.fileData.entry.toURL()] = myFilesVolume.fileData; - currentState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; + initialState.volumes[myFilesVolume.volume.volumeId] = myFilesVolume.volume; // Put drive entry in the store. - const driveRootEntryFileData = createDriveRootEntryFileData(); - currentState.allEntries[driveRootEntryListKey] = driveRootEntryFileData; + const driveRootEntryFileData = createDriveRootEntryListFileData(); + initialState.allEntries[driveRootEntryListKey] = driveRootEntryFileData; // Put removable volume 'hoge' in the store. const hogeVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge', 'Hoge', 'device/path/1'); - currentState.allEntries[hogeVolume.fileData.entry.toURL()] = + initialState.allEntries[hogeVolume.fileData.entry.toURL()] = hogeVolume.fileData; - currentState.volumes[hogeVolume.volume.volumeId] = hogeVolume.volume; + initialState.volumes[hogeVolume.volume.volumeId] = hogeVolume.volume; // Create a shortcut for the 'hoge' volume in the store. const hogeShortcutEntryFileData = createShortcutEntryFileData( hogeVolume.volume.volumeId, 'shortcut-hoge', 'Hoge shortcut'); - currentState.allEntries[hogeShortcutEntryFileData.entry.toURL()] = + initialState.allEntries[hogeShortcutEntryFileData.entry.toURL()] = hogeShortcutEntryFileData; - currentState.folderShortcuts.push(hogeShortcutEntryFileData.entry.toURL()); + initialState.folderShortcuts.push(hogeShortcutEntryFileData.entry.toURL()); // Put removable volume 'fuga' in the store. Not a partition, so set a // different device path to 'hoge'. const fugaVolume = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga', 'Fuga', 'device/path/2'); - currentState.allEntries[fugaVolume.fileData.entry.toURL()] = + initialState.allEntries[fugaVolume.fileData.entry.toURL()] = fugaVolume.fileData; - currentState.volumes[fugaVolume.volume.volumeId] = fugaVolume.volume; + initialState.volumes[fugaVolume.volume.volumeId] = fugaVolume.volume; + + const store = setupStore(initialState); - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); // Expect 6 navigation roots. - const {roots} = newState.navigation; - assertEquals(6, roots.length); - assertEquals(recentEntryFileData.entry.toURL(), roots[0]!.key); - assertDeepEquals( - { - key: hogeShortcutEntryFileData.entry.toURL(), - section: NavigationSection.TOP, - separator: false, - type: NavigationType.SHORTCUT, - }, - roots[1]); - assertEquals(myFilesVolume.fileData.entry.toURL(), roots[2]!.key); - assertEquals(driveRootEntryFileData.entry.toURL(), roots[3]!.key); - assertDeepEquals( - { - key: hogeVolume.fileData.entry.toURL(), - section: NavigationSection.REMOVABLE, - separator: true, - type: NavigationType.VOLUME, - }, - roots[4]); - assertDeepEquals( - { - key: fugaVolume.fileData.entry.toURL(), - section: NavigationSection.REMOVABLE, - separator: false, - type: NavigationType.VOLUME, - }, - roots[5]); + const want: State['navigation']['roots'] = [ + // recent. + { + key: recentEntryFileData.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.RECENT, + }, + // hoge shortcut. + { + key: hogeShortcutEntryFileData.entry.toURL(), + section: NavigationSection.TOP, + separator: false, + type: NavigationType.SHORTCUT, + }, + // My Files. + { + key: myFilesVolume.fileData.entry.toURL(), + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.VOLUME, + }, + // Drive. + { + key: driveRootEntryFileData.entry.toURL(), + section: NavigationSection.CLOUD, + separator: true, + type: NavigationType.DRIVE, + }, + // hoge volume. + { + key: hogeVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: true, + type: NavigationType.VOLUME, + }, + // fuga volume. + { + key: fugaVolume.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: false, + type: NavigationType.VOLUME, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** * Tests that for multiple partition volumes, only the parent entry will be * added to the navigation roots. */ -export function testMultipleUsbPartitionsGrouping() { - const currentState = getEmptyState(); +export async function testMultipleUsbPartitionsGrouping(done: () => void) { + const initialState = getEmptyState(); // Add parent entry list to the store. const devicePath = 'device/path/1'; const parentEntry = new EntryList( 'Partition wrap', VolumeManagerCommon.RootType.REMOVABLE, devicePath); - currentState.allEntries[parentEntry.toURL()] = createFakeFileData({ - entry: parentEntry, - label: 'Partition wrap', - type: EntryType.ENTRY_LIST, - }); + initialState.allEntries[parentEntry.toURL()] = + convertEntryToFileData(parentEntry); // Create 3 volumes with the same device path so the partitions are grouped. const partitionVolume1 = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition1', 'partition1', devicePath); partitionVolume1.volume.prefixKey = parentEntry.toURL(); - currentState.allEntries[partitionVolume1.fileData.entry.toURL()] = + initialState.allEntries[partitionVolume1.fileData.entry.toURL()] = partitionVolume1.fileData; - currentState.volumes[partitionVolume1.volume.volumeId] = + initialState.volumes[partitionVolume1.volume.volumeId] = partitionVolume1.volume; const partitionVolume2 = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition2', 'partition2', devicePath); - currentState.allEntries[partitionVolume2.fileData.entry.toURL()] = + initialState.allEntries[partitionVolume2.fileData.entry.toURL()] = partitionVolume2.fileData; - currentState.volumes[partitionVolume2.volume.volumeId] = + initialState.volumes[partitionVolume2.volume.volumeId] = partitionVolume2.volume; partitionVolume2.volume.prefixKey = parentEntry.toURL(); const partitionVolume3 = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition3', 'partition3', devicePath); - currentState.allEntries[partitionVolume3.fileData.entry.toURL()] = + initialState.allEntries[partitionVolume3.fileData.entry.toURL()] = partitionVolume3.fileData; - currentState.volumes[partitionVolume3.volume.volumeId] = + initialState.volumes[partitionVolume3.volume.volumeId] = partitionVolume3.volume; partitionVolume3.volume.prefixKey = parentEntry.toURL(); - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); + const store = setupStore(initialState); + + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); // Expect only the parent entry and MyFiles being added to the navigation // roots. - const {roots} = newState.navigation; - assertEquals(2, roots.length); - assertEquals(myFilesEntryListKey, roots[0]!.key); - assertDeepEquals( - { - key: parentEntry.toURL(), - section: NavigationSection.REMOVABLE, - separator: true, - type: NavigationType.VOLUME, - }, - roots[1]); + const want: State['navigation']['roots'] = [ + // My Files entry list. + { + key: myFilesEntryListKey, + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.ENTRY_LIST, + }, + // parent entry for all removable partitions. + { + key: parentEntry.toURL(), + section: NavigationSection.REMOVABLE, + separator: true, + type: NavigationType.VOLUME, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** * Tests that the volumes filtered by the volume manager won't be shown in the * navigation roots. */ -export function testNavigationRootsWithFilteredVolume() { - const currentState = getEmptyState(); - // Put 2 volumes in the store. +export async function testNavigationRootsWithFilteredVolume(done: () => void) { + const initialState = getEmptyState(); + // Put volume1 in the store. const volume1 = createVolumeFileData( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable1'); - currentState.allEntries[volume1.fileData.entry.toURL()] = volume1.fileData; - currentState.volumes[volume1.volume.volumeId] = volume1.volume; - // Volume2 is not added to VolumeManager's volumeInfoList. + initialState.allEntries[volume1.fileData.entry.toURL()] = volume1.fileData; + initialState.volumes[volume1.volume.volumeId] = volume1.volume; + // Put volume2 in the store. Note: without calling createVolumeFileData(), + // volume2 won't be in volumeManager's volumeInfoList, thus should be filtered + // out. const volumeInfo2 = MockVolumeManager.createMockVolumeInfo( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable2'); const volumeEntry2 = new VolumeEntry(volumeInfo2); - currentState.allEntries[volumeEntry2.toURL()] = createFakeFileData({ - entry: volumeEntry2, - label: volumeInfo2.label, - type: EntryType.VOLUME_ROOT, - }); - currentState.volumes[volumeInfo2.volumeId] = createFakeVolume({ - volumeId: volumeInfo2.volumeId, - volumeType: volumeInfo2.volumeType, - label: volumeInfo2.label, - rootKey: volumeEntry2.toURL(), - }); - - const newState = - refreshNavigationRoots(currentState, refreshNavigationRootsAction()); + initialState.allEntries[volumeEntry2.toURL()] = + convertEntryToFileData(volumeEntry2); + initialState.volumes[volumeInfo2.volumeId] = + convertVolumeInfoAndMetadataToVolume( + volumeInfo2, createFakeVolumeMetadata(volumeInfo2)); + + const store = setupStore(initialState); + + // Dispatch an action to refresh navigation roots. + store.dispatch(refreshNavigationRoots()); // Expect only volume1 and MyFiles in the navigation roots. - const {roots} = newState.navigation; - assertEquals(2, roots.length); - assertEquals(myFilesEntryListKey, roots[0]!.key); - assertEquals(volume1.fileData.entry.toURL(), roots[1]!.key); + const want: State['navigation']['roots'] = [ + // My Files entry list. + { + key: myFilesEntryListKey, + section: NavigationSection.MY_FILES, + separator: true, + type: NavigationType.ENTRY_LIST, + }, + // volume1. + { + key: volume1.fileData.entry.toURL(), + section: NavigationSection.REMOVABLE, + separator: true, + type: NavigationType.VOLUME, + }, + ]; + await waitDeepEquals(store, want, (state) => state.navigation.roots); + + done(); } /** Tests that navigation entry can be updated correctly. */ -export function testUpdateNavigationEntry() { - const currentState = getEmptyState(); +export async function testUpdateNavigationEntry(done: () => void) { + const initialState = getEmptyState(); // Add MyFiles entry to the store. const myFilesVolume = createMyFilesEntryFileData(); const myFilesEntryKey = myFilesVolume.fileData.entry.toURL(); - currentState.allEntries[myFilesEntryKey] = myFilesVolume.fileData; + initialState.allEntries[myFilesEntryKey] = myFilesVolume.fileData; - assertFalse(currentState.allEntries[myFilesEntryKey].expanded); - const newState = updateNavigationEntry( - currentState, - updateNavigationEntryAction({key: myFilesEntryKey, expanded: true})); - assertTrue(newState.allEntries[myFilesEntryKey].expanded); + const store = setupStore(initialState); + + // Dispatch an action to update navigation entry. + store.dispatch(updateNavigationEntry({key: myFilesEntryKey, expanded: true})); + + // Expect MyFiles entry is expanded in the store. + await waitDeepEquals( + store, true, (state) => state.allEntries[myFilesEntryKey].expanded); + + done(); } /** Tests that navigation entry won't be updated without valid file data. */ -export function testUpdateNavigationEntryWithoutValidFileData() { - const currentState = getEmptyState(); +export async function testUpdateNavigationEntryWithoutValidFileData( + done: () => void) { + const initialState = getEmptyState(); + const store = setupStore(initialState); + + // Dispatch an action to update an non existed navigation entry. + store.dispatch(updateNavigationEntry({key: 'not-exist-key', expanded: true})); - const newState = updateNavigationEntry( - currentState, - updateNavigationEntryAction({key: 'not-exist-key', expanded: true})); // Check state won't be touched. - assertEquals(currentState, newState); + await waitDeepEquals(store, initialState, (state) => state); + + done(); } diff --git a/ui/file_manager/file_manager/state/reducers/ui_entries_unittest.ts b/ui/file_manager/file_manager/state/reducers/ui_entries_unittest.ts index ae1d835434d7bf..0901bd7d0805fc 100644 --- a/ui/file_manager/file_manager/state/reducers/ui_entries_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/ui_entries_unittest.ts @@ -2,18 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertArrayEquals, assertEquals} from 'chrome://webui-test/chai_assert.js'; +import {assertEquals} from 'chrome://webui-test/chai_assert.js'; import {MockVolumeManager} from '../../background/js/mock_volume_manager.js'; import {FakeEntryImpl, GuestOsPlaceholder, VolumeEntry} from '../../common/js/files_app_entry_types.js'; import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; -import {EntryType, FileData, State} from '../../externs/ts/state.js'; +import {FileData, State} from '../../externs/ts/state.js'; import {VolumeInfo} from '../../externs/volume_info.js'; -import {addUiEntry as addUiEntryAction, removeUiEntry as removeUiEntryAction} from '../actions/ui_entries.js'; -import {createFakeFileData, createFakeVolume, setUpFileManagerOnWindow} from '../for_tests.js'; +import {addUiEntry, removeUiEntry} from '../actions/ui_entries.js'; +import {createFakeVolumeMetadata, setUpFileManagerOnWindow, setupStore, waitDeepEquals} from '../for_tests.js'; import {getEmptyState} from '../store.js'; -import {addUiEntry, removeUiEntry} from './ui_entries.js'; +import {convertEntryToFileData} from './all_entries.js'; +import {convertVolumeInfoAndMetadataToVolume} from './volumes.js'; export function setUp() { // sortEntries() from addUiEntry() reducer requires volumeManager and @@ -27,224 +28,265 @@ function createMyFilesDataWithVolumeEntry(): const {volumeManager} = window.fileManager; const downloadsVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( VolumeManagerCommon.VolumeType.DOWNLOADS)!; - const fileData = createFakeFileData({ - entry: new VolumeEntry(downloadsVolumeInfo), - volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS, - label: 'My files', - type: EntryType.VOLUME_ROOT, - }); + const fileData = convertEntryToFileData(new VolumeEntry(downloadsVolumeInfo)); return {fileData, volumeInfo: downloadsVolumeInfo}; } -/** Tests a normal ui entry can be added correctly. */ -export function testAddUiEntry() { - const currentState: State = getEmptyState(); +/** Tests a normal UI entry can be added correctly. */ +export async function testAddUiEntry(done: () => void) { + const initialState = getEmptyState(); + const store = setupStore(initialState); + + // Dispatch an action to add a UI entry. const uiEntry = new FakeEntryImpl('Ui entry', VolumeManagerCommon.RootType.RECENT); - const newState = addUiEntry(currentState, addUiEntryAction({entry: uiEntry})); - assertEquals(1, newState.uiEntries.length); - assertEquals(uiEntry.toURL(), newState.uiEntries[0]); + store.dispatch(addUiEntry({entry: uiEntry})); + + // Expect the newly added entry is in the store. + const want: Partial = { + allEntries: { + [uiEntry.toURL()]: convertEntryToFileData(uiEntry), + }, + uiEntries: [uiEntry.toURL()], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + uiEntries: state.uiEntries, + })); + + done(); } -/** Tests that a duplicate ui entry won't be added. */ -export function testAddDuplicateUiEntry() { - const currentState: State = getEmptyState(); +/** Tests that a duplicate UI entry won't be added. */ +export async function testAddDuplicateUiEntry(done: () => void) { + const initialState = getEmptyState(); + // Add one UI entry in the store. const uiEntry = new FakeEntryImpl('Ui entry', VolumeManagerCommon.RootType.RECENT); - currentState.uiEntries.push(uiEntry.toURL()); - const newState = addUiEntry(currentState, addUiEntryAction({entry: uiEntry})); - assertEquals(1, newState.uiEntries.length); - assertEquals(uiEntry.toURL(), newState.uiEntries[0]); - // The reference of uiEntries won't change. - assertEquals(currentState.uiEntries, newState.uiEntries); + initialState.uiEntries.push(uiEntry.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to add an already existed UI entry. + store.dispatch(addUiEntry({entry: uiEntry})); + + // Expect nothing changes in the store. + const want: State['uiEntries'] = [uiEntry.toURL()]; + await waitDeepEquals(store, want, (state) => state.uiEntries); + + done(); } /** - * Tests that adding ui entry for MyFiles will reset the children filed of + * Tests that adding UI entry for MyFiles will reset the children filed of * MyFiles entry. */ -export function testAddUiEntryForMyFiles() { - const currentState: State = getEmptyState(); +export async function testAddUiEntryForMyFiles(done: () => void) { + const initialState = getEmptyState(); // Setup MyFiles entry in the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); const myFilesEntry = fileData.entry as VolumeEntry; - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; + const myFilesVolume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.allEntries[fileData.entry.toURL()] = fileData; + initialState.volumes[volumeInfo.volumeId] = myFilesVolume; // Add children to the MyFiles entry. const childEntry = new GuestOsPlaceholder( 'Play files', 0, chrome.fileManagerPrivate.VmType.ARCVM); - currentState.allEntries[childEntry.toURL()] = createFakeFileData({ - entry: childEntry, - label: 'Play files', - type: EntryType.PLACEHOLDER, - }); + initialState.allEntries[childEntry.toURL()] = + convertEntryToFileData(childEntry); myFilesEntry.addEntry(childEntry); fileData.children.push(childEntry.toURL()); + initialState.uiEntries.push(childEntry.toURL()); + const store = setupStore(initialState); + + // Dispatch an action to add a new UI entry which belongs to MyFiles. const uiEntry = new FakeEntryImpl('Linux files', VolumeManagerCommon.RootType.CROSTINI); - // Before calling addUiEntry(), the new uiEntry should already be in store, - // this is handled by cacheEntries(), we should emulate the process here. This - // is required for sortEntries(). - currentState.allEntries[uiEntry.toURL()] = createFakeFileData({ - entry: uiEntry, - label: 'Linux files', - type: EntryType.PLACEHOLDER, - }); - const newState = addUiEntry(currentState, addUiEntryAction({entry: uiEntry})); - assertEquals(1, newState.uiEntries.length); - assertEquals(uiEntry.toURL(), newState.uiEntries[0]); - // Check the ui entry is added to MyFiles entry. + store.dispatch(addUiEntry({entry: uiEntry})); + + // Expect 2 ui entries in the store. + const want: Partial = { + allEntries: { + [myFilesEntry.toURL()]: { + ...fileData, + children: [ + // Children are in sorted order. + uiEntry.toURL(), + childEntry.toURL(), + ], + }, + [childEntry.toURL()]: convertEntryToFileData(childEntry), + [uiEntry.toURL()]: convertEntryToFileData(uiEntry), + }, + // No sorting order, order is based on the push order. + uiEntries: [childEntry.toURL(), uiEntry.toURL()], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + uiEntries: state.uiEntries, + })); + + // Check the UI entry is added to MyFiles entry. assertEquals(2, myFilesEntry.getUIChildren().length); assertEquals(uiEntry, myFilesEntry.getUIChildren()[1]); - // Check the children of MyFiles entry gets updated and resorted. - assertArrayEquals( - [ - uiEntry.toURL(), - childEntry.toURL(), - ], - newState.allEntries[myFilesEntry.toURL()].children); + + done(); } /** - * Tests that ui entry won't be added to MyFiles if it's already existed. + * Tests that UI entry won't be added to MyFiles if it's already existed. */ -export function testAddDuplicateUiEntryForMyFiles() { - const currentState: State = getEmptyState(); +export async function testAddDuplicateUiEntryForMyFiles(done: () => void) { + const initialState = getEmptyState(); const uiEntry = new GuestOsPlaceholder( 'Play files', 0, chrome.fileManagerPrivate.VmType.ARCVM); // Setup MyFiles entry and add the new ui entry in the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); const myFilesEntry = fileData.entry as VolumeEntry; - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; + const myFilesVolume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.allEntries[fileData.entry.toURL()] = fileData; + initialState.volumes[volumeInfo.volumeId] = myFilesVolume; myFilesEntry.addEntry(uiEntry); + fileData.children.push(uiEntry.toURL()); + initialState.uiEntries.push(uiEntry.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to add an already existed UI entry. + store.dispatch(addUiEntry({entry: uiEntry})); - const newState = addUiEntry(currentState, addUiEntryAction({entry: uiEntry})); - assertEquals(1, newState.uiEntries.length); - assertEquals(uiEntry.toURL(), newState.uiEntries[0]); - // Check the ui entry is not being added to MyFiles entry again. + // Expect no changes in the store. + await waitDeepEquals(store, initialState, (state) => state); + + // Check the UI entry is not being added to MyFiles entry again. assertEquals(1, myFilesEntry.getUIChildren().length); assertEquals(uiEntry, myFilesEntry.getUIChildren()[0]); - // Check the children of MyFiles has not been touched. - assertEquals( - newState.allEntries[myFilesEntry.toURL()].children, - currentState.allEntries[myFilesEntry.toURL()].children); + + done(); } /** - * Tests that ui entry won't be added to MyFiles if the corresponding volume + * Tests that UI entry won't be added to MyFiles if the corresponding volume * is already existed. */ -export function testAddDuplicateUiEntryForMyFilesWhenVolumeExists() { - const currentState: State = getEmptyState(); - // Placeholder ui entry and the volume entry it represents have the same +export async function testAddDuplicateUiEntryForMyFilesWhenVolumeExists( + done: () => void) { + const initialState = getEmptyState(); + // Placeholder UI entry and the volume entry it represents have the same // label. const label = 'Play files'; - const uiEntry = - new GuestOsPlaceholder(label, 0, chrome.fileManagerPrivate.VmType.ARCVM); - // Setup MyFiles entry and add the new volume entry in the store. + // Setup MyFiles entry and add the volume entry in the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); const myFilesEntry = fileData.entry as VolumeEntry; - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; + const myFilesVolume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.allEntries[fileData.entry.toURL()] = fileData; + initialState.volumes[volumeInfo.volumeId] = myFilesVolume; const playFilesVolumeInfo = MockVolumeManager.createMockVolumeInfo( VolumeManagerCommon.VolumeType.ANDROID_FILES, label); const playFilesVolumeEntry = new VolumeEntry(playFilesVolumeInfo); myFilesEntry.addEntry(playFilesVolumeEntry); + fileData.children.push(playFilesVolumeEntry.toURL()); - const newState = addUiEntry(currentState, addUiEntryAction({entry: uiEntry})); - // Check the ui entry has not been touched. - assertEquals(currentState.uiEntries, newState.uiEntries); - // Check the ui entry is not being added to MyFiles entry again. + const store = setupStore(initialState); + + // Dispatch an action to add UI entry. + const uiEntry = + new GuestOsPlaceholder(label, 0, chrome.fileManagerPrivate.VmType.ARCVM); + store.dispatch(addUiEntry({entry: uiEntry})); + + // Expect the UI entry is not being added to the store. + await waitDeepEquals(store, [], (state) => state.uiEntries); + + // Check the UI entry is not being added to MyFiles entry again. assertEquals(1, myFilesEntry.getUIChildren().length); assertEquals(playFilesVolumeEntry, myFilesEntry.getUIChildren()[0]); - // Check the children of MyFiles has not been touched. - assertEquals( - newState.allEntries[myFilesEntry.toURL()].children, - currentState.allEntries[myFilesEntry.toURL()].children); + + done(); } -/** Tests that ui entry can be removed from store correctly. */ -export function testRemoveUiEntry() { - const currentState: State = getEmptyState(); +/** Tests that UI entry can be removed from store correctly. */ +export async function testRemoveUiEntry(done: () => void) { + const initialState = getEmptyState(); const uiEntry = new FakeEntryImpl('Ui entry', VolumeManagerCommon.RootType.RECENT); - // Setup the ui entry in both uiEntries and allEntries in the store. - currentState.allEntries[uiEntry.toURL()] = createFakeFileData({ - entry: uiEntry, - label: 'Ui entry', - type: EntryType.PLACEHOLDER, - }); - currentState.uiEntries.push(uiEntry.toURL()); - - const newState = - removeUiEntry(currentState, removeUiEntryAction({key: uiEntry.toURL()})); - assertEquals(0, newState.uiEntries.length); + // Setup the UI entry in both uiEntries and allEntries in the store. + initialState.allEntries[uiEntry.toURL()] = convertEntryToFileData(uiEntry); + initialState.uiEntries.push(uiEntry.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to remove the UI entry. + store.dispatch(removeUiEntry({key: uiEntry.toURL()})); + + // Expect the UI entry has been removed. + await waitDeepEquals(store, [], (state) => state.uiEntries); + + done(); } -/** Tests that removing non-existed ui entry won't do anything. */ -export function testRemoveNonExistedUiEntry() { - const currentState: State = getEmptyState(); +/** Tests that removing non-existed UI entry won't do anything. */ +export async function testRemoveNonExistedUiEntry(done: () => void) { + const initialState = getEmptyState(); + const store = setupStore(initialState); + + // Dispatch an action to remove a non-existed UI entry. const uiEntry = new FakeEntryImpl('Ui entry', VolumeManagerCommon.RootType.TRASH); - const newState = - removeUiEntry(currentState, removeUiEntryAction({key: uiEntry.toURL()})); - // Check that uiEntries won't be touched. - assertEquals(newState.uiEntries, currentState.uiEntries); + store.dispatch(removeUiEntry({key: uiEntry.toURL()})); + + // Expect nothing changes in the store. + await waitDeepEquals(store, initialState, (state) => state); + + done(); } /** - * Tests removing ui entry from MyFiles will reset the children field of + * Tests removing UI entry from MyFiles will reset the children field of * MyFiles entry. */ -export function testRemoveUiEntryFromMyFiles() { - const currentState: State = getEmptyState(); +export async function testRemoveUiEntryFromMyFiles(done: () => void) { + const initialState = getEmptyState(); const uiEntry = new FakeEntryImpl('Linux files', VolumeManagerCommon.RootType.CROSTINI); // Setup MyFiles entry and add the ui entry in the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); const myFilesEntry = fileData.entry as VolumeEntry; - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; + const myFilesVolume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.allEntries[fileData.entry.toURL()] = fileData; + initialState.volumes[volumeInfo.volumeId] = myFilesVolume; myFilesEntry.addEntry(uiEntry); fileData.children.push(uiEntry.toURL()); - currentState.allEntries[uiEntry.toURL()] = createFakeFileData({ - entry: uiEntry, - volumeType: VolumeManagerCommon.VolumeType.CROSTINI, - label: 'Linux files', - type: EntryType.PLACEHOLDER, - }); - currentState.uiEntries.push(uiEntry.toURL()); - - const newState = - removeUiEntry(currentState, removeUiEntryAction({key: uiEntry.toURL()})); - assertEquals(0, newState.uiEntries.length); - // Check the ui entry has also been removed from MyFiles entry. + initialState.allEntries[uiEntry.toURL()] = convertEntryToFileData(uiEntry); + initialState.uiEntries.push(uiEntry.toURL()); + + const store = setupStore(initialState); + + // Dispatch an action to + store.dispatch(removeUiEntry({key: uiEntry.toURL()})); + + // Expect the entry has been removed from MyFiles. + const want: Partial = { + allEntries: { + [myFilesEntry.toURL()]: { + ...convertEntryToFileData(myFilesEntry), + children: [], + }, + [uiEntry.toURL()]: convertEntryToFileData(uiEntry), + }, + uiEntries: [], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + uiEntries: state.uiEntries, + })); + + // Check the UI entry has also been removed from MyFiles entry. assertEquals(0, myFilesEntry.getUIChildren().length); - assertEquals(0, newState.allEntries[myFilesEntry.toURL()].children.length); + + done(); } diff --git a/ui/file_manager/file_manager/state/reducers/volumes_unittest.ts b/ui/file_manager/file_manager/state/reducers/volumes_unittest.ts index 55bff85b06fb18..bec3179cd9bf56 100644 --- a/ui/file_manager/file_manager/state/reducers/volumes_unittest.ts +++ b/ui/file_manager/file_manager/state/reducers/volumes_unittest.ts @@ -2,226 +2,349 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {assertEquals} from 'chrome://webui-test/chai_assert.js'; - import {MockVolumeManager} from '../../background/js/mock_volume_manager.js'; -import {EntryList, VolumeEntry} from '../../common/js/files_app_entry_types.js'; +import {EntryList, FakeEntryImpl, VolumeEntry} from '../../common/js/files_app_entry_types.js'; +import {str} from '../../common/js/util.js'; import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; -import {EntryType, FileData, PropStatus} from '../../externs/ts/state.js'; +import {FileData, State} from '../../externs/ts/state.js'; import {VolumeInfo} from '../../externs/volume_info.js'; -import {addVolume as addVolumeAction, removeVolume as removeVolumeAction} from '../actions/volumes.js'; -import {createFakeFileData, createFakeVolume, createFakeVolumeMetadata} from '../for_tests.js'; +import {constants} from '../../foreground/js/constants.js'; +import {addVolume, removeVolume} from '../actions/volumes.js'; +import {createFakeVolumeMetadata, setUpFileManagerOnWindow, setupStore, waitDeepEquals} from '../for_tests.js'; import {getEmptyState} from '../store.js'; -import {addVolume, driveRootEntryListKey, removeVolume} from './volumes.js'; +import {convertEntryToFileData} from './all_entries.js'; +import {convertVolumeInfoAndMetadataToVolume, driveRootEntryListKey} from './volumes.js'; + +export function setUp() { + setUpFileManagerOnWindow(); +} + +/** Generate MyFiles entry with fake entry list. */ +function createMyFilesDataWithEntryList(): FileData { + const myFilesEntryList = + new EntryList('My files', VolumeManagerCommon.RootType.MY_FILES); + return convertEntryToFileData(myFilesEntryList); +} /** Generate MyFiles entry with real volume entry. */ function createMyFilesDataWithVolumeEntry(): {fileData: FileData, volumeInfo: VolumeInfo} { - const volumeManager = new MockVolumeManager(); + const {volumeManager} = window.fileManager; const downloadsVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( VolumeManagerCommon.VolumeType.DOWNLOADS)!; - const fileData = createFakeFileData({ - entry: new VolumeEntry(downloadsVolumeInfo), - volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS, - label: 'My files', - type: EntryType.VOLUME_ROOT, - }); + const fileData = convertEntryToFileData(new VolumeEntry(downloadsVolumeInfo)); return {fileData, volumeInfo: downloadsVolumeInfo}; } /** Tests that MyFiles volume can be added correctly. */ -export function testAddMyFilesVolume() { - const currentState = getEmptyState(); +export async function testAddMyFilesVolume(done: () => void) { + const initialState = getEmptyState(); + // Put MyFiles entry list in the store. + const myFilesFileData = createMyFilesDataWithEntryList(); + const myFilesEntryList = myFilesFileData.entry as EntryList; + initialState.allEntries[myFilesEntryList.toURL()] = myFilesFileData; + initialState.uiEntries.push(myFilesEntryList.toURL()); + // Put Play files placeholder UI entry in the store. + const playFilesEntry = new FakeEntryImpl( + 'Play files', VolumeManagerCommon.RootType.ANDROID_FILES); + initialState.allEntries[playFilesEntry.toURL()] = + convertEntryToFileData(playFilesEntry); + myFilesEntryList.addEntry(playFilesEntry); + initialState.uiEntries.push(playFilesEntry.toURL()); + myFilesFileData.children.push(playFilesEntry.toURL()); + // Put Linux files volume entry in the store. + const linuxFilesVolumeInfo = MockVolumeManager.createMockVolumeInfo( + VolumeManagerCommon.VolumeType.CROSTINI, 'linuxFilesId', 'Linux files'); + const linuxFilesEntry = new VolumeEntry(linuxFilesVolumeInfo); + const {volumeManager} = window.fileManager; + volumeManager.volumeInfoList.add(linuxFilesVolumeInfo); + initialState.allEntries[linuxFilesEntry.toURL()] = + convertEntryToFileData(linuxFilesEntry); + const linuxFilesVolume = convertVolumeInfoAndMetadataToVolume( + linuxFilesVolumeInfo, createFakeVolumeMetadata(linuxFilesVolumeInfo)); + initialState.volumes[linuxFilesVolume.volumeId] = linuxFilesVolume; + myFilesFileData.children.push(linuxFilesEntry.toURL()); + myFilesEntryList.addEntry(linuxFilesEntry); + + const store = setupStore(initialState); + + // Dispatch an action to add MyFiles volume. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; - // Mark its volume entry as disabled. - (fileData.entry as VolumeEntry).disabled = true; - // Put MyFiles and its sub volumes in the store. - const playFilesVolume = createFakeVolume({ - volumeId: 'playFilesId', - volumeType: VolumeManagerCommon.VolumeType.ANDROID_FILES, - label: 'Play files', - rootKey: 'filesystem:chrome://android-files-url', - }); - currentState.volumes[playFilesVolume.volumeId] = playFilesVolume; - const crostiniFilesVolume = createFakeVolume({ - volumeId: 'volumeInMyFiles2', - volumeType: VolumeManagerCommon.VolumeType.CROSTINI, - label: 'Linux files', - rootKey: 'filesystem:chrome://crostini-files-url', - }); - currentState.volumes[crostiniFilesVolume.volumeId] = crostiniFilesVolume; - - const volumeMetadata = createFakeVolumeMetadata( - {volumeType: volumeInfo.volumeType, volumeId: volumeInfo.volumeId}); - const newState = addVolume(currentState, addVolumeAction({ - volumeInfo, - volumeMetadata, - })); - const volume = newState.volumes[volumeInfo.volumeId]; - // Check all volume fields are set correctly. - assertEquals(volume.volumeId, volumeMetadata.volumeId); - assertEquals(volume.volumeType, volumeMetadata.volumeType); - assertEquals(volume.rootKey, volumeInfo.displayRoot.toURL()); - assertEquals(volume.status, PropStatus.SUCCESS); - assertEquals(volume.label, volumeInfo.label); - assertEquals(volume.error, volumeMetadata.mountCondition); - assertEquals(volume.deviceType, volumeMetadata.deviceType); - assertEquals(volume.devicePath, volumeMetadata.devicePath); - assertEquals(volume.isReadOnly, volumeMetadata.isReadOnly); - assertEquals( - volume.isReadOnlyRemovableDevice, - volumeMetadata.isReadOnlyRemovableDevice); - assertEquals(volume.providerId, volumeMetadata.providerId); - assertEquals(volume.configurable, volumeMetadata.configurable); - assertEquals(volume.watchable, volumeMetadata.watchable); - assertEquals(volume.source, volumeMetadata.source); - assertEquals(volume.diskFileSystemType, volumeMetadata.diskFileSystemType); - assertEquals(volume.iconSet, volumeMetadata.iconSet); - assertEquals(volume.driveLabel, volumeMetadata.driveLabel); - assertEquals(volume.vmType, volumeMetadata.vmType); - // Because its volume entry is disabled. - assertEquals(volume.isDisabled, true); - assertEquals(volume.prefixKey, undefined); - // Check all child volumes has prefix key setup. - assertEquals( - fileData.entry.toURL(), - newState.volumes[playFilesVolume.volumeId].prefixKey); - assertEquals( - fileData.entry.toURL(), - newState.volumes[crostiniFilesVolume.volumeId].prefixKey); + const myFilesVolumeEntry = fileData.entry as VolumeEntry; + const volumeMetadata = createFakeVolumeMetadata(volumeInfo); + store.dispatch(addVolume({ + volumeInfo, + volumeMetadata, + })); + + // Expect the newly added volume is in the store. + myFilesVolumeEntry.addEntry(playFilesEntry); + myFilesVolumeEntry.addEntry(linuxFilesEntry); + const want: Partial = { + allEntries: { + [playFilesEntry.toURL()]: convertEntryToFileData(playFilesEntry), + [linuxFilesEntry.toURL()]: convertEntryToFileData(linuxFilesEntry), + [myFilesVolumeEntry.toURL()]: fileData, + }, + volumes: { + [linuxFilesVolumeInfo.volumeId]: { + ...linuxFilesVolume, + // Updated to MyFiles volume key. + prefixKey: fileData.entry.toURL(), + }, + [volumeInfo.volumeId]: + convertVolumeInfoAndMetadataToVolume(volumeInfo, volumeMetadata), + }, + uiEntries: [playFilesEntry.toURL()], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + volumes: state.volumes, + uiEntries: state.uiEntries, + })); + + done(); } /** Tests that volume nested in MyFiles can be added correctly. */ -export function testAddNestedMyFilesVolume() { - const currentState = getEmptyState(); +export async function testAddNestedMyFilesVolume(done: () => void) { + const initialState = getEmptyState(); // Put MyFiles in the store. const {fileData, volumeInfo} = createMyFilesDataWithVolumeEntry(); - const myFilesVolume = createFakeVolume({ - volumeType: volumeInfo.volumeType, - volumeId: volumeInfo.volumeId, - label: volumeInfo.label, - rootKey: volumeInfo.displayRoot.toURL(), - }); - currentState.allEntries[fileData.entry.toURL()] = fileData; - currentState.volumes[volumeInfo.volumeId] = myFilesVolume; + const myFilesVolumeEntry = fileData.entry as VolumeEntry; + const myFilesVolume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.allEntries[myFilesVolumeEntry.toURL()] = fileData; + initialState.volumes[volumeInfo.volumeId] = myFilesVolume; + // Put Play files placeholder UI entry in the store. + const playFilesUiEntry = new FakeEntryImpl( + 'Play files', VolumeManagerCommon.RootType.ANDROID_FILES); + myFilesVolumeEntry.addEntry(playFilesUiEntry); + fileData.children.push(playFilesUiEntry.toURL()); + initialState.uiEntries.push(playFilesUiEntry.toURL()); + initialState.allEntries[playFilesUiEntry.toURL()] = + convertEntryToFileData(playFilesUiEntry); + const store = setupStore(initialState); + + // Dispatch an action to add Play files volume. + const {volumeManager} = window.fileManager; const playFilesVolumeInfo = MockVolumeManager.createMockVolumeInfo( VolumeManagerCommon.VolumeType.ANDROID_FILES, 'playFilesId', - 'Play files'); - const playFilesVolumeMetadata = createFakeVolumeMetadata({ - volumeType: playFilesVolumeInfo.volumeType, - volumeId: playFilesVolumeInfo.volumeId, - }); - const newState = addVolume(currentState, addVolumeAction({ - volumeInfo: playFilesVolumeInfo, - volumeMetadata: playFilesVolumeMetadata, - })); - // Check the newly added volume has prefix key setup. - assertEquals( - fileData.entry.toURL(), - newState.volumes[playFilesVolumeInfo.volumeId].prefixKey); + playFilesUiEntry.label); + volumeManager.volumeInfoList.add(playFilesVolumeInfo); + const playFilesVolumeMetadata = createFakeVolumeMetadata(playFilesVolumeInfo); + store.dispatch(addVolume({ + volumeInfo: playFilesVolumeInfo, + volumeMetadata: playFilesVolumeMetadata, + })); + + // Expect the new play file volume will be nested inside MyFiles and the old + // placeholder will be removed. + const playFilesVolumeEntry = new VolumeEntry(playFilesVolumeInfo); + myFilesVolumeEntry.addEntry(playFilesVolumeEntry); + const want: Partial = { + allEntries: { + [myFilesVolumeEntry.toURL()]: { + ...fileData, + children: [playFilesVolumeEntry.toURL()], + }, + [playFilesVolumeEntry.toURL()]: + convertEntryToFileData(playFilesVolumeEntry), + }, + volumes: { + [myFilesVolume.volumeId]: myFilesVolume, + [playFilesVolumeInfo.volumeId]: { + ...convertVolumeInfoAndMetadataToVolume( + playFilesVolumeInfo, playFilesVolumeMetadata), + prefixKey: myFilesVolumeEntry.toURL(), + }, + }, + uiEntries: [], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + volumes: state.volumes, + uiEntries: state.uiEntries, + })); + + done(); } /** Tests that drive volume can be added correctly. */ -export function testAddDriveVolume(done: () => void) { - const currentState = getEmptyState(); - // Put FakeDriveRoot in the store. - const fakeDriveRootEntry = new EntryList( - 'Google Drive', VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT); - currentState.allEntries[driveRootEntryListKey] = createFakeFileData({ - entry: fakeDriveRootEntry, - label: 'Google Drive', - type: EntryType.ENTRY_LIST, - }); - - const volumeManager = new MockVolumeManager(); +export async function testAddDriveVolume(done: () => void) { + const initialState = getEmptyState(); + const store = setupStore(initialState); + + // Dispatch an action to add Drive volume. + const {volumeManager} = window.fileManager; const driveVolumeInfo = volumeManager.getCurrentProfileVolumeInfo( VolumeManagerCommon.VolumeType.DRIVE)!; - const driveVolumeMetadata = createFakeVolumeMetadata({ - volumeType: driveVolumeInfo.volumeType, - volumeId: driveVolumeInfo.volumeId, - }); + const driveVolumeMetadata = createFakeVolumeMetadata(driveVolumeInfo); // DriveFS takes time to resolve. - driveVolumeInfo.resolveDisplayRoot(() => { - const newState = addVolume(currentState, addVolumeAction({ - volumeInfo: driveVolumeInfo, - volumeMetadata: driveVolumeMetadata, - })); - // Check the newly added volume has prefix key setup. - assertEquals( - fakeDriveRootEntry.toURL(), - newState.volumes[driveVolumeInfo.volumeId].prefixKey); - - done(); - }); + await driveVolumeInfo.resolveDisplayRoot(); + store.dispatch(addVolume({ + volumeInfo: driveVolumeInfo, + volumeMetadata: driveVolumeMetadata, + })); + + // Expect all fake entries inside Drive will be added as its children. + const myFilesFileData = createMyFilesDataWithEntryList(); + const driveFakeRootEntryList = new EntryList( + str('DRIVE_DIRECTORY_LABEL'), + VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT); + const driveVolumeEntry = new VolumeEntry(driveVolumeInfo); + const {sharedDriveDisplayRoot, computersDisplayRoot, fakeEntries} = + driveVolumeInfo; + const fakeSharedWithMeEntry = + fakeEntries[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME]; + const fakeOfflineEntry = + fakeEntries[VolumeManagerCommon.RootType.DRIVE_OFFLINE]; + driveFakeRootEntryList.addEntry(driveVolumeEntry); + driveFakeRootEntryList.addEntry(sharedDriveDisplayRoot); + driveFakeRootEntryList.addEntry(computersDisplayRoot); + driveFakeRootEntryList.addEntry(fakeSharedWithMeEntry); + driveFakeRootEntryList.addEntry(fakeOfflineEntry); + const want: Partial = { + allEntries: { + // My Drive. + [driveVolumeEntry.toURL()]: convertEntryToFileData(driveVolumeEntry), + // My Files entry list. + [myFilesFileData.entry.toURL()]: myFilesFileData, + // Fake Drive root entry list. + [driveRootEntryListKey]: convertEntryToFileData(driveFakeRootEntryList), + // Shared with me. + [fakeSharedWithMeEntry.toURL()]: + convertEntryToFileData(fakeSharedWithMeEntry), + // Offline. + [fakeOfflineEntry.toURL()]: convertEntryToFileData(fakeOfflineEntry), + // Shared drives and Computers won't be here because they will be cleared. + }, + volumes: { + [driveVolumeInfo.volumeId]: { + ...convertVolumeInfoAndMetadataToVolume( + driveVolumeInfo, driveVolumeMetadata), + prefixKey: driveRootEntryListKey, + }, + }, + uiEntries: [ + myFilesFileData.entry.toURL(), + driveRootEntryListKey, + fakeSharedWithMeEntry.toURL(), + fakeOfflineEntry.toURL(), + ], + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + volumes: state.volumes, + uiEntries: state.uiEntries, + })); + + done(); } /** Tests that multiple partition volumes can be added correctly. */ -export function testAddVolumeForMultipleUsbPartitionsGrouping() { - const currentState = getEmptyState(); - // Add USB/partition-1 into the store. - const devicePath = 'device/path/1'; - const partition1 = createFakeVolume({ - volumeId: 'removable:partition1', - volumeType: VolumeManagerCommon.VolumeType.REMOVABLE, - rootKey: 'partition1-url', - label: 'Partition 1', - devicePath, - driveLabel: 'USB_Drive', - }); - currentState.volumes[partition1.volumeId] = partition1; - // Add its parent entry to the store. - const parentEntry = new EntryList( - partition1.driveLabel!, VolumeManagerCommon.RootType.REMOVABLE, - partition1.devicePath); - currentState.allEntries[parentEntry.toURL()] = createFakeFileData({ - entry: parentEntry, - label: partition1.driveLabel!, - type: EntryType.ENTRY_LIST, - }); +export async function testAddVolumeForMultipleUsbPartitionsGrouping( + done: () => void) { + const initialState = getEmptyState(); + // Put partition-1 volume in the store. + const {volumeManager} = window.fileManager; + const partition1VolumeInfo = MockVolumeManager.createMockVolumeInfo( + VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition1', + 'Partition 1', '/device/path/1'); + volumeManager.volumeInfoList.add(partition1VolumeInfo); + const partition1VolumeEntry = new VolumeEntry(partition1VolumeInfo); + const partition1FileData = convertEntryToFileData(partition1VolumeEntry); + const partition1VolumeMetadata = + createFakeVolumeMetadata(partition1VolumeInfo); + partition1VolumeMetadata.driveLabel = 'USB_Drive'; + const partition1Volume = convertVolumeInfoAndMetadataToVolume( + partition1VolumeInfo, partition1VolumeMetadata); + initialState.volumes[partition1Volume.volumeId] = partition1Volume; + initialState.allEntries[partition1VolumeEntry.toURL()] = partition1FileData; + + const store = setupStore(initialState); + // Dispatch an action to add partition-2 volume. const partition2VolumeInfo = MockVolumeManager.createMockVolumeInfo( VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition2', - 'Partition 2', devicePath); - const partition2VolumeMetadata = createFakeVolumeMetadata({ - volumeType: partition2VolumeInfo.volumeType, - volumeId: partition2VolumeInfo.volumeId, - devicePath: partition1.devicePath, - driveLabel: partition1.driveLabel, - }); - const newState = addVolume(currentState, addVolumeAction({ - volumeInfo: partition2VolumeInfo, - volumeMetadata: partition2VolumeMetadata, - })); - // Check the newly added volume and all existing volumes belonging to the same - // group have prefix key setup. - assertEquals( - parentEntry.toURL(), newState.volumes[partition1.volumeId].prefixKey); - assertEquals( - parentEntry.toURL(), - newState.volumes[partition2VolumeInfo.volumeId].prefixKey); + 'Partition 2', partition1VolumeInfo.devicePath); + const partition2VolumeMetadata = + createFakeVolumeMetadata(partition2VolumeInfo); + partition2VolumeMetadata.driveLabel = partition1VolumeMetadata.driveLabel; + store.dispatch(addVolume({ + volumeInfo: partition2VolumeInfo, + volumeMetadata: partition2VolumeMetadata, + })); + + // Expect the partition-2 volume is in the store and there will be a wrapper + // entry created to group both partition-1 and partition-2. + const myFilesFileData = createMyFilesDataWithEntryList(); + const partition2VolumeEntry = new VolumeEntry(partition2VolumeInfo); + const parentEntry = new EntryList( + partition1VolumeMetadata.driveLabel, + VolumeManagerCommon.RootType.REMOVABLE, partition1VolumeInfo.devicePath); + parentEntry.addEntry(partition1VolumeEntry); + parentEntry.addEntry(partition2VolumeEntry); + const want: Partial = { + allEntries: { + // Partition-1 volume. + [partition1VolumeEntry.toURL()]: { + ...partition1FileData, + icon: constants.ICON_TYPES.UNKNOWN_REMOVABLE, + }, + // Partition-2 volume. + [partition2VolumeEntry.toURL()]: { + ...convertEntryToFileData(partition2VolumeEntry), + icon: constants.ICON_TYPES.UNKNOWN_REMOVABLE, + }, + // My Files entry list. + [myFilesFileData.entry.toURL()]: myFilesFileData, + // Parent wrapper entry. + [parentEntry.toURL()]: { + ...convertEntryToFileData(parentEntry), + isEjectable: true, + }, + }, + volumes: { + [partition1VolumeInfo.volumeId]: { + ...partition1Volume, + prefixKey: parentEntry.toURL(), + }, + [partition2VolumeInfo.volumeId]: { + ...convertVolumeInfoAndMetadataToVolume( + partition2VolumeInfo, partition2VolumeMetadata), + prefixKey: parentEntry.toURL(), + }, + }, + }; + await waitDeepEquals(store, want, (state) => ({ + allEntries: state.allEntries, + volumes: state.volumes, + })); + + done(); } /** Tests that volume can be removed correctly. */ -export function testRemoveVolume() { - const currentState = getEmptyState(); - const volume = createFakeVolume({ - volumeId: 'test', - volumeType: VolumeManagerCommon.VolumeType.ARCHIVE, - label: 'test.zip', - rootKey: 'test-root', - }); - currentState.volumes[volume.volumeId] = volume; - const newState = removeVolume( - currentState, removeVolumeAction({volumeId: volume.volumeId})); - assertEquals(undefined, newState.volumes[volume.volumeId]); +export async function testRemoveVolume(done: () => void) { + const initialState = getEmptyState(); + const {volumeManager} = window.fileManager; + const volumeInfo = MockVolumeManager.createMockVolumeInfo( + VolumeManagerCommon.VolumeType.ARCHIVE, 'test', 'test.zip'); + volumeManager.volumeInfoList.add(volumeInfo); + const volume = convertVolumeInfoAndMetadataToVolume( + volumeInfo, createFakeVolumeMetadata(volumeInfo)); + initialState.volumes[volume.volumeId] = volume; + + const store = setupStore(initialState); + + // Dispatch an action to remove the volume. + store.dispatch(removeVolume({volumeId: volume.volumeId})); + + // Expect the volume will be removed from the store. + await waitDeepEquals(store, {}, (state) => state.volumes); + + done(); }