Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
f886fae
[BountyCoder] feat(ui): add optional subLabel caption to Scorecard
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
f62891e
[BountyCoder] fix(gooddata-widget): reduce AI Credits fetch window fr…
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
9092aa6
[BountyCoder] fix(gooddata-widget): show 3 decimal places on AI Credi…
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
e6d7a79
[BountyCoder] fix(gooddata-widget): relabel flow-rate scorecard as a …
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
7e3282c
[BountyCoder] fix(gooddata-widget): relabel total scorecard and show …
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
000c864
[BountyCoder] fix(gooddata-widget): show subscription/streaming sub-l…
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
2b4cc29
[BountyCoder] test(gooddata-widget): verify G$ Volume dual-axis legib…
goodbounties-nanoclaw-agent[bot] Aug 31, 2026
c2f7184
[BountyCoder] fix(ai-credits-widget): read latest complete daily reco…
goodbounties-nanoclaw-agent[bot] Sep 1, 2026
ae2b426
[BountyCoder] fix(ai-credits-widget): stop demo flow-rate precision l…
goodbounties-nanoclaw-agent[bot] Sep 1, 2026
1b1e078
[BountyCoder] test(ai-credits-widget): fix RealisticVolume fixture su…
goodbounties-nanoclaw-agent[bot] Sep 1, 2026
5e68495
[BountyCoder] test(ai-credits-widget): update baseline screenshots fo…
goodbounties-nanoclaw-agent[bot] Sep 1, 2026
0f3f5a7
[BountyCoder] fix(ai-credits-widget): use temporary hardcoded G$/USD …
goodbounties-nanoclaw-agent[bot] Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AiCreditsDashboardDemoStory,
AiCreditsDashboardLiveUnavailableStory,
AiCreditsDashboardEmptyStory,
AiCreditsDashboardRealisticVolumeStory,
} from '../helpers/goodDataWidgetStories'

const meta: Meta<typeof AiCreditsDashboard> = {
Expand Down Expand Up @@ -33,6 +34,11 @@ export const Demo: Story = {
render: () => <AiCreditsDashboardDemoStory />,
}

/** 30-day fixture shaped after real production magnitudes (sparse large deposit spikes vs a narrow, independently-moving streamed band) — exercises the G$ Volume chart's secondaryYAxis beyond the smooth demo ramp. */
export const RealisticVolume: Story = {
render: () => <AiCreditsDashboardRealisticVolumeStory />,
}

export const LiveUnavailable: Story = {
render: () => <AiCreditsDashboardLiveUnavailableStory />,
}
Expand Down
90 changes: 90 additions & 0 deletions examples/storybook/src/stories/helpers/goodDataWidgetStories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,88 @@ const FIXED_ANALYTICS_RESPONSE: AnalyticsResponse = {
},
}

