дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
Ruby test runs showed only console text: there was no Ruby output parser and test_*.rb was not a test file. A Minitest parser now builds the test tree (class, test, status, time, failure text) from verbose output (-v / TESTOPTS=-v), test_*.rb counts as a test file, jump to source finds the class and then its def, and the gutter gets a run action per test method (coloured by the last result) and per test class and module that holds test classes; one test, class or module runs with --name=.... The streaming Find in Files helper the jump to source uses moved from the search feature to src/platform, so the run feature doesn't import another feature. Co-authored-by: kjeldahl <[email protected]>
48 рядки
2.1 KiB
TypeScript
48 рядки
2.1 KiB
TypeScript
/**
|
|
* Where a Minitest test is defined. Minitest's output names `Class#test_name` and no file for tests that
|
|
* pass, so the tree finds the source by searching the project: the class, then the `def` in its file.
|
|
* Spec-style tests (`describe "Thing" do it "does x"`) are reported as `Thing#test_0001_does_x`.
|
|
*/
|
|
import type { FileMatches } from "@/platform/ipc";
|
|
|
|
/** Runs a regular-expression search over the project's Ruby files, or over `files` when given. */
|
|
export type RubySearch = (pattern: string, files?: string[]) => Promise<FileMatches[]>;
|
|
|
|
export interface RubyTestSite {
|
|
path: string;
|
|
/** 1-based. */
|
|
line: number;
|
|
}
|
|
|
|
const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const SPEC_TEST = /^test_\d+_(.+)$/;
|
|
|
|
const first = (hits: FileMatches[], file?: string) => {
|
|
const hit = file ? hits.find((h) => h.path === file) : hits[0];
|
|
return hit?.matches[0] ? { path: hit.path, line: hit.matches[0].line + 1 } : null;
|
|
};
|
|
|
|
export async function findRubyTest(search: RubySearch, className: string, test: string): Promise<RubyTestSite | null> {
|
|
const spec = SPEC_TEST.exec(test);
|
|
// `Outer::InnerTest` is declared as `class InnerTest` inside `module Outer`, or as written.
|
|
const last = className.split("::").pop()!;
|
|
const declared = spec
|
|
? `^\\s*(describe|context)\\s*\\(?\\s*(["'])${escape(className)}\\2`
|
|
: `^\\s*(class|module)\\s+([A-Z]\\w*::)*${escape(last)}\\b`;
|
|
const owners = await search(declared);
|
|
const files = owners.map((o) => o.path);
|
|
const inOwners = (pattern: string) => (files.length ? search(pattern, files) : search(pattern));
|
|
const body = spec
|
|
? // Spec descriptions lose their spaces to underscores in the method name.
|
|
`^\\s*it\\s*\\(?\\s*(["'])${escape(spec[1]!).replace(/_/g, "[ _]")}\\1`
|
|
: `^\\s*def\\s+${escape(test)}\\b`;
|
|
const hits = await inOwners(body);
|
|
// The same method name is common (`test_initialize`): prefer the file that declares the class.
|
|
const site =
|
|
first(
|
|
hits,
|
|
files.find((f) => hits.some((h) => h.path === f)),
|
|
) ?? first(hits);
|
|
return site ?? first(owners);
|
|
}
|