From 3e029e5ac9aa7ba4c32fdf768ca6bb65b2d78bb2 Mon Sep 17 00:00:00 2001 From: zhao33079-zhao <309040233+zhao33079-zhao@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:00:40 +0800 Subject: [PATCH] fix(codex): add Ultrafast catalog metadata and usage badges --- .../resources/codex_models/model-catalog.json | 18 ++++- src-tauri/src/codex_catalog.rs | 54 +++++++++++++- src/i18n/ja.ts | 5 ++ src/i18n/locales/en.ts | 5 ++ src/i18n/locales/zh-CN.ts | 5 ++ src/i18n/resources.ts | 4 ++ src/pages/UsageEventsView.css | 11 ++- src/pages/UsageEventsView.tsx | 11 ++- src/services/usageServiceTier.ts | 11 +++ src/styles/usage-subpages.css | 3 +- tests/fixtures/usage-response-model.tsx | 14 +++- tests/i18n.test.ts | 12 ++++ tests/usage-service-tier-ui.cjs | 72 +++++++++++++++++++ tests/usageServiceTier.test.ts | 28 ++++++++ 14 files changed, 242 insertions(+), 11 deletions(-) create mode 100644 src/services/usageServiceTier.ts create mode 100644 tests/usage-service-tier-ui.cjs create mode 100644 tests/usageServiceTier.test.ts diff --git a/src-tauri/resources/codex_models/model-catalog.json b/src-tauri/resources/codex_models/model-catalog.json index b53beb3fc..2cb6c5dda 100644 --- a/src-tauri/resources/codex_models/model-catalog.json +++ b/src-tauri/resources/codex_models/model-catalog.json @@ -1,5 +1,5 @@ { - "catalog_revision": 7, + "catalog_revision": 8, "models": [ { "slug": "gpt-6.1-sol", @@ -170,10 +170,16 @@ "id": "priority", "name": "Fast", "description": "2x speed, increased usage" + }, + { + "id": "ultrafast", + "name": "Ultrafast", + "description": "The fastest available responses for latency-sensitive work." } ], "additional_speed_tiers": [ - "fast" + "fast", + "ultrafast" ], "supports_reasoning_summary_parameter": true, "supports_reasoning_summaries": true, @@ -351,10 +357,16 @@ "id": "priority", "name": "Fast", "description": "2x speed, increased usage" + }, + { + "id": "ultrafast", + "name": "Ultrafast", + "description": "The fastest available responses for latency-sensitive work." } ], "additional_speed_tiers": [ - "fast" + "fast", + "ultrafast" ], "supports_reasoning_summary_parameter": true, "supports_reasoning_summaries": true, diff --git a/src-tauri/src/codex_catalog.rs b/src-tauri/src/codex_catalog.rs index eb85815a5..90b439b9e 100644 --- a/src-tauri/src/codex_catalog.rs +++ b/src-tauri/src/codex_catalog.rs @@ -956,7 +956,7 @@ mod tests { let state = catalog_state().unwrap().read().unwrap(); let sources = &state.sources; assert_eq!(sources.templates.len(), 10); - assert_eq!(sources.revision, 7); + assert_eq!(sources.revision, 8); let embedded: Value = serde_json::from_str(MODEL_CATALOG_JSON).unwrap(); assert!(embedded.get("fallback_model").is_none()); let fallback: Value = serde_json::from_str(FALLBACK_MODEL_JSON).unwrap(); @@ -1237,6 +1237,48 @@ mod tests { } } + #[test] + fn embedded_ultrafast_tiers_survive_catalog_generation() { + let sources = parse_sources(MODEL_CATALOG_JSON).unwrap(); + let runtime = runtime(serde_json::json!({"models": [ + {"id": "gpt-6.1-sol"}, + {"id": "gpt-6-astra"}, + {"id": "gpt-6-luna"} + ]})); + let catalog = prepare_catalog_with_sources(&runtime, &sources).unwrap(); + let models = output_models(&catalog); + + for slug in ["gpt-6.1-sol", "gpt-6-astra"] { + let model = models + .iter() + .find(|model| model["slug"] == slug) + .unwrap(); + let template = &sources.templates[slug].value; + assert_eq!(model["service_tiers"], template["service_tiers"]); + assert_eq!( + model["additional_speed_tiers"], + serde_json::json!(["fast", "ultrafast"]) + ); + assert!(model["service_tiers"].as_array().unwrap().iter().any(|tier| { + tier["id"] == "ultrafast" + && tier["name"] == "Ultrafast" + && tier["description"].as_str().is_some_and(|text| !text.is_empty()) + })); + assert_eq!(model["default_service_tier"], Value::Null); + } + + let luna = models + .iter() + .find(|model| model["slug"] == "gpt-6-luna") + .unwrap(); + assert_eq!(luna["additional_speed_tiers"], serde_json::json!(["fast"])); + assert!(luna["service_tiers"] + .as_array() + .unwrap() + .iter() + .all(|tier| tier["id"] != "ultrafast")); + } + #[test] fn template_speed_tiers_are_preserved_when_fast_mode_is_enabled() { let mut sources = test_sources(); @@ -1529,7 +1571,10 @@ mod tests { assert_eq!(model["shell_type"], "shell_command"); assert_eq!(model["supports_reasoning_effort_updates"], true); assert_eq!(model["default_service_tier"], Value::Null); - assert_eq!(model["additional_speed_tiers"], serde_json::json!(["fast"])); + assert_eq!( + model["additional_speed_tiers"], + serde_json::json!(["fast", "ultrafast"]) + ); assert_eq!(model["service_tiers"], template["service_tiers"]); assert_eq!( model["base_instructions"], @@ -1561,7 +1606,10 @@ mod tests { ["low", "medium", "high", "xhigh", "max", "ultra"] ); assert_eq!(model["default_service_tier"], Value::Null); - assert_eq!(model["additional_speed_tiers"], serde_json::json!(["fast"])); + assert_eq!( + model["additional_speed_tiers"], + serde_json::json!(["fast", "ultrafast"]) + ); assert_eq!(model["service_tiers"], template["service_tiers"]); } diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 77c6203c2..b7c2006db 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -900,6 +900,11 @@ export const jaOverrides = { 'usage.provider.unknownAccess': '接続方式の記録なし', 'usage.column.effort': '推論レベル', 'usage.speedMode.fast': 'fast', + 'usage.speedMode.ultrafast': 'ultrafast', + 'usage.speedMode.requested': 'リクエストのサービス階層', + 'usage.speedMode.response': '応答のサービス階層', + 'usage.speedMode.unrecorded': '記録なし', + 'usage.speedMode.requestHint': 'バッジはリクエストの階層を示し、上流での適用や課金を確定するものではありません。', 'usage.column.request': 'リクエスト', 'usage.column.tokens': 'Token', 'usage.column.provider': 'プロバイダー', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 7540cd195..386ed40d9 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -888,6 +888,11 @@ export const en: Record = { 'usage.provider.unknownAccess': 'Access type unrecorded', 'usage.column.effort': 'Reasoning Effort', 'usage.speedMode.fast': 'fast', + 'usage.speedMode.ultrafast': 'ultrafast', + 'usage.speedMode.requested': 'Requested service tier', + 'usage.speedMode.response': 'Response service tier', + 'usage.speedMode.unrecorded': 'Not recorded', + 'usage.speedMode.requestHint': 'The badge shows the requested tier, not confirmation of upstream scheduling or billing.', 'usage.column.request': 'Request', 'usage.column.tokens': 'Token', 'usage.column.provider': 'Provider', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 1821c0863..665603e49 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -873,6 +873,11 @@ export const zhCN = { 'usage.provider.unknownAccess': '未记录接入方式', 'usage.column.effort': '推理强度', 'usage.speedMode.fast': 'fast', + 'usage.speedMode.ultrafast': 'ultrafast', + 'usage.speedMode.requested': '请求服务档位', + 'usage.speedMode.response': '响应服务档位', + 'usage.speedMode.unrecorded': '未记录', + 'usage.speedMode.requestHint': '标记表示请求档位,不代表上游调度或计费已确认。', 'usage.column.request': '请求', 'usage.column.tokens': 'Token', 'usage.column.provider': '提供商', diff --git a/src/i18n/resources.ts b/src/i18n/resources.ts index fe6b00467..4ff04bad1 100644 --- a/src/i18n/resources.ts +++ b/src/i18n/resources.ts @@ -9,6 +9,10 @@ export type MessageVariables = Record; export const zhTW: Record = { ...createTraditionalMessages(zhCN), + 'usage.speedMode.requested': '請求服務檔位', + 'usage.speedMode.response': '響應服務檔位', + 'usage.speedMode.unrecorded': '未記錄', + 'usage.speedMode.requestHint': '標記表示請求檔位,不代表上游調度或計費已確認。', "app.theme.lightGroup": "淺色主題", "app.theme.darkGroup": "深色主題", 'app.theme.triggerLabel': '外觀', diff --git a/src/pages/UsageEventsView.css b/src/pages/UsageEventsView.css index 65624ef7e..90b2f300e 100644 --- a/src/pages/UsageEventsView.css +++ b/src/pages/UsageEventsView.css @@ -9,6 +9,7 @@ --usage-log-cache-read: #0f766e; --usage-log-cache-write: #b45309; --usage-log-response-model: #9a5808; + --usage-log-ultrafast: #7e22ce; --usage-log-duration-good: #16a34a; --usage-log-duration-warn: #d97706; --usage-log-duration-bad: #dc2626; @@ -133,7 +134,8 @@ white-space: nowrap; } -.usage-request-log .usage-events-table .usage-model-badges > .usage-model-fast { +.usage-request-log .usage-events-table .usage-model-badges > .usage-model-fast, +.usage-request-log .usage-events-table .usage-model-badges > .usage-model-ultrafast { display: inline-block; width: fit-content; padding: 1px 5px; @@ -146,6 +148,12 @@ white-space: nowrap; } +.usage-request-log .usage-events-table .usage-model-badges > .usage-model-ultrafast { + border-color: color-mix(in srgb, var(--usage-log-ultrafast) 36%, transparent); + background: color-mix(in srgb, var(--usage-log-ultrafast) 10%, transparent); + color: var(--usage-log-ultrafast); +} + .usage-request-log .usage-events-table .usage-td-model > .usage-response-model { color: var(--usage-log-response-model); } @@ -460,6 +468,7 @@ --usage-log-cache-read: #5eead4; --usage-log-cache-write: #fbbf24; --usage-log-response-model: #f0bc73; + --usage-log-ultrafast: #d8b4fe; --usage-log-duration-good: #4ade80; --usage-log-duration-warn: #fbbf24; --usage-log-duration-bad: #f87171; diff --git a/src/pages/UsageEventsView.tsx b/src/pages/UsageEventsView.tsx index 53880a260..549b43f9a 100644 --- a/src/pages/UsageEventsView.tsx +++ b/src/pages/UsageEventsView.tsx @@ -16,6 +16,7 @@ import { formatDuration, formatUsageNumber } from '../services/usageNumber'; import { Dialog } from '../components/Dialog'; import { usageProviderDetails } from '../services/usageProvider'; import { usageModelDetails } from '../services/usageModel'; +import { usageServiceTierDetails } from '../services/usageServiceTier'; import { Select } from '../components/Select'; const compactNumber = (value: number) => formatUsageNumber(value, getCurrentLocale()); @@ -326,7 +327,12 @@ function UsageEventCell({ case 'model': { const model = usageModelDetails(record.model, record.alias, record.response_model); const effort = record.reasoning_effort || 'auto'; - const fast = ['priority', 'fast'].includes(record.service_tier?.trim().toLowerCase() ?? ''); + const serviceTier = usageServiceTierDetails(record.service_tier, record.response_service_tier); + const serviceTierTitle = [ + `${t('usage.speedMode.requested')}: ${serviceTier.requested || t('usage.speedMode.unrecorded')}`, + `${t('usage.speedMode.response')}: ${serviceTier.response || t('usage.speedMode.unrecorded')}`, + t('usage.speedMode.requestHint'), + ].join('\n'); const modelTitle = [ `${t('usage.model.request')}: ${model.requested}`, model.showResolved ? `${t('usage.model.upstream')}: ${model.resolved}` : '', @@ -334,13 +340,14 @@ function UsageEventCell({ model.mismatch ? t('usage.model.mismatch') : '', model.showResponseInTooltip ? t('usage.model.responseHint') : '', `${t('usage.column.effort')}: ${effort}`, + serviceTierTitle, ].filter(Boolean).join('\n'); return ( {model.requested} {effort} - {fast ? {t('usage.speedMode.fast')} : null} + {serviceTier.badge ? {t(serviceTier.badge === 'ultrafast' ? 'usage.speedMode.ultrafast' : 'usage.speedMode.fast')} : null} {model.showResolved ? {model.resolved} : null} {model.mismatch ? ( diff --git a/src/services/usageServiceTier.ts b/src/services/usageServiceTier.ts new file mode 100644 index 000000000..e52695f91 --- /dev/null +++ b/src/services/usageServiceTier.ts @@ -0,0 +1,11 @@ +/** Keep the requested and reported tiers separate: a badge is not a billing confirmation. */ +export function usageServiceTierDetails(requested?: string | null, response?: string | null) { + const requestedTier = requested?.trim() || ''; + const responseTier = response?.trim() || ''; + const normalized = requestedTier.toLowerCase(); + const badge = normalized === 'ultrafast' + ? 'ultrafast' + : normalized === 'priority' || normalized === 'fast' ? 'fast' : null; + + return { requested: requestedTier, response: responseTier, badge }; +} diff --git a/src/styles/usage-subpages.css b/src/styles/usage-subpages.css index 78720422a..7702fc9ac 100644 --- a/src/styles/usage-subpages.css +++ b/src/styles/usage-subpages.css @@ -286,7 +286,8 @@ body.table-col-resizing .usage-records-page .usage-request-log .usage-events-tab } .usage-records-page .usage-request-log .usage-model-badges > .usage-model-effort, -.usage-records-page .usage-request-log .usage-model-badges > .usage-model-fast { +.usage-records-page .usage-request-log .usage-model-badges > .usage-model-fast, +.usage-records-page .usage-request-log .usage-model-badges > .usage-model-ultrafast { margin: 0; font-size: 11px; font-weight: 650; diff --git a/tests/fixtures/usage-response-model.tsx b/tests/fixtures/usage-response-model.tsx index 2ce87532b..4c4dcca66 100644 --- a/tests/fixtures/usage-response-model.tsx +++ b/tests/fixtures/usage-response-model.tsx @@ -54,7 +54,7 @@ const record = (id: string, model: string, alias: string, response_model?: strin }); // Keep these cases in a stable order; the browser test uses it to inspect each model cell. -const records: UsageRecord[] = [ +const baseRecords: UsageRecord[] = [ { ...record('differing', 'gpt-6-astra', 'astra', 'gpt-5.6-luna'), service_tier: 'priority' }, { ...record('matching', 'gpt-6-sol', 'same-alias', 'gpt-6-sol'), service_tier: 'fast' }, { ...record('snapshot-prefix', 'openai/gpt-4o-latest', 'gpt4o', 'gpt-4o-2024-08-06'), service_tier: 'default' }, @@ -66,6 +66,18 @@ const records: UsageRecord[] = [ record('formula', 'formula-model', 'formula-alias', '=SUM(A1)'), ]; +const tierRecords: UsageRecord[] = [ + { ...record('priority', 'gpt-6.1-sol', ''), service_tier: 'priority', response_service_tier: 'default' }, + { ...record('fast', 'gpt-6.1-sol', ''), service_tier: 'fast', response_service_tier: 'fast' }, + { ...record('ultrafast-default', 'gpt-6.1-sol', ''), reasoning_effort: 'xhigh', service_tier: 'ultrafast', response_service_tier: 'default' }, + { ...record('ultrafast-confirmed', 'gpt-6-astra', ''), service_tier: 'ultrafast', response_service_tier: 'ultrafast' }, + { ...record('normalized-ultrafast', 'gpt-6.1-sol', ''), service_tier: ' UltraFast ', response_service_tier: ' DEFAULT ' }, + { ...record('response-only', 'gpt-6.1-sol', ''), service_tier: 'default', response_service_tier: 'ultrafast' }, + { ...record('unknown-tier', 'gpt-6.1-sol', ''), service_tier: 'future-tier' }, + record('legacy-tier', 'gpt-6.1-sol', ''), +]; +const records = params.get('scenario') === 'service-tiers' ? tierRecords : baseRecords; + mockIPC(async () => null); createRoot(document.getElementById('root')!).render( diff --git a/tests/i18n.test.ts b/tests/i18n.test.ts index d0978a9af..24eb3fb98 100644 --- a/tests/i18n.test.ts +++ b/tests/i18n.test.ts @@ -5,6 +5,18 @@ import { jaOverrides } from '../src/i18n/ja'; import { pluginConfigText } from '../src/i18n/pluginConfig'; describe('i18n', () => { + it('localizes requested and reported service tiers without changing the technical badge', () => { + for (const locale of supportedLocales) { + expect(translate(locale, 'usage.speedMode.ultrafast')).toBe('ultrafast'); + expect(translate(locale, 'usage.speedMode.requested')).not.toBe(''); + expect(translate(locale, 'usage.speedMode.response')).not.toBe(''); + expect(translate(locale, 'usage.speedMode.requestHint')).not.toBe(''); + } + expect(translate('zh-TW', 'usage.speedMode.requested')).toBe('請求服務檔位'); + expect(translate('zh-TW', 'usage.speedMode.response')).toBe('響應服務檔位'); + expect(translate('zh-TW', 'usage.speedMode.unrecorded')).toBe('未記錄'); + }); + it('localizes the plugin discard confirmation in every language', () => { for (const [locale, cancel, confirm] of [ ['zh-CN', '继续编辑', '放弃修改'], ['zh-TW', '繼續編輯', '放棄修改'], diff --git a/tests/usage-service-tier-ui.cjs b/tests/usage-service-tier-ui.cjs new file mode 100644 index 000000000..e834e213b --- /dev/null +++ b/tests/usage-service-tier-ui.cjs @@ -0,0 +1,72 @@ +// Run Vite first, then node tests/usage-service-tier-ui.cjs. +const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const base = process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:1421'; +const locales = { + 'zh-CN': { requested: '请求服务档位', response: '响应服务档位', missing: '未记录' }, + 'zh-TW': { requested: '請求服務檔位', response: '響應服務檔位', missing: '未記錄' }, + en: { requested: 'Requested service tier', response: 'Response service tier', missing: 'Not recorded' }, + ja: { requested: 'リクエストのサービス階層', response: '応答のサービス階層', missing: '記録なし' }, +}; + +(async () => { + const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--no-proxy-server'] }); + try { + const page = await browser.newPage({ viewport: { width: 1480, height: 1000 } }); + const errors = []; + page.on('pageerror', error => errors.push(String(error))); + await page.route('**/*', route => route.request().url().startsWith(`${base}/`) ? route.continue() : route.abort()); + const open = async (locale, theme) => { + await page.goto(`${base}/tests/fixtures/usage-response-model.html?scenario=service-tiers&locale=${locale}&theme=${theme}`, { waitUntil: 'domcontentloaded' }); + await page.locator('.usage-td-model').first().waitFor(); + await page.evaluate(() => document.fonts.ready); + }; + + for (const [locale, labels] of Object.entries(locales)) { + await open(locale, 'light'); + const cells = page.locator('.usage-td-model'); + assert.equal(await cells.count(), 8); + assert.deepEqual(await page.locator('.usage-model-fast').allTextContents(), ['fast', 'fast']); + assert.deepEqual(await page.locator('.usage-model-ultrafast').allTextContents(), ['ultrafast', 'ultrafast', 'ultrafast']); + const title = await cells.nth(2).locator('.usage-model-ultrafast').getAttribute('title'); + assert.ok(title.includes(`${labels.requested}: ultrafast`), `${locale}: requested tier is explicit`); + assert.ok(title.includes(`${labels.response}: default`), `${locale}: default response is retained`); + assert.ok((await cells.nth(3).getAttribute('title')).includes(`${labels.response}: ultrafast`)); + assert.ok((await cells.nth(4).getAttribute('title')).includes(`${labels.requested}: UltraFast`)); + for (const index of [5, 6, 7]) { + assert.equal(await cells.nth(index).locator('.usage-model-fast, .usage-model-ultrafast').count(), 0); + } + assert.ok((await cells.nth(5).getAttribute('title')).includes(`${labels.response}: ultrafast`)); + assert.ok((await cells.nth(6).getAttribute('title')).includes(`${labels.requested}: future-tier`)); + assert.ok((await cells.nth(6).getAttribute('title')).includes(`${labels.response}: ${labels.missing}`)); + assert.ok((await cells.nth(7).getAttribute('title')).includes(`${labels.requested}: ${labels.missing}`)); + } + + for (const theme of ['light', 'dark']) { + await open('zh-CN', theme); + const cell = page.locator('.usage-td-model').nth(2); + const positions = await cell.locator('.usage-model-badges > small').evaluateAll(elements => elements.map(element => element.getBoundingClientRect().top)); + assert.ok(Math.abs(positions[0] - positions[1]) < 2, `${theme}: Ultrafast stays beside xhigh`); + const colors = await page.locator('.usage-model-fast, .usage-model-ultrafast').evaluateAll(elements => elements.map(element => getComputedStyle(element).color)); + assert.notEqual(colors[0], colors[2], `${theme}: Ultrafast is distinct from Fast`); + const clipped = await page.locator('.usage-model-ultrafast').evaluateAll(elements => elements.some(element => element.scrollWidth > element.clientWidth)); + assert.equal(clipped, false, `${theme}: labels are not clipped`); + if (process.env.USAGE_SERVICE_TIER_SCREENSHOT_DIR) { + fs.mkdirSync(process.env.USAGE_SERVICE_TIER_SCREENSHOT_DIR, { recursive: true }); + await page.screenshot({ path: path.join(process.env.USAGE_SERVICE_TIER_SCREENSHOT_DIR, `service-tiers-${theme}.png`), fullPage: true }); + } + } + + await page.setViewportSize({ width: 390, height: 850 }); + await open('zh-CN', 'light'); + const bounds = await page.evaluate(() => ({ width: document.documentElement.clientWidth, scrollWidth: document.documentElement.scrollWidth })); + assert.ok(bounds.scrollWidth <= bounds.width + 1, 'Narrow layout keeps scrolling within the table'); + assert.deepEqual(errors, []); + console.log('PASS: service-tier badges, requested/reported distinction, four locales, both themes, and narrow layout.'); + } finally { + await browser.close(); + } +})().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tests/usageServiceTier.test.ts b/tests/usageServiceTier.test.ts new file mode 100644 index 000000000..937adf76f --- /dev/null +++ b/tests/usageServiceTier.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from 'bun:test'; +import { usageServiceTierDetails } from '../src/services/usageServiceTier'; + +test('keeps requested Ultrafast visible when the upstream reports default', () => { + expect(usageServiceTierDetails('ultrafast', 'default')).toEqual({ + requested: 'ultrafast', response: 'default', badge: 'ultrafast', + }); + expect(usageServiceTierDetails('ultrafast', 'ultrafast').badge).toBe('ultrafast'); +}); + +test('normalizes badge matching without replacing reported tier values', () => { + expect(usageServiceTierDetails(' UltraFast ', ' DEFAULT ')).toEqual({ + requested: 'UltraFast', response: 'DEFAULT', badge: 'ultrafast', + }); + for (const tier of ['priority', 'fast', ' PRIORITY ', ' FAST ']) { + expect(usageServiceTierDetails(tier, 'default').badge).toBe('fast'); + } +}); + +test('does not invent a requested tier from a response or an unknown value', () => { + for (const tier of [undefined, null, '', ' ', 'auto', 'default', 'future-tier']) { + expect(usageServiceTierDetails(tier, 'ultrafast').badge).toBeNull(); + } + expect(usageServiceTierDetails('future-tier')).toEqual({ + requested: 'future-tier', response: '', badge: null, + }); + expect(usageServiceTierDetails()).toEqual({ requested: '', response: '', badge: null }); +});