File size: 6,389 Bytes
dcedb25 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | // @vitest-environment jsdom
//
// The prop-shape tests next door prove `categoryAxisProps()` returns the right
// numbers. They cannot prove those numbers change what recharts draws — the
// original #890 fix would have passed them even if the props had been spread
// onto the wrong element. So this file mounts a real recharts <BarChart> and
// counts the <text> ticks that come out, both with and without our props.
//
// Two things make that work under jsdom:
//
// 1. recharts needs a laid-out size, and jsdom lays nothing out. We pass
// explicit width/height to the chart instead of using ResponsiveContainer.
// 2. recharts decides which ticks to drop by *measuring* each label: it puts
// the text in a hidden span and reads getBoundingClientRect(). jsdom
// returns zeroes there, so every label would look infinitely thin and
// nothing would ever be dropped — the "before" case would silently pass.
// We stub the rect with a simple monospace-ish metric so the drop logic
// has real widths to work with.
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { act } from 'react'
import type { ReactElement } from 'react'
import { createRoot } from 'react-dom/client'
import { Bar, BarChart, XAxis, YAxis } from 'recharts'
import { categoryAxisProps, verticalCategoryAxisProps } from './chart-axis'
/** Stand-in glyph width, in px, for the measurement stub below. */
const MEASURED_CHAR_WIDTH = 6
const realGetBoundingClientRect = Element.prototype.getBoundingClientRect
beforeAll(() => {
;(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true
Element.prototype.getBoundingClientRect = function measured(this: Element): DOMRect {
const text = this.textContent ?? ''
const fontSize = Number.parseFloat((this as HTMLElement).style?.fontSize ?? '') || 11
const width = text.length * MEASURED_CHAR_WIDTH
const height = fontSize * 1.2
return {
width,
height,
top: 0,
left: 0,
right: width,
bottom: height,
x: 0,
y: 0,
toJSON: () => ({}),
} as DOMRect
}
})
afterAll(() => {
Element.prototype.getBoundingClientRect = realGetBoundingClientRect
})
const CHART_WIDTH = 400
const CHART_HEIGHT = 240
const tickStyle = { fontSize: 11 } as const
// 16 providers, each name long enough that recharts has to make a choice, and
// each still distinguishable after the steep tier truncates it to 12 chars.
const CATEGORY_COUNT = 16
const platforms = Array.from({ length: CATEGORY_COUNT }, (_, i) => ({
platform: `p${String(i).padStart(2, '0')}-provider-name`,
requests: i + 1,
}))
const errorCategories = Array.from({ length: CATEGORY_COUNT }, (_, i) => ({
category: `e${String(i).padStart(2, '0')}-error-kind`,
count: i + 1,
}))
function renderTicks(node: ReactElement, axisSelector: string): string[] {
const host = document.createElement('div')
document.body.appendChild(host)
const root = createRoot(host)
act(() => {
root.render(node)
})
const ticks = [...host.querySelectorAll(`${axisSelector} .recharts-cartesian-axis-tick-value`)]
.map((el) => el.textContent ?? '')
act(() => {
root.unmount()
})
host.remove()
return ticks
}
describe('category XAxis rendering (#890)', () => {
it('drops most provider labels with recharts defaults', () => {
const ticks = renderTicks(
<BarChart width={CHART_WIDTH} height={CHART_HEIGHT} data={platforms}>
<XAxis dataKey="platform" tick={tickStyle} />
<Bar dataKey="requests" />
</BarChart>,
'.recharts-xAxis-tick-labels',
)
// This is the bug: 'preserveEnd' thins the axis right out. (The guard on
// the low end keeps this from passing vacuously if the chart ever stops
// rendering at all under jsdom.)
expect(ticks.length).toBeGreaterThan(0)
expect(ticks.length).toBeLessThan(CATEGORY_COUNT)
expect(ticks).not.toContain(platforms[0].platform)
})
it('renders every provider label with categoryAxisProps()', () => {
const props = categoryAxisProps(CATEGORY_COUNT)
const ticks = renderTicks(
<BarChart width={CHART_WIDTH} height={CHART_HEIGHT} data={platforms}>
<XAxis dataKey="platform" tick={tickStyle} {...props} />
<Bar dataKey="requests" />
</BarChart>,
'.recharts-xAxis-tick-labels',
)
expect(ticks).toHaveLength(CATEGORY_COUNT)
// Every category is present, and each is still telling them apart.
expect(ticks).toEqual(platforms.map((p) => props.tickFormatter(p.platform)))
expect(new Set(ticks).size).toBe(CATEGORY_COUNT)
})
it('still renders every label when the categories are few', () => {
const few = platforms.slice(0, 6)
const props = categoryAxisProps(few.length)
const ticks = renderTicks(
<BarChart width={CHART_WIDTH} height={CHART_HEIGHT} data={few}>
<XAxis dataKey="platform" tick={tickStyle} {...props} />
<Bar dataKey="requests" />
</BarChart>,
'.recharts-xAxis-tick-labels',
)
expect(ticks).toHaveLength(few.length)
})
})
describe('category YAxis rendering, layout="vertical" (#890)', () => {
it('drops error-category labels with recharts defaults', () => {
const ticks = renderTicks(
<BarChart width={CHART_WIDTH} height={CHART_HEIGHT} data={errorCategories} layout="vertical">
<XAxis type="number" tick={tickStyle} />
<YAxis type="category" dataKey="category" tick={tickStyle} width={128} />
<Bar dataKey="count" />
</BarChart>,
'.recharts-yAxis-tick-labels',
)
expect(ticks.length).toBeGreaterThan(0)
expect(ticks.length).toBeLessThan(CATEGORY_COUNT)
expect(ticks).not.toContain(errorCategories[0].category)
})
it('renders every error-category label with verticalCategoryAxisProps()', () => {
const props = verticalCategoryAxisProps()
const ticks = renderTicks(
<BarChart width={CHART_WIDTH} height={CHART_HEIGHT} data={errorCategories} layout="vertical">
<XAxis type="number" tick={tickStyle} />
<YAxis type="category" dataKey="category" tick={tickStyle} {...props} />
<Bar dataKey="count" />
</BarChart>,
'.recharts-yAxis-tick-labels',
)
expect(ticks).toHaveLength(CATEGORY_COUNT)
expect(ticks).toEqual(errorCategories.map((e) => props.tickFormatter(e.category)))
})
})
|