2025-10-15 10:12:44 +03:00

178 lines
8.5 KiB
Go

package project_test
import (
"context"
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/bundled"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/projecttestutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
"gotest.tools/v3/assert"
)
// These tests explicitly verify ProgramUpdateKind using subtests with shared helpers.
func TestProjectProgramUpdateKind(t *testing.T) {
t.Parallel()
if !bundled.Embedded {
t.Skip("bundled files are not embedded")
}
// Use the default session setup for tests.
t.Run("NewFiles on initial build", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": "{}",
"/src/index.ts": "export const x = 1;",
}
session, _ := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
snapshot, release := session.Snapshot()
defer release()
configured := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/src/tsconfig.json"))
assert.Assert(t, configured != nil)
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindNewFiles)
})
t.Run("Cloned on single-file change", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": "{}",
"/src/index.ts": "console.log('Hello');",
}
session, _ := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
session.DidChangeFile(context.Background(), "file:///src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{{
Partial: &lsproto.TextDocumentContentChangePartial{Text: "\n", Range: lsproto.Range{Start: lsproto.Position{Line: 0, Character: 20}, End: lsproto.Position{Line: 0, Character: 20}}},
}})
_, err = session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
snapshot, release := session.Snapshot()
defer release()
configured := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/src/tsconfig.json"))
assert.Assert(t, configured != nil)
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindCloned)
})
t.Run("SameFileNames on config change without root changes", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": `{"compilerOptions": {"strict": true}}`,
"/src/index.ts": "export const x = 1;",
}
session, utils := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
err = utils.FS().WriteFile("/src/tsconfig.json", `{"compilerOptions": {"strict": false}}`, false)
assert.NilError(t, err)
session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{{Uri: lsproto.DocumentUri("file:///src/tsconfig.json"), Type: lsproto.FileChangeTypeChanged}})
_, err = session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
snapshot, release := session.Snapshot()
defer release()
configured := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/src/tsconfig.json"))
assert.Assert(t, configured != nil)
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindSameFileNames)
})
t.Run("NewFiles on root addition", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": "{}",
"/src/index.ts": "export {}",
}
session, utils := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
content := "export const y = 2;"
err = utils.FS().WriteFile("/src/newfile.ts", content, false)
assert.NilError(t, err)
session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{{Uri: lsproto.DocumentUri("file:///src/newfile.ts"), Type: lsproto.FileChangeTypeCreated}})
session.DidOpenFile(context.Background(), "file:///src/newfile.ts", 1, content, lsproto.LanguageKindTypeScript)
_, err = session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/newfile.ts"))
assert.NilError(t, err)
snapshot, release := session.Snapshot()
defer release()
configured := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/src/tsconfig.json"))
assert.Assert(t, configured != nil)
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindNewFiles)
})
t.Run("SameFileNames when adding an unresolvable import with multi-file change", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": "{}",
"/src/index.ts": "export const x = 1;",
"/src/other.ts": "export const z = 3;",
}
session, _ := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
// Change index.ts to add an unresolvable import
session.DidChangeFile(context.Background(), "file:///src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{{
Partial: &lsproto.TextDocumentContentChangePartial{Text: "\nimport \"./does-not-exist\";\n", Range: lsproto.Range{Start: lsproto.Position{Line: 0, Character: 0}, End: lsproto.Position{Line: 0, Character: 0}}},
}})
_, err = session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
snapshot, release := session.Snapshot()
defer release()
configured := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/src/tsconfig.json"))
assert.Assert(t, configured != nil)
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindSameFileNames)
})
}
func TestProject(t *testing.T) {
t.Parallel()
if !bundled.Embedded {
t.Skip("bundled files are not embedded")
}
t.Run("commandLineWithTypingsFiles is reset on CommandLine change", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/user/username/projects/project1/app.js": ``,
"/user/username/projects/project1/package.json": `{"name":"p1","dependencies":{"jquery":"^3.1.0"}}`,
"/user/username/projects/project2/app.js": ``,
}
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
PackageToFile: map[string]string{
// Provide typings content to be installed for jquery so ATA actually installs something
"jquery": `declare const $: { x: number }`,
},
})
// 1) Open an inferred project file that triggers ATA
uri1 := lsproto.DocumentUri("file:///user/username/projects/project1/app.js")
session.DidOpenFile(context.Background(), uri1, 1, files["/user/username/projects/project1/app.js"].(string), lsproto.LanguageKindJavaScript)
// 2) Wait for ATA/background tasks to finish, then get a language service for the first file
session.WaitForBackgroundTasks()
// Sanity check: ensure ATA performed at least one install
npmCalls := utils.NpmExecutor().NpmInstallCalls()
assert.Assert(t, len(npmCalls) > 0, "expected at least one npm install call from ATA")
_, err := session.GetLanguageService(context.Background(), uri1)
assert.NilError(t, err)
// 3) Open another inferred project file
uri2 := lsproto.DocumentUri("file:///user/username/projects/project2/app.js")
session.DidOpenFile(context.Background(), uri2, 1, ``, lsproto.LanguageKindJavaScript)
// 4) Get a language service for the second file
// If commandLineWithTypingsFiles was not reset, the new program command line
// won't include the newly opened file and this will fail.
_, err = session.GetLanguageService(context.Background(), uri2)
assert.NilError(t, err)
})
}