opencode-voice-dictation/tests/insert.test.ts
Sergey 960acd8a7a
feat: cancel button, question prompt mic, solid timer (#10)
## Changes

### Cancel button (✕)
- New cancel button appears during recording next to stop button
- Tapping ✕ stops recording and **discards audio** — no text inserted
- Tapping ⏹ stops recording and **inserts text** (as before)

### Question prompt support
- Mic button injected into `[data-slot=question-custom-input]` (custom
answer textarea)
- Text insertion via `selectionStart`/`selectionEnd` (textarea ≠
contenteditable)
- MutationObserver detects when question prompt appears

### Solid timer
- Timer background: `#e53935` (opaque red) with white text
- Was `rgba(229, 57, 53, 0.1)` (semi-transparent — text showed through)
- Now fully visible over any text

### Technical
- Refactored UI to use CSS classes instead of single IDs (supports
multiple buttons)
- `insertText()` now takes `target: "composer" | "question"`
- 47 tests, 100% line coverage

Co-authored-by: opencode-agent <agent@slaid098.dev>
2026-07-08 09:40:24 +03:00

113 lines
3.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { insertText, isOpencodePage, submitPrompt } from "../src/insert.js";
beforeEach(() => {
document.execCommand = vi.fn(() => true);
});
describe("isOpencodePage", () => {
it("should return false when prompt-input not present", () => {
document.body.innerHTML = "";
expect(isOpencodePage()).toBe(false);
});
it("should return true when prompt-input is present", () => {
document.body.innerHTML =
'<div data-component="prompt-input" contenteditable="true" role="textbox"></div>';
expect(isOpencodePage()).toBe(true);
});
});
describe("insertText", () => {
beforeEach(() => {
document.body.innerHTML =
'<div data-component="prompt-input" contenteditable="true" role="textbox"></div>';
});
afterEach(() => {
document.body.innerHTML = "";
});
it("should return false when input not found", () => {
document.body.innerHTML = "";
expect(insertText("hello")).toBe(false);
});
it("should call execCommand with insertText", () => {
const result = insertText("hello world");
expect(result).toBe(true);
expect(document.execCommand).toHaveBeenCalledWith("insertText", false, "hello world");
});
it("should dispatch input event", () => {
let eventCount = 0;
const input = document.querySelector('[data-component="prompt-input"]') as HTMLElement;
input.addEventListener("input", () => {
eventCount++;
});
insertText("test text");
expect(eventCount).toBeGreaterThanOrEqual(1);
});
});
describe("submitPrompt", () => {
afterEach(() => {
document.body.innerHTML = "";
});
it("should return false when submit button not found", () => {
expect(submitPrompt()).toBe(false);
});
it("should click submit button when found", () => {
let clicked = false;
const btn = document.createElement("button");
btn.setAttribute("data-action", "prompt-submit");
btn.addEventListener("click", () => {
clicked = true;
});
document.body.appendChild(btn);
expect(submitPrompt()).toBe(true);
expect(clicked).toBe(true);
});
});
describe("insertText into question textarea", () => {
afterEach(() => {
document.body.innerHTML = "";
});
it("should return false when question textarea not found", () => {
expect(insertText("hello", "question")).toBe(false);
});
it("should insert text into textarea at cursor position", () => {
const textarea = document.createElement("textarea");
textarea.setAttribute("data-slot", "question-custom-input");
textarea.value = "hello world";
textarea.selectionStart = 6;
textarea.selectionEnd = 11;
document.body.appendChild(textarea);
const result = insertText("there", "question");
expect(result).toBe(true);
expect(textarea.value).toBe("hello there");
expect(textarea.selectionStart).toBe(11);
});
it("should dispatch input event", () => {
let eventCount = 0;
const textarea = document.createElement("textarea");
textarea.setAttribute("data-slot", "question-custom-input");
textarea.value = "";
textarea.addEventListener("input", () => {
eventCount++;
});
document.body.appendChild(textarea);
insertText("test", "question");
expect(eventCount).toBeGreaterThanOrEqual(1);
});
});