mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
feat: allow setting an address book as default (#924)
This commit is contained in:
@@ -161,6 +161,69 @@ describe('JMAPClient contact methods', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultAddressBook', () => {
|
||||
it('should send onSuccessSetIsDefault rather than an isDefault update', async () => {
|
||||
const client = createClient();
|
||||
const spy = mockFetch({
|
||||
methodResponses: [['AddressBook/set', { updated: null }, '0']],
|
||||
});
|
||||
|
||||
await client.setDefaultAddressBook('ab-2');
|
||||
|
||||
const body = JSON.parse((spy.mock.calls[0][1] as RequestInit).body as string);
|
||||
expect(body.methodCalls[0][0]).toBe('AddressBook/set');
|
||||
expect(body.methodCalls[0][1]).toMatchObject({
|
||||
accountId: 'account-1',
|
||||
onSuccessSetIsDefault: 'ab-2',
|
||||
});
|
||||
expect(body.methodCalls[0][1].update).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should target a shared account when one is given', async () => {
|
||||
const client = createClient();
|
||||
const spy = mockFetch({
|
||||
methodResponses: [['AddressBook/set', { updated: null }, '0']],
|
||||
});
|
||||
|
||||
await client.setDefaultAddressBook('ab-2', 'account-2');
|
||||
|
||||
const body = JSON.parse((spy.mock.calls[0][1] as RequestInit).body as string);
|
||||
expect(body.methodCalls[0][1].accountId).toBe('account-2');
|
||||
});
|
||||
|
||||
it('should throw on a JMAP method error', async () => {
|
||||
const client = createClient();
|
||||
mockFetch({
|
||||
methodResponses: [['error', { type: 'forbidden', description: 'Not allowed' }, '0']],
|
||||
});
|
||||
|
||||
await expect(client.setDefaultAddressBook('ab-2')).rejects.toThrow('Not allowed');
|
||||
});
|
||||
|
||||
it('should throw for an unexpected response method', async () => {
|
||||
const client = createClient();
|
||||
mockFetch({
|
||||
methodResponses: [['SomethingElse', {}, '0']],
|
||||
});
|
||||
|
||||
await expect(client.setDefaultAddressBook('ab-2')).rejects.toThrow('Failed to set default address book');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAddressBook', () => {
|
||||
it('should drop isDefault, which AddressBook/set rejects as read-only', async () => {
|
||||
const client = createClient();
|
||||
const spy = mockFetch({
|
||||
methodResponses: [['AddressBook/set', { updated: { 'ab-1': null } }, '0']],
|
||||
});
|
||||
|
||||
await client.updateAddressBook('ab-1', { name: 'Work', isDefault: true });
|
||||
|
||||
const body = JSON.parse((spy.mock.calls[0][1] as RequestInit).body as string);
|
||||
expect(body.methodCalls[0][1].update['ab-1']).toEqual({ name: 'Work' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContacts', () => {
|
||||
it('should return contacts from server', async () => {
|
||||
const client = createClient();
|
||||
|
||||
@@ -779,6 +779,12 @@ export class DemoJMAPClient implements IJMAPClient {
|
||||
if (book) Object.assign(book, updates);
|
||||
}
|
||||
|
||||
async setDefaultAddressBook(addressBookId: string): Promise<void> {
|
||||
for (const book of this.data.addressBooks) {
|
||||
book.isDefault = book.id === addressBookId;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAddressBook(addressBookId: string): Promise<void> {
|
||||
this.data.addressBooks = this.data.addressBooks.filter(b => b.id !== addressBookId);
|
||||
this.data.contacts = this.data.contacts.filter(c => !c.addressBookIds?.[addressBookId]);
|
||||
|
||||
@@ -341,6 +341,7 @@ export interface IJMAPClient {
|
||||
getAllAddressBooks(): Promise<AddressBook[]>;
|
||||
createAddressBook(name: string): Promise<AddressBook>;
|
||||
updateAddressBook(addressBookId: string, updates: Partial<AddressBook>, targetAccountId?: string): Promise<void>;
|
||||
setDefaultAddressBook(addressBookId: string, targetAccountId?: string): Promise<void>;
|
||||
deleteAddressBook(addressBookId: string, targetAccountId?: string): Promise<void>;
|
||||
getContacts(addressBookId?: string, options?: { throwOnError?: boolean }): Promise<ContactCard[]>;
|
||||
getAllContacts(): Promise<ContactCard[]>;
|
||||
|
||||
+29
-3
@@ -4815,13 +4815,14 @@ export class JMAPClient implements IJMAPClient {
|
||||
|
||||
async updateAddressBook(addressBookId: string, updates: Partial<AddressBook>, targetAccountId?: string): Promise<void> {
|
||||
const accountId = targetAccountId || this.getContactsAccountId();
|
||||
// Only forward server-settable properties
|
||||
const { name, description, sortOrder, isDefault, color } = updates as Record<string, unknown>;
|
||||
// Only forward server-settable properties. `isDefault` is deliberately not
|
||||
// among them: it is read-only in AddressBook/set and including it fails the
|
||||
// whole update with invalidProperties - use setDefaultAddressBook instead.
|
||||
const { name, description, sortOrder, color } = updates as Record<string, unknown>;
|
||||
const patch: Record<string, unknown> = {};
|
||||
if (name !== undefined) patch.name = name;
|
||||
if (description !== undefined) patch.description = description;
|
||||
if (sortOrder !== undefined) patch.sortOrder = sortOrder;
|
||||
if (isDefault !== undefined) patch.isDefault = isDefault;
|
||||
if (color !== undefined) patch.color = color;
|
||||
|
||||
const response = await this.request([
|
||||
@@ -4842,6 +4843,31 @@ export class JMAPClient implements IJMAPClient {
|
||||
throw new Error("Failed to update address book");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an address book as the account default. `isDefault` is read-only in
|
||||
* AddressBook/set - the default is changed via the `onSuccessSetIsDefault`
|
||||
* request argument instead (same shape as Calendar/set).
|
||||
*/
|
||||
async setDefaultAddressBook(addressBookId: string, targetAccountId?: string): Promise<void> {
|
||||
const accountId = targetAccountId || this.getContactsAccountId();
|
||||
|
||||
const response = await this.request([
|
||||
["AddressBook/set", {
|
||||
accountId,
|
||||
onSuccessSetIsDefault: addressBookId,
|
||||
}, "0"]
|
||||
], this.contactUsing());
|
||||
|
||||
const methodName = response.methodResponses?.[0]?.[0];
|
||||
if (methodName === "error") {
|
||||
const error = response.methodResponses?.[0]?.[1];
|
||||
throw new Error(error?.description || error?.type || "Failed to set default address book");
|
||||
}
|
||||
if (methodName !== "AddressBook/set") {
|
||||
throw new Error("Failed to set default address book");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAddressBook(addressBookId: string, targetAccountId?: string): Promise<void> {
|
||||
const accountId = targetAccountId || this.getContactsAccountId();
|
||||
const response = await this.request([
|
||||
|
||||
Reference in New Issue
Block a user