Asia/Dhaka
Case StudiesSeptember 19, 2025

Refactoring Legacy Code With No Test Suite

image
The risk of refactoring 13+ dependent files with zero test coverage, managed through compensating practices, with no regressions shipped - but tests next time.
I refactored PostX's shared CSS component system - a system with 13+ dependent files - in a codebase with no automated test suite. The risk was real: any regression I introduced wouldn't be caught until someone noticed it visually or in production. I managed it through a component-by-component approach, keeping each commit focused and the working branch in sync with main throughout. It worked. But I'd add test coverage before starting if I did it again.
Zero test files in the codebase, 13+ dependent files at risk, zero regressions shipped through manual verification alone.
The riskNo *.test.js or *.spec.js files existed in PostX at the time of the refactor
Scope of change13+ files depended on the shared component system being refactored
How I managed itComponent-by-component commits, continuous branch sync, manual UI verification
Did it work?Yes - no regressions shipped
Paired case studyCSS architecture refactoring →
The shared CSS component system and the rest of the JS/React codebase both carried no automated test coverage, a deferral common in older plugin codebases that had compounded by the time of the refactor.
PostX is a WordPress plugin used by 60,000+ sites. Its shared CSS component system - the file that controlled the appearance of every UI field across every block's settings panel - had no automated tests. Neither did the rest of the JS/React codebase. This is common in older plugin codebases. PostX's frontend code dates back to 2019, and test infrastructure is often the thing that gets deferred when a small team is moving fast on features. By the time I was refactoring the shared stylesheet, that deferral had compounded: I was making structural changes to a system 13+ other files depended on, with no safety net to catch what I might break.
Removing a style rule one field type depended on could silently break a different field type in a panel never opened, with no automated check to catch it.
To be specific: the shared component system controlled the visual behavior of toggles, color pickers, dropdowns, typography controls, spacing sliders, and more - across every block settings panel in the plugin. A regression here wouldn't be subtle. If I accidentally removed a style rule that a different field type depended on, a toggle might render incorrectly in a completely different block's panel - one I hadn't opened, wasn't thinking about, and had no automated check against.
Four compensating practices: one component per commit, continuous branch sync with main, manual UI verification per component, and focused, bisectable commit messages.
One component per commit. I went field type by field type - toggle, range, select, color, typography, dimension, accordion, and so on. Each commit extracted one component's styles into its own file and nothing else. Continuous branch sync. Rather than letting the working branch drift from main and doing a large merge at the end, I kept it in sync throughout. Merge conflicts on a shared stylesheet are painful - catching them early is much safer than resolving a month's worth at once. Manual UI verification per component. After each extraction, I opened the relevant settings panels in the WordPress editor and checked that every field type still rendered correctly - not just the one I'd moved, but adjacent ones that shared the same file. Focused commit messages. Every commit in the series was named refactor: redesign [component name]. That naming made it easy to bisect if something went wrong - I could identify exactly which extraction introduced a problem and revert just that commit.
Automated tests would have caught regressions after every extraction; instead, checking the main dependent files meant nothing broke that was caught - not the same claim as nothing breaking.
Looking back clearly: the risk wasn't the refactoring approach. The risk was the missing test coverage that should have existed before I started. Tests written against the existing behavior - "toggle renders with correct spacing," "color picker displays at expected width" - would have caught regressions automatically after each extraction. Instead, I was the test suite. That scales poorly, relies on catching things visually, and doesn't protect against subtle regressions in states you didn't manually check. I checked the main dependent files. I can't say I checked every rendering permutation across every block. I can say nothing broke that we caught - but absence of evidence isn't the same as evidence of absence.
Refactoring without tests is like moving furniture in the dark - careful, but relying on not bumping into things rather than being able to see where they are.
Refactoring without tests is like moving furniture in the dark. You can do it. You can be careful. You can go slowly and feel your way around. But you're relying on not bumping into things rather than being able to see where things are. Most production codebases have areas with no test coverage. The skill isn't pretending the risk doesn't exist - it's managing it explicitly, naming it to the team, and building in compensating practices while also being honest that those practices are a substitute for the right solution, not equivalent to it.
The three questions this case study answers: how to safely refactor without tests, whether to add tests before or after, and what the actual risk is.
How do you safely refactor code with no test suite? Go component by component rather than all at once. Keep your branch in sync with main throughout. Verify each change manually in the UI before moving on. Use focused commit messages so you can bisect quickly if something breaks. Should you add tests before or after a refactor? Before. Tests written against existing behavior give you a safety net that catches regressions as you move code. Tests written after only verify the refactored version - you've lost the comparison point. What is the risk of refactoring shared components without automated tests? Any regression you introduce won't be caught until someone notices it visually or in production. With 13+ files depending on one shared system, a single missed side effect can break unrelated parts of the UI.
Part of the PostX frontend refactoring case study series →

Related case studies

WordPress Plugin Frontend Refactoring - 5 Case Studies
October 9, 2025

WordPress Plugin Frontend Refactoring - 5 Case Studies

A self-proposed, solo-executed refactor of a WordPress Gutenberg plugin used by 60,000+ sites - five linked case studies covering the technical execution, the decision, the trade-offs, and the risk.
What a Self-Proposed Engineering Initiative Looks Like
October 5, 2025

What a Self-Proposed Engineering Initiative Looks Like

I noticed a legacy system blocking my feature work and fixed it without being asked - a case study in product-minded engineering judgment.