Skip to content

Commit d59feee

Browse files
naibanaiba/CloudCode
andcommitted
fix: move contact vault selector stuck loading forever (#58)
The vault selector in the Move Contact modal never loaded because vaultsList() response was double-unwrapped (.data.data instead of .data), always yielding undefined and falling back to an empty array. Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
1 parent c70cc96 commit d59feee

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

web/e2e/bugfix-58.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
let counter = 0;
4+
5+
function uniqueEmail(prefix: string): string {
6+
return `${prefix}-${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 6)}@example.com`;
7+
}
8+
9+
async function registerAndCreateTwoVaults(page: import('@playwright/test').Page, prefix: string) {
10+
const email = uniqueEmail(prefix);
11+
await page.goto('/register');
12+
await page.getByPlaceholder('First name').fill('Test');
13+
await page.getByPlaceholder('Last name').fill('User');
14+
await page.getByPlaceholder('Email').fill(email);
15+
await page.getByPlaceholder(/password/i).fill('password123');
16+
await page.getByRole('button', { name: /create account/i }).click();
17+
await expect(page).toHaveURL(/\/vaults/, { timeout: 15000 });
18+
19+
// Create first vault
20+
await page.getByRole('button', { name: /new vault/i }).click();
21+
await page.getByPlaceholder(/e\.g\. family/i).fill('Work Vault');
22+
await page.getByPlaceholder(/what is this vault/i).fill('Work contacts');
23+
await page.getByRole('button', { name: /create vault/i }).click();
24+
await expect(page).toHaveURL(/\/vaults\/[a-f0-9-]{36}$/, { timeout: 20000 });
25+
await page.waitForLoadState('networkidle');
26+
const firstVaultUrl = page.url();
27+
28+
// Go back to vault list and create second vault
29+
await page.goto('/vaults');
30+
await expect(page).toHaveURL(/\/vaults/, { timeout: 10000 });
31+
await page.getByRole('button', { name: /new vault/i }).click();
32+
await page.getByPlaceholder(/e\.g\. family/i).fill('Friends Vault');
33+
await page.getByPlaceholder(/what is this vault/i).fill('Friends contacts');
34+
await page.getByRole('button', { name: /create vault/i }).click();
35+
await expect(page).toHaveURL(/\/vaults\/[a-f0-9-]{36}$/, { timeout: 20000 });
36+
await page.waitForLoadState('networkidle');
37+
38+
return firstVaultUrl;
39+
}
40+
41+
// =====================================================================================
42+
// Issue #58: "Move contact to another vault" selector stays loading forever
43+
// =====================================================================================
44+
test.describe('Bug #58 - Move contact vault selector does not load', () => {
45+
test.setTimeout(90000);
46+
47+
test('should load vault options in the Move Contact modal selector', async ({ page }) => {
48+
const firstVaultUrl = await registerAndCreateTwoVaults(page, 'bug58');
49+
50+
// 1. Create a contact in the first vault
51+
await page.goto(firstVaultUrl + '/contacts');
52+
await page.waitForLoadState('networkidle');
53+
await page.getByRole('button', { name: /add contact/i }).click();
54+
await page.getByPlaceholder('First name').fill('MoveMe');
55+
await page.getByPlaceholder('Last name').fill('Please');
56+
await page.getByRole('button', { name: /create contact/i }).click();
57+
await expect(page).toHaveURL(/\/contacts\/[a-f0-9-]+$/, { timeout: 15000 });
58+
await page.waitForLoadState('networkidle');
59+
await expect(page.getByText('MoveMe Please').first()).toBeVisible({ timeout: 10000 });
60+
61+
// 2. Open the three-dot dropdown menu and click "Move"
62+
const moreBtn = page.locator('button').filter({ has: page.locator('[aria-label="more"]') });
63+
await expect(moreBtn).toBeVisible({ timeout: 5000 });
64+
await moreBtn.click();
65+
await page.getByText('Move', { exact: true }).click();
66+
67+
// 3. The Move Contact modal should appear
68+
const modal = page.locator('.ant-modal').filter({ hasText: /Move Contact/i });
69+
await expect(modal).toBeVisible({ timeout: 5000 });
70+
71+
// 4. Click the Select to open dropdown — vault options should load (not spin forever)
72+
const select = modal.locator('.ant-select');
73+
await select.click();
74+
// Bug #58: selector stays loading with spinner, vault names never appear.
75+
// The other vault ("Friends Vault") should appear as an option.
76+
await expect(page.locator('.ant-select-item-option').filter({ hasText: 'Friends Vault' }))
77+
.toBeVisible({ timeout: 10000 });
78+
});
79+
});

web/src/pages/contact/ContactDetail.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ export default function ContactDetail() {
121121
queryKey: ["vaults"],
122122
queryFn: async () => {
123123
const res = await api.vaults.vaultsList();
124-
return res.data?.data || [];
124+
// Fix #58: vaultsList() returns { data: VaultResponse[] } — don't double-unwrap with .data.data
125+
return res.data ?? [];
125126
},
126127
enabled: isMoveModalOpen,
127128
});

0 commit comments

Comments
 (0)