/**
* 30-day fixture shaped after real production data pulled from the live
* AntSeed Worker (values rounded/synthesized, not the literal live figures):
* `gdOneTimeDepositsWei` is sparse with occasional large spikes (0 most days,
* then a jump into the thousands-to-tens-of-thousands range), while
* `gdStreamedWei` moves independently in a much narrower band two orders of
* magnitude smaller. The original `Live` fixture's smooth, proportionally-
* scaled 3-day ramp doesn't exercise this — it's what motivated fix #4
* (verifying the G$ Volume chart's secondaryYAxis stays legible at this
* magnitude disparity, not just in the demo ramp).
*/
function buildRealisticVolumeDaily(): AnalyticsResponse['daily'] {
const gdWei = (gd: number) => String(BigInt(Math.round(gd * 10000)) * 10n ** 14n)
const rows: Array<{ date: string; depositsGd: number; streamedGd: number; creditUsersGd: number }> = [
{ date: '2026-08-02', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-03', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-04', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-05', depositsGd: 8800, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-06', depositsGd: 0, streamedGd: 590, creditUsersGd: 1 },
{ date: '2026-08-07', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-08', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-09', depositsGd: 0, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-10', depositsGd: 10000, streamedGd: 590, creditUsersGd: 0 },
{ date: '2026-08-11', depositsGd: 0, streamedGd: 590, creditUsersGd: 1 },
{ date: '2026-08-12', depositsGd: 31500, streamedGd: 672, creditUsersGd: 1 },
{ date: '2026-08-13', depositsGd: 8610, streamedGd: 875, creditUsersGd: 2 },
{ date: '2026-08-14', depositsGd: 15000, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-15', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-16', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-17', depositsGd: 7828, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-18', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-19', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-20', depositsGd: 49800, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-21', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-22', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-23', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-24', depositsGd: 0, streamedGd: 880, creditUsersGd: 2 },
{ date: '2026-08-25', depositsGd: 0, streamedGd: 587, creditUsersGd: 2 },
{ date: '2026-08-26', depositsGd: 0, streamedGd: 587, creditUsersGd: 2 },
{ date: '2026-08-27', depositsGd: 10000, streamedGd: 774, creditUsersGd: 2 },
{ date: '2026-08-28', depositsGd: 8002, streamedGd: 1321, creditUsersGd: 3 },
{ date: '2026-08-29', depositsGd: 0, streamedGd: 917, creditUsersGd: 3 },
{ date: '2026-08-30', depositsGd: 0, streamedGd: 917, creditUsersGd: 3 },
{ date: '2026-08-31', depositsGd: 10000, streamedGd: 1134, creditUsersGd: 3 },
]

return rows.map((row, index) => ({
date: row.date,
gdOneTimeDepositsWei: gdWei(row.depositsGd),
gdStreamedWei: gdWei(row.streamedGd),
gdTotalFlowRateWeiPerSecond: gdWei(row.streamedGd / 86400),
aiCreditsUsedWei: String((row.creditUsersGd > 0 ? row.creditUsersGd : 0) * 500000),
uniqueGdBuyers: row.depositsGd > 0 ? 1 : 0,
uniqueCreditUsers: row.creditUsersGd,
updatedAt: `${row.date}T23:59:59.999Z`,
missing: false,
}))
}

const REALISTIC_VOLUME_DAILY = buildRealisticVolumeDaily()
const REALISTIC_VOLUME_RESPONSE: AnalyticsResponse = {
days: REALISTIC_VOLUME_DAILY.length,
daily: REALISTIC_VOLUME_DAILY,
global: {
gdOneTimeDepositsWei: String(
REALISTIC_VOLUME_DAILY.reduce((sum, day) => sum + BigInt(day.gdOneTimeDepositsWei), 0n),
),
gdStreamedWei: String(
REALISTIC_VOLUME_DAILY.reduce((sum, day) => sum + BigInt(day.gdStreamedWei), 0n),
),
aiCreditsUsedWei: String(
REALISTIC_VOLUME_DAILY.reduce((sum, day) => sum + BigInt(day.aiCreditsUsedWei), 0n),
),
gdTotalFlowRateWeiPerSecond: REALISTIC_VOLUME_DAILY[REALISTIC_VOLUME_DAILY.length - 1].gdTotalFlowRateWeiPerSecond,
updatedAt: `${REALISTIC_VOLUME_DAILY[REALISTIC_VOLUME_DAILY.length - 1].date}T23:59:59.999Z`,
Comment on lines +127 to +138
},
lastRun: {
currentDate: REALISTIC_VOLUME_DAILY[REALISTIC_VOLUME_DAILY.length - 1].date,
updatedAt: `${REALISTIC_VOLUME_DAILY[REALISTIC_VOLUME_DAILY.length - 1].date}T23:59:59.999Z`,
},
}

/** Wraps the dashboard in the same bare TamaguiProvider the thin-host app uses (no GoodWidgetProvider — this widget has no wallet/connect concerns). */
function GoodDataWidgetStoryShell({ dataTestId, children }: { dataTestId: string; children: React.ReactNode }) {
return (
Expand Down Expand Up @@ -94,6 +176,14 @@ export function AiCreditsDashboardDemoStory() {
)
}

export function AiCreditsDashboardRealisticVolumeStory() {
return (
<GoodDataWidgetStoryShell dataTestId="GoodDataWidget-realistic-volume">
<AiCreditsDashboard mockState={{ kind: 'live', response: REALISTIC_VOLUME_RESPONSE }} />
</GoodDataWidgetStoryShell>
)
}

export function AiCreditsDashboardLiveUnavailableStory() {
return (
<GoodDataWidgetStoryShell dataTestId="GoodDataWidget-live-unavailable">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
YStack,
} from '@goodwidget/ui'
import type { DataTableColumnDef } from '@goodwidget/ui'
import { flowRateToDaily, weiToGd, weiToUsd } from './analyticsConversions'
import { flowRateToMonthly, weiToGd, weiToUsd } from './analyticsConversions'
import type { DailyAnalyticsRecord } from './connector'
import { useAiCreditsDashboardData, type AnalyticsDataSource } from './useAiCreditsDashboardData'
import {
Expand Down Expand Up @@ -78,7 +78,10 @@ const TABLE_COLUMNS: Array<DataTableColumnDef<TableRow>> = [
key: 'aiCreditsUsd',
label: 'AI Credits (USD)',
type: 'currency',
formatter: (value) => `$${(value as number).toFixed(2)}`,
// 3 decimal places, not 2 — per-day AI Credits USD amounts are small enough
// that 2 decimals rounded most rows to $0.00, per CEO feedback after the
// PR #179 production deploy.
formatter: (value) => `$${(value as number).toFixed(3)}`,
sortable: true,
description: 'AI Credits consumed this day, converted to USD.',
},
Expand All @@ -103,6 +106,18 @@ function filterCompleteRecords(daily: DailyAnalyticsRecord[]): DailyAnalyticsRec
return daily.filter((record) => !record.missing)
}

/**
* TEMP_GD_USD_RATE = 0.00013 // hardcoded until Worker exposes a live rate — Thales following up with Mike/Hadar
*
* Data-source gap: the Worker's schema (`/v1/analytics`, `/v1/analytics/refresh`) exposes no
* G$/USD exchange rate anywhere, so the USD equivalent of the G$ *deposited/bought* total
* (the "Total Credits Bought in G$" scorecard's value) can't be computed from any data this
* widget receives. `aiCreditsUsedWei` ("AI credits consumed") is a different metric and must
* not be substituted here even though it's denominated in USD too. This rate is a stand-in
* pending a real rate field/endpoint from the data team — swap it out once that lands.
*/
const TEMP_GD_USD_RATE = 0.00013

function toTableRow(record: DailyAnalyticsRecord): TableRow {
const gdDeposited = weiToGd(record.gdOneTimeDepositsWei)
const gdStreamed = weiToGd(record.gdStreamedWei)
Expand Down Expand Up @@ -239,12 +254,23 @@ function AiCreditsDashboardView({
const dailyRecords = data ? filterCompleteRecords(data.daily) : []
const hasDailyData = dailyRecords.length > 0

// Scorecards derive straight from `global`, matching the reference dashboard's renderHero().
const totalGdSpent = data
? weiToGd(data.global.gdOneTimeDepositsWei) + weiToGd(data.global.gdStreamedWei)
: 0
// Scorecards derive straight from `global`, matching the reference dashboard's renderHero() —
// except the flow rate below, which can't safely use `global` as-is (see comment there).
const gdStreamedTotal = data ? weiToGd(data.global.gdStreamedWei) : 0
const totalGdSpent = data ? weiToGd(data.global.gdOneTimeDepositsWei) + gdStreamedTotal : 0
// Temporary USD estimate for the deposited/bought G$ total — see TEMP_GD_USD_RATE above.
const totalGdSpentUsd = totalGdSpent * TEMP_GD_USD_RATE
const aiCreditsUsedUsd = data ? weiToUsd(data.global.aiCreditsUsedWei) : 0
const gdFlowRatePerDay = data ? flowRateToDaily(data.global.gdTotalFlowRateWeiPerSecond) : 0
// `data.global.gdTotalFlowRateWeiPerSecond` mirrors whatever the Worker's most recent daily
// record is, even when that record is today's still-accumulating, `missing: true` snapshot —
// right after a day boundary this reads as "0" until the new day's first streaming event
// lands, understating the real current rate. `dailyRecords` above already filters out
// incomplete records for the chart/table, so reuse its latest entry here too instead of
// reading `global` directly.
const latestCompleteDailyRecord = dailyRecords[dailyRecords.length - 1]
const gdFlowRatePerMonth = latestCompleteDailyRecord
? flowRateToMonthly(latestCompleteDailyRecord.gdTotalFlowRateWeiPerSecond)
: 0

const volumeChartData = dailyRecords.flatMap((record) => [
{ x: record.date, y: weiToGd(record.gdOneTimeDepositsWei), series: 'deposits' },
Expand Down Expand Up @@ -278,9 +304,11 @@ function AiCreditsDashboardView({
<Scorecard
variant="card"
value={totalGdSpent}
label="Total G$ Spent"
label="Total Credits Bought in G$"
prefix="G$"
suffix={`(≈ ${formatMetricValue(totalGdSpentUsd, 'decimal', 2)} USD)`}
format="decimal"
subLabel={`out of ${formatMetricValue(gdStreamedTotal, 'decimal', 2)} G$ in subscription (streaming)`}
testID="scorecard-total-gd"
/>
<Scorecard
Expand All @@ -293,10 +321,9 @@ function AiCreditsDashboardView({
/>
<Scorecard
variant="card"
value={gdFlowRatePerDay}
label="G$ Flow Rate"
value={gdFlowRatePerMonth}
label="Total Monthly Subscriptions"
prefix="G$"
suffix="/day"
format="decimal"
testID="scorecard-flow-rate"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const GD_DECIMALS = 18n
const USD_DECIMALS = 6n
/** How many of the source's decimal digits survive into the final Number — both conversions below keep 4. */
const OUTPUT_PRECISION_DECIMALS = 4n
const SECONDS_PER_DAY = 86400n
/** Average seconds per month (365.2425-day Gregorian year / 12), not a fixed 30-day month — plain multiplication, not exponentiation. */
const SECONDS_PER_MONTH = 2629746n

/**
* Reduces an 18-decimal wei BigInt down to a 4-decimal-precision BigInt
Expand Down Expand Up @@ -47,15 +48,15 @@ export function weiToUsd(weiStr: string): number {
}

/**
* Converts a wei/second flow rate (18 decimals) to a G$/day figure. Scales
* to a daily amount first while still in BigInt space (multiplying, not
* Converts a wei/second flow rate (18 decimals) to a G$/month figure. Scales
* to a monthly amount first while still in BigInt space (multiplying, not
* dividing, so no precision is lost), then reuses weiToGd's reduction to
* safely cross into Number range.
*/
export function flowRateToDaily(weiPerSecStr: string): number {
export function flowRateToMonthly(weiPerSecStr: string): number {
if (!weiPerSecStr || weiPerSecStr === '0') return 0
const weiPerSecond = BigInt(weiPerSecStr)
const dailyWei = weiPerSecond * SECONDS_PER_DAY
const reduced = dailyWei / 10n ** (GD_DECIMALS - OUTPUT_PRECISION_DECIMALS)
const monthlyWei = weiPerSecond * SECONDS_PER_MONTH
const reduced = monthlyWei / 10n ** (GD_DECIMALS - OUTPUT_PRECISION_DECIMALS)
return Number(reduced) / 10000
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import type { DataConnectorFactory } from '../../connectors/types'

const DEFAULT_WORKER_URL = 'https://gooddollar-antseed-integration.goodworker.workers.dev'
/** Number of days of history requested from the analytics endpoint, matching the reference dashboard. */
const ANALYTICS_DAYS_REQUESTED = 365
/** Number of days of history requested from the analytics endpoint. Reduced from the reference dashboard's 365 — a full year of daily points was too dense to read and slowed the fetch, per CEO feedback after the PR #179 production deploy. */
const ANALYTICS_DAYS_REQUESTED = 30

/** One row of daily analytics. Wei amounts stay as strings end-to-end (see analyticsConversions.ts) so no precision is lost before the caller explicitly converts them. */
export interface DailyAnalyticsRecord {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ function buildDailyRecord(daysAgo: number): DailyAnalyticsRecord {
// `|| 1` mirrors the reference: a day with near-zero ramp should still show at least one active wallet, not zero.
const uniqueGdBuyers = rampedRandomInt(2, 10, ramp) || 1
const uniqueCreditUsers = rampedRandomInt(1, 6, ramp) || 1
const flowRatePerSecondGd = Math.floor(streamedGd / SECONDS_PER_DAY)
// Dividing streamedGd (a small integer, 200-800) by SECONDS_PER_DAY before scaling to wei
// always floors to exactly 0 — do the division in wei space, after scaling up, so the
// fractional per-second rate survives instead of being truncated away first.
const flowRatePerSecondWei = (BigInt(streamedGd) * GD_DECIMALS) / BigInt(SECONDS_PER_DAY)

return {
date: isoDateDaysAgo(daysAgo),
gdOneTimeDepositsWei: String(BigInt(depositsGd) * GD_DECIMALS),
gdStreamedWei: String(BigInt(streamedGd) * GD_DECIMALS),
gdTotalFlowRateWeiPerSecond: String(BigInt(flowRatePerSecondGd) * GD_DECIMALS),
gdTotalFlowRateWeiPerSecond: String(flowRatePerSecondWei),
aiCreditsUsedWei: String(BigInt(creditsUsd) * USD_DECIMALS),
uniqueGdBuyers,
uniqueCreditUsers,
Expand Down
23 changes: 23 additions & 0 deletions packages/ui/src/components/Scorecard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export interface ScorecardProps {
suffix?: string
format?: MetricFormat
decimals?: number
/** Optional caption line below the value row, e.g. a breakdown of the headline value ("out of X in subscription"). */
subLabel?: string
trend?: ScorecardTrend
trendLabel?: string
variant?: ScorecardVariant
Expand Down Expand Up @@ -154,6 +156,25 @@ const ScorecardAffixText = createComponent(TamaguiText, {
defaultVariants: { size: 'md' },
})

/** Same vertical rhythm as the trend row — the two are mutually exclusive in practice, but neither is aware of the other, so each gets its own top margin off the value row. */
const ScorecardSubLabelText = createComponent(TamaguiText, {
name: 'ScorecardSubLabelText',
fontFamily: '$body',
color: '$placeholderColor',
textAlign: 'center',
marginTop: VALUE_TO_TREND_GAP_PX,

variants: {
size: {
sm: { fontSize: SECONDARY_SIZE_PX.sm },
md: { fontSize: SECONDARY_SIZE_PX.md },
lg: { fontSize: SECONDARY_SIZE_PX.lg },
},
} as const,

defaultVariants: { size: 'md' },
})

const ScorecardTrendRow = createComponent(XStack, {
name: 'ScorecardTrendRow',
alignItems: 'center',
Expand Down Expand Up @@ -235,6 +256,7 @@ function ScorecardContent({
suffix,
format = 'compact',
decimals,
subLabel,
trend,
trendLabel,
size = 'md',
Expand All @@ -253,6 +275,7 @@ function ScorecardContent({
<ScorecardValueText size={size}>{formattedValue}</ScorecardValueText>
{suffix ? <ScorecardAffixText size={size}>{suffix}</ScorecardAffixText> : null}
</ScorecardValueRow>
{subLabel ? <ScorecardSubLabelText size={size}>{subLabel}</ScorecardSubLabelText> : null}
{trend ? (
<ScorecardTrendRow>
<TrendGlyph
Expand Down
38 changes: 36 additions & 2 deletions tests/widgets/gooddata-widget/states.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const STORY_IDS = {
liveUnavailable:
'/iframe.html?id=qa-aicreditsdashboard-runtime-fixtures--live-unavailable&viewMode=story',
empty: '/iframe.html?id=qa-aicreditsdashboard-runtime-fixtures--empty&viewMode=story',
realisticVolume:
'/iframe.html?id=qa-aicreditsdashboard-runtime-fixtures--realistic-volume&viewMode=story',
} as const

async function gotoStory(page: Page, storyUrl: string): Promise<void> {
Expand Down Expand Up @@ -37,9 +39,15 @@ test('AiCreditsDashboard live state — scorecards, charts, and table render fro
await gotoStory(page, STORY_IDS.live)
const root = await expectWidget(page, 'GoodDataWidget-live')
await expect(root.getByTestId('demo-banner')).not.toBeVisible()
await expect(root.getByTestId('scorecard-total-gd')).toBeVisible()
const totalGdScorecard = root.getByTestId('scorecard-total-gd')
await expect(totalGdScorecard).toBeVisible()
await expect(totalGdScorecard).toContainText('Total Credits Bought in G$')
await expect(totalGdScorecard).toContainText('USD')
await expect(totalGdScorecard).toContainText('in subscription (streaming)')
await expect(root.getByTestId('scorecard-ai-credits')).toBeVisible()
await expect(root.getByTestId('scorecard-flow-rate')).toBeVisible()
const flowRateScorecard = root.getByTestId('scorecard-flow-rate')
await expect(flowRateScorecard).toBeVisible()
await expect(flowRateScorecard).toContainText('Total Monthly Subscriptions')
await expect(root.getByTestId('chart-gd-volume')).toBeVisible()
await expect(root.getByTestId('chart-ai-credits')).toBeVisible()
await expect(root.getByTestId('chart-unique-wallets')).toBeVisible()
Expand Down Expand Up @@ -85,3 +93,29 @@ test('AiCreditsDashboard empty state — no daily records', async ({ page }) =>
fullPage: true,
})
})

// Guards fix #4: the G$ Volume chart's secondaryYAxis must keep the streamed
// series legible even at realistic production magnitudes (sparse, much-larger
// one-time deposit spikes vs a narrower, independently-moving streamed band),
// not just the smooth demo/live ramp — both axis labels must render.
test('AiCreditsDashboard live state — G$ Volume dual-axis stays legible at realistic magnitude disparity', async ({
page,
}) => {
await gotoStory(page, STORY_IDS.realisticVolume)
const root = await expectWidget(page, 'GoodDataWidget-realistic-volume')
const volumeChart = root.getByTestId('chart-gd-volume')
await expect(volumeChart).toBeVisible()
// Axis titles render as <text> nodes inside the chart's own <svg>; the legend renders as a
// separate sibling YStack below it and would also match "Deposits"/"Streamed" text, letting
// this assertion pass even if the axis titles themselves were broken. Scoping to the <svg>
// guards specifically what this test is meant to guard: axis-title legibility.
const volumeChartSvg = volumeChart.locator('svg')
// The rotated axis title can wrap onto two lines (each its own text node), so
// match the distinctive word rather than the full label string.
await expect(volumeChartSvg.getByText('Deposits', { exact: false }).first()).toBeVisible()
await expect(volumeChartSvg.getByText('Streamed', { exact: false }).first()).toBeVisible()
await page.screenshot({
path: 'tests/widgets/gooddata-widget/test-results/gdw-06-realistic-volume.png',
fullPage: true,
})
})
Binary file modified tests/widgets/gooddata-widget/test-results/gdw-01-loading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/widgets/gooddata-widget/test-results/gdw-02-live.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/widgets/gooddata-widget/test-results/gdw-03-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/widgets/gooddata-widget/test-results/gdw-05-empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading