Comprehensive testing utilities for creating in-memory Angular projects and testing language service integrations. Provides tools for unit testing Angular Language Service features and IDE integrations.
Main testing environment class for creating isolated, in-memory TypeScript server instances with Angular Language Service support.
/**
* Main testing environment for Angular Language Service integration tests
* Creates isolated in-memory tsserver instances for testing
*/
class LanguageServiceTestEnv {
/**
* Creates and initializes a new test environment
* @returns Configured test environment instance
*/
static setup(): LanguageServiceTestEnv;
/**
* Creates a new test project with Angular Language Service support
* @param name - Project name
* @param files - Project files as name/content mapping
* @param angularCompilerOptions - Angular-specific compiler options
* @param tsCompilerOptions - TypeScript compiler options
* @returns Configured test project
*/
addProject(
name: string,
files: ProjectFiles,
angularCompilerOptions?: TestableOptions,
tsCompilerOptions?: ts.CompilerOptions
): Project;
/**
* Extracts text content from a TypeScript text span
* @param fileName - File name containing the span
* @param span - Text span to extract
* @returns Extracted text or null if not found
*/
getTextFromTsSpan(fileName: string, span: ts.TextSpan): string | null;
/**
* Asserts that there are no TypeScript source diagnostics across all projects
* Throws if any source file has diagnostic errors
*/
expectNoSourceDiagnostics(): void;
}Represents an individual Angular project within the test environment with full language service capabilities.
/**
* Test project with Angular Language Service integration
* Provides methods for testing language service features
*/
class Project {
/**
* Initializes a new test project
* @param projectName - Name of the project
* @param projectService - TypeScript project service instance
* @param files - Project files
* @param angularCompilerOptions - Angular compiler options
* @param tsCompilerOptions - TypeScript compiler options
* @returns Initialized project instance
*/
static initialize(
projectName: string,
projectService: ts.server.ProjectService,
files: ProjectFiles,
angularCompilerOptions?: TestableOptions,
tsCompilerOptions?: ts.CompilerOptions
): Project;
/** Project name */
readonly name: string;
/** Angular Language Service instance for this project */
readonly ngLS: LanguageService;
/**
* Opens a file for editing and returns a buffer interface
* @param projectFileName - File name to open
* @returns OpenBuffer instance for the file
*/
openFile(projectFileName: string): OpenBuffer;
/**
* Gets the TypeScript SourceFile for a project file
* @param projectFileName - File name
* @returns SourceFile instance or undefined if not found
*/
getSourceFile(projectFileName: string): ts.SourceFile | undefined;
/**
* Gets the TypeScript type checker for this project
* @returns TypeChecker instance
*/
getTypeChecker(): ts.TypeChecker;
/**
* Gets semantic diagnostics for a specific file
* @param projectFileName - File name to check
* @returns Array of diagnostic messages
*/
getDiagnosticsForFile(projectFileName: string): ts.Diagnostic[];
/**
* Gets suggestion diagnostics for a specific file
* @param projectFileName - File name to check
* @returns Array of suggestion diagnostics
*/
getSuggestionDiagnosticsForFile(projectFileName: string): ts.Diagnostic[];
/**
* Gets available code fixes at a specific position
* @param projectFileName - File name
* @param start - Start position
* @param end - End position
* @param errorCodes - Error codes to get fixes for
* @returns Array of code fix actions
*/
getCodeFixesAtPosition(
projectFileName: string,
start: number,
end: number,
errorCodes: readonly number[]
): readonly ts.CodeFixAction[];
/**
* Gets available refactorings at a position or range
* @param projectFileName - File name
* @param positionOrRange - Position or text range
* @returns Array of applicable refactoring info
*/
getRefactoringsAtPosition(
projectFileName: string,
positionOrRange: number | ts.TextRange
): readonly ts.ApplicableRefactorInfo[];
/**
* Applies a refactoring operation
* @param projectFileName - File name
* @param positionOrRange - Position or text range
* @param refactorName - Name of refactoring to apply
* @param reportProgress - Progress callback
* @returns Promise resolving to refactoring result
*/
applyRefactoring(
projectFileName: string,
positionOrRange: number | ts.TextRange,
refactorName: string,
reportProgress: ApplyRefactoringProgressFn
): Promise<ApplyRefactoringResult | undefined>;
/**
* Gets combined code fix for a specific fix ID
* @param projectFileName - File name
* @param fixId - Fix identifier
* @returns Combined code actions
*/
getCombinedCodeFix(projectFileName: string, fixId: string): ts.CombinedCodeActions;
/**
* Asserts that there are no source diagnostics in any TypeScript files
* Throws if diagnostics are found
*/
expectNoSourceDiagnostics(): void;
/**
* Asserts that there are no template diagnostics for a specific component
* @param projectFileName - Component file name
* @param className - Component class name
*/
expectNoTemplateDiagnostics(projectFileName: string, className: string): void;
/**
* Gets the Angular template type checker for this project
* @returns TemplateTypeChecker instance
*/
getTemplateTypeChecker(): TemplateTypeChecker;
/**
* Gets the logger instance for this project
* @returns TypeScript server logger
*/
getLogger(): ts.server.Logger;
}Represents an open file in a test project with cursor positioning and language service operations.
/**
* Represents an open file buffer with cursor positioning and language service operations
*/
class OpenBuffer {
/**
* Creates a new open buffer instance
* @param ngLS - Angular Language Service instance
* @param project - TypeScript server project
* @param projectFileName - File name
* @param scriptInfo - Script info for the file
*/
constructor(
ngLS: LanguageService,
project: ts.server.Project,
projectFileName: string,
scriptInfo: ts.server.ScriptInfo
);
/** Current cursor position in the file */
get cursor(): number;
/** File contents */
get contents(): string;
/** Updates file contents */
set contents(newContents: string);
/**
* Moves cursor to a position marked in text snippet
* @param snippetWithCursor - Text with cursor marker (¦ or |)
*/
moveCursorToText(snippetWithCursor: string): void;
// Language Service Operations
/**
* Gets definition and bound span at cursor position
* @returns Definition info with bound span or undefined
*/
getDefinitionAndBoundSpan(): ts.DefinitionInfoAndBoundSpan | undefined;
/**
* Gets encoded semantic classifications for the file
* @param span - Optional text span to classify
* @param format - Classification format
* @returns Semantic classifications
*/
getEncodedSemanticClassifications(
span?: ts.TextSpan,
format?: ts.SemanticClassificationFormat
): ts.Classifications;
/**
* Gets completions at cursor position
* @param options - Completion options
* @returns Completion info with metadata or undefined
*/
getCompletionsAtPosition(
options?: ts.GetCompletionsAtPositionOptions
): ts.WithMetadata<ts.CompletionInfo> | undefined;
/**
* Gets completion entry details
* @param entryName - Completion entry name
* @param formatOptions - Format options
* @param preferences - User preferences
* @param data - Completion entry data
* @returns Completion entry details or undefined
*/
getCompletionEntryDetails(
entryName: string,
formatOptions?: ts.FormatCodeOptions | ts.FormatCodeSettings,
preferences?: ts.UserPreferences,
data?: ts.CompletionEntryData
): ts.CompletionEntryDetails | undefined;
/**
* Gets Type Check Block information at cursor position
* @returns TCB response or undefined
*/
getTcb(): GetTcbResponse | undefined;
/**
* Gets outlining spans for the file
* @returns Array of outlining spans
*/
getOutliningSpans(): ts.OutliningSpan[];
/**
* Gets template location for component at cursor position
* @returns Document span or undefined
*/
getTemplateLocationForComponent(): ts.DocumentSpan | undefined;
/**
* Gets quick info at cursor position
* @returns Quick info or undefined
*/
getQuickInfoAtPosition(): ts.QuickInfo | undefined;
/**
* Gets type definition at cursor position
* @returns Array of definition info or undefined
*/
getTypeDefinitionAtPosition(): readonly ts.DefinitionInfo[] | undefined;
/**
* Gets references at cursor position
* @returns Array of reference entries or undefined
*/
getReferencesAtPosition(): ts.ReferenceEntry[] | undefined;
/**
* Finds rename locations for symbol at cursor position
* @returns Array of rename locations or undefined
*/
findRenameLocations(): readonly ts.RenameLocation[] | undefined;
/**
* Gets rename info for symbol at cursor position
* @returns Rename info
*/
getRenameInfo(): ts.RenameInfo;
/**
* Gets signature help at cursor position
* @returns Signature help items or undefined
*/
getSignatureHelpItems(): ts.SignatureHelpItems | undefined;
}Mock implementation of TypeScript's ServerHost for controlled testing environments.
/**
* Mock implementation of TypeScript ServerHost for testing
* Provides controlled file system and environment simulation
*/
class MockServerHost implements ts.server.ServerHost {
/**
* Creates a mock server host with a mock file system
* @param fs - Mock file system instance
*/
constructor(fs: MockFileSystem);
/** Line ending character for this host */
get newLine(): string;
/** Whether file names are case sensitive */
get useCaseSensitiveFileNames(): boolean;
/**
* Reads file content
* @param path - File path
* @param encoding - Text encoding
* @returns File content or undefined if not found
*/
readFile(path: string, encoding?: string): string | undefined;
/**
* Resolves a path to absolute form
* @param path - Path to resolve
* @returns Resolved absolute path
*/
resolvePath(path: string): string;
/**
* Checks if file exists
* @param path - File path
* @returns True if file exists
*/
fileExists(path: string): boolean;
/**
* Checks if directory exists
* @param path - Directory path
* @returns True if directory exists
*/
directoryExists(path: string): boolean;
/**
* Creates a directory
* @param path - Directory path to create
*/
createDirectory(path: string): void;
/**
* Gets the executing file path
* @returns Path to the executing file
*/
getExecutingFilePath(): string;
/**
* Gets the current working directory
* @returns Current directory path
*/
getCurrentDirectory(): string;
/**
* Creates a hash of data
* @param data - Data to hash
* @returns Hash string
*/
createHash(data: string): string;
}/**
* Mapping of file names to their content for test projects
*/
type ProjectFiles = { [fileName: string]: string };/**
* Combined type checking and compiler options for testing
* Includes Angular-specific options and TypeScript compatibility options
*/
type TestableOptions = TypeCheckingOptions &
InternalOptions &
Pick<LegacyNgcOptions, 'fullTemplateTypeCheck'> & {
/** Enable selectorless components (internal testing option) */
_enableSelectorless?: boolean;
};Mock implementation of TypeScript's ServerHost for controlled testing environments.
/**
* Mock implementation of TypeScript ServerHost for testing
* Provides controlled file system and environment simulation
*/
class MockServerHost implements ts.server.ServerHost {
/**
* Creates a mock server host with a mock file system
* @param fs - Mock file system instance
*/
constructor(fs: MockFileSystem);
/** Line ending character for this host */
get newLine(): string;
/** Whether file names are case sensitive */
get useCaseSensitiveFileNames(): boolean;
/**
* Reads file content
* @param path - File path
* @param encoding - Text encoding
* @returns File content or undefined if not found
*/
readFile(path: string, encoding?: string): string | undefined;
/**
* Resolves a path to absolute form
* @param path - Path to resolve
* @returns Resolved absolute path
*/
resolvePath(path: string): string;
/**
* Checks if file exists
* @param path - File path
* @returns True if file exists
*/
fileExists(path: string): boolean;
/**
* Checks if directory exists
* @param path - Directory path
* @returns True if directory exists
*/
directoryExists(path: string): boolean;
/**
* Creates a directory
* @param path - Directory path to create
*/
createDirectory(path: string): void;
/**
* Gets the executing file path
* @returns Path to the executing file
*/
getExecutingFilePath(): string;
/**
* Gets the current working directory
* @returns Current directory path
*/
getCurrentDirectory(): string;
/**
* Creates a hash of data
* @param data - Data to hash
* @returns Hash string
*/
createHash(data: string): string;
}/**
* Asserts that array of objects with fileName property matches expected file names
* @param refs - Array of objects with fileName property
* @param expectedFileNames - Expected file names
*/
function assertFileNames(
refs: Array<{fileName: string}>,
expectedFileNames: string[]
): void;
/**
* Asserts that array of objects with fileName property matches expected paths (regex)
* @param refs - Array of objects with fileName property
* @param expectedPaths - Expected path patterns as regex
*/
function assertFilePaths(
refs: Array<{fileName: string}>,
expectedPaths: RegExp[]
): void;
/**
* Asserts that array of objects with textSpan property matches expected text spans
* @param items - Array of objects with textSpan property
* @param expectedTextSpans - Expected text span content
*/
function assertTextSpans(
items: Array<{textSpan: string}>,
expectedTextSpans: string[]
): void;/**
* Checks if a diagnostic is Angular-specific (not from TypeScript)
* @param diag - TypeScript diagnostic
* @returns True if diagnostic originates from Angular
*/
function isNgSpecificDiagnostic(diag: ts.Diagnostic): boolean;/**
* Creates test project with both module-based and standalone declarations
* @param env - Test environment
* @param projectName - Project name
* @param projectFiles - Main project files
* @param angularCompilerOptions - Angular options
* @param standaloneFiles - Additional standalone files
* @param tsCompilerOptions - TypeScript options
* @returns Configured project
*/
function createModuleAndProjectWithDeclarations(
env: LanguageServiceTestEnv,
projectName: string,
projectFiles: ProjectFiles,
angularCompilerOptions?: TestableOptions,
standaloneFiles?: ProjectFiles,
tsCompilerOptions?: ts.CompilerOptions
): Project;
/**
* Creates test project with standalone declarations only
* @param env - Test environment
* @param projectName - Project name
* @param projectFiles - Project files
* @param angularCompilerOptions - Angular options
* @param standaloneFiles - Standalone files
* @returns Configured project
*/
function createProjectWithStandaloneDeclarations(
env: LanguageServiceTestEnv,
projectName: string,
projectFiles: ProjectFiles,
angularCompilerOptions?: TestableOptions,
standaloneFiles?: ProjectFiles
): Project;/**
* Humanizes document span objects for better test output
* @param item - Document span-like object
* @param env - Test environment for context
* @returns Humanized object with string representations
*/
function humanizeDocumentSpanLike<T extends ts.DocumentSpan>(
item: T,
env: LanguageServiceTestEnv
): T & Stringy<ts.DocumentSpan>;
/**
* Extracts text from content using a text span
* @param contents - Full text content
* @param textSpan - Span to extract
* @returns Extracted text
*/
function getText(contents: string, textSpan: ts.TextSpan): string;
/**
* Patches language service projects with test host for controlled testing
* Internal function for test environment setup
*/
function patchLanguageServiceProjectsWithTestHost(): void;import {
LanguageServiceTestEnv,
Project,
ProjectFiles
} from "@angular/language-service/testing";
describe('Angular Language Service', () => {
let env: LanguageServiceTestEnv;
let project: Project;
beforeEach(() => {
env = LanguageServiceTestEnv.setup();
const files: ProjectFiles = {
'app.component.ts': `
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: '<div>{{ title }}</div>'
})
export class AppComponent {
title = 'Hello World';
}
`,
'app.module.ts': `
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
`
};
project = env.addProject('test-project', files);
});
it('should provide completions in template', () => {
const buffer = project.openFile('app.component.ts');
buffer.moveCursorToText('{{ title¦ }}');
const completions = buffer.getCompletionsAtPosition();
expect(completions?.entries).toBeDefined();
expect(completions!.entries.some(e => e.name === 'title')).toBe(true);
});
});import {
LanguageServiceTestEnv,
Project,
ApplyRefactoringProgressFn
} from "@angular/language-service/testing";
describe('Signal Input Refactoring', () => {
let env: LanguageServiceTestEnv;
let project: Project;
beforeEach(() => {
env = LanguageServiceTestEnv.setup();
project = env.addProject('signal-test', {
'component.ts': `
import { Component, Input } from '@angular/core';
@Component({
selector: 'my-comp',
template: '<div>{{ value }}</div>'
})
export class MyComponent {
@Input() value: string = '';
}
`
});
});
it('should convert @Input to signal input', async () => {
const buffer = project.openFile('component.ts');
buffer.moveCursorToText('@Input() value¦: string');
const progressCallback: ApplyRefactoringProgressFn = jest.fn();
const result = await buffer.applyRefactoring(
'component.ts',
buffer.cursor,
'convert-to-signal-input',
progressCallback
);
expect(result).toBeDefined();
expect(result!.errorMessage).toBeUndefined();
expect(progressCallback).toHaveBeenCalled();
});
});import {
LanguageServiceTestEnv,
Project,
isNgSpecificDiagnostic
} from "@angular/language-service/testing";
describe('Template Diagnostics', () => {
let env: LanguageServiceTestEnv;
let project: Project;
beforeEach(() => {
env = LanguageServiceTestEnv.setup();
project = env.addProject('diagnostic-test', {
'component.ts': `
import { Component } from '@angular/core';
@Component({
selector: 'my-comp',
template: '<div>{{ unknownProperty }}</div>'
})
export class MyComponent {
knownProperty = 'test';
}
`
}, { strictTemplates: true });
});
it('should report template errors', () => {
const diagnostics = project.getDiagnosticsForFile('component.ts');
const angularDiagnostics = diagnostics.filter(isNgSpecificDiagnostic);
expect(angularDiagnostics.length).toBeGreaterThan(0);
expect(angularDiagnostics[0].messageText).toContain('unknownProperty');
});
});