mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-10-07 14:58:44 +08:00
Merge pull request #600 from thejesh23/feat/understandignore-swift-test-patterns
feat(core): broaden .understandignore starter — Swift XCTest/Quick patterns
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import ignore from "ignore";
|
||||
import { generateStarterIgnoreFile } from "../ignore-generator";
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
@@ -284,12 +285,94 @@ describe("generateStarterIgnoreFile", () => {
|
||||
expect(content).toContain("# **/rails_helper.rb");
|
||||
});
|
||||
|
||||
it("scopes Swift XCTest patterns to exactly-named test directories", () => {
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
expect(content).toContain("# Swift");
|
||||
// Exact name, not a *Tests suffix — Contests/ must not be matchable.
|
||||
expect(content).toContain("# **/Tests/**/*.swift");
|
||||
});
|
||||
|
||||
it("includes Swift Quick/Nimble BDD spec directories", () => {
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
// Quick is the Swift RSpec-equivalent — dominant in codebases that
|
||||
// adopted BDD styling before Swift Testing shipped.
|
||||
expect(content).toContain("# **/Specs/**/*.swift");
|
||||
});
|
||||
|
||||
it("suggests Xcode target dirs by real name, only when they exist", () => {
|
||||
mkdirSync(join(testDir, "MyAppTests"), { recursive: true });
|
||||
mkdirSync(join(testDir, "MyAppUITests"), { recursive: true });
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
expect(content).toContain("# MyAppTests/");
|
||||
expect(content).toContain("# MyAppUITests/");
|
||||
// Not speculatively globbed for projects that have no such dir.
|
||||
expect(generateStarterIgnoreFile(join(testDir, "nope"))).not.toContain(
|
||||
"MyAppTests/",
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces a production *tests dir under its real name, not silently", () => {
|
||||
// Contests/ does match the unanchored suffix rule — that is acceptable
|
||||
// only because the user sees this exact line and can leave it commented.
|
||||
mkdirSync(join(testDir, "Contests"), { recursive: true });
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
expect(content).toContain("# Contests/");
|
||||
});
|
||||
|
||||
it("keeps production Swift source when the Swift group is uncommented", () => {
|
||||
// These starter lines exist to be uncommented, so assert on real
|
||||
// matcher behaviour rather than on the emitted text alone.
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
const swiftPatterns = content
|
||||
.split("\n")
|
||||
.map((l) => l.replace(/^#\s*/, "").trim())
|
||||
.filter((l) => l.endsWith(".swift"));
|
||||
expect(swiftPatterns.length).toBeGreaterThan(0);
|
||||
|
||||
const ig = ignore().add(swiftPatterns);
|
||||
for (const kept of [
|
||||
"Sources/App/Contest.swift",
|
||||
"Sources/App/Latest.swift",
|
||||
"Sources/App/Backtest.swift",
|
||||
"Sources/App/Protest.swift",
|
||||
"Sources/App/Contests.swift",
|
||||
"Sources/App/Inspec.swift",
|
||||
"Sources/Requests/LoginRequest.swift",
|
||||
"Sources/Interests/InterestPicker.swift",
|
||||
// Directory-level collisions the exact-name globs must also avoid.
|
||||
"Sources/Contests/ContestList.swift",
|
||||
"Sources/Protests/ProtestFeed.swift",
|
||||
]) {
|
||||
expect(ig.ignores(kept), `${kept} must not be ignored`).toBe(false);
|
||||
}
|
||||
for (const dropped of [
|
||||
"Tests/AppTests/AppTests.swift",
|
||||
"Tests/AppTests/Helpers.swift",
|
||||
"Tests/File.swift",
|
||||
"Modules/Feature/Tests/A.swift",
|
||||
"SignalServiceKit/tests/CryptoTest.swift",
|
||||
"Specs/LoginSpec.swift",
|
||||
]) {
|
||||
expect(ig.ignores(dropped), `${dropped} must be ignored`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not emit bare Swift file-suffix globs", () => {
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
// The runtime matcher is case-insensitive (`ignore` defaults to
|
||||
// ignorecase: true), so these would silently drop production files
|
||||
// named Contest.swift / Latest.swift / Backtest.swift / Inspec.swift.
|
||||
expect(content).not.toContain("# **/*Test.swift");
|
||||
expect(content).not.toContain("# **/*Tests.swift");
|
||||
expect(content).not.toContain("# **/*Spec.swift");
|
||||
});
|
||||
|
||||
it("groups patterns under the JS / TS sub-header", () => {
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
expect(content).toContain("# JS / TS");
|
||||
});
|
||||
|
||||
it("emits language groups in stable order: JS, C#, Java, Go, C++, Python, Rust, Ruby", () => {
|
||||
it("emits language groups in stable order: JS, C#, Java, Go, C++, Python, Rust, Ruby, Swift", () => {
|
||||
const content = generateStarterIgnoreFile(testDir);
|
||||
const jsIdx = content.indexOf("# JS / TS");
|
||||
const csIdx = content.indexOf("# C# / .NET");
|
||||
@@ -299,6 +382,7 @@ describe("generateStarterIgnoreFile", () => {
|
||||
const pyIdx = content.indexOf("# Python");
|
||||
const rustIdx = content.indexOf("# Rust");
|
||||
const rubyIdx = content.indexOf("# Ruby");
|
||||
const swiftIdx = content.indexOf("# Swift");
|
||||
expect(jsIdx).toBeGreaterThan(-1);
|
||||
expect(csIdx).toBeGreaterThan(jsIdx);
|
||||
expect(javaIdx).toBeGreaterThan(csIdx);
|
||||
@@ -307,6 +391,7 @@ describe("generateStarterIgnoreFile", () => {
|
||||
expect(pyIdx).toBeGreaterThan(cppIdx);
|
||||
expect(rustIdx).toBeGreaterThan(pyIdx);
|
||||
expect(rubyIdx).toBeGreaterThan(rustIdx);
|
||||
expect(swiftIdx).toBeGreaterThan(rubyIdx);
|
||||
});
|
||||
|
||||
it("keeps all suggestions commented even with no detected dirs and no .gitignore", () => {
|
||||
|
||||
@@ -39,14 +39,22 @@ const EXACT_DIR_NAMES = [
|
||||
];
|
||||
|
||||
// Directory-name suffixes matched case-insensitively via String.endsWith.
|
||||
// Primarily intended for C# / .NET project-suffix conventions like Foo.Tests,
|
||||
// Foo.UnitTests, Foo.IntegrationTests, but note the match is unanchored —
|
||||
// e.g. a hypothetical `.storybook.tests` would also match. Suggestions stay
|
||||
// commented-out so the user reviews before activating.
|
||||
// Covers C# / .NET project-suffix conventions (Foo.Tests, Foo.UnitTests,
|
||||
// Foo.IntegrationTests) and Xcode target conventions (MyAppTests,
|
||||
// MyAppUITests) in one rule — the dotted C# forms all end in "tests", so
|
||||
// they need no separate entries.
|
||||
//
|
||||
// The match is deliberately unanchored, which means a production directory
|
||||
// ending in "tests"/"specs" (Contests/, Protests/) also matches. That is
|
||||
// safe *here* in a way the equivalent file-glob is not: this list only ever
|
||||
// runs against directories that actually exist on disk (see
|
||||
// detectDirectories), and the result is emitted commented-out under the
|
||||
// directory's real name — so a repo with a genuine Contests/ dir sees a
|
||||
// literal `# Contests/` line it can decline to uncomment. A speculative
|
||||
// `**/*Tests/**` glob offers the user no such signal.
|
||||
const SUFFIX_DIR_GLOBS = [
|
||||
".tests",
|
||||
".unittests",
|
||||
".integrationtests",
|
||||
"tests",
|
||||
"specs",
|
||||
];
|
||||
|
||||
// Test file patterns grouped by language. Emitted as commented suggestions
|
||||
@@ -156,6 +164,46 @@ const TEST_PATTERN_GROUPS: Array<{ label: string; patterns: string[] }> = [
|
||||
"**/rails_helper.rb",
|
||||
],
|
||||
},
|
||||
{
|
||||
// Swift patterns are scoped to test *directories*, not file
|
||||
// suffixes, because the runtime matcher (the `ignore` package in
|
||||
// ignore-filter.ts) runs with its default `ignorecase: true`. Under
|
||||
// case-insensitive matching a bare `**/*Test.swift` also swallows
|
||||
// production names like Contest.swift, Latest.swift, Backtest.swift
|
||||
// and Protest.swift; `**/*Tests.swift` catches Contests.swift, and
|
||||
// `**/*Spec.swift` catches Inspec.swift. That is not recoverable by
|
||||
// writing the glob more carefully — `ignore` compiles patterns to a
|
||||
// RegExp with the `i` flag, so a `[Tt]` character class buys nothing.
|
||||
// Since these starter lines are meant to be uncommented by users,
|
||||
// silently dropping real source is the worst failure mode available,
|
||||
// so the file-suffix form was abandoned.
|
||||
//
|
||||
// The globs below use *exact* directory names rather than a `*Tests`
|
||||
// suffix, so they carry no false-positive risk at all: Contests/ and
|
||||
// Protests/ do not match. They cover SPM's `Tests/` and Quick's
|
||||
// `Specs/` at any depth, including nested module layouts like
|
||||
// Modules/Feature/Tests/ that `detectDirectories()` cannot see (it
|
||||
// enumerates only direct children of projectRoot).
|
||||
//
|
||||
// Xcode's target-suffix convention (`MyAppTests/`, `MyAppUITests/`)
|
||||
// is deliberately NOT handled here. It is handled by the "tests"
|
||||
// entry in SUFFIX_DIR_GLOBS instead, which only fires for directories
|
||||
// observed on disk and emits them under their real name — see the
|
||||
// note there for why that is the safe place for suffix matching.
|
||||
// The residual gap is a *nested* Xcode-style dir (Modules/Feature/
|
||||
// FeatureTests/), which neither rule reaches. That is an accepted
|
||||
// miss: under-matching costs tokens, over-matching silently drops
|
||||
// source, and only one of those is a correctness bug.
|
||||
//
|
||||
// NOTE: token-savings measurement still to be redone for this
|
||||
// directory-scoped shape — the earlier file-suffix figures do not
|
||||
// carry over and have been removed rather than restated.
|
||||
label: "Swift",
|
||||
patterns: [
|
||||
"**/Tests/**/*.swift",
|
||||
"**/Specs/**/*.swift",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user