import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { Provider } from '../provider/widget-provider'
import {
clearAllWidgetStores,
useTransformEnabled,
useWidgetId,
} from '../stores'
import { Category } from './category'
import { RelativeData } from '../actions/relative-data/relative-data'
import type { CategoryWidgetData } from './types'
beforeEach(() => clearAllWidgetStores())
afterEach(() => clearAllWidgetStores())
// Replicates `CategoryActionWiring` from the storybook composer
// (apps/storybook/stories/modules/widgets/category/category.tsx). The
// composer-level wiring isn't testable in `apps/storybook` (no vitest
// infra), so we re-implement just the relative-data branch here and
// assert the bar-width math end-to-end through Provider → action →
// bridge → UI.
function Wired({ maxOverride }: { maxOverride?: number }) {
const id = useWidgetId()
const relativeDataActive = useTransformEnabled(id, 'relative-data')
const effectiveMaxOverride = relativeDataActive ? 100 : maxOverride
return
}
// Single-series dataset where the max value is 1000 so percent values
// map to round numbers we can match exactly against bar widths.
const DATA: CategoryWidgetData = [
[
{ name: 'A', value: 1000 },
{ name: 'B', value: 500 },
{ name: 'C', value: 250 },
],
]
function getBarFills(): HTMLElement[] {
return Array.from(
document.querySelectorAll('[data-bar-fill="true"]'),
)
}
describe('Category + RelativeData composer wiring', () => {
it('default (RelativeData off) → bars scale to data max', () => {
render(
,
)
const fills = getBarFills()
expect(fills).toHaveLength(3)
// 1000/1000, 500/1000, 250/1000
expect(fills[0]!.style.width).toBe('100%')
expect(fills[1]!.style.width).toBe('50%')
expect(fills[2]!.style.width).toBe('25%')
})
it('clicking the % action flips bars to a 100-denominator', () => {
render(
,
)
// Toggle ON. RelativeData rewrites data values into percentages
// (A=1000/1750 ≈ 57.14, B=500/1750 ≈ 28.57, C=250/1750 ≈ 14.29) AND
// the composer wiring forces maxOverride=100 so the bar widths
// match those percent values directly.
fireEvent.click(screen.getByRole('button', { name: /relative|percent/i }))
const fills = getBarFills()
expect(fills).toHaveLength(3)
// Allow a small rounding tolerance — the percent transform produces
// floats, the bar math multiplies by 100 again.
const a = parseFloat(fills[0]!.style.width)
const b = parseFloat(fills[1]!.style.width)
const c = parseFloat(fills[2]!.style.width)
expect(a).toBeGreaterThan(55)
expect(a).toBeLessThan(60)
expect(b).toBeGreaterThan(27)
expect(b).toBeLessThan(30)
expect(c).toBeGreaterThan(13)
expect(c).toBeLessThan(16)
// The largest bar must be NOT 100% — that's the whole point of the
// fix. Without the wiring, A would have been 100% (largest of the
// percent series).
expect(a).toBeLessThan(100)
})
it('toggling off again restores data-max scaling', () => {
render(
,
)
const button = screen.getByRole('button', { name: /relative|percent/i })
fireEvent.click(button) // on
fireEvent.click(button) // off
const fills = getBarFills()
expect(fills[0]!.style.width).toBe('100%')
expect(fills[1]!.style.width).toBe('50%')
expect(fills[2]!.style.width).toBe('25%')
})
})