From 194399fd3eeac9f8fdf8956d644a3714246fea3f Mon Sep 17 00:00:00 2001 From: junman140 Date: Thu, 24 Sep 2026 10:30:37 +0100 Subject: [PATCH] fix: address oracle and badge issue reports --- src/__tests__/OracleDataWidget.test.tsx | 21 ++++++++++++++++++++ src/__tests__/TriggerConditionBadge.test.tsx | 12 +++++------ src/components/Badge.tsx | 1 - src/components/OracleDataWidget.tsx | 3 ++- src/components/TriggerConditionBadge.tsx | 12 +++++------ src/lib/oracle.ts | 15 +++++++++++++- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/__tests__/OracleDataWidget.test.tsx b/src/__tests__/OracleDataWidget.test.tsx index 4ce263c..f11cd06 100644 --- a/src/__tests__/OracleDataWidget.test.tsx +++ b/src/__tests__/OracleDataWidget.test.tsx @@ -124,6 +124,27 @@ describe('OracleDataWidget', () => { expect(html).toContain('mm'); }); + it('renders oracle metadata as escaped text', () => { + mockUseOracleReading.mockReturnValue({ + reading: { + key: 'rainfall:1,1:2025-01', + value: '324000000', + confidence: 95, + timestamp: 1720000000, + source: '', + dataType: 'weather', + }, + loading: false, + error: null, + refetch: vi.fn(), + }); + + const html = renderToStaticMarkup(); + expect(html).toContain('Source: '); + expect(html).toContain('<script>alert(1)</script>'); + expect(html).not.toContain(''); + }); + it('displays temperature value with °C unit', () => { mockUseOracleReading.mockReturnValue({ reading: { diff --git a/src/__tests__/TriggerConditionBadge.test.tsx b/src/__tests__/TriggerConditionBadge.test.tsx index 8264e66..533c336 100644 --- a/src/__tests__/TriggerConditionBadge.test.tsx +++ b/src/__tests__/TriggerConditionBadge.test.tsx @@ -21,25 +21,25 @@ function makeProduct(overrides: Partial = {}): Product { } describe('TriggerConditionBadge', () => { - it('renders "<" symbol for LessThan comparison', () => { + it('renders plain-language text for LessThan comparison', () => { const html = renderToStaticMarkup( ); - expect(html).toContain('< 30'); + expect(html).toContain('Less than 30 mm'); }); - it('renders ">" symbol for GreaterThan comparison', () => { + it('renders plain-language text for GreaterThan comparison', () => { const html = renderToStaticMarkup( ); - expect(html).toContain('> 50'); + expect(html).toContain('Greater than 50 mm'); }); - it('renders "=" symbol for Equal comparison', () => { + it('renders plain-language text for Equal comparison', () => { const html = renderToStaticMarkup( ); - expect(html).toContain('= 100'); + expect(html).toContain('Equal to 100 mm'); }); it('displays the triggerType', () => { diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index b4a64f5..a21878c 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -43,7 +43,6 @@ export function Badge({ label, variant, className, icon: customIcon }: BadgeProp return ( {icon && } {label} diff --git a/src/components/OracleDataWidget.tsx b/src/components/OracleDataWidget.tsx index 176cfae..7802f91 100644 --- a/src/components/OracleDataWidget.tsx +++ b/src/components/OracleDataWidget.tsx @@ -74,6 +74,7 @@ function OracleDataWidgetComponent({ oracleKey, className }: OracleDataWidgetPro const confLabel = confidenceLabel(reading.confidence); const confColour = confidenceColour(reading.confidence); + const formattedOracleValue = String(formatOracleValue(reading.value, parseOracleKey(oracleKey).dataType)); return (
@@ -81,7 +82,7 @@ function OracleDataWidgetComponent({ oracleKey, className }: OracleDataWidgetPro

{oracleKeyLabel(oracleKey)}

- {formatOracleValue(reading.value, parseOracleKey(oracleKey).dataType)} + {formattedOracleValue}

diff --git a/src/components/TriggerConditionBadge.tsx b/src/components/TriggerConditionBadge.tsx index 290fa74..8ebc557 100644 --- a/src/components/TriggerConditionBadge.tsx +++ b/src/components/TriggerConditionBadge.tsx @@ -5,10 +5,10 @@ interface TriggerConditionBadgeProps { className?: string; } -function comparisonSymbol(c: Comparison): string { - if (c === 'LessThan') return '<'; - if (c === 'GreaterThan') return '>'; - return '='; +function comparisonLabel(c: Comparison): string { + if (c === 'LessThan') return 'Less than'; + if (c === 'GreaterThan') return 'Greater than'; + return 'Equal to'; } const CATEGORY_UNITS: Record = { @@ -20,7 +20,7 @@ const CATEGORY_UNITS: Record = { }; export function TriggerConditionBadge({ product, className }: TriggerConditionBadgeProps) { - const symbol = comparisonSymbol(product.comparison); + const comparison = comparisonLabel(product.comparison); const units = CATEGORY_UNITS[product.category] ?? ''; return ( {product.triggerType} · - {symbol} {product.threshold}{units ? ` ${units}` : ''} + {comparison} {product.threshold}{units ? ` ${units}` : ''} ); } diff --git a/src/lib/oracle.ts b/src/lib/oracle.ts index c3e24fb..b25c0a5 100644 --- a/src/lib/oracle.ts +++ b/src/lib/oracle.ts @@ -101,14 +101,27 @@ export function confidenceIcon(confidence: number): string { return '○'; } +const rainfallKeyCache = new Map(); +const MAX_RAINFALL_KEY_CACHE_SIZE = 100; + export function buildRainfallKey(lat: number, lng: number, year: number, month: number): string { + const cacheKey = `${lat}|${lng}|${year}|${month}`; + const cachedKey = rainfallKeyCache.get(cacheKey); + if (cachedKey) return cachedKey; + const monthStr = String(month).padStart(2, '0'); // Clamp coordinate precision to 4 decimals (~11m). Without a cap, values like // parseFloat('-0.09171234567') produce long keys that can blow past Soroban's // 32-char limit. toFixed(4) keeps keys well within budget (issue #222). const latStr = clampCoord(lat); const lngStr = clampCoord(lng); - return `rainfall:${latStr},${lngStr}:${year}-${monthStr}`; + const key = `rainfall:${latStr},${lngStr}:${year}-${monthStr}`; + if (rainfallKeyCache.size >= MAX_RAINFALL_KEY_CACHE_SIZE) { + const oldestKey = rainfallKeyCache.keys().next().value; + if (oldestKey !== undefined) rainfallKeyCache.delete(oldestKey); + } + rainfallKeyCache.set(cacheKey, key); + return key; } // Format a coordinate to at most 4 decimals without trailing-zero padding,