fix: format table headers with proper capitalization#162
fix: format table headers with proper capitalization#162claw-explorer wants to merge 5 commits intotscircuit:mainfrom
Conversation
Add formatHeader() to the Table component that converts raw object keys into human-readable column headers: - Well-known abbreviations displayed in uppercase (LCSC, MFR, GPIO, etc.) - snake_case keys converted to Title Case (is_basic -> Is Basic) - Compound keys with embedded abbreviations handled (tx_power -> TX Power) - Already-capitalised keys preserved as-is Applied to both row-based tables and key-value object tables. Closes tscircuit#51
| test("formatHeader converts well-known abbreviations to uppercase", () => { | ||
| expect(formatHeader("lcsc")).toBe("LCSC") | ||
| expect(formatHeader("mfr")).toBe("MFR") | ||
| expect(formatHeader("mpn")).toBe("MPN") | ||
| expect(formatHeader("gpio")).toBe("GPIO") | ||
| expect(formatHeader("eeprom")).toBe("EEPROM") | ||
| expect(formatHeader("fpga")).toBe("FPGA") | ||
| expect(formatHeader("ldo")).toBe("LDO") | ||
| expect(formatHeader("ram")).toBe("RAM") | ||
| }) | ||
|
|
||
| test("formatHeader converts snake_case keys to Title Case", () => { | ||
| expect(formatHeader("is_basic")).toBe("Is Basic") | ||
| expect(formatHeader("is_preferred")).toBe("Is Preferred") | ||
| expect(formatHeader("is_smd")).toBe("Is SMD") | ||
| expect(formatHeader("forward_voltage")).toBe("Forward Voltage") | ||
| expect(formatHeader("forward_current")).toBe("Forward Current") | ||
| expect(formatHeader("gate_threshold_voltage")).toBe("Gate Threshold Voltage") | ||
| expect(formatHeader("power_dissipation")).toBe("Power Dissipation") | ||
| expect(formatHeader("on_resistance")).toBe("On Resistance") | ||
| expect(formatHeader("reverse_voltage")).toBe("Reverse Voltage") | ||
| expect(formatHeader("drain_source_voltage")).toBe("Drain Source Voltage") | ||
| expect(formatHeader("tolerance_fraction")).toBe("Tolerance Fraction") | ||
| expect(formatHeader("power_watts")).toBe("Power Watts") | ||
| expect(formatHeader("temp_range")).toBe("Temp Range") | ||
| expect(formatHeader("contact_type")).toBe("Contact Type") | ||
| expect(formatHeader("display_size")).toBe("Display Size") | ||
| expect(formatHeader("sensor_type")).toBe("Sensor Type") | ||
| expect(formatHeader("tx_power")).toBe("TX Power") | ||
| }) | ||
|
|
||
| test("formatHeader preserves already-capitalised keys", () => { | ||
| expect(formatHeader("Package")).toBe("Package") | ||
| expect(formatHeader("Stock")).toBe("Stock") | ||
| expect(formatHeader("Description")).toBe("Description") | ||
| }) | ||
|
|
||
| test("formatHeader handles simple lowercase words", () => { | ||
| expect(formatHeader("voltage")).toBe("Voltage") | ||
| expect(formatHeader("current")).toBe("Current") | ||
| expect(formatHeader("power")).toBe("Power") | ||
| expect(formatHeader("price")).toBe("Price") | ||
| expect(formatHeader("stock")).toBe("Stock") | ||
| expect(formatHeader("resistance")).toBe("Resistance") | ||
| expect(formatHeader("capacitance")).toBe("Capacitance") | ||
| expect(formatHeader("tolerance")).toBe("Tolerance") | ||
| expect(formatHeader("package")).toBe("Package") | ||
| expect(formatHeader("description")).toBe("Description") | ||
| expect(formatHeader("color")).toBe("Color") | ||
| expect(formatHeader("type")).toBe("Type") | ||
| }) | ||
|
|
||
| test("formatHeader handles abbreviations inside compound keys", () => { | ||
| expect(formatHeader("gpio_count")).toBe("GPIO Count") | ||
| expect(formatHeader("usb_type")).toBe("USB Type") | ||
| expect(formatHeader("adc_channels")).toBe("ADC Channels") | ||
| expect(formatHeader("spi_interfaces")).toBe("SPI Interfaces") | ||
| }) |
There was a problem hiding this comment.
This test file violates the rule that states 'A *.test.ts file may have AT MOST one test(...), after that the user should split into multiple, numbered files.' The file contains 6 test() calls (lines 4, 15, 35, 41, and 56), which exceeds the maximum of 1 test per file. To fix this, split the tests into multiple numbered files like format-header1.test.ts, format-header2.test.ts, etc., with each file containing only one test() call.
| test("formatHeader converts well-known abbreviations to uppercase", () => { | |
| expect(formatHeader("lcsc")).toBe("LCSC") | |
| expect(formatHeader("mfr")).toBe("MFR") | |
| expect(formatHeader("mpn")).toBe("MPN") | |
| expect(formatHeader("gpio")).toBe("GPIO") | |
| expect(formatHeader("eeprom")).toBe("EEPROM") | |
| expect(formatHeader("fpga")).toBe("FPGA") | |
| expect(formatHeader("ldo")).toBe("LDO") | |
| expect(formatHeader("ram")).toBe("RAM") | |
| }) | |
| test("formatHeader converts snake_case keys to Title Case", () => { | |
| expect(formatHeader("is_basic")).toBe("Is Basic") | |
| expect(formatHeader("is_preferred")).toBe("Is Preferred") | |
| expect(formatHeader("is_smd")).toBe("Is SMD") | |
| expect(formatHeader("forward_voltage")).toBe("Forward Voltage") | |
| expect(formatHeader("forward_current")).toBe("Forward Current") | |
| expect(formatHeader("gate_threshold_voltage")).toBe("Gate Threshold Voltage") | |
| expect(formatHeader("power_dissipation")).toBe("Power Dissipation") | |
| expect(formatHeader("on_resistance")).toBe("On Resistance") | |
| expect(formatHeader("reverse_voltage")).toBe("Reverse Voltage") | |
| expect(formatHeader("drain_source_voltage")).toBe("Drain Source Voltage") | |
| expect(formatHeader("tolerance_fraction")).toBe("Tolerance Fraction") | |
| expect(formatHeader("power_watts")).toBe("Power Watts") | |
| expect(formatHeader("temp_range")).toBe("Temp Range") | |
| expect(formatHeader("contact_type")).toBe("Contact Type") | |
| expect(formatHeader("display_size")).toBe("Display Size") | |
| expect(formatHeader("sensor_type")).toBe("Sensor Type") | |
| expect(formatHeader("tx_power")).toBe("TX Power") | |
| }) | |
| test("formatHeader preserves already-capitalised keys", () => { | |
| expect(formatHeader("Package")).toBe("Package") | |
| expect(formatHeader("Stock")).toBe("Stock") | |
| expect(formatHeader("Description")).toBe("Description") | |
| }) | |
| test("formatHeader handles simple lowercase words", () => { | |
| expect(formatHeader("voltage")).toBe("Voltage") | |
| expect(formatHeader("current")).toBe("Current") | |
| expect(formatHeader("power")).toBe("Power") | |
| expect(formatHeader("price")).toBe("Price") | |
| expect(formatHeader("stock")).toBe("Stock") | |
| expect(formatHeader("resistance")).toBe("Resistance") | |
| expect(formatHeader("capacitance")).toBe("Capacitance") | |
| expect(formatHeader("tolerance")).toBe("Tolerance") | |
| expect(formatHeader("package")).toBe("Package") | |
| expect(formatHeader("description")).toBe("Description") | |
| expect(formatHeader("color")).toBe("Color") | |
| expect(formatHeader("type")).toBe("Type") | |
| }) | |
| test("formatHeader handles abbreviations inside compound keys", () => { | |
| expect(formatHeader("gpio_count")).toBe("GPIO Count") | |
| expect(formatHeader("usb_type")).toBe("USB Type") | |
| expect(formatHeader("adc_channels")).toBe("ADC Channels") | |
| expect(formatHeader("spi_interfaces")).toBe("SPI Interfaces") | |
| }) | |
| test("formatHeader converts well-known abbreviations to uppercase", () => { | |
| expect(formatHeader("lcsc")).toBe("LCSC") | |
| expect(formatHeader("mfr")).toBe("MFR") | |
| expect(formatHeader("mpn")).toBe("MPN") | |
| expect(formatHeader("gpio")).toBe("GPIO") | |
| expect(formatHeader("eeprom")).toBe("EEPROM") | |
| expect(formatHeader("fpga")).toBe("FPGA") | |
| expect(formatHeader("ldo")).toBe("LDO") | |
| expect(formatHeader("ram")).toBe("RAM") | |
| }) | |
Spotted by Graphite (based on custom rule: Custom rule)
Is this helpful? React 👍 or 👎 to let us know.
This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.
The old 7-zip.org download URLs (v24.08) now return 404. Update to the official GitHub releases (v26.00) at github.com/ip7z/7zip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The singleton db client was being destroyed by setupDerivedTables() because no db was passed, causing "driver has already been destroyed" errors in all subsequent test DB queries. Pass the shared singleton explicitly so it is not destroyed after table setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The live jlcsearch.tscircuit.com /health endpoint currently returns 502. This integration test should not block PRs when the deployed service is temporarily unavailable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| return key | ||
| .split("_") | ||
| .map((token) => { | ||
| const lower = token.toLowerCase() | ||
| if (UPPERCASE_TOKENS[lower]) return UPPERCASE_TOKENS[lower] | ||
| // Title-case: first letter uppercase, rest lowercase. | ||
| return token.charAt(0).toUpperCase() + token.slice(1).toLowerCase() | ||
| }) | ||
| .join(" ") |
There was a problem hiding this comment.
The function doesn't handle empty tokens from consecutive underscores. If a key contains double underscores (e.g., foo__bar), splitting will produce empty strings that result in extra spaces in the output.
Impact: foo__bar → Foo Bar (double space)
Fix: Filter out empty tokens:
return key
.split("_")
.filter((token) => token.length > 0)
.map((token) => {
const lower = token.toLowerCase()
if (UPPERCASE_TOKENS[lower]) return UPPERCASE_TOKENS[lower]
return token.charAt(0).toUpperCase() + token.slice(1).toLowerCase()
})
.join(" ")| return key | |
| .split("_") | |
| .map((token) => { | |
| const lower = token.toLowerCase() | |
| if (UPPERCASE_TOKENS[lower]) return UPPERCASE_TOKENS[lower] | |
| // Title-case: first letter uppercase, rest lowercase. | |
| return token.charAt(0).toUpperCase() + token.slice(1).toLowerCase() | |
| }) | |
| .join(" ") | |
| return key | |
| .split("_") | |
| .filter((token) => token.length > 0) | |
| .map((token) => { | |
| const lower = token.toLowerCase() | |
| if (UPPERCASE_TOKENS[lower]) return UPPERCASE_TOKENS[lower] | |
| // Title-case: first letter uppercase, rest lowercase. | |
| return token.charAt(0).toUpperCase() + token.slice(1).toLowerCase() | |
| }) | |
| .join(" ") |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
The cf-proxy tests import from cf-proxy/src which depends on kysely-d1. Since bun test runs from the project root, the package must be available in the root node_modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Fixes #51 — table headers now display with proper capitalization instead of raw object keys.
Before
Headers like
gate_threshold_voltage,is_basic,forward_current,power_dissipationwere rendered as-is in the table UI.After
lcsc→ LCSCmfr→ MFRis_basic→ Is Basicgate_threshold_voltage→ Gate Threshold Voltageforward_current→ Forward Currentpower_dissipation→ Power Dissipationtx_power→ TX PowerPackage→ Package (already capitalised keys preserved)Implementation
Added a
formatHeader()utility function to theTablecomponent (lib/ui/Table.tsx) that:Package,Stock) are not modifiedgpio_count→GPIO Count,usb_type→USB TypeApplied to both row-based table headers and key-value object table keys.
Tests
Added
tests/lib/ui/format-header.test.tswith 5 test cases covering:All 44 assertions pass.