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