Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Write the steps that test your changes. This helps reviewers check your work.
- [ ] **Self-Review**: I reviewed my own code with care.
- [ ] **Writing Style**: The PR title and description use Simplified Technical English (ASD-STE100): short sentences, plain words, active voice, one idea per sentence, no contractions.
- [ ] **Testing**: New and/or updated tests have been added to cover my changes (if applicable).
- [ ] **Test Helpers** (test changes only): Shared test helpers (`src/test-utils/`, `@/utils/test-utils`) are used only for mechanical duplication; test intent and scenario-specific mocks remain visible.
- [ ] **Visual Snapshot** (UI changes only): If a user would notice this change at a glance (layout, theme tokens, brand elements, empty/error states), I added or updated a `*.visual.tsx` snapshot in `webview-ui/`. See `webview-ui/AGENTS.md` → "When a UI change needs a snapshot".
- [ ] **Documentation Impact**: I checked if my changes need documentation updates (see "Documentation Updates" section below).
- [ ] **Contribution Guidelines**: I read and agree to the [Contributor Guidelines](/CONTRIBUTING.md).
Expand Down
6 changes: 3 additions & 3 deletions src/api/providers/__tests__/github-copilot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const mockLanguageModelChat = {
}

describe("connectGitHubCopilot", () => {
beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it("authenticates through VS Code and selects only Copilot models", async () => {
vi.mocked(vscode.authentication.getSession).mockResolvedValue({
Expand Down Expand Up @@ -413,7 +413,7 @@ describe("Copilot model capabilities", () => {
}

beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
mockLanguageModelChat.countTokens.mockResolvedValue(10)
mockLanguageModelChat.sendRequest.mockImplementation(async () => ({
stream: (async function* () {
Expand Down Expand Up @@ -1322,7 +1322,7 @@ describe("getGitHubCopilotModels", () => {
...overrides,
})

beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it("describes each model by identity and by the capabilities the host reports", async () => {
vi.mocked(vscode.lm.selectChatModels).mockResolvedValue([
Expand Down
3 changes: 2 additions & 1 deletion src/api/providers/__tests__/io-intelligence.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ioIntelligenceDefaultModelId, providerIdentifiers } from "@roo-code/typ

import { buildApiHandler } from "../../index"
import { asyncStreamFrom, collectStream } from "../../../test-utils/stream"
import { clearAllMocks } from "../../../test-utils/reset"
import { IOIntelligenceHandler } from "../io-intelligence"
import { getModels } from "../fetchers/modelCache"

Expand Down Expand Up @@ -42,7 +43,7 @@ const loggedText = (spy: { mock: { calls: unknown[][] } }) =>

describe("IOIntelligenceHandler", () => {
beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
vi.mocked(getModels).mockResolvedValue({
"meta-llama/Llama-3.3-70B-Instruct": {
maxTokens: 8192,
Expand Down
3 changes: 2 additions & 1 deletion src/api/providers/__tests__/nanogpt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { nanoGptDefaultModelId, providerIdentifiers } from "@roo-code/types"

import { buildApiHandler } from "../../index"
import { asyncStreamFrom, collectStream } from "../../../test-utils/stream"
import { clearAllMocks } from "../../../test-utils/reset"
import { createReadFileTool } from "../../../core/prompts/tools/native-tools/read_file"
import { NanoGptHandler } from "../nanogpt"
import { getModels } from "../fetchers/modelCache"
Expand Down Expand Up @@ -45,7 +46,7 @@ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "H

describe("NanoGptHandler", () => {
beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
vi.mocked(getModels).mockResolvedValue({
"model:thinking": {
maxTokens: 128000,
Expand Down
2 changes: 1 addition & 1 deletion src/api/providers/__tests__/vscode-lm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6676,7 +6676,7 @@ describe("getVsCodeLmModels", () => {
...overrides,
})

beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it("returns serializable model metadata with host-reported image support", async () => {
const model = liveModel({ capabilities: { supportsImageToText: true } })
Expand Down
18 changes: 9 additions & 9 deletions src/core/config/__tests__/CustomModesManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ describe("CustomModesManager", () => {
const firstResult = await manager.getCustomModes()

// Reset mock to verify it's not called again
vi.clearAllMocks()
clearAllMocks()

// Setup mocks again for second call
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
Expand Down Expand Up @@ -236,7 +236,7 @@ describe("CustomModesManager", () => {
await manager.getCustomModes()

// Reset mocks to track new calls
vi.clearAllMocks()
clearAllMocks()

// Update a mode
const updatedMode: ModeConfig = {
Expand All @@ -260,7 +260,7 @@ describe("CustomModesManager", () => {
await manager.updateCustomMode("mode1", updatedMode)

// Reset mocks again
vi.clearAllMocks()
clearAllMocks()

// Next call should read from file again (cache invalidated)
await manager.getCustomModes()
Expand All @@ -282,7 +282,7 @@ describe("CustomModesManager", () => {
await manager.getCustomModes()

// Reset mocks to track new calls
vi.clearAllMocks()
clearAllMocks()

// Delete a mode
await manager.deleteCustomMode("mode1")
Expand All @@ -296,7 +296,7 @@ describe("CustomModesManager", () => {
})

// Reset mocks again
vi.clearAllMocks()
clearAllMocks()

// Next call should read from file again (cache invalidated)
await manager.getCustomModes()
Expand All @@ -321,7 +321,7 @@ describe("CustomModesManager", () => {
await manager.getCustomModes()

// Reset mocks to track new calls
vi.clearAllMocks()
clearAllMocks()

// Setup for update
const updatedMode: ModeConfig = {
Expand All @@ -346,7 +346,7 @@ describe("CustomModesManager", () => {
await manager.updateCustomMode("mode1", updatedMode)

// Reset mocks again
vi.clearAllMocks()
clearAllMocks()

// Setup mocks again
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
Expand Down Expand Up @@ -387,7 +387,7 @@ describe("CustomModesManager", () => {
await manager.getCustomModes()

// Reset mock to verify it's not called again
vi.clearAllMocks()
clearAllMocks()

// Setup mocks again for second call
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
Expand All @@ -408,7 +408,7 @@ describe("CustomModesManager", () => {
currentTime += 11000

// Reset mocks again
vi.clearAllMocks()
clearAllMocks()

// Setup mocks again for third call
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
Expand Down
42 changes: 39 additions & 3 deletions src/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ const testPathTraversalSelectors = [
},
]

// Test-utils rollout guardrail: lanes converted to the shared reset helper
// stay converted. Use clearAllMocks from test-utils/reset.
const testResetHelperSelector = {
selector:
"CallExpression[callee.object.name='vi'][callee.property.name='clearAllMocks'], CallExpression[callee.object.name='vitest'][callee.property.name='clearAllMocks']",
message:
"Use the shared clearAllMocks() helper from src/test-utils/reset instead of calling vi.clearAllMocks() directly.",
}

/** @type {import("eslint").Linter.Config} */
export default [
...config,
Expand Down Expand Up @@ -85,9 +94,6 @@ export default [
"@typescript-eslint/no-floating-promises": "error",
},
},
{
ignores: [".stryker-tmp", "webview-ui", "out"],
},
{
// Tests inside __tests__/ directories must not traverse 3+ levels above
// the test file. That escapes the src/ package root and makes Turbo
Expand All @@ -112,4 +118,34 @@ export default [
"no-restricted-syntax": ["error", ...testPathTraversalSelectors, ...apiProviderTestSelectors],
},
},
{
// Test-utils rollout guardrail: lanes converted to the shared reset
// helpers stay converted. Use clearAllMocks from test-utils/reset.
// no-restricted-syntax replaces earlier blocks for the same files, so
// repeat the traversal selectors here.
files: [
"core/config/__tests__/**/*.ts",
"services/code-index/**/__tests__/**/*.ts",
"integrations/terminal/**/__tests__/**/*.ts",
],
rules: {
"no-restricted-syntax": ["error", ...testPathTraversalSelectors, testResetHelperSelector],
},
},
{
// Same guardrail for the API provider lane; this block also replaces the
// API provider hygiene selectors, so repeat those too.
files: ["api/providers/__tests__/**/*.ts"],
rules: {
"no-restricted-syntax": [
"error",
...testPathTraversalSelectors,
...apiProviderTestSelectors,
testResetHelperSelector,
],
},
},
{
ignores: [".stryker-tmp", "webview-ui", "out"],
},
]
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from "vscode"
import { makeExtensionContext, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode"
import { clearAllMocks } from "../../../test-utils/reset"
import { CodeIndexManager } from "../manager"
import { CodeIndexManagerRegistry } from "../code-index-manager-registry"
import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope"
Expand All @@ -25,7 +26,7 @@ describe("CodeIndexManagerRegistry", () => {
let second: vscode.WorkspaceFolder

beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
context = makeExtensionContext()
first = { uri: makeUri("/first"), name: "first", index: 0 }
second = { uri: makeUri("/second"), name: "second", index: 1 }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeExtensionContext } from "../../../test-utils/vscode"
import { clearAllMocks } from "../../../test-utils/reset"
import { CodeIndexScope } from "../code-index-scope"
import { CodeIndexStatusManager } from "../code-index-status-manager"
import { CodeIndexManagerRegistry } from "../code-index-manager-registry"
Expand All @@ -9,7 +10,7 @@ vi.mock("../code-index-manager-registry", () => ({
}))

describe("CodeIndexScope", () => {
beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it("creates, initializes and disposes its status manager", () => {
const context = makeExtensionContext()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
makeExtensionContext,
makeWorkspaceConfiguration,
} from "../../../test-utils/vscode"
import { clearAllMocks } from "../../../test-utils/reset"
import { CodeIndexManager } from "../manager"
import { CodeIndexStateManager } from "../state-manager"
import { CodeIndexStatusManager, type CodeIndexStatus } from "../code-index-status-manager"
Expand Down Expand Up @@ -68,7 +69,7 @@ describe("CodeIndexStatusManager", () => {
let disposeWorkspaceFolders: ReturnType<typeof vi.fn<() => void>>

beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(makeWorkspaceConfiguration())
Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: undefined })
Object.defineProperty(vscode.workspace, "workspaceFolders", {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeExtensionContext, makeUri } from "../../../test-utils/vscode"
import { clearAllMocks } from "../../../test-utils/reset"
import { CodeIndexManager } from "../manager"
import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope"
import { CodeIndexStateManager } from "../state-manager"
Expand All @@ -13,7 +14,7 @@ vi.mock("../manager", () => ({
}))

describe("CodeIndexWorkspaceScope", () => {
beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it("owns a guarded workspace indexing manager and recreates it after disposal", () => {
const scope = new CodeIndexWorkspaceScope("/workspace", makeUri("/workspace"), makeExtensionContext())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
import { t } from "../../../../i18n"
import { clearAllMocks } from "../../../../test-utils/reset"
import embeddings from "../../../../i18n/locales/en/embeddings.json"
import { OpenAiEmbedder } from "../openai"
import { EmbedderValidationManager } from "../embedder-validation-manager"
Expand All @@ -14,7 +15,7 @@ vi.mock("@roo-code/telemetry", () => ({
describe("EmbedderValidationManager", () => {
const manager = new EmbedderValidationManager()

beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())

it.each([{ valid: true }, { valid: false, error: "Invalid credentials" }])(
"preserves the validation result %j without reporting an exception",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode"
import ignore from "ignore"
import { makeExtensionContext } from "../../../../test-utils/vscode"
import { clearAllMocks } from "../../../../test-utils/reset"
import { Package } from "../../../../shared/package"
import { CacheManager } from "../../cache-manager"
import { BATCH_SEGMENT_THRESHOLD } from "../../constants"
Expand Down Expand Up @@ -30,7 +31,7 @@ vi.mock("../scanner")
vi.mock("../file-watcher")

describe("processor factories", () => {
beforeEach(() => vi.clearAllMocks())
beforeEach(() => clearAllMocks())
afterEach(() => vi.restoreAllMocks())

function dependencies() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { providerIdentifiers } from "@roo-code/types/provider-identifiers"
import { getDefaultModelId, getModelDimension } from "../../../../shared/embeddingModels"
import { clearAllMocks } from "../../../../test-utils/reset"
import type { CodeIndexConfig } from "../../interfaces/config"
import { QdrantVectorStore } from "../qdrant-client"
import { VectorStoreFactory } from "../vector-store-factory"
Expand All @@ -15,7 +16,7 @@ describe("VectorStoreFactory", () => {
let config: CodeIndexConfig

beforeEach(() => {
vi.clearAllMocks()
clearAllMocks()
vi.mocked(getDefaultModelId).mockReturnValue("default-model")
vi.mocked(getModelDimension).mockReturnValue(undefined)
factory = new VectorStoreFactory()
Expand Down
28 changes: 28 additions & 0 deletions webview-ui/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,32 @@ export default [
"no-undef": "off",
},
},
{
// Test-utils rollout guardrail: component specs render through the shared
// renderWithExtensionState helper instead of hand-wrapping providers.
// Hook specs (including .tsx ones) keep local renderHook wrappers since
// they pass a specific QueryClient instance to the hook, and Playwright
// visual fixtures mount real providers inside stories rather than Vitest
// renders.
files: ["src/components/**/__tests__/**/*.tsx"],
ignores: [
"src/components/**/hooks/**/__tests__/**/*.tsx",
"src/components/**/__tests__/**/*.visual.fixture.tsx",
],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@tanstack/react-query",
importNames: ["QueryClientProvider"],
message:
"Use renderWithExtensionState from @/utils/test-utils instead of hand-wrapping QueryClientProvider in component specs.",
},
],
},
],
},
},
]
Loading
Loading