package jslang import ( "errors" "os" "strings" "testing" "rickub.com/turbo-editors/turbo-core/acp" ) // The agents file Turbo JS writes. What the *feature* does is turbo-core's to // test; what is checked here is the one part that belongs to this editor — // that the starter file is filled in correctly, loads back as the agent it // describes, and says enough for somebody to point it at their own. // readAgentsFile returns the agents file a project was given. func readAgentsFile(t *testing.T, dir string) string { t.Helper() data, err := os.ReadFile(acp.ProjectPath(Profile(), dir)) if err != nil { t.Fatalf("reading the agents file: %v", err) } return string(data) } // createAgents writes the starter agents file into a fresh project. func createAgents(t *testing.T) string { t.Helper() dir := t.TempDir() if _, err := acp.Create(Profile(), dir); err != nil { t.Fatalf("acp.Create() error = %v", err) } return dir } func TestTheCreatedAgentsFileFillsBothOfItsBlanks(t *testing.T) { // Two different values — the project directory the example agent points // into, and the user's own file that a comment names. Go writes // %!s(MISSING) into the output rather than failing, so a miscounted verb // produces a starter file that is written, opened, and wrong. contents := readAgentsFile(t, createAgents(t)) if strings.Contains(contents, "%!") { t.Errorf("the created file has an unfilled verb in it:\n%s", contents) } if want := Profile().ProjectDir() + "/agent.yaml"; !strings.Contains(contents, want) { t.Errorf("the example agent does not point at %q:\n%s", want, contents) } if want := acp.UserPath(Profile()); want != "" && !strings.Contains(contents, want) { t.Errorf("the created file never names the user's own file %q:\n%s", want, contents) } } func TestTheCreatedAgentsFileLoadsBackAsOneAgent(t *testing.T) { // The file is mostly comments, and a comment carrying an [[agent]] example // that the loader read as real would put an agent nobody configured into // the menu. dir := createAgents(t) list, err := acp.Load(Profile(), dir) if err != nil { t.Fatalf("acp.Load() error = %v", err) } if list.Len() != 1 { t.Fatalf("the created file holds %d agents, want 1: %v", list.Len(), list.Agents()) } agent := list.Agents()[0] if agent.Command != "docker" { t.Errorf("the example agent runs %q, want docker", agent.Command) } if want := "agent serve acp"; !strings.Contains(agent.CommandLine(), want) { t.Errorf("the example command line is %q, want %q in it", agent.CommandLine(), want) } } func TestTheCreatedAgentsFileExplainsItself(t *testing.T) { // The keys, the window's keyboard and how to copy out of it are all // invisible otherwise: this is the only document the editor hands a user. contents := readAgentsFile(t, createAgents(t)) for _, want := range []string{ "[[agent]]", "name", "command", "args", "env", "cwd", "agentclientprotocol.com", "Alt-Enter", "Tab", "Ctrl-W", "Esc", "Ctrl-C", } { if !strings.Contains(contents, want) { t.Errorf("the created file never mentions %q:\n%s", want, contents) } } } func TestTheCreatedAgentsFileNamesThisEditorsOwnLanguages(t *testing.T) { // The comment about coloured code blocks is the one line of this file that // is about Turbo JS rather than about the protocol, and a copy left naming // another editor's language would be the obvious way to get it wrong. The // js alias is named because it is what a model writes by habit, and json // because this editor colours it too. contents := readAgentsFile(t, createAgents(t)) for _, want := range []string{"```javascript", "```js", "```json", ".js files", "package.json"} { if !strings.Contains(contents, want) { t.Errorf("the created file never mentions %q:\n%s", want, contents) } } } func TestCreatingAgentsTwiceLeavesTheFirstAlone(t *testing.T) { dir := createAgents(t) path := acp.ProjectPath(Profile(), dir) if err := os.WriteFile(path, []byte("# mine\n"), 0o644); err != nil { t.Fatalf("writing over it: %v", err) } if _, err := acp.Create(Profile(), dir); !errors.Is(err, acp.ErrExists) { t.Errorf("acp.Create() error = %v, want ErrExists", err) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("reading it back: %v", err) } if string(data) != "# mine\n" { t.Errorf("the file was overwritten: %q", data) } }