style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,172 +1,192 @@
import { describe, test, expect, mock, beforeEach } from "bun:test";
import { describe, test, expect, mock, beforeEach } from 'bun:test'
// In-memory localStorage mock
let store: Record<string, string> = {};
let store: Record<string, string> = {}
beforeEach(() => {
store = {};
(globalThis as any).localStorage = {
store = {}
;(globalThis as any).localStorage = {
getItem: (k: string) => store[k] ?? null,
setItem: (k: string, v: string) => { store[k] = v; },
removeItem: (k: string) => { delete store[k]; },
clear: () => { store = {}; },
get length() { return Object.keys(store).length; },
setItem: (k: string, v: string) => {
store[k] = v
},
removeItem: (k: string) => {
delete store[k]
},
clear: () => {
store = {}
},
get length() {
return Object.keys(store).length
},
key: () => null,
};
});
}
})
// Mock fetch
const fetchMock = {
lastUrl: "",
lastUrl: '',
lastOpts: {} as RequestInit,
response: { ok: true, status: 200, statusText: "OK" },
response: { ok: true, status: 200, statusText: 'OK' },
responseData: {} as any,
};
}
beforeEach(() => {
fetchMock.lastUrl = "";
fetchMock.lastOpts = {};
fetchMock.response = { ok: true, status: 200, statusText: "OK" };
fetchMock.responseData = {};
client.setActiveApiToken(null);
});
fetchMock.lastUrl = ''
fetchMock.lastOpts = {}
fetchMock.response = { ok: true, status: 200, statusText: 'OK' }
fetchMock.responseData = {}
client.setActiveApiToken(null)
})
(globalThis as any).fetch = async (url: string, opts: RequestInit) => {
fetchMock.lastUrl = url;
fetchMock.lastOpts = opts;
;(globalThis as any).fetch = async (url: string, opts: RequestInit) => {
fetchMock.lastUrl = url
fetchMock.lastOpts = opts
return {
ok: fetchMock.response.ok,
status: fetchMock.response.status,
statusText: fetchMock.response.statusText,
json: async () => fetchMock.responseData,
} as Response;
};
} as Response
}
const { getUuid, setUuid } = await import("../api/client");
const { getUuid, setUuid } = await import('../api/client')
// Import api* functions - they depend on getUuid and fetch
const client = await import("../api/client");
const relayClient = await import("../acp/relay-client");
const client = await import('../api/client')
const relayClient = await import('../acp/relay-client')
// =============================================================================
// getUuid()
// =============================================================================
describe("getUuid", () => {
test("returns existing UUID from localStorage", () => {
store["rcs_uuid"] = "existing-uuid";
expect(getUuid()).toBe("existing-uuid");
});
describe('getUuid', () => {
test('returns existing UUID from localStorage', () => {
store['rcs_uuid'] = 'existing-uuid'
expect(getUuid()).toBe('existing-uuid')
})
test("generates and stores new UUID when none exists", () => {
const uuid = getUuid();
test('generates and stores new UUID when none exists', () => {
const uuid = getUuid()
expect(uuid).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
);
expect(store["rcs_uuid"]).toBe(uuid);
});
)
expect(store['rcs_uuid']).toBe(uuid)
})
test("returns same UUID on subsequent calls", () => {
const a = getUuid();
const b = getUuid();
expect(a).toBe(b);
});
});
test('returns same UUID on subsequent calls', () => {
const a = getUuid()
const b = getUuid()
expect(a).toBe(b)
})
})
// =============================================================================
// setUuid()
// =============================================================================
describe("setUuid", () => {
test("writes UUID to localStorage", () => {
setUuid("custom-uuid-999");
expect(store["rcs_uuid"]).toBe("custom-uuid-999");
});
describe('setUuid', () => {
test('writes UUID to localStorage', () => {
setUuid('custom-uuid-999')
expect(store['rcs_uuid']).toBe('custom-uuid-999')
})
test("getUuid returns the set UUID", () => {
setUuid("my-uuid");
expect(getUuid()).toBe("my-uuid");
});
});
test('getUuid returns the set UUID', () => {
setUuid('my-uuid')
expect(getUuid()).toBe('my-uuid')
})
})
// =============================================================================
// api() — tested via apiFetchSession (GET) and apiBind (POST)
// =============================================================================
describe("api functions", () => {
test("GET request appends uuid to URL", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.responseData = [];
await client.apiFetchSessions();
expect(fetchMock.lastUrl).toContain("uuid=test-uuid");
expect(fetchMock.lastOpts.method).toBe("GET");
});
describe('api functions', () => {
test('GET request appends uuid to URL', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.responseData = []
await client.apiFetchSessions()
expect(fetchMock.lastUrl).toContain('uuid=test-uuid')
expect(fetchMock.lastOpts.method).toBe('GET')
})
test("GET request uses ? for URL without existing query params", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.responseData = [];
await client.apiFetchSessions();
expect(fetchMock.lastUrl).toContain("?uuid=");
});
test('GET request uses ? for URL without existing query params', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.responseData = []
await client.apiFetchSessions()
expect(fetchMock.lastUrl).toContain('?uuid=')
})
test("GET request uses & for URL with existing query params", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.responseData = [];
await client.apiFetchAllSessions();
test('GET request uses & for URL with existing query params', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.responseData = []
await client.apiFetchAllSessions()
// apiFetchAllSessions calls GET /web/sessions/all
expect(fetchMock.lastUrl).toContain("?uuid=");
});
expect(fetchMock.lastUrl).toContain('?uuid=')
})
test("POST request includes JSON body", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.responseData = {};
await client.apiBind("sess-1");
expect(fetchMock.lastOpts.method).toBe("POST");
expect(fetchMock.lastOpts.body).toBe(JSON.stringify({ sessionId: "sess-1" }));
expect(fetchMock.lastOpts.headers).toEqual({ "Content-Type": "application/json" });
});
test("active API token is sent only in Authorization header", async () => {
store["rcs_uuid"] = "browser-uuid";
fetchMock.responseData = [];
client.setActiveApiToken("secret-token");
await client.apiFetchSessions();
expect(fetchMock.lastUrl).toContain("uuid=browser-uuid");
expect(fetchMock.lastUrl).not.toContain("secret-token");
test('POST request includes JSON body', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.responseData = {}
await client.apiBind('sess-1')
expect(fetchMock.lastOpts.method).toBe('POST')
expect(fetchMock.lastOpts.body).toBe(
JSON.stringify({ sessionId: 'sess-1' }),
)
expect(fetchMock.lastOpts.headers).toEqual({
"Content-Type": "application/json",
Authorization: "Bearer secret-token",
});
});
'Content-Type': 'application/json',
})
})
test("throws error on non-ok response", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.response = { ok: false, status: 401, statusText: "Unauthorized" };
fetchMock.responseData = { error: { type: "auth", message: "Invalid UUID" } };
await expect(client.apiFetchSessions()).rejects.toThrow("Invalid UUID");
});
test('active API token is sent only in Authorization header', async () => {
store['rcs_uuid'] = 'browser-uuid'
fetchMock.responseData = []
client.setActiveApiToken('secret-token')
test("throws with statusText when error message is missing", async () => {
store["rcs_uuid"] = "test-uuid";
fetchMock.response = { ok: false, status: 500, statusText: "Internal Server Error" };
fetchMock.responseData = {};
await expect(client.apiFetchSessions()).rejects.toThrow("Internal Server Error");
});
});
await client.apiFetchSessions()
describe("ACP relay client", () => {
test("builds relay URLs without UUID or token query params", () => {
(globalThis as any).window = {
expect(fetchMock.lastUrl).toContain('uuid=browser-uuid')
expect(fetchMock.lastUrl).not.toContain('secret-token')
expect(fetchMock.lastOpts.headers).toEqual({
'Content-Type': 'application/json',
Authorization: 'Bearer secret-token',
})
})
test('throws error on non-ok response', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.response = { ok: false, status: 401, statusText: 'Unauthorized' }
fetchMock.responseData = {
error: { type: 'auth', message: 'Invalid UUID' },
}
await expect(client.apiFetchSessions()).rejects.toThrow('Invalid UUID')
})
test('throws with statusText when error message is missing', async () => {
store['rcs_uuid'] = 'test-uuid'
fetchMock.response = {
ok: false,
status: 500,
statusText: 'Internal Server Error',
}
fetchMock.responseData = {}
await expect(client.apiFetchSessions()).rejects.toThrow(
'Internal Server Error',
)
})
})
describe('ACP relay client', () => {
test('builds relay URLs without UUID or token query params', () => {
;(globalThis as any).window = {
location: {
protocol: "https:",
host: "rcs.example.test",
protocol: 'https:',
host: 'rcs.example.test',
},
};
}
expect(relayClient.buildRelayUrl("agent_123")).toBe(
"wss://rcs.example.test/acp/relay/agent_123",
);
});
});
expect(relayClient.buildRelayUrl('agent_123')).toBe(
'wss://rcs.example.test/acp/relay/agent_123',
)
})
})

