mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 00:09:30 +01:00
582 lines
25 KiB
TypeScript
582 lines
25 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
// This should be the only place that is allowed to import from @vscode/component-explorer
|
|
// eslint-disable-next-line local/code-import-patterns
|
|
import { defineFixture, defineFixtureGroup, defineFixtureVariants } from '@vscode/component-explorer';
|
|
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
|
|
import { URI } from '../../../../base/common/uri.js';
|
|
// eslint-disable-next-line local/code-import-patterns
|
|
import '../../../../../../build/vite/style.css';
|
|
import '../../../browser/media/style.css';
|
|
|
|
// Theme
|
|
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
|
|
import { Registry } from '../../../../platform/registry/common/platform.js';
|
|
import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js';
|
|
import { ColorScheme } from '../../../../platform/theme/common/theme.js';
|
|
import { IColorTheme, IThemeService, IThemingRegistry, Extensions as ThemingExtensions } from '../../../../platform/theme/common/themeService.js';
|
|
import { generateColorThemeCSS } from '../../../services/themes/browser/colorThemeCss.js';
|
|
import { ColorThemeData } from '../../../services/themes/common/colorThemeData.js';
|
|
import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../services/themes/common/workbenchThemeService.js';
|
|
|
|
// Instantiation
|
|
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
|
import { ServiceIdentifier } from '../../../../platform/instantiation/common/instantiation.js';
|
|
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
|
|
import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
|
|
|
// Test service implementations
|
|
import { Emitter, Event } from '../../../../base/common/event.js';
|
|
import { mock } from '../../../../base/test/common/mock.js';
|
|
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
|
import { IInlineCompletionsService, InlineCompletionsService } from '../../../../editor/browser/services/inlineCompletionsService.js';
|
|
import { ILanguageService } from '../../../../editor/common/languages/language.js';
|
|
import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js';
|
|
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
|
|
import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../../editor/common/services/languageFeatureDebounce.js';
|
|
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
|
import { LanguageFeaturesService } from '../../../../editor/common/services/languageFeaturesService.js';
|
|
import { LanguageService } from '../../../../editor/common/services/languageService.js';
|
|
import { IModelService } from '../../../../editor/common/services/model.js';
|
|
import { ModelService } from '../../../../editor/common/services/modelService.js';
|
|
import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js';
|
|
import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js';
|
|
import { ICodeLensCache } from '../../../../editor/contrib/codelens/browser/codeLensCache.js';
|
|
import { TestCodeEditorService, TestCommandService } from '../../../../editor/test/browser/editorTestServices.js';
|
|
import { TestLanguageConfigurationService } from '../../../../editor/test/common/modes/testLanguageConfigurationService.js';
|
|
import { TestEditorWorkerService } from '../../../../editor/test/common/services/testEditorWorkerService.js';
|
|
import { TestTextResourcePropertiesService } from '../../../../editor/test/common/services/testTextResourcePropertiesService.js';
|
|
import { TestTreeSitterLibraryService } from '../../../../editor/test/common/services/testTreeSitterLibraryService.js';
|
|
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
|
|
import { TestAccessibilityService } from '../../../../platform/accessibility/test/common/testAccessibilityService.js';
|
|
import { IActionViewItemService, NullActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
|
import { IMenuService } from '../../../../platform/actions/common/actions.js';
|
|
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
|
import { TestClipboardService } from '../../../../platform/clipboard/test/common/testClipboardService.js';
|
|
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
|
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
|
import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js';
|
|
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
|
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
|
|
import { IDataChannelService, NullDataChannelService } from '../../../../platform/dataChannel/common/dataChannel.js';
|
|
import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';
|
|
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
|
import { TestDialogService } from '../../../../platform/dialogs/test/common/testDialogService.js';
|
|
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
|
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
|
import { MockContextKeyService, MockKeybindingService } from '../../../../platform/keybinding/test/common/mockKeybindingService.js';
|
|
import { ILabelService } from '../../../../platform/label/common/label.js';
|
|
import { ILoggerService, ILogService, NullLoggerService, NullLogService } from '../../../../platform/log/common/log.js';
|
|
import { INotificationService } from '../../../../platform/notification/common/notification.js';
|
|
import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js';
|
|
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
|
import { NullOpenerService } from '../../../../platform/opener/test/common/nullOpenerService.js';
|
|
import { IApplicationStorageValueChangeEvent, IProfileStorageValueChangeEvent, IStorageEntry, IStorageService, IStorageTargetChangeEvent, IStorageValueChangeEvent, IWillSaveStateEvent, IWorkspaceStorageValueChangeEvent, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js';
|
|
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
|
import { NullTelemetryServiceShape } from '../../../../platform/telemetry/common/telemetryUtils.js';
|
|
import { TestThemeService } from '../../../../platform/theme/test/common/testThemeService.js';
|
|
import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
|
|
import { UndoRedoService } from '../../../../platform/undoRedo/common/undoRedoService.js';
|
|
import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js';
|
|
import { IUserInteractionService, MockUserInteractionService } from '../../../../platform/userInteraction/browser/userInteractionService.js';
|
|
import { IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js';
|
|
import { TestMenuService } from '../workbenchTestServices.js';
|
|
|
|
// Editor
|
|
import { ITextModel } from '../../../../editor/common/model.js';
|
|
|
|
|
|
|
|
// Import color registrations to ensure colors are available
|
|
import { isThenable } from '../../../../base/common/async.js';
|
|
import '../../../../platform/theme/common/colors/baseColors.js';
|
|
import '../../../../platform/theme/common/colors/editorColors.js';
|
|
import '../../../../platform/theme/common/colors/listColors.js';
|
|
import '../../../../platform/theme/common/colors/miscColors.js';
|
|
import '../../../common/theme.js';
|
|
|
|
/**
|
|
* A storage service that never stores anything and always returns the default/fallback value.
|
|
* This is useful for fixtures where we want consistent behavior without persisted state.
|
|
*/
|
|
class NullStorageService implements IStorageService {
|
|
|
|
declare readonly _serviceBrand: undefined;
|
|
|
|
private readonly _onDidChangeValue = new Emitter<IStorageValueChangeEvent>();
|
|
onDidChangeValue(scope: StorageScope.WORKSPACE, key: string | undefined, disposable: DisposableStore): Event<IWorkspaceStorageValueChangeEvent>;
|
|
onDidChangeValue(scope: StorageScope.PROFILE, key: string | undefined, disposable: DisposableStore): Event<IProfileStorageValueChangeEvent>;
|
|
onDidChangeValue(scope: StorageScope.APPLICATION, key: string | undefined, disposable: DisposableStore): Event<IApplicationStorageValueChangeEvent>;
|
|
onDidChangeValue(scope: StorageScope, key: string | undefined, disposable: DisposableStore): Event<IStorageValueChangeEvent> {
|
|
return Event.filter(this._onDidChangeValue.event, e => e.scope === scope && (key === undefined || e.key === key), disposable);
|
|
}
|
|
|
|
private readonly _onDidChangeTarget = new Emitter<IStorageTargetChangeEvent>();
|
|
readonly onDidChangeTarget: Event<IStorageTargetChangeEvent> = this._onDidChangeTarget.event;
|
|
|
|
private readonly _onWillSaveState = new Emitter<IWillSaveStateEvent>();
|
|
readonly onWillSaveState: Event<IWillSaveStateEvent> = this._onWillSaveState.event;
|
|
|
|
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
|
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
|
|
get(_key: string, _scope: StorageScope, fallbackValue?: string): string | undefined {
|
|
return fallbackValue;
|
|
}
|
|
|
|
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
|
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
|
|
getBoolean(_key: string, _scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
|
|
return fallbackValue;
|
|
}
|
|
|
|
getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
|
getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
|
getNumber(_key: string, _scope: StorageScope, fallbackValue?: number): number | undefined {
|
|
return fallbackValue;
|
|
}
|
|
|
|
getObject<T extends object>(key: string, scope: StorageScope, fallbackValue: T): T;
|
|
getObject<T extends object>(key: string, scope: StorageScope, fallbackValue?: T): T | undefined;
|
|
getObject<T extends object>(_key: string, _scope: StorageScope, fallbackValue?: T): T | undefined {
|
|
return fallbackValue;
|
|
}
|
|
|
|
store(_key: string, _value: string | boolean | number | undefined | null, _scope: StorageScope, _target: StorageTarget): void {
|
|
// no-op
|
|
}
|
|
|
|
storeAll(_entries: IStorageEntry[], _external: boolean): void {
|
|
// no-op
|
|
}
|
|
|
|
remove(_key: string, _scope: StorageScope): void {
|
|
// no-op
|
|
}
|
|
|
|
isNew(_scope: StorageScope): boolean {
|
|
return true;
|
|
}
|
|
|
|
flush(_reason?: WillSaveStateReason): Promise<void> {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
optimize(_scope: StorageScope): Promise<void> {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
log(): void {
|
|
// no-op
|
|
}
|
|
|
|
keys(_scope: StorageScope, _target: StorageTarget): string[] {
|
|
return [];
|
|
}
|
|
|
|
switch(): Promise<void> {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
hasScope(_scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// Themes
|
|
// ============================================================================
|
|
|
|
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);
|
|
const mockEnvironmentService: IEnvironmentService = Object.create(null);
|
|
|
|
export const darkTheme = ColorThemeData.createUnloadedThemeForThemeType(
|
|
ColorScheme.DARK,
|
|
COLOR_THEME_DARK_INITIAL_COLORS
|
|
);
|
|
|
|
export const lightTheme = ColorThemeData.createUnloadedThemeForThemeType(
|
|
ColorScheme.LIGHT,
|
|
COLOR_THEME_LIGHT_INITIAL_COLORS
|
|
);
|
|
|
|
let globalStyleSheet: CSSStyleSheet | undefined;
|
|
let iconsStyleSheetCache: CSSStyleSheet | undefined;
|
|
let darkThemeStyleSheet: CSSStyleSheet | undefined;
|
|
let lightThemeStyleSheet: CSSStyleSheet | undefined;
|
|
|
|
function getGlobalStyleSheet(): CSSStyleSheet {
|
|
if (!globalStyleSheet) {
|
|
globalStyleSheet = new CSSStyleSheet();
|
|
const globalRules: string[] = [];
|
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
try {
|
|
for (const rule of Array.from(sheet.cssRules)) {
|
|
globalRules.push(rule.cssText);
|
|
}
|
|
} catch {
|
|
// Cross-origin stylesheets can't be read
|
|
}
|
|
}
|
|
globalStyleSheet.replaceSync(globalRules.join('\n'));
|
|
}
|
|
return globalStyleSheet;
|
|
}
|
|
|
|
function getIconsStyleSheetCached(): CSSStyleSheet {
|
|
if (!iconsStyleSheetCache) {
|
|
iconsStyleSheetCache = new CSSStyleSheet();
|
|
const iconsSheet = getIconsStyleSheet(undefined);
|
|
iconsStyleSheetCache.replaceSync(iconsSheet.getCSS() as string);
|
|
iconsSheet.dispose();
|
|
}
|
|
return iconsStyleSheetCache;
|
|
}
|
|
|
|
function getThemeStyleSheet(theme: ColorThemeData): CSSStyleSheet {
|
|
const isDark = theme.type === ColorScheme.DARK;
|
|
if (isDark && darkThemeStyleSheet) {
|
|
return darkThemeStyleSheet;
|
|
}
|
|
if (!isDark && lightThemeStyleSheet) {
|
|
return lightThemeStyleSheet;
|
|
}
|
|
|
|
const scopeSelector = '.' + theme.classNames[0];
|
|
const sheet = new CSSStyleSheet();
|
|
const css = generateColorThemeCSS(
|
|
theme,
|
|
scopeSelector,
|
|
themingRegistry.getThemingParticipants(),
|
|
mockEnvironmentService
|
|
);
|
|
sheet.replaceSync(css.code);
|
|
|
|
if (isDark) {
|
|
darkThemeStyleSheet = sheet;
|
|
} else {
|
|
lightThemeStyleSheet = sheet;
|
|
}
|
|
return sheet;
|
|
}
|
|
|
|
let globalStylesInstalled = false;
|
|
|
|
function installGlobalStyles(): void {
|
|
if (globalStylesInstalled) {
|
|
return;
|
|
}
|
|
globalStylesInstalled = true;
|
|
document.adoptedStyleSheets = [
|
|
...document.adoptedStyleSheets,
|
|
getGlobalStyleSheet(),
|
|
getIconsStyleSheetCached(),
|
|
getThemeStyleSheet(darkTheme),
|
|
getThemeStyleSheet(lightTheme),
|
|
];
|
|
}
|
|
|
|
export function setupTheme(container: HTMLElement, theme: ColorThemeData): void {
|
|
installGlobalStyles();
|
|
container.classList.add('monaco-workbench', getPlatformClass(), ...theme.classNames);
|
|
}
|
|
|
|
function getPlatformClass(): string {
|
|
const alwaysUseMac = true;
|
|
if (alwaysUseMac) {
|
|
return 'mac';
|
|
} else {
|
|
const ua = navigator.userAgent;
|
|
if (ua.includes('Macintosh')) {
|
|
return 'mac';
|
|
}
|
|
if (ua.includes('Linux')) {
|
|
return 'linux';
|
|
}
|
|
return 'windows';
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// Services
|
|
// ============================================================================
|
|
|
|
export interface ServiceRegistration {
|
|
define<T>(id: ServiceIdentifier<T>, ctor: new (...args: never[]) => T): void;
|
|
defineInstance<T>(id: ServiceIdentifier<T>, instance: T): void;
|
|
}
|
|
|
|
export interface CreateServicesOptions {
|
|
/**
|
|
* The color theme to use for the theme service.
|
|
*/
|
|
colorTheme?: IColorTheme;
|
|
/**
|
|
* Additional services to register after the base editor services.
|
|
*/
|
|
additionalServices?: (registration: ServiceRegistration) => void;
|
|
}
|
|
|
|
/**
|
|
* Creates a TestInstantiationService with all services needed for CodeEditorWidget.
|
|
* Additional services can be registered via the options callback.
|
|
*/
|
|
export function createEditorServices(disposables: DisposableStore, options?: CreateServicesOptions): TestInstantiationService {
|
|
const services = new ServiceCollection();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const serviceIdentifiers: ServiceIdentifier<any>[] = [];
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const define = <T>(id: ServiceIdentifier<T>, ctor: new (...args: any[]) => T) => {
|
|
if (!services.has(id)) {
|
|
services.set(id, new SyncDescriptor(ctor));
|
|
}
|
|
serviceIdentifiers.push(id);
|
|
};
|
|
|
|
const defineInstance = <T>(id: ServiceIdentifier<T>, instance: T) => {
|
|
if (!services.has(id)) {
|
|
services.set(id, instance);
|
|
}
|
|
serviceIdentifiers.push(id);
|
|
};
|
|
|
|
// Base editor services
|
|
define(IAccessibilityService, TestAccessibilityService);
|
|
define(IKeybindingService, MockKeybindingService);
|
|
define(IClipboardService, TestClipboardService);
|
|
define(IEditorWorkerService, TestEditorWorkerService);
|
|
defineInstance(IOpenerService, NullOpenerService);
|
|
define(INotificationService, TestNotificationService);
|
|
define(IDialogService, TestDialogService);
|
|
define(IUndoRedoService, UndoRedoService);
|
|
define(ILanguageService, LanguageService);
|
|
define(ILanguageConfigurationService, TestLanguageConfigurationService);
|
|
define(IConfigurationService, TestConfigurationService);
|
|
define(ITextResourcePropertiesService, TestTextResourcePropertiesService);
|
|
defineInstance(IStorageService, new NullStorageService());
|
|
if (options?.colorTheme) {
|
|
defineInstance(IThemeService, new TestThemeService(options.colorTheme));
|
|
} else {
|
|
define(IThemeService, TestThemeService);
|
|
}
|
|
define(ILogService, NullLogService);
|
|
define(IModelService, ModelService);
|
|
define(ICodeEditorService, TestCodeEditorService);
|
|
define(IContextKeyService, MockContextKeyService);
|
|
define(ICommandService, TestCommandService);
|
|
define(ITelemetryService, NullTelemetryServiceShape);
|
|
define(ILoggerService, NullLoggerService);
|
|
define(IDataChannelService, NullDataChannelService);
|
|
define(IEnvironmentService, class extends mock<IEnvironmentService>() {
|
|
declare readonly _serviceBrand: undefined;
|
|
override isBuilt: boolean = true;
|
|
override isExtensionDevelopment: boolean = false;
|
|
});
|
|
define(ILanguageFeatureDebounceService, LanguageFeatureDebounceService);
|
|
define(ILanguageFeaturesService, LanguageFeaturesService);
|
|
define(ITreeSitterLibraryService, TestTreeSitterLibraryService);
|
|
define(IInlineCompletionsService, InlineCompletionsService);
|
|
defineInstance(ICodeLensCache, {
|
|
_serviceBrand: undefined,
|
|
put: () => { },
|
|
get: () => undefined,
|
|
delete: () => { },
|
|
});
|
|
defineInstance(IHoverService, {
|
|
_serviceBrand: undefined,
|
|
showDelayedHover: () => undefined,
|
|
setupDelayedHover: () => ({ dispose: () => { } }),
|
|
setupDelayedHoverAtMouse: () => ({ dispose: () => { } }),
|
|
showInstantHover: () => undefined,
|
|
hideHover: () => { },
|
|
showAndFocusLastHover: () => { },
|
|
setupManagedHover: () => ({ dispose: () => { }, show: () => { }, hide: () => { }, update: () => { } }),
|
|
showManagedHover: () => { },
|
|
});
|
|
defineInstance(IDefaultAccountService, {
|
|
_serviceBrand: undefined,
|
|
onDidChangeDefaultAccount: new Emitter<null>().event,
|
|
onDidChangePolicyData: new Emitter<null>().event,
|
|
policyData: null,
|
|
copilotTokenInfo: null,
|
|
onDidChangeCopilotTokenInfo: new Emitter<null>().event,
|
|
getDefaultAccount: async () => null,
|
|
getDefaultAccountAuthenticationProvider: () => ({ id: 'test', name: 'Test', scopes: [], enterprise: false }),
|
|
setDefaultAccountProvider: () => { },
|
|
refresh: async () => null,
|
|
signIn: async () => null,
|
|
signOut: async () => { },
|
|
});
|
|
|
|
// User interaction service with focus simulation enabled (all elements appear focused in fixtures)
|
|
defineInstance(IUserInteractionService, new MockUserInteractionService(true, false));
|
|
|
|
// Allow additional services to be registered
|
|
options?.additionalServices?.({ define, defineInstance });
|
|
|
|
const instantiationService = disposables.add(new TestInstantiationService(services, true));
|
|
|
|
disposables.add(toDisposable(() => {
|
|
for (const id of serviceIdentifiers) {
|
|
const instanceOrDescriptor = services.get(id);
|
|
if (typeof instanceOrDescriptor?.dispose === 'function') {
|
|
instanceOrDescriptor.dispose();
|
|
}
|
|
}
|
|
}));
|
|
|
|
return instantiationService;
|
|
}
|
|
|
|
/**
|
|
* Registers additional services needed by workbench components (merge editor, etc.).
|
|
* Use with createEditorServices additionalServices option.
|
|
*/
|
|
export function registerWorkbenchServices(registration: ServiceRegistration): void {
|
|
registration.defineInstance(IContextMenuService, {
|
|
showContextMenu: () => { },
|
|
onDidShowContextMenu: () => ({ dispose: () => { } }),
|
|
onDidHideContextMenu: () => ({ dispose: () => { } }),
|
|
_serviceBrand: undefined,
|
|
});
|
|
|
|
registration.defineInstance(IContextViewService, {
|
|
showContextView: () => ({ close: () => { } }),
|
|
hideContextView: () => { },
|
|
getContextViewElement: () => { throw new Error('Not implemented'); },
|
|
layout: () => { },
|
|
anchorAlignment: 0,
|
|
_serviceBrand: undefined,
|
|
});
|
|
|
|
registration.defineInstance(ILabelService, {
|
|
getUriLabel: (uri: URI) => uri.path,
|
|
getUriBasenameLabel: (uri: URI) => uri.path.split('/').pop() ?? '',
|
|
getWorkspaceLabel: () => '',
|
|
getHostLabel: () => '',
|
|
getSeparator: () => '/',
|
|
registerFormatter: () => ({ dispose: () => { } }),
|
|
onDidChangeFormatters: () => ({ dispose: () => { } }),
|
|
registerCachedFormatter: () => ({ dispose: () => { } }),
|
|
_serviceBrand: undefined,
|
|
getHostTooltip: () => '',
|
|
});
|
|
|
|
registration.define(IMenuService, TestMenuService);
|
|
registration.define(IActionViewItemService, NullActionViewItemService);
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// Text Models
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Creates a text model using the ModelService.
|
|
*/
|
|
export function createTextModel(
|
|
instantiationService: TestInstantiationService,
|
|
text: string,
|
|
uri: URI,
|
|
languageId?: string
|
|
): ITextModel {
|
|
const modelService = instantiationService.get(IModelService);
|
|
const languageService = instantiationService.get(ILanguageService);
|
|
const languageSelection = languageId ? languageService.createById(languageId) : null;
|
|
return modelService.createModel(text, languageSelection, uri);
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// Fixture Adapters
|
|
// ============================================================================
|
|
|
|
export interface ThemedFixtureGroupLabels {
|
|
readonly kind?: 'screenshot' | 'animated';
|
|
readonly blocksCi?: true;
|
|
}
|
|
|
|
function resolveLabels(labels: ThemedFixtureGroupLabels | undefined): string[] {
|
|
const result: string[] = [];
|
|
if (labels?.kind === 'screenshot') {
|
|
result.push('.screenshot');
|
|
} else if (labels?.kind === 'animated') {
|
|
result.push('animated');
|
|
}
|
|
if (labels?.blocksCi) {
|
|
result.push('blocks-ci');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export interface ComponentFixtureContext {
|
|
container: HTMLElement;
|
|
disposableStore: DisposableStore;
|
|
theme: ColorThemeData;
|
|
}
|
|
|
|
export interface ComponentFixtureOptions {
|
|
render: (context: ComponentFixtureContext) => void | Promise<void>;
|
|
labels?: ThemedFixtureGroupLabels;
|
|
}
|
|
|
|
type ThemedFixtures = ReturnType<typeof defineFixtureVariants>;
|
|
|
|
/**
|
|
* Creates Dark and Light fixture variants from a single render function.
|
|
* The render function receives a context with container and disposableStore.
|
|
*
|
|
* Note: If render returns a Promise, the async work will run in background.
|
|
* Component-explorer waits 2 animation frames after sync render returns,
|
|
* which should be sufficient for most async setup, but timing is not guaranteed.
|
|
*/
|
|
export function defineComponentFixture(options: ComponentFixtureOptions): ThemedFixtures {
|
|
const createFixture = (theme: typeof darkTheme | typeof lightTheme) => defineFixture({
|
|
isolation: 'none',
|
|
displayMode: { type: 'component' },
|
|
background: theme === darkTheme ? 'dark' : 'light',
|
|
render: (container: HTMLElement) => {
|
|
const disposableStore = new DisposableStore();
|
|
setupTheme(container, theme);
|
|
// Start render (may be async) - component-explorer will wait 2 rAF after this returns
|
|
const result = options.render({ container, disposableStore, theme });
|
|
return isThenable(result) ? result.then(() => disposableStore) : disposableStore;
|
|
},
|
|
});
|
|
|
|
const labels = resolveLabels(options.labels);
|
|
return defineFixtureVariants(labels.length > 0 ? { labels } : {}, {
|
|
Dark: createFixture(darkTheme),
|
|
Light: createFixture(lightTheme),
|
|
});
|
|
}
|
|
|
|
interface ThemedFixtureGroupOptions {
|
|
readonly path?: string;
|
|
readonly labels?: ThemedFixtureGroupLabels;
|
|
}
|
|
|
|
type ThemedFixtureGroupFixtures = Record<string, ThemedFixtures>;
|
|
|
|
/**
|
|
* Creates a nested fixture group from themed fixtures.
|
|
* E.g., { MergeEditor: { Dark: ..., Light: ... } } becomes a nested group: MergeEditor > Dark/Light
|
|
*/
|
|
export function defineThemedFixtureGroup(options: ThemedFixtureGroupOptions, fixtures: ThemedFixtureGroupFixtures): ReturnType<typeof defineFixtureGroup>;
|
|
export function defineThemedFixtureGroup(fixtures: ThemedFixtureGroupFixtures): ReturnType<typeof defineFixtureGroup>;
|
|
export function defineThemedFixtureGroup(optionsOrFixtures: ThemedFixtureGroupOptions | ThemedFixtureGroupFixtures, fixtures?: ThemedFixtureGroupFixtures): ReturnType<typeof defineFixtureGroup> {
|
|
if (fixtures) {
|
|
const options = optionsOrFixtures as ThemedFixtureGroupOptions;
|
|
return defineFixtureGroup({
|
|
labels: resolveLabels(options.labels),
|
|
path: options.path,
|
|
}, fixtures as ThemedFixtureGroupFixtures);
|
|
}
|
|
return defineFixtureGroup(optionsOrFixtures as ThemedFixtureGroupFixtures);
|
|
}
|