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) }) }