Files
NapCatQQ/packages/napcat-test/offlineStatusEvent.test.ts
T
TanTan f954252a34
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
Release Tag / validate-version (push) Waiting to run
NapCatQQ source snapshot
2026-11-13 09:01:47 +08:00

124 lines
4.0 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'vitest';
import { NapCatCore } from '../napcat-core/index';
import type { SelfStatusInfo, SelfInfo } from '../napcat-core/types';
interface FakeProfileListener {
onSelfStatusChanged: (info: SelfStatusInfo) => void;
}
interface FakeMessageListener {
onKickedOffLine: (info: { tipsTitle: string; tipsDesc: string; }) => void;
}
function status (value: number): SelfStatusInfo {
return {
uid: 'self-uid',
status: value,
extStatus: 0,
termType: 0,
netType: 0,
iconType: 0,
customStatus: undefined,
setTime: '0',
};
}
function createCore (online: boolean | undefined) {
let profileListener: FakeProfileListener | undefined;
let messageListener: FakeMessageListener | undefined;
const session = {
getMsgService: () => ({
addKernelMsgListener: vi.fn((listener: FakeMessageListener) => {
messageListener = listener;
}),
}),
getProfileService: () => ({
addKernelProfileListener: vi.fn((listener: FakeProfileListener) => {
profileListener = listener;
}),
}),
};
const logger = {
log: vi.fn(),
logError: vi.fn(),
logWarn: vi.fn(),
logDebug: vi.fn(),
};
const emit = vi.fn();
// NapCatCore's constructor loads the native wrapper, which is unavailable in tests.
// Only the listener wiring is under test, so the instance is built field-by-field.
const core = Object.create(NapCatCore.prototype) as NapCatCore;
Object.assign(core, {
context: { logger, session },
selfInfo: { uid: 'self-uid', uin: '1', online } as SelfInfo,
event: { emit },
});
return {
core,
emit,
profileListener: () => profileListener,
messageListener: () => messageListener,
};
}
describe('silent offline (SelfOffline) event', () => {
afterEach(() => {
vi.restoreAllMocks();
});
test('emits SelfOffline when the account transitions online -> offline', async () => {
const { core, emit, profileListener } = createCore(true);
await core.initNapCatCoreListeners();
profileListener()!.onSelfStatusChanged(status(20));
expect(emit).toHaveBeenCalledTimes(1);
expect(emit).toHaveBeenCalledWith('SelfOffline', expect.stringContaining('账号状态变更为离线'));
expect(core.selfInfo.online).toBe(false);
});
test('does not emit again while the account stays offline', async () => {
const { core, emit, profileListener } = createCore(true);
await core.initNapCatCoreListeners();
profileListener()!.onSelfStatusChanged(status(20));
profileListener()!.onSelfStatusChanged(status(20));
profileListener()!.onSelfStatusChanged(status(20));
expect(emit).toHaveBeenCalledTimes(1);
});
test('emits again after the account recovered and went offline once more', async () => {
const { core, emit, profileListener } = createCore(true);
await core.initNapCatCoreListeners();
profileListener()!.onSelfStatusChanged(status(20));
profileListener()!.onSelfStatusChanged(status(10));
profileListener()!.onSelfStatusChanged(status(20));
expect(emit).toHaveBeenCalledTimes(2);
expect(core.selfInfo.online).toBe(false);
});
test('does not emit during startup when the account was never online', async () => {
const { core, emit, profileListener } = createCore(undefined);
await core.initNapCatCoreListeners();
profileListener()!.onSelfStatusChanged(status(20));
expect(emit).not.toHaveBeenCalled();
expect(core.selfInfo.online).toBe(false);
});
test('keeps emitting KickedOffLine as before', async () => {
const { core, emit, messageListener } = createCore(true);
await core.initNapCatCoreListeners();
messageListener()!.onKickedOffLine({ tipsTitle: '下线通知', tipsDesc: '当前账号已在其它设备登录' });
expect(emit).toHaveBeenCalledTimes(1);
expect(emit).toHaveBeenCalledWith('KickedOffLine', expect.stringContaining('当前账号已在其它设备登录'));
expect(core.selfInfo.online).toBe(false);
});
});