Fix missing all chat folder on startup without new manifest
This commit is contained in:
		
					parent
					
						
							
								bf217a8513
							
						
					
				
			
			
				commit
				
					
						4973b9b204
					
				
			
		
					 9 changed files with 192 additions and 92 deletions
				
			
		|  | @ -1014,6 +1014,17 @@ export async function startApp(): Promise<void> { | ||||||
|       ) { |       ) { | ||||||
|         await window.storage.put('needProfileMovedModal', true); |         await window.storage.put('needProfileMovedModal', true); | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|  |       if (window.isBeforeVersion(lastVersion, 'v7.75.0-beta.1')) { | ||||||
|  |         const hasAllChatsChatFolder = await DataReader.hasAllChatsChatFolder(); | ||||||
|  |         if (!hasAllChatsChatFolder) { | ||||||
|  |           log.info('Creating "all chats" chat folder'); | ||||||
|  |           await DataWriter.createAllChatsChatFolder(); | ||||||
|  |           StorageService.storageServiceUploadJobAfterEnabled({ | ||||||
|  |             reason: 'createAllChatsChatFolder', | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     setAppLoadingScreenMessage( |     setAppLoadingScreenMessage( | ||||||
|  |  | ||||||
|  | @ -88,11 +88,7 @@ import { isDone as isRegistrationDone } from '../util/registration.js'; | ||||||
| import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue.js'; | import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue.js'; | ||||||
| import { isMockEnvironment } from '../environment.js'; | import { isMockEnvironment } from '../environment.js'; | ||||||
| import { validateConversation } from '../util/validateConversation.js'; | import { validateConversation } from '../util/validateConversation.js'; | ||||||
| import { | import { hasAllChatsChatFolder, type ChatFolder } from '../types/ChatFolder.js'; | ||||||
|   ChatFolderType, |  | ||||||
|   toCurrentChatFolders, |  | ||||||
|   type ChatFolder, |  | ||||||
| } from '../types/ChatFolder.js'; |  | ||||||
| 
 | 
 | ||||||
| const { debounce, isNumber, chunk } = lodash; | const { debounce, isNumber, chunk } = lodash; | ||||||
| 
 | 
 | ||||||
|  | @ -1663,20 +1659,9 @@ async function processManifest( | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const chatFoldersHasAllChatsFolder = chatFolders.some(chatFolder => { |     if (!hasAllChatsChatFolder(chatFolders)) { | ||||||
|       return ( |  | ||||||
|         chatFolder.folderType === ChatFolderType.ALL && |  | ||||||
|         chatFolder.deletedAtTimestampMs === 0 |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     if (!chatFoldersHasAllChatsFolder) { |  | ||||||
|       log.info(`process(${version}): creating all chats chat folder`); |       log.info(`process(${version}): creating all chats chat folder`); | ||||||
|       await DataWriter.createAllChatsChatFolder(); |       window.reduxActions.chatFolders.createAllChatsChatFolder(); | ||||||
|       const currentChatFolders = await DataReader.getCurrentChatFolders(); |  | ||||||
|       window.reduxActions.chatFolders.replaceAllChatFolderRecords( |  | ||||||
|         toCurrentChatFolders(currentChatFolders) |  | ||||||
|       ); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -2235,6 +2220,7 @@ async function upload({ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let storageServiceEnabled = false; | let storageServiceEnabled = false; | ||||||
|  | let storageServiceNeedsUploadAfterEnabled = false; | ||||||
| 
 | 
 | ||||||
| export function enableStorageService(): void { | export function enableStorageService(): void { | ||||||
|   if (storageServiceEnabled) { |   if (storageServiceEnabled) { | ||||||
|  | @ -2243,6 +2229,12 @@ export function enableStorageService(): void { | ||||||
| 
 | 
 | ||||||
|   storageServiceEnabled = true; |   storageServiceEnabled = true; | ||||||
|   log.info('enableStorageService'); |   log.info('enableStorageService'); | ||||||
|  | 
 | ||||||
|  |   if (storageServiceNeedsUploadAfterEnabled) { | ||||||
|  |     storageServiceUploadJob({ | ||||||
|  |       reason: 'storageServiceNeedsUploadAfterEnabled', | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function disableStorageService(reason: string): void { | export function disableStorageService(reason: string): void { | ||||||
|  | @ -2351,6 +2343,18 @@ export async function reprocessUnknownFields(): Promise<void> { | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function storageServiceUploadJobAfterEnabled({ | ||||||
|  |   reason, | ||||||
|  | }: { | ||||||
|  |   reason: string; | ||||||
|  | }): void { | ||||||
|  |   if (storageServiceEnabled) { | ||||||
|  |     return storageServiceUploadJob({ reason }); | ||||||
|  |   } | ||||||
|  |   log.info(`storageServiceNeedsUploadAfterEnabled: ${reason}`); | ||||||
|  |   storageServiceNeedsUploadAfterEnabled = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export const storageServiceUploadJob = debounce( | export const storageServiceUploadJob = debounce( | ||||||
|   ({ reason }: { reason: string }) => { |   ({ reason }: { reason: string }) => { | ||||||
|     if (!storageServiceEnabled) { |     if (!storageServiceEnabled) { | ||||||
|  |  | ||||||
|  | @ -923,6 +923,7 @@ type ReadableInterface = { | ||||||
|   getAllChatFolders: () => ReadonlyArray<ChatFolder>; |   getAllChatFolders: () => ReadonlyArray<ChatFolder>; | ||||||
|   getCurrentChatFolders: () => ReadonlyArray<ChatFolder>; |   getCurrentChatFolders: () => ReadonlyArray<ChatFolder>; | ||||||
|   getChatFolder: (id: ChatFolderId) => ChatFolder | null; |   getChatFolder: (id: ChatFolderId) => ChatFolder | null; | ||||||
|  |   hasAllChatsChatFolder: () => boolean; | ||||||
|   getOldestDeletedChatFolder: () => ChatFolder | null; |   getOldestDeletedChatFolder: () => ChatFolder | null; | ||||||
| 
 | 
 | ||||||
|   getMessagesNeedingUpgrade: ( |   getMessagesNeedingUpgrade: ( | ||||||
|  |  | ||||||
|  | @ -240,6 +240,7 @@ import { | ||||||
|   getCurrentChatFolders, |   getCurrentChatFolders, | ||||||
|   getChatFolder, |   getChatFolder, | ||||||
|   createChatFolder, |   createChatFolder, | ||||||
|  |   hasAllChatsChatFolder, | ||||||
|   createAllChatsChatFolder, |   createAllChatsChatFolder, | ||||||
|   updateChatFolder, |   updateChatFolder, | ||||||
|   markChatFolderDeleted, |   markChatFolderDeleted, | ||||||
|  | @ -459,6 +460,7 @@ export const DataReader: ServerReadableInterface = { | ||||||
|   getAllChatFolders, |   getAllChatFolders, | ||||||
|   getCurrentChatFolders, |   getCurrentChatFolders, | ||||||
|   getChatFolder, |   getChatFolder, | ||||||
|  |   hasAllChatsChatFolder, | ||||||
|   getOldestDeletedChatFolder, |   getOldestDeletedChatFolder, | ||||||
| 
 | 
 | ||||||
|   callLinkExists, |   callLinkExists, | ||||||
|  |  | ||||||
|  | @ -146,6 +146,19 @@ export function createChatFolder(db: WritableDB, chatFolder: ChatFolder): void { | ||||||
|   })(); |   })(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function hasAllChatsChatFolder(db: ReadableDB): boolean { | ||||||
|  |   const [query, params] = sql` | ||||||
|  |     SELECT EXISTS ( | ||||||
|  |       SELECT 1 FROM chatFolders | ||||||
|  |       WHERE folderType IS ${ChatFolderType.ALL} | ||||||
|  |       AND deletedAtTimestampMs IS 0 | ||||||
|  |       LIMIT 1 | ||||||
|  |     ) | ||||||
|  |   `;
 | ||||||
|  |   const result = db.prepare(query, { pluck: true }).get<number>(params); | ||||||
|  |   return result === 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function createAllChatsChatFolder(db: WritableDB): ChatFolder { | export function createAllChatsChatFolder(db: WritableDB): ChatFolder { | ||||||
|   return db.transaction(() => { |   return db.transaction(() => { | ||||||
|     const allChatsChatFolder: ChatFolder = { |     const allChatsChatFolder: ChatFolder = { | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ import { | ||||||
|   type CurrentChatFolders, |   type CurrentChatFolders, | ||||||
| } from '../../types/ChatFolder.js'; | } from '../../types/ChatFolder.js'; | ||||||
| import { getCurrentChatFolders } from '../selectors/chatFolders.js'; | import { getCurrentChatFolders } from '../selectors/chatFolders.js'; | ||||||
| import { DataWriter } from '../../sql/Client.js'; | import { DataReader, DataWriter } from '../../sql/Client.js'; | ||||||
| import { storageServiceUploadJob } from '../../services/storage.js'; | import { storageServiceUploadJob } from '../../services/storage.js'; | ||||||
| import { parseStrict } from '../../util/schemas.js'; | import { parseStrict } from '../../util/schemas.js'; | ||||||
| import { chatFolderCleanupService } from '../../services/expiring/chatFolderCleanupService.js'; | import { chatFolderCleanupService } from '../../services/expiring/chatFolderCleanupService.js'; | ||||||
|  | @ -142,6 +142,20 @@ function createChatFolder( | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function createAllChatsChatFolder(): ThunkAction< | ||||||
|  |   void, | ||||||
|  |   RootStateType, | ||||||
|  |   unknown, | ||||||
|  |   ChatFolderRecordReplaceAll | ||||||
|  | > { | ||||||
|  |   return async dispatch => { | ||||||
|  |     await DataWriter.createAllChatsChatFolder(); | ||||||
|  |     storageServiceUploadJob({ reason: 'createAllChatsChatFolder' }); | ||||||
|  |     const chatFolders = await DataReader.getCurrentChatFolders(); | ||||||
|  |     dispatch(replaceAllChatFolderRecords(toCurrentChatFolders(chatFolders))); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function updateChatFolder( | function updateChatFolder( | ||||||
|   chatFolderId: ChatFolderId, |   chatFolderId: ChatFolderId, | ||||||
|   chatFolderParams: ChatFolderParams |   chatFolderParams: ChatFolderParams | ||||||
|  | @ -210,6 +224,7 @@ export const actions = { | ||||||
|   replaceChatFolderRecord, |   replaceChatFolderRecord, | ||||||
|   removeChatFolderRecord, |   removeChatFolderRecord, | ||||||
|   createChatFolder, |   createChatFolder, | ||||||
|  |   createAllChatsChatFolder, | ||||||
|   updateChatFolder, |   updateChatFolder, | ||||||
|   deleteChatFolder, |   deleteChatFolder, | ||||||
|   updateChatFoldersPositions, |   updateChatFoldersPositions, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,11 @@ | ||||||
| import Long from 'long'; | import Long from 'long'; | ||||||
| import { v4 as generateUuid } from 'uuid'; | import { v4 as generateUuid } from 'uuid'; | ||||||
| import { Proto, StorageState } from '@signalapp/mock-server'; | import { Proto, StorageState } from '@signalapp/mock-server'; | ||||||
|  | import type { Page } from 'playwright/test'; | ||||||
| import { expect } from 'playwright/test'; | import { expect } from 'playwright/test'; | ||||||
| import * as durations from '../../util/durations/index.js'; | import * as durations from '../../util/durations/index.js'; | ||||||
| import type { App } from './fixtures.js'; | import type { App } from './fixtures.js'; | ||||||
| import { Bootstrap, debug } from './fixtures.js'; | import { Bootstrap, debug, getChatFolderRecordPredicate } from './fixtures.js'; | ||||||
| import { uuidToBytes } from '../../util/uuidToBytes.js'; | import { uuidToBytes } from '../../util/uuidToBytes.js'; | ||||||
| import { CHAT_FOLDER_DELETED_POSITION } from '../../types/ChatFolder.js'; | import { CHAT_FOLDER_DELETED_POSITION } from '../../types/ChatFolder.js'; | ||||||
| 
 | 
 | ||||||
|  | @ -48,10 +49,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|     await bootstrap.teardown(); |     await bootstrap.teardown(); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should update from storage service', async () => { |   async function openChatFolderSettings(window: Page) { | ||||||
|     const { phone } = bootstrap; |  | ||||||
|     const window = await app.getWindow(); |  | ||||||
| 
 |  | ||||||
|     const openSettingsBtn = window.locator( |     const openSettingsBtn = window.locator( | ||||||
|       '[data-testid="NavTabsItem--Settings"]' |       '[data-testid="NavTabsItem--Settings"]' | ||||||
|     ); |     ); | ||||||
|  | @ -60,50 +58,42 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       '.Preferences__control:has-text("Add a chat folder")' |       '.Preferences__control:has-text("Add a chat folder")' | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const ALL_CHATS_ID = generateUuid(); |     await openSettingsBtn.click(); | ||||||
|  |     await openChatsSettingsBtn.click(); | ||||||
|  |     await openChatFoldersSettingsBtn.click(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   it('should update from storage service', async () => { | ||||||
|  |     const { phone } = bootstrap; | ||||||
|  |     const window = await app.getWindow(); | ||||||
|  | 
 | ||||||
|  |     const ALL_CHATS_PREDICATE = getChatFolderRecordPredicate('ALL', '', false); | ||||||
|  | 
 | ||||||
|     const ALL_GROUPS_ID = generateUuid(); |     const ALL_GROUPS_ID = generateUuid(); | ||||||
|     const ALL_GROUPS_NAME = 'All Groups'; |     const ALL_GROUPS_NAME = 'All Groups'; | ||||||
|     const ALL_GROUPS_NAME_UPDATED = 'The Groups'; |     const ALL_GROUPS_NAME_UPDATED = 'The Groups'; | ||||||
| 
 | 
 | ||||||
|     const allChatsListItem = window.getByTestId(`ChatFolder--${ALL_CHATS_ID}`); |     const allChatsListItem = window | ||||||
|  |       .getByTestId('ChatFoldersList') | ||||||
|  |       .locator('.Preferences__ChatFolders__ChatSelection__Item') | ||||||
|  |       .getByText('All chats'); | ||||||
|  | 
 | ||||||
|     const allGroupsListItem = window.getByTestId( |     const allGroupsListItem = window.getByTestId( | ||||||
|       `ChatFolder--${ALL_GROUPS_ID}` |       `ChatFolder--${ALL_GROUPS_ID}` | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     await openSettingsBtn.click(); |  | ||||||
|     await openChatsSettingsBtn.click(); |  | ||||||
|     await openChatFoldersSettingsBtn.click(); |  | ||||||
| 
 |  | ||||||
|     debug('adding ALL chat folder via storage service'); |  | ||||||
|     { |     { | ||||||
|       let state = await phone.expectStorageState('initial state'); |       let state = await phone.expectStorageState('initial state'); | ||||||
| 
 |       // wait for initial creation of story distribution list and "all chats" chat folder
 | ||||||
|       state = state.addRecord({ |       state = await phone.waitForStorageState({ after: state }); | ||||||
|         type: IdentifierType.CHAT_FOLDER, |       expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); | ||||||
|         record: { |  | ||||||
|           chatFolder: { |  | ||||||
|             id: uuidToBytes(ALL_CHATS_ID), |  | ||||||
|             folderType: Proto.ChatFolderRecord.FolderType.ALL, |  | ||||||
|             name: null, |  | ||||||
|             position: 0, |  | ||||||
|             showOnlyUnread: false, |  | ||||||
|             showMutedChats: false, |  | ||||||
|             includeAllIndividualChats: true, |  | ||||||
|             includeAllGroupChats: true, |  | ||||||
|             includedRecipients: [], |  | ||||||
|             excludedRecipients: [], |  | ||||||
|             deletedAtTimestampMs: Long.fromNumber(0), |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       await phone.setStorageState(state); |  | ||||||
|       await phone.sendFetchStorage({ timestamp: bootstrap.getTimestamp() }); |  | ||||||
|       await app.waitForManifestVersion(state.version); |  | ||||||
| 
 |  | ||||||
|       await expect(allChatsListItem).toBeVisible(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     await openChatFolderSettings(window); | ||||||
|  | 
 | ||||||
|  |     debug('expect all chats folder to be created'); | ||||||
|  |     await expect(allChatsListItem).toBeVisible(); | ||||||
|  | 
 | ||||||
|     debug('adding "All Groups" chat folder via storage service'); |     debug('adding "All Groups" chat folder via storage service'); | ||||||
|     { |     { | ||||||
|       let state = await phone.expectStorageState('adding all groups'); |       let state = await phone.expectStorageState('adding all groups'); | ||||||
|  | @ -140,9 +130,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       let state = await phone.expectStorageState('updating all groups'); |       let state = await phone.expectStorageState('updating all groups'); | ||||||
| 
 | 
 | ||||||
|       state = state.updateRecord( |       state = state.updateRecord( | ||||||
|         item => { |         getChatFolderRecordPredicate('CUSTOM', ALL_GROUPS_NAME, false), | ||||||
|           return item.record.chatFolder?.name === ALL_GROUPS_NAME; |  | ||||||
|         }, |  | ||||||
|         item => { |         item => { | ||||||
|           return { |           return { | ||||||
|             ...item, |             ...item, | ||||||
|  | @ -168,9 +156,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       let state = await phone.expectStorageState('removing all groups'); |       let state = await phone.expectStorageState('removing all groups'); | ||||||
| 
 | 
 | ||||||
|       state = state.updateRecord( |       state = state.updateRecord( | ||||||
|         item => { |         getChatFolderRecordPredicate('CUSTOM', ALL_GROUPS_NAME_UPDATED, false), | ||||||
|           return item.record.chatFolder?.name === ALL_GROUPS_NAME_UPDATED; |  | ||||||
|         }, |  | ||||||
|         item => { |         item => { | ||||||
|           return { |           return { | ||||||
|             ...item, |             ...item, | ||||||
|  | @ -196,13 +182,12 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|     const { phone } = bootstrap; |     const { phone } = bootstrap; | ||||||
|     const window = await app.getWindow(); |     const window = await app.getWindow(); | ||||||
| 
 | 
 | ||||||
|     const openSettingsBtn = window.locator( |     const ALL_CHATS_PREDICATE = getChatFolderRecordPredicate('ALL', '', false); | ||||||
|       '[data-testid="NavTabsItem--Settings"]' | 
 | ||||||
|     ); |     const allChatsListItem = window | ||||||
|     const openChatsSettingsBtn = window.locator('.Preferences__button--chats'); |       .getByTestId('ChatFoldersList') | ||||||
|     const openChatFoldersSettingsBtn = window.locator( |       .locator('.Preferences__ChatFolders__ChatSelection__Item') | ||||||
|       '.Preferences__control:has-text("Add a chat folder")' |       .getByText('All chats'); | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     const groupPresetBtn = window |     const groupPresetBtn = window | ||||||
|       .getByTestId('ChatFolderPreset--GroupChats') |       .getByTestId('ChatFolderPreset--GroupChats') | ||||||
|  | @ -228,27 +213,26 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       .locator('button:has-text("Delete")'); |       .locator('button:has-text("Delete")'); | ||||||
| 
 | 
 | ||||||
|     let state = await phone.expectStorageState('initial state'); |     let state = await phone.expectStorageState('initial state'); | ||||||
|     // wait for initial creation of story distribution list
 |     // wait for initial creation of story distribution list and "all chats" chat folder
 | ||||||
|     state = await phone.waitForStorageState({ after: state }); |     state = await phone.waitForStorageState({ after: state }); | ||||||
|  |     expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); | ||||||
|  | 
 | ||||||
|  |     await openChatFolderSettings(window); | ||||||
|  | 
 | ||||||
|  |     debug('expect all chats folder to be created'); | ||||||
|  |     await expect(allChatsListItem).toBeVisible(); | ||||||
| 
 | 
 | ||||||
|     debug('creating group'); |     debug('creating group'); | ||||||
|     { |     { | ||||||
|       await openSettingsBtn.click(); |  | ||||||
|       await openChatsSettingsBtn.click(); |  | ||||||
|       await openChatFoldersSettingsBtn.click(); |  | ||||||
| 
 |  | ||||||
|       await groupPresetBtn.click(); |       await groupPresetBtn.click(); | ||||||
|       await expect(groupsFolderBtn).toBeVisible(); |       await expect(groupsFolderBtn).toBeVisible(); | ||||||
| 
 | 
 | ||||||
|       debug('waiting for storage sync'); |       debug('waiting for storage sync'); | ||||||
|       state = await phone.waitForStorageState({ after: state }); |       state = await phone.waitForStorageState({ after: state }); | ||||||
| 
 | 
 | ||||||
|       const found = state.hasRecord(item => { |       const found = state.hasRecord( | ||||||
|         return ( |         getChatFolderRecordPredicate('CUSTOM', 'Groups', false) | ||||||
|           item.type === IdentifierType.CHAT_FOLDER && |  | ||||||
|           item.record.chatFolder?.name === 'Groups' |  | ||||||
|       ); |       ); | ||||||
|       }); |  | ||||||
| 
 | 
 | ||||||
|       expect(found).toBe(true); |       expect(found).toBe(true); | ||||||
|     } |     } | ||||||
|  | @ -262,12 +246,9 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       debug('waiting for storage sync'); |       debug('waiting for storage sync'); | ||||||
|       state = await phone.waitForStorageState({ after: state }); |       state = await phone.waitForStorageState({ after: state }); | ||||||
| 
 | 
 | ||||||
|       const found = state.hasRecord(item => { |       const found = state.hasRecord( | ||||||
|         return ( |         getChatFolderRecordPredicate('CUSTOM', 'My Groups', false) | ||||||
|           item.type === IdentifierType.CHAT_FOLDER && |  | ||||||
|           item.record.chatFolder?.name === 'My Groups' |  | ||||||
|       ); |       ); | ||||||
|       }); |  | ||||||
| 
 | 
 | ||||||
|       expect(found).toBe(true); |       expect(found).toBe(true); | ||||||
|     } |     } | ||||||
|  | @ -281,12 +262,9 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       debug('waiting for storage sync'); |       debug('waiting for storage sync'); | ||||||
|       state = await phone.waitForStorageState({ after: state }); |       state = await phone.waitForStorageState({ after: state }); | ||||||
| 
 | 
 | ||||||
|       const found = state.findRecord(item => { |       const found = state.findRecord( | ||||||
|         return ( |         getChatFolderRecordPredicate('CUSTOM', 'My Groups', true) | ||||||
|           item.type === IdentifierType.CHAT_FOLDER && |  | ||||||
|           item.record.chatFolder?.name === 'My Groups' |  | ||||||
|       ); |       ); | ||||||
|       }); |  | ||||||
| 
 | 
 | ||||||
|       await expect(groupsFolderBtn).not.toBeAttached(); |       await expect(groupsFolderBtn).not.toBeAttached(); | ||||||
|       await expect(groupPresetBtn).toBeVisible(); |       await expect(groupPresetBtn).toBeVisible(); | ||||||
|  | @ -296,4 +274,46 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { | ||||||
|       ).toBeGreaterThan(0); |       ).toBeGreaterThan(0); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   it('should recover from all chats folder being deleted', async () => { | ||||||
|  |     const { phone } = bootstrap; | ||||||
|  |     const window = await app.getWindow(); | ||||||
|  | 
 | ||||||
|  |     const ALL_CHATS_PREDICATE = getChatFolderRecordPredicate('ALL', '', false); | ||||||
|  | 
 | ||||||
|  |     let state = await phone.expectStorageState('initial state'); | ||||||
|  |     expect(state.version).toBe(1); | ||||||
|  |     expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(false); | ||||||
|  | 
 | ||||||
|  |     // wait for initial creation of story distribution list and "all chats" chat folder
 | ||||||
|  |     state = await phone.waitForStorageState({ after: state }); | ||||||
|  |     expect(state.version).toBe(2); | ||||||
|  |     expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); | ||||||
|  | 
 | ||||||
|  |     await openChatFolderSettings(window); | ||||||
|  | 
 | ||||||
|  |     // update record
 | ||||||
|  |     state = state.updateRecord(ALL_CHATS_PREDICATE, item => { | ||||||
|  |       return { | ||||||
|  |         ...item, | ||||||
|  |         chatFolder: { | ||||||
|  |           ...item.chatFolder, | ||||||
|  |           position: CHAT_FOLDER_DELETED_POSITION, | ||||||
|  |           deletedAtTimestampMs: Long.fromNumber(Date.now()), | ||||||
|  |         }, | ||||||
|  |       }; | ||||||
|  |     }); | ||||||
|  |     state = await phone.setStorageState(state); | ||||||
|  |     expect(state.version).toBe(3); | ||||||
|  |     expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(false); | ||||||
|  | 
 | ||||||
|  |     // sync from phone to app
 | ||||||
|  |     await phone.sendFetchStorage({ timestamp: bootstrap.getTimestamp() }); | ||||||
|  |     await app.waitForManifestVersion(state.version); | ||||||
|  | 
 | ||||||
|  |     // wait for app to insert a new "All chats" chat folder
 | ||||||
|  |     state = await phone.waitForStorageState({ after: state }); | ||||||
|  |     expect(state.version).toBe(4); | ||||||
|  |     expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -239,3 +239,26 @@ export function getCallLinkRecordPredicate( | ||||||
|     return roomId === recordRoomId; |     return roomId === recordRoomId; | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function getChatFolderRecordPredicate( | ||||||
|  |   folderType: keyof typeof Proto.ChatFolderRecord.FolderType, | ||||||
|  |   name: string, | ||||||
|  |   deleted: boolean | ||||||
|  | ): (record: StorageStateRecord) => boolean { | ||||||
|  |   return ({ type, record }) => { | ||||||
|  |     const { chatFolder } = record; | ||||||
|  |     if (type !== IdentifierType.CHAT_FOLDER || chatFolder == null) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const deletedAtTimestampMs = | ||||||
|  |       chatFolder.deletedAtTimestampMs?.toNumber() ?? 0; | ||||||
|  |     const isDeleted = deletedAtTimestampMs > 0; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       chatFolder.folderType === Proto.ChatFolderRecord.FolderType[folderType] && | ||||||
|  |       chatFolder.name === name && | ||||||
|  |       isDeleted === deleted | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -271,3 +271,14 @@ export function lookupCurrentChatFolder( | ||||||
|   strictAssert(chatFolder != null, 'Missing chat folder'); |   strictAssert(chatFolder != null, 'Missing chat folder'); | ||||||
|   return chatFolder; |   return chatFolder; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function hasAllChatsChatFolder( | ||||||
|  |   chatFolders: ReadonlyArray<ChatFolder> | ||||||
|  | ): boolean { | ||||||
|  |   return chatFolders.some(chatFolder => { | ||||||
|  |     return ( | ||||||
|  |       chatFolder.folderType === ChatFolderType.ALL && | ||||||
|  |       chatFolder.deletedAtTimestampMs === 0 | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jamie Kyle
				Jamie Kyle