View File

@@ -1,4 +1,4 @@
import { afterEach, describe, test, expect } from "bun:test";
import { afterEach, describe, test, expect } from 'bun:test'
const {
formatTime,
@@ -8,273 +8,284 @@ const {
generateMessageUuid,
extractEventText,
isConversationClearedStatus,
} = await import("../lib/utils");
} = await import('../lib/utils')
type UuidCrypto = {
randomUUID?: () => string;
getRandomValues?: (array: Uint8Array) => Uint8Array;
};
randomUUID?: () => string
getRandomValues?: (array: Uint8Array) => Uint8Array
}
const originalCryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, "crypto");
const originalCryptoDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
'crypto',
)
function setCryptoForTest(value: UuidCrypto): void {
Object.defineProperty(globalThis, "crypto", {
Object.defineProperty(globalThis, 'crypto', {
configurable: true,
writable: true,
value,
});
})
}
function restoreCryptoForTest(): void {
if (originalCryptoDescriptor) {
Object.defineProperty(globalThis, "crypto", originalCryptoDescriptor);
Object.defineProperty(globalThis, 'crypto', originalCryptoDescriptor)
} else {
Reflect.deleteProperty(globalThis, "crypto");
Reflect.deleteProperty(globalThis, 'crypto')
}
}
afterEach(() => {
restoreCryptoForTest();
});
restoreCryptoForTest()
})
// =============================================================================
// formatTime()
// =============================================================================
describe("formatTime", () => {
test("returns empty string for null", () => {
expect(formatTime(null)).toBe("");
});
describe('formatTime', () => {
test('returns empty string for null', () => {
expect(formatTime(null)).toBe('')
})
test("returns empty string for undefined", () => {
expect(formatTime(undefined)).toBe("");
});
test('returns empty string for undefined', () => {
expect(formatTime(undefined)).toBe('')
})
test("returns empty string for 0", () => {
expect(formatTime(0)).toBe("");
});
test('returns empty string for 0', () => {
expect(formatTime(0)).toBe('')
})
test("formats valid unix timestamp", () => {
const result = formatTime(1700000000);
expect(result).toContain("2023");
});
});
test('formats valid unix timestamp', () => {
const result = formatTime(1700000000)
expect(result).toContain('2023')
})
})
// =============================================================================
// statusClass()
// =============================================================================
describe("statusClass", () => {
test("maps known statuses correctly", () => {
expect(statusClass("active")).toBe("active");
expect(statusClass("running")).toBe("running");
expect(statusClass("idle")).toBe("idle");
expect(statusClass("inactive")).toBe("inactive");
expect(statusClass("requires_action")).toBe("requires_action");
expect(statusClass("archived")).toBe("archived");
expect(statusClass("error")).toBe("error");
});
describe('statusClass', () => {
test('maps known statuses correctly', () => {
expect(statusClass('active')).toBe('active')
expect(statusClass('running')).toBe('running')
expect(statusClass('idle')).toBe('idle')
expect(statusClass('inactive')).toBe('inactive')
expect(statusClass('requires_action')).toBe('requires_action')
expect(statusClass('archived')).toBe('archived')
expect(statusClass('error')).toBe('error')
})
test("returns default for unknown status", () => {
expect(statusClass("unknown")).toBe("default");
});
test('returns default for unknown status', () => {
expect(statusClass('unknown')).toBe('default')
})
test("returns default for null", () => {
expect(statusClass(null)).toBe("default");
});
test('returns default for null', () => {
expect(statusClass(null)).toBe('default')
})
test("returns default for undefined", () => {
expect(statusClass(undefined)).toBe("default");
});
test('returns default for undefined', () => {
expect(statusClass(undefined)).toBe('default')
})
test("returns default for empty string", () => {
expect(statusClass("")).toBe("default");
});
});
test('returns default for empty string', () => {
expect(statusClass('')).toBe('default')
})
})
// =============================================================================
// isClosedSessionStatus()
// =============================================================================
describe("isClosedSessionStatus", () => {
test("returns true for archived", () => {
expect(isClosedSessionStatus("archived")).toBe(true);
});
describe('isClosedSessionStatus', () => {
test('returns true for archived', () => {
expect(isClosedSessionStatus('archived')).toBe(true)
})
test("returns true for inactive", () => {
expect(isClosedSessionStatus("inactive")).toBe(true);
});
test('returns true for inactive', () => {
expect(isClosedSessionStatus('inactive')).toBe(true)
})
test("returns false for active", () => {
expect(isClosedSessionStatus("active")).toBe(false);
});
test('returns false for active', () => {
expect(isClosedSessionStatus('active')).toBe(false)
})
test("returns false for null", () => {
expect(isClosedSessionStatus(null)).toBe(false);
});
test('returns false for null', () => {
expect(isClosedSessionStatus(null)).toBe(false)
})
test("returns false for undefined", () => {
expect(isClosedSessionStatus(undefined)).toBe(false);
});
});
test('returns false for undefined', () => {
expect(isClosedSessionStatus(undefined)).toBe(false)
})
})
// =============================================================================
// truncate()
// =============================================================================
describe("truncate", () => {
test("returns empty string for null", () => {
expect(truncate(null, 10)).toBe("");
});
describe('truncate', () => {
test('returns empty string for null', () => {
expect(truncate(null, 10)).toBe('')
})
test("returns empty string for undefined", () => {
expect(truncate(undefined, 10)).toBe("");
});
test('returns empty string for undefined', () => {
expect(truncate(undefined, 10)).toBe('')
})
test("returns original string when shorter than max", () => {
expect(truncate("hello", 10)).toBe("hello");
});
test('returns original string when shorter than max', () => {
expect(truncate('hello', 10)).toBe('hello')
})
test("returns original string when exactly max length", () => {
expect(truncate("12345", 5)).toBe("12345");
});
test('returns original string when exactly max length', () => {
expect(truncate('12345', 5)).toBe('12345')
})
test("truncates and appends ... when longer than max", () => {
expect(truncate("hello world", 5)).toBe("hello...");
});
});
test('truncates and appends ... when longer than max', () => {
expect(truncate('hello world', 5)).toBe('hello...')
})
})
// =============================================================================
// generateMessageUuid()
// =============================================================================
describe("generateMessageUuid", () => {
test("returns an RFC 4122 v4 UUID", () => {
const uuid = generateMessageUuid();
expect(typeof uuid).toBe("string");
describe('generateMessageUuid', () => {
test('returns an RFC 4122 v4 UUID', () => {
const uuid = generateMessageUuid()
expect(typeof uuid).toBe('string')
expect(uuid).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
);
});
)
})
test("uses crypto.randomUUID when available", () => {
test('uses crypto.randomUUID when available', () => {
setCryptoForTest({
randomUUID: () => "11111111-1111-4111-8111-111111111111",
randomUUID: () => '11111111-1111-4111-8111-111111111111',
getRandomValues: () => {
throw new Error("getRandomValues should not be called");
throw new Error('getRandomValues should not be called')
},
});
})
expect(generateMessageUuid()).toBe("11111111-1111-4111-8111-111111111111");
});
expect(generateMessageUuid()).toBe('11111111-1111-4111-8111-111111111111')
})
test("uses crypto.getRandomValues when randomUUID is unavailable", () => {
test('uses crypto.getRandomValues when randomUUID is unavailable', () => {
setCryptoForTest({
getRandomValues: (array) => {
getRandomValues: array => {
for (let i = 0; i < array.length; i++) {
array[i] = i;
array[i] = i
}
return array;
return array
},
});
})
expect(generateMessageUuid()).toBe("00010203-0405-4607-8809-0a0b0c0d0e0f");
});
expect(generateMessageUuid()).toBe('00010203-0405-4607-8809-0a0b0c0d0e0f')
})
test("throws when no secure random source is available", () => {
setCryptoForTest({});
test('throws when no secure random source is available', () => {
setCryptoForTest({})
expect(() => generateMessageUuid()).toThrow("crypto.getRandomValues is required");
});
});
expect(() => generateMessageUuid()).toThrow(
'crypto.getRandomValues is required',
)
})
})
// =============================================================================
// extractEventText()
// =============================================================================
describe("extractEventText", () => {
test("returns empty string for null", () => {
expect(extractEventText(null)).toBe("");
});
describe('extractEventText', () => {
test('returns empty string for null', () => {
expect(extractEventText(null)).toBe('')
})
test("returns empty string for undefined", () => {
expect(extractEventText(undefined)).toBe("");
});
test('returns empty string for undefined', () => {
expect(extractEventText(undefined)).toBe('')
})
test("returns empty string for non-object", () => {
expect(extractEventText("string" as any)).toBe("");
});
test('returns empty string for non-object', () => {
expect(extractEventText('string' as any)).toBe('')
})
test("extracts payload.content string", () => {
expect(extractEventText({ content: "hello" })).toBe("hello");
});
test('extracts payload.content string', () => {
expect(extractEventText({ content: 'hello' })).toBe('hello')
})
test("extracts from message.content text blocks array", () => {
test('extracts from message.content text blocks array', () => {
const payload = {
message: {
content: [
{ type: "text", text: "line 1" },
{ type: "text", text: "line 2" },
{ type: 'text', text: 'line 1' },
{ type: 'text', text: 'line 2' },
],
},
};
expect(extractEventText(payload)).toBe("line 1\nline 2");
});
}
expect(extractEventText(payload)).toBe('line 1\nline 2')
})
test("ignores non-text blocks", () => {
test('ignores non-text blocks', () => {
const payload = {
message: {
content: [
{ type: "image", data: "base64..." },
{ type: "text", text: "only text" },
{ type: 'image', data: 'base64...' },
{ type: 'text', text: 'only text' },
],
},
};
expect(extractEventText(payload)).toBe("only text");
});
}
expect(extractEventText(payload)).toBe('only text')
})
test("returns empty string when message.content has no text blocks", () => {
test('returns empty string when message.content has no text blocks', () => {
const payload = {
message: { content: [{ type: "image", data: "base64" }] },
};
expect(extractEventText(payload)).toBe("");
});
message: { content: [{ type: 'image', data: 'base64' }] },
}
expect(extractEventText(payload)).toBe('')
})
test("returns empty string for empty object", () => {
expect(extractEventText({})).toBe("");
});
});
test('returns empty string for empty object', () => {
expect(extractEventText({})).toBe('')
})
})
// =============================================================================
// isConversationClearedStatus()
// =============================================================================
describe("isConversationClearedStatus", () => {
test("returns true when payload.status is conversation_cleared", () => {
expect(isConversationClearedStatus({ status: "conversation_cleared" })).toBe(true);
});
describe('isConversationClearedStatus', () => {
test('returns true when payload.status is conversation_cleared', () => {
expect(
isConversationClearedStatus({ status: 'conversation_cleared' }),
).toBe(true)
})
test("returns true when payload.raw.status is conversation_cleared", () => {
expect(isConversationClearedStatus({ raw: { status: "conversation_cleared" } })).toBe(true);
});
test('returns true when payload.raw.status is conversation_cleared', () => {
expect(
isConversationClearedStatus({ raw: { status: 'conversation_cleared' } }),
).toBe(true)
})
test("returns false for null", () => {
expect(isConversationClearedStatus(null)).toBe(false);
});
test('returns false for null', () => {
expect(isConversationClearedStatus(null)).toBe(false)
})
test("returns false for undefined", () => {
expect(isConversationClearedStatus(undefined)).toBe(false);
});
test('returns false for undefined', () => {
expect(isConversationClearedStatus(undefined)).toBe(false)
})
test("returns false for other status", () => {
expect(isConversationClearedStatus({ status: "active" })).toBe(false);
});
test('returns false for other status', () => {
expect(isConversationClearedStatus({ status: 'active' })).toBe(false)
})
test("returns false when raw has different status", () => {
expect(isConversationClearedStatus({ raw: { status: "running" } })).toBe(false);
});
test('returns false when raw has different status', () => {
expect(isConversationClearedStatus({ raw: { status: 'running' } })).toBe(
false,
)
})
test("returns false for empty object", () => {
expect(isConversationClearedStatus({})).toBe(false);
});
});
test('returns false for empty object', () => {
expect(isConversationClearedStatus({})).toBe(false)
})
})