chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
"""Tests for ZedIntegration."""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from specify_cli.integrations import get_integration
|
||||
|
||||
from .test_integration_base_skills import SkillsIntegrationTests
|
||||
|
||||
|
||||
class TestZedIntegration(SkillsIntegrationTests):
|
||||
KEY = "zed"
|
||||
FOLDER = ".agents/"
|
||||
COMMANDS_SUBDIR = "skills"
|
||||
REGISTRAR_DIR = ".agents/skills"
|
||||
|
||||
def test_options_include_skills_flag(self):
|
||||
"""Not applicable to Zed — Zed is always skills-based with no --skills flag."""
|
||||
pytest.skip("Zed is always skills-based and does not expose a --skills option")
|
||||
|
||||
def test_options_do_not_include_skills_flag(self):
|
||||
"""Zed is always skills-based; no --skills option is exposed."""
|
||||
i = get_integration(self.KEY)
|
||||
assert i is not None
|
||||
opts = i.options()
|
||||
skills_opts = [o for o in opts if o.name == "--skills"]
|
||||
assert len(skills_opts) == 0, (
|
||||
"Zed is always skills-based and should not expose a --skills option"
|
||||
)
|
||||
|
||||
def test_requires_cli_is_false(self):
|
||||
"""Zed is IDE-based; requires_cli must remain False."""
|
||||
i = get_integration(self.KEY)
|
||||
assert i is not None
|
||||
assert i.config is not None
|
||||
assert i.config["requires_cli"] is False
|
||||
|
||||
|
||||
class TestZedHookInvocations:
|
||||
"""Zed hook messages should reference slash-invokable skills."""
|
||||
|
||||
def test_hooks_render_skill_invocation(self, tmp_path):
|
||||
"""Zed is always skills-based: renders /speckit-plan even with ai_skills=False."""
|
||||
from specify_cli.extensions import HookExecutor
|
||||
|
||||
project = tmp_path / "zed-hooks"
|
||||
project.mkdir()
|
||||
init_options = project / ".specify" / "init-options.json"
|
||||
init_options.parent.mkdir(parents=True, exist_ok=True)
|
||||
init_options.write_text(json.dumps({"ai": "zed", "ai_skills": False}))
|
||||
|
||||
hook_executor = HookExecutor(project)
|
||||
message = hook_executor.format_hook_message(
|
||||
"before_plan",
|
||||
[
|
||||
{
|
||||
"extension": "test-ext",
|
||||
"command": "speckit.plan",
|
||||
"optional": False,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert "EXECUTE_COMMAND_INVOCATION: /speckit-plan" in message
|
||||
|
||||
def test_init_persists_ai_skills_for_zed(self, tmp_path, monkeypatch):
|
||||
"""specify init --integration zed must persist ai_skills: true,
|
||||
so HookExecutor renders slash-skill invocations without manual
|
||||
init-options manipulation."""
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from specify_cli import app
|
||||
from specify_cli.extensions import HookExecutor
|
||||
|
||||
project = tmp_path / "zed-init-test"
|
||||
project.mkdir()
|
||||
monkeypatch.chdir(project)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"init",
|
||||
"--here",
|
||||
"--integration",
|
||||
"zed",
|
||||
"--script",
|
||||
"sh",
|
||||
"--ignore-agent-tools",
|
||||
],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, f"init failed: {result.output}"
|
||||
|
||||
opts_path = project / ".specify" / "init-options.json"
|
||||
assert opts_path.exists()
|
||||
opts = json.loads(opts_path.read_text(encoding="utf-8"))
|
||||
assert opts.get("ai") == "zed"
|
||||
assert opts.get("ai_skills") is True, (
|
||||
f"init must persist ai_skills=true for Zed, got: {opts.get('ai_skills')}"
|
||||
)
|
||||
|
||||
hook_executor = HookExecutor(project)
|
||||
message = hook_executor.format_hook_message(
|
||||
"before_plan",
|
||||
[
|
||||
{
|
||||
"extension": "test-ext",
|
||||
"command": "speckit.plan",
|
||||
"optional": False,
|
||||
}
|
||||
],
|
||||
)
|
||||
assert "Executing: `/speckit-plan`" in message, (
|
||||
"Hook rendering must produce /speckit-plan for Zed without hint injection"
|
||||
)
|
||||
assert "EXECUTE_COMMAND_INVOCATION: /speckit-plan" in message
|
||||
|
||||
|
||||
class TestSlashSkillsSets:
|
||||
"""Parameterized coverage for ALWAYS_SLASH_AGENTS / CONDITIONAL_SLASH_AGENTS."""
|
||||
|
||||
@staticmethod
|
||||
def _render_invocation(project_path, ai: str, ai_skills: bool) -> str:
|
||||
"""Return the rendered invocation for ``speckit.plan`` via HookExecutor."""
|
||||
from specify_cli.extensions import HookExecutor
|
||||
|
||||
init_options = project_path / ".specify" / "init-options.json"
|
||||
init_options.parent.mkdir(parents=True, exist_ok=True)
|
||||
init_options.write_text(json.dumps({"ai": ai, "ai_skills": ai_skills}))
|
||||
hook_executor = HookExecutor(project_path)
|
||||
result = hook_executor.execute_hook(
|
||||
{"extension": "test-ext", "command": "speckit.plan", "optional": False}
|
||||
)
|
||||
return result.get("invocation", "")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ai", "ai_skills", "expected"),
|
||||
[
|
||||
# ALWAYS_SLASH_AGENTS — unconditional on ai_skills
|
||||
("devin", True, "/speckit-plan"),
|
||||
("devin", False, "/speckit-plan"),
|
||||
("trae", True, "/speckit-plan"),
|
||||
("trae", False, "/speckit-plan"),
|
||||
("zed", True, "/speckit-plan"),
|
||||
("zed", False, "/speckit-plan"),
|
||||
# CONDITIONAL_SLASH_AGENTS — only when ai_skills is enabled
|
||||
("agy", True, "/speckit-plan"),
|
||||
("agy", False, "/speckit.plan"),
|
||||
("claude", True, "/speckit-plan"),
|
||||
("claude", False, "/speckit.plan"),
|
||||
("copilot", True, "/speckit-plan"),
|
||||
("copilot", False, "/speckit.plan"),
|
||||
("cursor-agent", True, "/speckit-plan"),
|
||||
("cursor-agent", False, "/speckit.plan"),
|
||||
],
|
||||
)
|
||||
def test_hook_invocation_format(self, tmp_path, ai, ai_skills, expected):
|
||||
result = self._render_invocation(tmp_path, ai, ai_skills)
|
||||
assert result == expected, (
|
||||
f"{ai} (ai_skills={ai_skills}): expected {expected!r}, got {result!r}"
|
||||
)
|
||||
Reference in New Issue
Block a user