Files
김영준E 41059d8e43 fix: Support Windows markdown paths in CLI annotate flow (#267)
* fix(annotate): support Windows markdown paths in CLI annotate flow

* fix tmp path

* fix resolve-file.ts

* Update command to use EXE_PATH variable

* Update command path for plugin hooks in install.ps1

* test: add core test suite for path resolution, storage, remote detection, and install scripts

- resolve-file: absolute paths, relative paths, case-insensitive search, ignored dirs, extension filtering, ambiguity, Windows separators
- storage: slug generation, tilde expansion, version history, deduplication
- remote: env var detection (PLANNOTATOR_REMOTE, SSH_TTY), port config and validation
- image: tmpdir usage, extension validation
- install scripts: JSON structure validation, checksum verification, arch detection, full exe path in hooks

79 tests, 35ms

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add test workflow and gate release builds on tests

- New test.yml: runs `bun test` on PRs and pushes to main
- release.yml: tests must pass before build job runs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve pre-existing test failures for CI compatibility

- project.test.ts: make repo name assertion portable (works in CI where
  checkout dir differs from local dev)
- vscode mock: add missing APIs needed by editor-annotations.ts
  (comments, languages, Range, CodeActionKind, decorations)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: remove Viewer.test.tsx and happy-dom dependency

Tests didn't exercise any application code — they manually constructed
DOM elements inline and verified DOM API behavior, not Viewer.tsx logic.
The mock highlighter didn't simulate real hljs, and one test could never
fail due to its try/catch structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use targeted markdown glob and restore clean lockfile

- Replace **/* glob with **/*.[mM][dD]{,[xX]} to only scan markdown
  files during case-insensitive search (avoids iterating every file)
- Restore package.json key ordering from main, only removing happy-dom
- Regenerate bun.lock from main's base to eliminate version drift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: drop redundant test and fix weak assertion

- Remove redundant PLANNOTATOR_REMOTE=TRUE test (already covered by true)
- Fix UPLOAD_DIR assertion that would pass even with hardcoded /tmp

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:47:07 -07:00

116 lines
3.3 KiB
TypeScript

/**
* Project Detection Tests
*
* Run: bun test apps/hook/server/project.test.ts
*/
import { describe, expect, test } from "bun:test";
import { sanitizeTag, extractRepoName, extractDirName, detectProjectName } from "./project";
describe("sanitizeTag", () => {
test("lowercases input", () => {
expect(sanitizeTag("MyProject")).toBe("myproject");
});
test("replaces spaces with hyphens", () => {
expect(sanitizeTag("my project")).toBe("my-project");
});
test("replaces underscores with hyphens", () => {
expect(sanitizeTag("my_project")).toBe("my-project");
});
test("removes special characters", () => {
expect(sanitizeTag("my@project!name")).toBe("myprojectname");
});
test("collapses multiple hyphens", () => {
expect(sanitizeTag("my--project")).toBe("my-project");
});
test("trims to 30 chars", () => {
const long = "a".repeat(50);
expect(sanitizeTag(long)?.length).toBe(30);
});
test("returns null for empty string", () => {
expect(sanitizeTag("")).toBeNull();
});
test("returns null for single char", () => {
expect(sanitizeTag("a")).toBeNull();
});
test("returns null for null/undefined", () => {
expect(sanitizeTag(null as any)).toBeNull();
expect(sanitizeTag(undefined as any)).toBeNull();
});
});
describe("extractRepoName", () => {
test("extracts name from full path", () => {
expect(extractRepoName("/Users/dev/projects/my-app")).toBe("my-app");
});
test("handles trailing slash", () => {
expect(extractRepoName("/Users/dev/my-app/")).toBe("my-app");
});
test("handles multiple trailing slashes", () => {
expect(extractRepoName("/home/user/repo///")).toBe("repo");
});
test("returns null for empty string", () => {
expect(extractRepoName("")).toBeNull();
});
test("returns null for just slash", () => {
expect(extractRepoName("/")).toBeNull();
});
});
describe("extractDirName", () => {
test("extracts directory name", () => {
expect(extractDirName("/home/user/workspace")).toBe("workspace");
});
test("skips generic names", () => {
expect(extractDirName("/home")).toBeNull();
expect(extractDirName("/Users")).toBeNull();
expect(extractDirName("/root")).toBeNull();
expect(extractDirName("/tmp")).toBeNull();
});
test("returns null for root path", () => {
expect(extractDirName("/")).toBeNull();
});
test("sanitizes the result", () => {
expect(extractDirName("/home/user/My Project")).toBe("my-project");
});
});
describe("detectProjectName", () => {
test("returns a string or null", async () => {
const result = await detectProjectName();
expect(result === null || typeof result === "string").toBe(true);
});
test("result is sanitized if not null", async () => {
const result = await detectProjectName();
if (result) {
expect(result).toMatch(/^[a-z0-9-]+$/);
expect(result.length).toBeGreaterThanOrEqual(2);
expect(result.length).toBeLessThanOrEqual(30);
}
});
// Verify we detect a repo name from the current working directory.
// The exact name depends on the checkout path (local vs CI).
test("detects a valid repo name", async () => {
const result = await detectProjectName();
expect(result).not.toBeNull();
expect(result!.length).toBeGreaterThanOrEqual(2);
});
});