fix: 收敛消息推送弹窗保存异常
This commit is contained in:
parent
c67fc1b3de
commit
b42a393a41
@ -532,6 +532,20 @@ describe('account message-push modal', () => {
|
||||
expect(mocks.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not persist or lock when normal form validation fails', async () => {
|
||||
mocks.formApi.validate.mockResolvedValueOnce({ valid: false });
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.create).not.toHaveBeenCalled();
|
||||
expect(mocks.update).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.lock).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.unlock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('closes and invalidates an open session when the implicit selfId changes', async () => {
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
@ -616,7 +630,7 @@ describe('account message-push modal', () => {
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps a rejected session open and closes/emits exactly once on success', async () => {
|
||||
it('contains a rejected session and closes/emits exactly once on retry success', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
@ -627,17 +641,55 @@ describe('account message-push modal', () => {
|
||||
targets: [{ targetId: '12345', targetType: 'group' }],
|
||||
templateId: '50000000000000001',
|
||||
};
|
||||
const expectedPayload = {
|
||||
enabled: true,
|
||||
subscriptionId: '20000000000000001',
|
||||
targets: [{ targetId: '12345', targetType: 'group' as const }],
|
||||
templateId: '50000000000000001',
|
||||
};
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).rejects.toThrow('save failed');
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
expect(mocks.modalApi.lock).toHaveBeenCalledOnce();
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'10000000000000001',
|
||||
expectedPayload,
|
||||
);
|
||||
|
||||
mocks.create.mockResolvedValue({});
|
||||
await mocks.modalOptions.onConfirm();
|
||||
expect(mocks.modalApi.close).toHaveBeenCalledOnce();
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledTimes(2);
|
||||
expect(wrapper.emitted('saved')).toHaveLength(1);
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'10000000000000001',
|
||||
expectedPayload,
|
||||
);
|
||||
});
|
||||
|
||||
it('contains persistence failure when the real modal owner discards the callback promise', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('discarded save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
mocks.currentValues = {
|
||||
enabled: true,
|
||||
subscriptionId: '20000000000000001',
|
||||
targets: [{ targetId: '12345', targetType: 'group' }],
|
||||
templateId: '50000000000000001',
|
||||
};
|
||||
|
||||
void mocks.modalOptions.onConfirm();
|
||||
await vi.waitFor(() => {
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.create).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@ -107,9 +107,16 @@ export default defineComponent({
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[760px]',
|
||||
fullscreenButton: false,
|
||||
/** Validates and persists the active account-scoped session. */
|
||||
/**
|
||||
* Persists the active session while containing failures at ModalApi's
|
||||
* non-awaited callback boundary.
|
||||
*/
|
||||
async onConfirm() {
|
||||
await submit();
|
||||
try {
|
||||
await submit();
|
||||
} catch {
|
||||
// The request/form layer already presents the persistence error.
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Restores only the latest session after destroy-on-close content mounts.
|
||||
|
||||
@ -422,6 +422,20 @@ describe('message subscription modal', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not persist or lock when normal form validation fails', async () => {
|
||||
mocks.formApi.validate.mockResolvedValueOnce({ valid: false });
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.create).not.toHaveBeenCalled();
|
||||
expect(mocks.update).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.lock).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.unlock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('locks, closes, emits once, and unlocks only after successful persistence', async () => {
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
@ -443,18 +457,29 @@ describe('message subscription modal', () => {
|
||||
expect(lockOrder).toBeLessThan(createOrder);
|
||||
});
|
||||
|
||||
it('keeps the modal usable and emits nothing after a rejected mutation', async () => {
|
||||
mocks.create.mockRejectedValue(new Error('save failed'));
|
||||
it('contains a rejected mutation and remains usable for retry', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
const expectedPayload = {
|
||||
enabled: true,
|
||||
name: '帕鲁端口变更',
|
||||
remark: 'managed',
|
||||
sourceConfig: {
|
||||
ddnsRecordId: '2041700000000000002',
|
||||
portForwardId: '2041700000000000001',
|
||||
},
|
||||
sourceKey: 'network.stun.mapping-port-changed',
|
||||
};
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).rejects.toThrow('save failed');
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.modalApi.lock).toHaveBeenCalledOnce();
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(1, expectedPayload);
|
||||
|
||||
mocks.create.mockResolvedValue({});
|
||||
await mocks.modalOptions.onConfirm();
|
||||
@ -462,5 +487,22 @@ describe('message subscription modal', () => {
|
||||
expect(mocks.modalApi.close).toHaveBeenCalledOnce();
|
||||
expect(wrapper.emitted('saved')).toHaveLength(1);
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(2, expectedPayload);
|
||||
});
|
||||
|
||||
it('contains persistence failure when the real modal owner discards the callback promise', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('discarded save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
|
||||
void mocks.modalOptions.onConfirm();
|
||||
await vi.waitFor(() => {
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.create).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@ -95,9 +95,16 @@ export default defineComponent({
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[680px]',
|
||||
fullscreenButton: false,
|
||||
/** Validates and persists the current modal session. */
|
||||
/**
|
||||
* Persists the current session while containing failures at ModalApi's
|
||||
* non-awaited callback boundary.
|
||||
*/
|
||||
async onConfirm() {
|
||||
await submit();
|
||||
try {
|
||||
await submit();
|
||||
} catch {
|
||||
// The request/form layer already presents the persistence error.
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Restores session values after destroy-on-close modal content has mounted.
|
||||
|
||||
@ -513,22 +513,75 @@ describe('message template modal', () => {
|
||||
expect(mocks.update).toHaveBeenCalledWith('10000000000000001', payload);
|
||||
});
|
||||
|
||||
it('stays open/unlocked after failure and closes/emits once on retry success', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('save failed'));
|
||||
it('does not persist or lock when normal form validation fails', async () => {
|
||||
mocks.formApi.validate.mockResolvedValueOnce({ valid: false });
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).rejects.toThrow('save failed');
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.create).not.toHaveBeenCalled();
|
||||
expect(mocks.update).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.lock).not.toHaveBeenCalled();
|
||||
expect(mocks.modalApi.unlock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('contains failure and closes/emits once on retry success', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
Object.assign(mocks.formValues, {
|
||||
content: '${{endpoint}}',
|
||||
enabled: true,
|
||||
name: ' template ',
|
||||
remark: ' note ',
|
||||
sourceKey: 'source-a',
|
||||
});
|
||||
const expectedPayload = {
|
||||
content: '${{endpoint}}',
|
||||
enabled: true,
|
||||
name: 'template',
|
||||
remark: 'note',
|
||||
sourceKey: 'source-a',
|
||||
};
|
||||
|
||||
await expect(mocks.modalOptions.onConfirm()).resolves.toBeUndefined();
|
||||
expect(mocks.modalApi.lock).toHaveBeenCalledOnce();
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(1, expectedPayload);
|
||||
|
||||
mocks.create.mockResolvedValueOnce({});
|
||||
await mocks.modalOptions.onConfirm();
|
||||
expect(mocks.modalApi.close).toHaveBeenCalledOnce();
|
||||
expect(wrapper.emitted('saved')).toHaveLength(1);
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.create).toHaveBeenNthCalledWith(2, expectedPayload);
|
||||
});
|
||||
|
||||
it('contains persistence failure when the real modal owner discards the callback promise', async () => {
|
||||
mocks.create.mockRejectedValueOnce(new Error('discarded save failed'));
|
||||
const wrapper = mountModal();
|
||||
(wrapper.vm as any).openCreate();
|
||||
await flushPromises();
|
||||
Object.assign(mocks.formValues, {
|
||||
content: '${{endpoint}}',
|
||||
enabled: true,
|
||||
name: 'template',
|
||||
remark: '',
|
||||
sourceKey: 'source-a',
|
||||
});
|
||||
|
||||
void mocks.modalOptions.onConfirm();
|
||||
await vi.waitFor(() => {
|
||||
expect(mocks.modalApi.unlock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
expect(mocks.modalApi.close).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('saved')).toBeUndefined();
|
||||
expect(mocks.create).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@ -103,9 +103,16 @@ export default defineComponent({
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[760px]',
|
||||
fullscreenButton: false,
|
||||
/** Validates and persists the exact five-field template payload. */
|
||||
/**
|
||||
* Persists the exact payload while containing failures at ModalApi's
|
||||
* non-awaited callback boundary.
|
||||
*/
|
||||
async onConfirm() {
|
||||
await submit();
|
||||
try {
|
||||
await submit();
|
||||
} catch {
|
||||
// The request/form layer already presents the persistence error.
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Resets every mounted form/session state before loading authoritative detail.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user