Update tests and reload state after sync (#1617)

This commit is contained in:
Leendert de Borst
2026-02-04 11:48:45 +01:00
committed by Leendert de Borst
parent 0148a2967a
commit c8ee8dabde
4 changed files with 47 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
import { useCallback } from 'react';
import { sendMessage } from 'webext-bridge/popup';
import type { FullVaultSyncResult } from '@/entrypoints/background/VaultMessageHandler';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { EncryptionUtility } from '@/utils/EncryptionUtility';
@@ -56,6 +57,9 @@ export function useVaultMutate(): {
* Trigger a sync in the background script.
* This is fire-and-forget - the sync runs entirely in the background context
* and continues even if the popup closes.
*
* If a merge happened during sync (hasNewVault=true), reload the database
* so the popup shows the merged data.
*/
const triggerBackgroundSync = useCallback((): void => {
/*
@@ -63,7 +67,11 @@ export function useVaultMutate(): {
* The background script will handle the full sync orchestration
* and will re-sync if mutations happened during the sync.
*/
sendMessage('FULL_VAULT_SYNC', {}, 'background').then(async () => {
sendMessage('FULL_VAULT_SYNC', {}, 'background').then(async (result: FullVaultSyncResult) => {
// If a merge happened, reload the database to show merged data
if (result.hasNewVault) {
await dbContext.loadStoredDatabase();
}
// Refresh sync state if popup is still open
await dbContext.refreshSyncState();
}).catch((error) => {

View File

@@ -73,8 +73,10 @@ test.describe.serial('5. Vault Merge', () => {
});
test('5.4 Client B vault should contain both credentials after merge', async () => {
// Trigger sync to ensure background merge has completed and database is reloaded
await clientB
.goToVault()
.triggerSync()
.then((c) => c.goToVault())
.then((c) => c.screenshot('5.4-client-b-vault-state.png'))
.then((c) => c.verifyCredentialExists(credentialNameA));

View File

@@ -81,7 +81,9 @@ test.describe.serial('6. Field-Level Merge', () => {
.then((c) => c.fillNotes(clientBNotes))
.then((c) => c.screenshot('6.4-client-b-before-save.png'))
.then((c) => c.saveCredential())
.then((c) => c.screenshot('6.4-client-b-after-save.png'));
.then((c) => c.screenshot('6.4-client-b-after-save.png'))
// Wait for background sync (including merge) to complete before next test
.then((c) => c.triggerSync());
});
test('6.5 Client B verifies field-level merge result', async () => {

View File

@@ -65,6 +65,11 @@ export async function waitForVaultReady(popup: Page, timeout: number = Timeouts.
* Wait for a sync operation to complete.
* Waits for vault UI to be ready, then waits for sync/pending indicators to disappear.
*
* Indicator elements:
* - Syncing (green): <div> with bg-green-100
* - Pending (blue): <button> with bg-blue-100
* - Offline (amber): <button> with bg-amber-100
*
* @param popup - The popup page
* @param timeout - Timeout in milliseconds
*/
@@ -78,20 +83,38 @@ export async function waitForSyncComplete(popup: Page, timeout: number = Timeout
// Loading overlay might not exist, which is fine
});
// Wait for the sync indicator (green spinning icon) to disappear
// Wait for the sync indicator (green spinning div) to disappear
const syncIndicator = popup.locator('div.bg-green-100, div.bg-green-900\\/30');
await syncIndicator.waitFor({ state: 'hidden', timeout }).catch(() => {
// Sync indicator might not exist or sync was very fast, which is fine
});
// Also wait for pending sync indicator (blue icon) to disappear
const pendingSyncIndicator = popup.locator('div.bg-blue-100, div.bg-blue-900\\/30');
// Also wait for pending sync indicator (blue button) to disappear
const pendingSyncIndicator = popup.locator('button.bg-blue-100, button.bg-blue-900\\/30');
await pendingSyncIndicator.waitFor({ state: 'hidden', timeout }).catch(() => {
// Pending sync indicator might not exist, which is fine
});
// Small buffer to ensure async operations complete after UI indicators disappear
await popup.waitForTimeout(100);
// Wait for isDirty to be false, indicating sync completed successfully.
// This is more reliable than UI indicators since sync now runs in background.
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const isDirty = await popup.evaluate(() => {
return new Promise<boolean>((resolve) => {
chrome.storage.local.get('isDirty', (result) => {
resolve(result.isDirty ?? false);
});
});
});
if (!isDirty) {
break;
}
await popup.waitForTimeout(100);
}
// Additional buffer to ensure React state updates and database reload have completed
await popup.waitForTimeout(500);
}
/**
@@ -152,12 +175,13 @@ export async function waitForNavigation(popup: Page, timeout: number = Timeouts.
/**
* Wait for the offline indicator to appear on the page.
* The offline indicator is a <button> element with amber background.
*
* @param popup - The popup page
* @param timeout - Timeout in milliseconds
*/
export async function waitForOfflineIndicator(popup: Page, timeout: number = Timeouts.SHORT): Promise<void> {
const offlineIndicator = popup.locator('div.bg-amber-100, div.bg-amber-900\\/30').filter({ hasText: 'Offline' });
const offlineIndicator = popup.locator('button.bg-amber-100, button.bg-amber-900\\/30').filter({ hasText: 'Offline' });
await expect(offlineIndicator).toBeVisible({ timeout });
}
@@ -173,11 +197,12 @@ export async function waitForLoginForm(popup: Page, timeout: number = Timeouts.S
/**
* Check if the offline indicator is visible.
* The offline indicator is a <button> element with amber background.
*
* @param popup - The popup page
* @returns True if the offline indicator is visible
*/
export async function isOfflineIndicatorVisible(popup: Page): Promise<boolean> {
const offlineIndicator = popup.locator('div.bg-amber-100, div.bg-amber-900\\/30').filter({ hasText: 'Offline' });
const offlineIndicator = popup.locator('button.bg-amber-100, button.bg-amber-900\\/30').filter({ hasText: 'Offline' });
return offlineIndicator.isVisible().catch(() => false);
}