JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
128: Code Coverage Reporting
I've seen a lot of developers treat the code coverage report as a scoreboard. You run npm test -- --coverage, see a 92% in the terminal, and feel a surge of confidence. Or worse, you're in a company with a strict "100% coverage or the PR is rejected" policy. When that happens, the goal shifts from "making the software robust" to "making the numbers turn green." This is where we run into trouble.
The Trap of the 100% Metric
The naive approach to coverage is treating it as a proxy for quality. Imagine you've written a function that handles complex shipping discounts based on user tier, order volume, and geographic region. It's a mess of if/else blocks. To hit 100% coverage, you might write a test that passes in a valid user and a valid order, which triggers the "success" path. Then, you write one test that passes a null value to trigger the "error" path.
// The kind of function we're talking about
function calculateShipping(user, order) {
if (!user || !order) return 0; // Path A
if (user.isPremium && order.total > 100) return 0; // Path B
if (user.region === 'International') return 50; // Path C
return 10; // Path D
}
If you write four tests that simply execute those paths, your coverage report will scream "100%!" You've touched every line. But did you actually test the logic? You might have checked that the function doesn't crash, but you didn't check if a premium user with a $101 order actually gets free shipping, or if an international premium user gets the $50 rate or the $0 rate. You've achieved "Line Coverage," but you have zero "Logic Coverage." I've seen entire systems crash in production despite having 100% line coverage because the tests were written to satisfy the tool, not to challenge the code.
Using Coverage to Find the Blind Spots
The better way to use coverage is as a "gap analysis" tool. Instead of starting with the report, start by writing tests based on the requirements and the edges of your logic. Once those are passing, then you run the coverage report. You aren't looking for a high number; you're looking for the red lines.
When I see a red line in a coverage report, I don't immediately think, "I need to write a test to cover that." Instead, I ask, "Why is this code here? Is there a scenario I forgot to consider?" If the report shows that a specific catch block is never hit, it's a signal. Maybe that error is actually impossible to trigger, meaning I've written dead code that should be deleted. Or, more likely, I've discovered a failure mode—like a network timeout or a database disconnection—that I completely ignored in my test suite. In this workflow, the coverage report is a prompt for critical thinking, not a checklist for completion.
Where the Numbers Lie
The trade-off here is between speed and certainty. Chasing 100% is fast because it's a mechanical process; you just keep adding inputs until the red goes away. Thinking through the "why" of the uncovered lines is slower and more mentally taxing. But it's the only way to actually prevent regressions.
Keep in mind that "Branch Coverage" is far more valuable than "Line Coverage." Line coverage tells you if the engine started; branch coverage tells you if the car can actually turn left and right. If you have a complex ternary operator on one line, line coverage will mark it as "covered" the moment either side of the condition is hit. Branch coverage will tell you that you've only tested the true case and the false case is still a mystery. If you're going to trust any number in your report, make it the branch percentage.
📋 Practical Task
Audit and Fill the Gaps in the Order Validator
You have been handed a validateOrder function and a corresponding test file. The current test suite is designed to hit 100% line coverage, but it ignores several critical edge cases (logic gaps).
// orderValidator.js
export function validateOrder(order) {
if (!order) return { valid: false, error: 'No order provided' };
if (order.items && order.items.length === 0) return { valid: false, error: 'Empty cart' };
if (order.total < 0) return { valid: false, error: 'Invalid total' };
if (order.customer && order.customer.isBlacklisted) return { valid: false, error: 'Customer blocked' };
return { valid: true };
}
// orderValidator.test.js
test('coverage check', () => {
expect(validateOrder(null)).toEqual({ valid: false, error: 'No order provided' });
expect(validateOrder({ items: [] })).toEqual({ valid: false, error: 'Empty cart' });
expect(validateOrder({ items: [1], total: -1 })).toEqual({ valid: false, error: 'Invalid total' });
expect(validateOrder({ items: [1], total: 10, customer: { isBlacklisted: true } })).toEqual({ valid: false, error: 'Customer blocked' });
expect(validateOrder({ items: [1], total: 10, customer: { isBlacklisted: false } })).toEqual({ valid: true });
});
Your Task: The current test hits 100% line coverage but is fragile. Refactor the tests to move away from a single "coverage check" block. Add specific test cases that challenge the logic, specifically addressing:
- What happens if
order.itemsis undefined? (The current code handles this, but is it explicitly tested?) - What happens if
order.customeris missing? (Does the code crash when checkingisBlacklisted?) - Ensure each validation rule is tested in its own
test()` orit()` block to ensure a failure in one doesn't mask a failure in another.
There are no comments for now.