Files
hermes-agent/tests/agent/lsp/test_vue_server.py
teknium1 d0db722786 fix(lsp): Vue reinstall hint routes through hermes lsp install; recipe test is a major-bound invariant
- `_VUE_TUNNEL_MSG` / `_VUE_TSDK_MSG` no longer hardcode an npm argv: they
  tell the user to delete `<HERMES_HOME>/lsp/node_modules/@vue` and the staged
  `lsp/bin/vue-language-server*` launcher, then run
  `hermes lsp install vue-language-server` — the recipe (and whatever package
  manager the installer is configured with) does the rest. Why: `hermes lsp
  install` skips when the binary exists, so a stale 3.x never auto-migrates,
  and a hardcoded `npm install` contradicts a non-npm install policy.
- `test_js_toolchain_recipes_pin_a_javascript_typescript_sdk` asserts the
  invariant (typescript major < 7 and vue major < 3, parsed from the recipe
  strings) with the import inside the test, so on main the parametrized spawn
  cases are what go red instead of a module-level ImportError at collection.
- docs: same reinstall instructions.

Review follow-up on #115726.
2026-09-19 01:29:20 -07:00

71 lines
3.4 KiB
Python

"""Tests for the Vue server registration.
``@vue/language-server`` 3.x only works behind a client-hosted tsserver tunnel
(``tsserver/request`` notifications) that Hermes's generic client does not
run, so it never publishes diagnostics. Hermes pins the self-hosting 2.x line
and starts it with ``vue.hybridMode`` off plus an explicit JS TypeScript SDK.
"""
from __future__ import annotations
import json
import os
import pytest
from agent.lsp.servers import ServerContext, find_server_for_file
def _stage(tmp_path, monkeypatch, vue_version: str, *, js_sdk: bool):
"""Fake ``<HERMES_HOME>/lsp`` tree: a staged launcher, the Vue package, optionally a JS TypeScript SDK."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
node_modules = tmp_path / "lsp" / "node_modules"
pkg = node_modules / "@vue" / "language-server"
(pkg / "bin").mkdir(parents=True)
(pkg / "package.json").write_text(json.dumps({"version": vue_version}), encoding="utf-8")
launcher = pkg / "bin" / "vue-language-server.js"
launcher.write_text("#!/usr/bin/env node\n", encoding="utf-8")
launcher.chmod(0o755)
(tmp_path / "lsp" / "bin").mkdir()
staged = tmp_path / "lsp" / "bin" / "vue-language-server"
staged.write_text(launcher.read_text(encoding="utf-8"), encoding="utf-8") # copied like the Windows path, not symlinked
staged.chmod(0o755)
if js_sdk:
(node_modules / "typescript" / "lib").mkdir(parents=True)
(node_modules / "typescript" / "lib" / "typescript.js").write_text("", encoding="utf-8")
return str(staged)
@pytest.mark.parametrize("vue_version, js_sdk, spawns", [("2.2.12", True, True), ("3.3.11", True, False), ("2.2.12", False, False)])
def test_vue_spawns_only_a_self_hosting_server_with_a_js_typescript_sdk(tmp_path, monkeypatch, vue_version, js_sdk, spawns):
"""2.x + JS SDK → spawn with ``hybridMode`` off and ``tsdk`` pointing at that SDK; a 3.x install
(tunnel-only) or a missing/Go-only TypeScript must be skipped instead of spawning a server that
can never publish diagnostics."""
_stage(tmp_path, monkeypatch, vue_version, js_sdk=js_sdk)
root = tmp_path / "project"
root.mkdir()
ctx = ServerContext(workspace_root=str(root), install_strategy="manual")
spec = find_server_for_file("App.vue").build_spawn(str(root), ctx)
if not spawns:
assert spec is None
return
assert spec.command[1:] == ["--stdio"]
assert spec.initialization_options["vue"] == {"hybridMode": False}
tsdk = spec.initialization_options["typescript"]["tsdk"]
assert os.path.isfile(os.path.join(tsdk, "typescript.js"))
def test_js_toolchain_recipes_pin_a_javascript_typescript_sdk():
"""Both TypeScript-hosting recipes co-install a JS-based TypeScript line (7+ is the Go port with no
``tsserver.js``), and the Vue recipe stays below the tunnel-only 3.x major."""
from agent.lsp.install import INSTALL_RECIPES
def major(spec: str, name: str) -> int:
assert spec.startswith(f"{name}@"), spec # unpinned = floats onto the next major
return int(spec[len(name) + 1:].split(".")[0])
vue, tsls = INSTALL_RECIPES["@vue/language-server"], INSTALL_RECIPES["typescript-language-server"]
assert major(vue["pkg"], "@vue/language-server") < 3
for recipe in (vue, tsls):
sdk = [p for p in recipe.get("extra_pkgs") or [] if p.split("@")[0] == "typescript"]
assert sdk and major(sdk[0], "typescript") < 7