mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-18 05:05:28 +00:00
* fix(web): make clipboard copy work over plain HTTP
The Clipboard API (navigator.clipboard) is only exposed in secure contexts. When the web UI is served over plain HTTP, every copy action threw synchronously and silently failed. Route all copy call sites through a helper that falls back to execCommand('copy') in insecure contexts, and surface success or failure feedback to the user.
* test: address review feedback and a flaky goal-badge test
- clipboard test: drop the jsdom environment and mock the small navigator/document surface in the default node environment, per the kimi-web "pure logic tests only" rule.
- footer-goal-badge test: assert the absence of the "[goal" badge instead of the bare "goal" substring, which could match a rotating working tip ("/goal ...") and fail depending on Date.now().
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { copyTextToClipboard } from '../src/lib/clipboard';
|
|
|
|
// The web test suite runs in the default node environment (no jsdom), so we
|
|
// mock the tiny `navigator` / `document` surface that the helper touches.
|
|
|
|
interface FakeDocument {
|
|
execCommand: ReturnType<typeof vi.fn>;
|
|
createElement: ReturnType<typeof vi.fn>;
|
|
body: { appendChild: ReturnType<typeof vi.fn>; removeChild: ReturnType<typeof vi.fn> };
|
|
textarea: { value: string; setAttribute: ReturnType<typeof vi.fn>; focus: ReturnType<typeof vi.fn>; select: ReturnType<typeof vi.fn> };
|
|
}
|
|
|
|
function installDocument(execResult: boolean | Error): FakeDocument {
|
|
const textarea = {
|
|
value: '',
|
|
style: {} as Record<string, string>,
|
|
setAttribute: vi.fn(),
|
|
focus: vi.fn(),
|
|
select: vi.fn(),
|
|
};
|
|
const doc: FakeDocument = {
|
|
execCommand: vi.fn().mockImplementation(() => {
|
|
if (execResult instanceof Error) throw execResult;
|
|
return execResult;
|
|
}),
|
|
createElement: vi.fn().mockReturnValue(textarea),
|
|
body: { appendChild: vi.fn(), removeChild: vi.fn() },
|
|
textarea: textarea as FakeDocument['textarea'],
|
|
};
|
|
vi.stubGlobal('document', doc);
|
|
return doc;
|
|
}
|
|
|
|
function installNavigator(clipboard: unknown): void {
|
|
vi.stubGlobal('navigator', clipboard === undefined ? {} : { clipboard });
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe('copyTextToClipboard', () => {
|
|
it('uses navigator.clipboard when available', async () => {
|
|
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
installNavigator({ writeText });
|
|
|
|
await expect(copyTextToClipboard('hello')).resolves.toBe(true);
|
|
expect(writeText).toHaveBeenCalledWith('hello');
|
|
});
|
|
|
|
it('falls back to execCommand when navigator.clipboard is undefined', async () => {
|
|
// Simulates an insecure (plain HTTP) context.
|
|
installNavigator(undefined);
|
|
const doc = installDocument(true);
|
|
|
|
await expect(copyTextToClipboard('abc')).resolves.toBe(true);
|
|
expect(doc.execCommand).toHaveBeenCalledWith('copy');
|
|
expect(doc.textarea.value).toBe('abc');
|
|
expect(doc.body.appendChild).toHaveBeenCalledTimes(1);
|
|
expect(doc.body.removeChild).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('falls back to execCommand when writeText rejects', async () => {
|
|
const writeText = vi.fn().mockRejectedValue(new Error('denied'));
|
|
installNavigator({ writeText });
|
|
const doc = installDocument(true);
|
|
|
|
await expect(copyTextToClipboard('retry')).resolves.toBe(true);
|
|
expect(writeText).toHaveBeenCalled();
|
|
expect(doc.execCommand).toHaveBeenCalledWith('copy');
|
|
});
|
|
|
|
it('resolves false when both paths fail', async () => {
|
|
installNavigator(undefined);
|
|
installDocument(false);
|
|
|
|
await expect(copyTextToClipboard('nope')).resolves.toBe(false);
|
|
});
|
|
});
|