H-43: fix WEB Captain takeover E2E flow (#8)

* test(shangwutong): cover CID rename reliability

* H-43: fix WEB Captain takeover flow

* H-48: preserve compatible provider model

* H-49: make Captain takeover atomic

* H-50: prevent duplicate widget initialization

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-13 14:49:04 +08:00
committed by GitHub
co-authored by rogee
parent fff3dce1bd
commit 74eb006985
21 changed files with 431 additions and 63 deletions
@@ -0,0 +1,58 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const { app, ActionCableConnector } = vi.hoisted(() => ({
app: {
use: vi.fn(),
directive: vi.fn(),
mount: vi.fn(() => ({ $store: {} })),
},
ActionCableConnector: vi.fn(),
}));
vi.mock('vue', () => ({ createApp: () => app }));
vi.mock('vue-i18n', () => ({ createI18n: vi.fn() }));
vi.mock('vue-dompurify-html', () => ({ default: {} }));
vi.mock('../../widget/store', () => ({ default: {} }));
vi.mock('../../widget/App.vue', () => ({ default: {} }));
vi.mock('../../widget/helpers/actionCable', () => ({
default: ActionCableConnector,
}));
vi.mock('../../widget/i18n', () => ({ default: {} }));
vi.mock('../../widget/router', () => ({ default: {} }));
vi.mock('vue3-click-away', () => ({ directive: {} }));
vi.mock('../../shared/helpers/HTMLSanitizer', () => ({
domPurifyConfig: {},
}));
vi.mock('@formkit/vue', () => ({
plugin: {},
defaultConfig: vi.fn(),
}));
vi.mock('shared/helpers/Validators', () => ({
startsWithPlus: vi.fn(),
isPhoneNumberValidWithDialCode: vi.fn(),
}));
describe('widget entrypoint', () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
delete window.WOOT_WIDGET;
delete window.actionCable;
window.chatwootPubsubToken = 'pubsub-token';
});
it('mounts and connects only once when load fires repeatedly', async () => {
vi.spyOn(document, 'readyState', 'get').mockReturnValue('loading');
await import('../widget');
window.dispatchEvent(new Event('load'));
window.dispatchEvent(new Event('load'));
expect(app.mount).toHaveBeenCalledOnce();
expect(ActionCableConnector).toHaveBeenCalledOnce();
expect(ActionCableConnector).toHaveBeenCalledWith(
window.WOOT_WIDGET,
'pubsub-token'
);
});
});
@@ -45,10 +45,18 @@ app.use(
// Vue.config.productionTip = false;
window.onload = () => {
export const initWidget = () => {
if (window.WOOT_WIDGET) return;
window.WOOT_WIDGET = app.mount('#app');
window.actionCable = new ActionCableConnector(
window.WOOT_WIDGET,
window.chatwootPubsubToken
);
};
if (document.readyState === 'loading') {
window.addEventListener('load', initWidget, { once: true });
} else {
initWidget();
}