Path: blob/main/src/vs/workbench/contrib/chat/test/browser/chatStatusDashboard.test.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { mainWindow } from '../../../../../base/browser/window.js';7import { CancellationToken } from '../../../../../base/common/cancellation.js';8import { Event } from '../../../../../base/common/event.js';9import { observableValue } from '../../../../../base/common/observable.js';10import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';11import { IInlineCompletionsService } from '../../../../../editor/browser/services/inlineCompletionsService.js';12import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';13import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';14import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';15import { ChatStatusDashboard, IChatStatusDashboardOptions } from '../../../chat/browser/chatStatus/chatStatusDashboard.js';16import { IChatStatusItemService } from '../../../chat/browser/chatStatus/chatStatusItemService.js';1718interface IQuotaConfig {19percentRemaining: number;20unlimited: boolean;21usageBasedBilling?: boolean;22resetAt?: number;23}2425function createEntitlementService(opts: {26chat?: IQuotaConfig;27completions?: IQuotaConfig;28premiumChat?: IQuotaConfig;29additionalUsageEnabled?: boolean;30entitlement?: ChatEntitlement;31}): IChatEntitlementService {32return {33_serviceBrand: undefined,34organisations: undefined,35isInternal: false,36sku: undefined,37copilotTrackingId: undefined,38onDidChangeQuotaExceeded: Event.None,39onDidChangeQuotaRemaining: Event.None,40quotas: {41chat: opts.chat,42completions: opts.completions,43premiumChat: opts.premiumChat,44additionalUsageEnabled: opts.additionalUsageEnabled,45},46update: (_token: CancellationToken) => Promise.resolve(),47onDidChangeSentiment: Event.None,48sentimentObs: observableValue({}, {}),49sentiment: { completed: true },50onDidChangeEntitlement: Event.None,51entitlement: opts.entitlement ?? ChatEntitlement.Free,52entitlementObs: observableValue({}, opts.entitlement ?? ChatEntitlement.Free),53anonymous: false,54onDidChangeAnonymous: Event.None,55anonymousObs: observableValue({}, false),56markAnonymousRateLimited: () => { },57setForceHidden: () => { },58previewFeaturesDisabled: false,59clientByokEnabled: false,60} as IChatEntitlementService;61}6263function getQuotaLabels(element: HTMLElement): string[] {64const indicators = element.querySelectorAll('.quota-indicator:not(.included) .quota-title');65return Array.from(indicators).map(el => el.textContent ?? '');66}6768function getIncludedLabels(element: HTMLElement): string[] {69const indicators = element.querySelectorAll('.quota-indicator.included .quota-title');70return Array.from(indicators).map(el => el.textContent ?? '');71}7273function getQuotaValues(element: HTMLElement): string[] {74const values = element.querySelectorAll('.quota-indicator:not(.included) .quota-value');75return Array.from(values).map(el => el.textContent ?? '');76}7778const dashboardOptions: IChatStatusDashboardOptions = {79disableInlineSuggestionsSettings: true,80disableModelSelection: true,81disableProviderOptions: true,82disableCompletionsSnooze: true,83};8485suite('ChatStatusDashboard', () => {86const store = ensureNoDisposablesAreLeakedInTestSuite();8788function createDashboard(entitlementService: IChatEntitlementService): ChatStatusDashboard {89const instantiationService = workbenchInstantiationService(undefined, store);9091instantiationService.stub(IChatEntitlementService, entitlementService);92instantiationService.stub(IChatStatusItemService, {93_serviceBrand: undefined,94onDidChange: Event.None,95setOrUpdateEntry: () => { },96deleteEntry: () => { },97getEntries: () => [],98});99instantiationService.stub(IInlineCompletionsService, {100_serviceBrand: undefined,101onDidChangeIsSnoozing: Event.None,102snoozeTimeLeft: 0,103snooze: () => { },104setSnoozeDuration: () => { },105});106instantiationService.stub(IMarkdownRendererService, {107_serviceBrand: undefined,108});109110const dashboard = store.add(instantiationService.createInstance(ChatStatusDashboard, dashboardOptions));111112mainWindow.document.body.appendChild(dashboard.element);113store.add({ dispose: () => dashboard.element.remove() });114115return dashboard;116}117118// --- COPILOT FREE ---119120test('Free — PRU: shows Chat messages and Inline Suggestions', () => {121const dashboard = createDashboard(createEntitlementService({122chat: { percentRemaining: 80, unlimited: false },123completions: { percentRemaining: 70, unlimited: false },124entitlement: ChatEntitlement.Free,125}));126127assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Inline Suggestions']);128assert.deepStrictEqual(getQuotaValues(dashboard.element), ['20%', '30%']);129});130131test('Free — PRU exhausted: shows Chat messages and Inline Suggestions at 0%', () => {132const dashboard = createDashboard(createEntitlementService({133chat: { percentRemaining: 0, unlimited: false },134completions: { percentRemaining: 0, unlimited: false },135entitlement: ChatEntitlement.Free,136}));137138assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Inline Suggestions']);139assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%', '100%']);140});141142test('Free — TBB: shows Monthly Limit and Inline Suggestions, not Chat messages', () => {143const dashboard = createDashboard(createEntitlementService({144chat: { percentRemaining: 80, unlimited: false },145premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },146completions: { percentRemaining: 70, unlimited: false },147entitlement: ChatEntitlement.Free,148}));149150assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit', 'Inline Suggestions']);151assert.deepStrictEqual(getQuotaValues(dashboard.element), ['40%', '30%']);152});153154test('Free — TBB exhausted: shows Monthly Limit and Inline Suggestions at 0%', () => {155const dashboard = createDashboard(createEntitlementService({156chat: { percentRemaining: 0, unlimited: false },157premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },158completions: { percentRemaining: 0, unlimited: false },159entitlement: ChatEntitlement.Free,160}));161162assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit', 'Inline Suggestions']);163assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%', '100%']);164});165166// --- COPILOT PRO (EDU/Pro) ---167168test('EDU/Pro — PRU: shows Chat messages, Premium requests, and Inline Suggestions', () => {169const dashboard = createDashboard(createEntitlementService({170chat: { percentRemaining: 80, unlimited: false },171premiumChat: { percentRemaining: 60, unlimited: false },172completions: { percentRemaining: 90, unlimited: false },173entitlement: ChatEntitlement.Pro,174}));175176assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Premium requests', 'Inline Suggestions']);177});178179test('EDU/Pro — TBB: shows only Monthly Limit, not Chat messages or Inline Suggestions', () => {180const dashboard = createDashboard(createEntitlementService({181chat: { percentRemaining: 80, unlimited: false },182premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },183completions: { percentRemaining: 90, unlimited: false },184entitlement: ChatEntitlement.Pro,185}));186187assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);188});189190test('EDU/Pro — TBB exhausted (no overages): shows only Monthly Limit', () => {191const dashboard = createDashboard(createEntitlementService({192chat: { percentRemaining: 0, unlimited: false },193premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },194completions: { percentRemaining: 90, unlimited: false },195additionalUsageEnabled: false,196entitlement: ChatEntitlement.Pro,197}));198199assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);200assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);201});202203test('EDU/Pro — TBB exhausted (with overages): shows only Monthly Limit', () => {204const dashboard = createDashboard(createEntitlementService({205chat: { percentRemaining: 0, unlimited: false },206premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },207completions: { percentRemaining: 90, unlimited: false },208additionalUsageEnabled: true,209entitlement: ChatEntitlement.Pro,210}));211212assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);213assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);214});215216// --- COPILOT PRO+ ---217218test('Pro+ — PRU: shows Premium requests and Inline Suggestions', () => {219const dashboard = createDashboard(createEntitlementService({220premiumChat: { percentRemaining: 60, unlimited: false },221completions: { percentRemaining: 90, unlimited: false },222entitlement: ChatEntitlement.ProPlus,223}));224225assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Premium requests', 'Inline Suggestions']);226});227228test('Pro+ — TBB with quota: shows only Monthly Limit', () => {229const dashboard = createDashboard(createEntitlementService({230chat: { percentRemaining: 80, unlimited: false },231premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },232completions: { percentRemaining: 90, unlimited: false },233entitlement: ChatEntitlement.ProPlus,234}));235236assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);237});238239test('Pro+ — TBB out of quota: shows only Monthly Limit', () => {240const dashboard = createDashboard(createEntitlementService({241chat: { percentRemaining: 0, unlimited: false },242premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },243completions: { percentRemaining: 90, unlimited: false },244entitlement: ChatEntitlement.ProPlus,245}));246247assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);248assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);249});250251// --- COPILOT MAX ---252253test('Max Yearly — no TBB: shows unlimited Premium Requests included indicator', () => {254const dashboard = createDashboard(createEntitlementService({255premiumChat: { percentRemaining: 100, unlimited: true },256completions: { percentRemaining: 100, unlimited: true },257entitlement: ChatEntitlement.Max,258}));259260// Unlimited quotas are not shown as quota indicators261assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);262// Instead shown as "included" indicator263assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Premium Requests']);264});265266test('Max Monthly — TBB: shows unlimited Monthly Limit included indicator', () => {267const dashboard = createDashboard(createEntitlementService({268premiumChat: { percentRemaining: 100, unlimited: true, usageBasedBilling: true },269completions: { percentRemaining: 100, unlimited: true },270entitlement: ChatEntitlement.Max,271}));272273assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);274assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Monthly Limit']);275});276277// --- BUSINESS / ENTERPRISE ---278279test('Enterprise Managed — PRU: shows Premium requests with unlimited included', () => {280const dashboard = createDashboard(createEntitlementService({281premiumChat: { percentRemaining: 100, unlimited: true },282completions: { percentRemaining: 100, unlimited: true },283entitlement: ChatEntitlement.Business,284}));285286assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);287assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Premium Requests']);288});289290test('Enterprise — TBB (multi-quota): shows only Monthly Limit, not Chat messages or Inline Suggestions', () => {291const dashboard = createDashboard(createEntitlementService({292chat: { percentRemaining: 80, unlimited: false },293premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },294completions: { percentRemaining: 70, unlimited: false },295entitlement: ChatEntitlement.Enterprise,296}));297298assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);299});300});301302303