diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 83c3874f06..3597cd2387 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -43,39 +43,37 @@ they appear in the UI. ### UI -| UI Label | Setting | Description | Default | -| ------------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| Auto Theme Switching | `ui.autoThemeSwitching` | Automatically switch between default light and dark themes based on terminal background color. | `true` | -| Terminal Background Polling Interval | `ui.terminalBackgroundPollingInterval` | Interval in seconds to poll the terminal background color. | `60` | -| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar | `false` | -| Inline Thinking | `ui.inlineThinkingMode` | Display model thinking inline: off or full. | `"off"` | -| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` | -| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` | -| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` | -| Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` | -| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` | -| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` | -| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` | -| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` | -| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` | -| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` | -| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` | -| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` | -| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` | -| New Footer Layout | `ui.newFooterLayout` | Use the new 2-row layout with inline tips. | `"legacy"` | -| Show Tips | `ui.showTips` | Show informative tips on the right side of the status line. | `true` | -| Show Witty Phrases | `ui.showWit` | Show witty phrases while waiting. | `true` | -| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI | `false` | -| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `true` | -| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` | -| Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` | -| Show User Identity | `ui.showUserIdentity` | Show the logged-in user's identity (e.g. email) in the UI. | `true` | -| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` | -| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` | -| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` | -| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` | -| Error Verbosity | `ui.errorVerbosity` | Controls whether recoverable errors are hidden (low) or fully shown (full). | `"low"` | -| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` | +| UI Label | Setting | Description | Default | +| ------------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| Auto Theme Switching | `ui.autoThemeSwitching` | Automatically switch between default light and dark themes based on terminal background color. | `true` | +| Terminal Background Polling Interval | `ui.terminalBackgroundPollingInterval` | Interval in seconds to poll the terminal background color. | `60` | +| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar | `false` | +| Inline Thinking | `ui.inlineThinkingMode` | Display model thinking inline: off or full. | `"off"` | +| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` | +| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` | +| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` | +| Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` | +| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` | +| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` | +| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` | +| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` | +| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` | +| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` | +| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` | +| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` | +| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` | +| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI | `false` | +| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `true` | +| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` | +| Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` | +| Show User Identity | `ui.showUserIdentity` | Show the logged-in user's identity (e.g. email) in the UI. | `true` | +| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` | +| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` | +| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` | +| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` | +| Loading Phrases | `ui.loadingPhrases` | What to show while the model is working: tips, witty comments, both, or nothing. | `"tips"` | +| Error Verbosity | `ui.errorVerbosity` | Controls whether recoverable errors are hidden (low) or fully shown (full). | `"low"` | +| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` | ### IDE diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index c74a6938ea..13b10c00a7 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -255,8 +255,18 @@ their corresponding top-level category object in your `settings.json` file. input. - **Default:** `false` +- **`ui.footer.items`** (array): + - **Description:** List of item IDs to display in the footer. Rendered in + order + - **Default:** `undefined` + +- **`ui.footer.showLabels`** (boolean): + - **Description:** Display a second line above the footer items with + descriptive headers (e.g., /model). + - **Default:** `true` + - **`ui.footer.hideCWD`** (boolean): - - **Description:** Hide the current working directory path in the footer. + - **Description:** Hide the current working directory in the footer. - **Default:** `false` - **`ui.footer.hideSandboxStatus`** (boolean): diff --git a/packages/cli/src/config/footerItems.test.ts b/packages/cli/src/config/footerItems.test.ts new file mode 100644 index 0000000000..c43ea7be0e --- /dev/null +++ b/packages/cli/src/config/footerItems.test.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { deriveItemsFromLegacySettings } from './footerItems.js'; +import { createMockSettings } from '../test-utils/settings.js'; + +describe('deriveItemsFromLegacySettings', () => { + it('returns defaults when no legacy settings are customized', () => { + const settings = createMockSettings({ + ui: { footer: { hideContextPercentage: true } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).toEqual([ + 'cwd', + 'git-branch', + 'sandbox-status', + 'model-name', + 'quota', + ]); + }); + + it('removes cwd when hideCWD is true', () => { + const settings = createMockSettings({ + ui: { footer: { hideCWD: true, hideContextPercentage: true } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).not.toContain('cwd'); + }); + + it('removes sandbox-status when hideSandboxStatus is true', () => { + const settings = createMockSettings({ + ui: { footer: { hideSandboxStatus: true, hideContextPercentage: true } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).not.toContain('sandbox-status'); + }); + + it('removes model-name, context-remaining, and quota when hideModelInfo is true', () => { + const settings = createMockSettings({ + ui: { footer: { hideModelInfo: true, hideContextPercentage: true } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).not.toContain('model-name'); + expect(items).not.toContain('context-remaining'); + expect(items).not.toContain('quota'); + }); + + it('includes context-remaining when hideContextPercentage is false', () => { + const settings = createMockSettings({ + ui: { footer: { hideContextPercentage: false } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).toContain('context-remaining'); + // Should be after model-name + const modelIdx = items.indexOf('model-name'); + const contextIdx = items.indexOf('context-remaining'); + expect(contextIdx).toBe(modelIdx + 1); + }); + + it('includes memory-usage when showMemoryUsage is true', () => { + const settings = createMockSettings({ + ui: { showMemoryUsage: true, footer: { hideContextPercentage: true } }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).toContain('memory-usage'); + }); + + it('handles combination of settings', () => { + const settings = createMockSettings({ + ui: { + showMemoryUsage: true, + footer: { + hideCWD: true, + hideModelInfo: true, + hideContextPercentage: false, + }, + }, + }).merged; + const items = deriveItemsFromLegacySettings(settings); + expect(items).toEqual([ + 'git-branch', + 'sandbox-status', + 'context-remaining', + 'memory-usage', + ]); + }); +}); diff --git a/packages/cli/src/config/footerItems.ts b/packages/cli/src/config/footerItems.ts new file mode 100644 index 0000000000..0716478eb5 --- /dev/null +++ b/packages/cli/src/config/footerItems.ts @@ -0,0 +1,132 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { MergedSettings } from './settings.js'; + +export const ALL_ITEMS = [ + { + id: 'cwd', + header: 'workspace (/directory)', + description: 'Current working directory', + }, + { + id: 'git-branch', + header: 'branch', + description: 'Current git branch name (not shown when unavailable)', + }, + { + id: 'sandbox-status', + header: 'sandbox', + description: 'Sandbox type and trust indicator', + }, + { + id: 'model-name', + header: '/model', + description: 'Current model identifier', + }, + { + id: 'context-remaining', + header: 'context', + description: 'Percentage of context window remaining', + }, + { + id: 'quota', + header: '/stats', + description: 'Remaining usage on daily limit (not shown when unavailable)', + }, + { + id: 'memory-usage', + header: 'memory', + description: 'Memory used by the application', + }, + { + id: 'session-id', + header: 'session', + description: 'Unique identifier for the current session', + }, + { + id: 'code-changes', + header: 'diff', + description: 'Lines added/removed in the session (not shown when zero)', + }, + { + id: 'token-count', + header: 'tokens', + description: 'Total tokens used in the session (not shown when zero)', + }, +] as const; + +export type FooterItemId = (typeof ALL_ITEMS)[number]['id']; + +export const DEFAULT_ORDER = [ + 'cwd', + 'git-branch', + 'sandbox-status', + 'model-name', + 'context-remaining', + 'quota', + 'memory-usage', + 'session-id', + 'code-changes', + 'token-count', +]; + +export function deriveItemsFromLegacySettings( + settings: MergedSettings, +): string[] { + const defaults = [ + 'cwd', + 'git-branch', + 'sandbox-status', + 'model-name', + 'quota', + ]; + const items = [...defaults]; + + const remove = (arr: string[], id: string) => { + const idx = arr.indexOf(id); + if (idx !== -1) arr.splice(idx, 1); + }; + + if (settings.ui.footer.hideCWD) remove(items, 'cwd'); + if (settings.ui.footer.hideSandboxStatus) remove(items, 'sandbox-status'); + if (settings.ui.footer.hideModelInfo) { + remove(items, 'model-name'); + remove(items, 'context-remaining'); + remove(items, 'quota'); + } + if ( + !settings.ui.footer.hideContextPercentage && + !items.includes('context-remaining') + ) { + const modelIdx = items.indexOf('model-name'); + if (modelIdx !== -1) items.splice(modelIdx + 1, 0, 'context-remaining'); + else items.push('context-remaining'); + } + if (settings.ui.showMemoryUsage) items.push('memory-usage'); + + return items; +} + +const VALID_IDS: Set = new Set(ALL_ITEMS.map((i) => i.id)); + +/** + * Resolves the ordered list and selected set of footer items from settings. + * Used by FooterConfigDialog to initialize and reset state. + */ +export function resolveFooterState(settings: MergedSettings): { + orderedIds: string[]; + selectedIds: Set; +} { + const source = ( + settings.ui?.footer?.items ?? deriveItemsFromLegacySettings(settings) + ).filter((id: string) => VALID_IDS.has(id)); + const others = DEFAULT_ORDER.filter((id) => !source.includes(id)); + return { + orderedIds: [...source, ...others], + selectedIds: new Set(source), + }; +} diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 4933de74a9..20b47189f3 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -571,14 +571,34 @@ const SETTINGS_SCHEMA = { description: 'Settings for the footer.', showInDialog: false, properties: { + items: { + type: 'array', + label: 'Footer Items', + category: 'UI', + requiresRestart: false, + default: undefined as string[] | undefined, + description: + 'List of item IDs to display in the footer. Rendered in order', + showInDialog: false, + items: { type: 'string' }, + }, + showLabels: { + type: 'boolean', + label: 'Show Footer Labels', + category: 'UI', + requiresRestart: false, + default: true, + description: + 'Display a second line above the footer items with descriptive headers (e.g., /model).', + showInDialog: false, + }, hideCWD: { type: 'boolean', label: 'Hide CWD', category: 'UI', requiresRestart: false, default: false, - description: - 'Hide the current working directory path in the footer.', + description: 'Hide the current working directory in the footer.', showInDialog: true, }, hideSandboxStatus: { diff --git a/packages/cli/src/services/BuiltinCommandLoader.ts b/packages/cli/src/services/BuiltinCommandLoader.ts index 31673e921a..f867f84c80 100644 --- a/packages/cli/src/services/BuiltinCommandLoader.ts +++ b/packages/cli/src/services/BuiltinCommandLoader.ts @@ -31,6 +31,7 @@ import { docsCommand } from '../ui/commands/docsCommand.js'; import { directoryCommand } from '../ui/commands/directoryCommand.js'; import { editorCommand } from '../ui/commands/editorCommand.js'; import { extensionsCommand } from '../ui/commands/extensionsCommand.js'; +import { footerCommand } from '../ui/commands/footerCommand.js'; import { helpCommand } from '../ui/commands/helpCommand.js'; import { shortcutsCommand } from '../ui/commands/shortcutsCommand.js'; import { rewindCommand } from '../ui/commands/rewindCommand.js'; @@ -119,6 +120,7 @@ export class BuiltinCommandLoader implements ICommandLoader { ] : [extensionsCommand(this.config?.getEnableExtensionReloading())]), helpCommand, + footerCommand, shortcutsCommand, ...(this.config?.getEnableHooksUI() ? [hooksCommand] : []), rewindCommand, diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx index 366e7d5619..a69c590774 100644 --- a/packages/cli/src/test-utils/render.tsx +++ b/packages/cli/src/test-utils/render.tsx @@ -17,6 +17,7 @@ import { vi } from 'vitest'; import stripAnsi from 'strip-ansi'; import { act, useState } from 'react'; import os from 'node:os'; +import path from 'node:path'; import { LoadedSettings } from '../config/settings.js'; import { KeypressProvider } from '../ui/contexts/KeypressContext.js'; import { SettingsContext } from '../ui/contexts/SettingsContext.js'; @@ -509,7 +510,22 @@ const configProxy = new Proxy({} as Config, { get(_target, prop) { if (prop === 'getTargetDir') { return () => - '/Users/test/project/foo/bar/and/some/more/directories/to/make/it/long'; + path.join( + path.parse(process.cwd()).root, + 'Users', + 'test', + 'project', + 'foo', + 'bar', + 'and', + 'some', + 'more', + 'directories', + 'to', + 'make', + 'it', + 'long', + ); } if (prop === 'getUseBackgroundColor') { return () => true; diff --git a/packages/cli/src/ui/commands/footerCommand.tsx b/packages/cli/src/ui/commands/footerCommand.tsx new file mode 100644 index 0000000000..4a6760e229 --- /dev/null +++ b/packages/cli/src/ui/commands/footerCommand.tsx @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + type SlashCommand, + type CommandContext, + type OpenCustomDialogActionReturn, + CommandKind, +} from './types.js'; +import { FooterConfigDialog } from '../components/FooterConfigDialog.js'; + +export const footerCommand: SlashCommand = { + name: 'footer', + altNames: ['statusline'], + description: 'Configure which items appear in the footer (statusline)', + kind: CommandKind.BUILT_IN, + autoExecute: true, + action: (context: CommandContext): OpenCustomDialogActionReturn => ({ + type: 'custom_dialog', + component: , + }), +}; diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx index ae272d6145..9d48bc858c 100644 --- a/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx +++ b/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx @@ -28,7 +28,7 @@ vi.mock('../../config/settings.js', () => ({ describe('ContextUsageDisplay', () => { it('renders correct percentage left', async () => { - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady } = render( { ); await waitUntilReady(); const output = lastFrame(); - expect(output).toContain('50% context left'); - unmount(); + expect(output).toContain('50% left'); }); it('renders short label when terminal width is small', async () => { - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady } = render( { const output = lastFrame(); expect(output).toContain('80%'); expect(output).not.toContain('context left'); - unmount(); }); it('renders 0% when full', async () => { - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady } = render( { ); await waitUntilReady(); const output = lastFrame(); - expect(output).toContain('0% context left'); - unmount(); + expect(output).toContain('0% left'); }); }); diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.tsx index 1c1d24cc2d..293edd9e3e 100644 --- a/packages/cli/src/ui/components/ContextUsageDisplay.tsx +++ b/packages/cli/src/ui/components/ContextUsageDisplay.tsx @@ -12,18 +12,20 @@ export const ContextUsageDisplay = ({ promptTokenCount, model, terminalWidth, + color = theme.text.primary, }: { promptTokenCount: number; model: string; terminalWidth: number; + color?: string; }) => { const percentage = getContextUsagePercentage(promptTokenCount, model); const percentageLeft = ((1 - percentage) * 100).toFixed(0); - const label = terminalWidth < 100 ? '%' : '% context left'; + const label = terminalWidth < 100 ? '%' : '% left'; return ( - + {percentageLeft} {label} diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index 9c253fec92..0543749b5a 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -4,46 +4,30 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; import { renderWithProviders } from '../../test-utils/render.js'; -import { createMockSettings } from '../../test-utils/settings.js'; import { Footer } from './Footer.js'; -import { - makeFakeConfig, - tildeifyPath, - ToolCallDecision, -} from '@google/gemini-cli-core'; -import type { SessionStatsState } from '../contexts/SessionContext.js'; +import { createMockSettings } from '../../test-utils/settings.js'; +import path from 'node:path'; -vi.mock('@google/gemini-cli-core', async (importOriginal) => { - const original = - await importOriginal(); - return { - ...original, - shortenPath: (p: string, len: number) => { - if (p.length > len) { - return '...' + p.slice(p.length - len + 3); - } - return p; - }, - }; -}); - -const defaultProps = { - model: 'gemini-pro', - targetDir: - '/Users/test/project/foo/bar/and/some/more/directories/to/make/it/long', - branchName: 'main', +// Normalize paths to POSIX slashes for stable cross-platform snapshots. +const normalizeFrame = (frame: string | undefined) => { + if (!frame) return frame; + return frame.replace(/\\/g, '/'); }; -const mockSessionStats: SessionStatsState = { - sessionId: 'test-session', +const mockSessionStats = { + sessionId: 'test-session-id', sessionStartTime: new Date(), - lastPromptTokenCount: 0, promptCount: 0, + lastPromptTokenCount: 150000, metrics: { - models: {}, + files: { + totalLinesAdded: 12, + totalLinesRemoved: 4, + }, tools: { + count: 0, totalCalls: 0, totalSuccess: 0, totalFail: 0, @@ -52,18 +36,47 @@ const mockSessionStats: SessionStatsState = { accept: 0, reject: 0, modify: 0, - [ToolCallDecision.AUTO_ACCEPT]: 0, + auto_accept: 0, }, byName: {}, + latency: { avg: 0, max: 0, min: 0 }, }, - files: { - totalLinesAdded: 0, - totalLinesRemoved: 0, + models: { + 'gemini-pro': { + api: { + totalRequests: 0, + totalErrors: 0, + totalLatencyMs: 0, + }, + tokens: { + input: 0, + prompt: 0, + candidates: 0, + total: 1500, + cached: 0, + thoughts: 0, + tool: 0, + }, + roles: {}, + }, }, }, }; +const defaultProps = { + model: 'gemini-pro', + targetDir: '/long/path/to/some/deeply/nested/directories/to/make/it/long', + debugMode: false, + branchName: 'main', + errorCount: 0, +}; + describe('