Compare commits

..

35 commits

Author SHA1 Message Date
2734ca29d7 Merge pull request GH-5 from gh/mars-rover
mars rover
2026-03-04 07:20:05 +07:00
7d5325d5ef chore: update .gitignore to include tdd_*.md files 2026-02-24 13:54:33 +07:00
d3c7eb9de7 chore: format code 2026-02-24 10:56:30 +07:00
e5a651951d docs: document Mars Rover kata 2026-02-24 10:53:19 +07:00
5ee4e1edc0 REFACTOR: introduce Position and Plateau value objects 2026-02-24 10:51:13 +07:00
bb868d8dd6 REFACTOR: introduce Direction enum and extract wrapping logic 2026-02-24 10:50:39 +07:00
7632401aa4 GREEN: implement plateau boundaries with wrapping 2026-02-24 10:49:58 +07:00
1078e1947e RED: test plateau boundaries with wrapping 2026-02-24 10:49:06 +07:00
e292e66c64 GREEN: implement command execution 2026-02-24 10:48:32 +07:00
9cc1ae42b0 RED: test executing command sequences 2026-02-24 10:48:04 +07:00
c6235bd38a GREEN: implement move forward 2026-02-24 10:47:38 +07:00
36003654c7 RED: test moving forward in all directions 2026-02-24 10:47:15 +07:00
99c22b3a8a GREEN: implement turn right 2026-02-24 10:46:50 +07:00
df3823e3a9 RED: test turning right from all directions 2026-02-24 10:46:23 +07:00
382262575d GREEN: implement turn left 2026-02-24 10:45:58 +07:00
630d366bbf RED: test turning left from all directions 2026-02-24 10:45:34 +07:00
51073d9301 GREEN: implement rover initialization 2026-02-24 10:45:04 +07:00
0afac966fc RED: test rover initialization 2026-02-24 10:44:39 +07:00
b4d5e995cf chore: code clean up 2026-02-18 15:45:02 +07:00
5b34454317 Merge pull request GH-4 from gh/string-calculator
string calculator
2026-02-18 14:30:43 +07:00
d38a8c1107 format code 2026-02-18 14:29:55 +07:00
ad774b89c8 docs: Document String Calculator Bug Hunt Kata
- Add comprehensive String Calculator section to README
- Document Bug Hunt approach: test-driven bug finding
- List all 5 bugs found and fixed with RED-GREEN commits
- Include code examples and usage
- Update comparison table to include all 4 katas
- Add 'What Each Kata Teaches' for String Calculator
- Update progressive learning path to include fourth kata
2026-02-18 13:00:26 +07:00
ab5a97acc8 GREEN: Implement feature to ignore numbers > 1000
- Added .where((n) => n <= 1000) filter
- Numbers greater than 1000 now excluded from sum
- Tests pass: '2,1001' returns 2, '1000,1001,2' returns 1002
2026-02-18 12:59:01 +07:00
e8d2b769f3 RED: Test exposing missing feature - ignore numbers > 1000
- Test expects '2,1001' to return 2 (1001 ignored)
- Currently returns 1003 (includes all numbers)
- Feature not implemented yet
- Expected: <2>, Actual: <1003>
2026-02-18 12:58:24 +07:00
e0417f0b62 GREEN: Fix custom delimiter bug
- Uncommented delimiter extraction line
- Now properly extracts custom delimiter from format '//[delimiter]\n'
- Tests pass: '//;\n1;2' returns 3, '//|\n10|20|30' returns 60
2026-02-18 12:58:09 +07:00
6deaaaa8dc RED: Test exposing custom delimiter bug
- Test expects '//;\n1;2' to return 3
- Currently fails with FormatException
- Custom delimiter extraction is commented out
- Tries to parse '1;2' as single number, fails
2026-02-18 12:57:56 +07:00
073afc0ab6 fix: Introduce real custom delimiter bug
- Comment out delimiter extraction line
- Custom delimiter now never gets extracted
- Will fail when testing '//;\n1;2'
2026-02-18 12:57:49 +07:00
0a6da5a651 GREEN: Fix summation loop bug
- Changed loop condition from 'i < length - 1' to 'i < length'
- Now includes last element in sum
- Tests pass: '1,2' returns 3, '1,2,3' returns 6
2026-02-18 12:57:05 +07:00
ebaa21ca27 RED: Test exposing comma-delimited summation bug
- Test expects '1,2' to return 3
- Currently returns 1 (missing last element)
- Summation loop has off-by-one: for (i < length - 1)
- Expected: <3>, Actual: <1>
- Expected: <6>, Actual: <3>
2026-02-18 12:56:53 +07:00
77d48e790a GREEN: Fix single number parsing bug
- Removed +1 off-by-one error from single number parsing
- Now correctly returns the parsed number
- Tests pass: '5' returns 5, '42' returns 42
2026-02-18 12:56:40 +07:00
d30267bcc6 RED: Test exposing single number parsing bug
- Test expects single number '5' to return 5
- Currently returns 6 (off-by-one error)
- Expected: <5>, Actual: <6>
2026-02-18 12:56:28 +07:00
164adf815d GREEN: Fix empty string bug
- Changed return value from 1 to 0 for empty string
- Test now passes: empty string returns 0
2026-02-18 12:56:17 +07:00
e38bac9441 RED: Test exposing empty string bug
- Test expects empty string to return 0
- Currently returns 1 instead
- Expected: <0>, Actual: <1>
2026-02-18 12:56:05 +07:00
279c381872 feat: Add buggy String Calculator implementation
- Implements basic string calculator with 5 intentional bugs
- Bug 1: Empty string returns 1 instead of 0
- Bug 2: Single number has off-by-one error
- Bug 3: Summation loop misses last element
- Bug 4: Newline delimiter not properly handled
- Bug 5: Custom delimiter parsing broken
- Ready for Bug Hunt Kata with TDD approach
2026-02-18 12:55:51 +07:00
3e737dfef3 Merge pull request GH-3 from gh/gilded-rose
gilded rose
2026-02-18 12:47:57 +07:00
8 changed files with 607 additions and 22 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
tdd_*.md

227
README.md
View file

@ -219,6 +219,179 @@ gildedRose.updateQuality(); // Updates all items per domain rules
---
### 4. String Calculator ✅
**Implementation:** [`lib/string_calculator.dart`](lib/string_calculator.dart)
**Tests:** [`test/string_calculator_test.dart`](test/string_calculator_test.dart)
A Bug Hunt Kata demonstrating how to use TDD to discover and fix bugs in existing code. Each bug is exposed with a RED test, then fixed with GREEN implementation.
#### Domain Context
A simple calculator that sums numbers from a string input with various delimiter support:
**Features:**
1. **Empty String:** Returns 0
2. **Single Number:** Returns that number (`"5"` → 5)
3. **Comma Delimiter:** Sums comma-separated numbers (`"1,2,3"` → 6)
4. **Custom Delimiters:** Supports format `"//[delimiter]\n[numbers]"` (`"//;\n1;2"` → 3)
5. **Ignore Large Numbers:** Numbers > 1000 are ignored (`"2,1001"` → 2)
#### The Bug Hunt Approach
**Different from Previous Katas:** This wasn't built test-first. Instead, we started with **buggy working code** and used tests to expose and fix bugs one by one.
**Bug Hunt Process:**
1. **RED:** Write test exposing a specific bug
2. **GREEN:** Fix only that bug
3. **Commit:** Document the bug found and fixed
4. **Repeat:** Move to next bug
#### Bugs Found & Fixed
**Bug #1: Empty String Returns Wrong Value**
- **Bug:** Returned 1 instead of 0
- **Test:** `expect(calculator.add(''), equals(0))`
- **Fix:** Changed return value from 1 to 0
- **Commits:** RED → GREEN
**Bug #2: Single Number Off-By-One**
- **Bug:** Added +1 to parsed number
- **Test:** `expect(calculator.add('5'), equals(5))`
- **Expected:** 5, **Actual:** 6
- **Fix:** Removed `+ 1` from parsing
- **Commits:** RED → GREEN
**Bug #3: Summation Loop Misses Last Element**
- **Bug:** Loop condition `i < length - 1` skipped last item
- **Test:** `expect(calculator.add('1,2'), equals(3))`
- **Expected:** 3, **Actual:** 1
- **Fix:** Changed to `i < length`
- **Commits:** RED → GREEN
**Bug #4: Custom Delimiter Not Extracted**
- **Bug:** Delimiter extraction line was commented out
- **Test:** `expect(calculator.add('//;\n1;2'), equals(3))`
- **Error:** FormatException trying to parse '1;2'
- **Fix:** Uncommented `delimiter = parts[0].substring(2)`
- **Commits:** RED → GREEN
**Bug #5: Missing Feature - Ignore Numbers > 1000**
- **Bug:** All numbers included in sum
- **Test:** `expect(calculator.add('2,1001'), equals(2))`
- **Expected:** 2, **Actual:** 1003
- **Fix:** Added `.where((n) => n <= 1000)` filter
- **Commits:** RED → GREEN
#### Usage
```dart
import 'package:tdd_katas/string_calculator.dart';
final calculator = StringCalculator();
calculator.add(''); // Returns: 0
calculator.add('5'); // Returns: 5
calculator.add('1,2,3'); // Returns: 6
calculator.add('//;\n1;2'); // Returns: 3
calculator.add('2,1001'); // Returns: 2 (1001 ignored)
```
#### The "Aha!" Moments
1. **Tests as Bug Detectors:** Each test acted like a spotlight, illuminating exactly ONE bug at a time. No guessing—the test tells you what's broken.
2. **RED-GREEN Still Works:** Even when fixing bugs (not adding features), the RED-GREEN rhythm provides safety. You're never fixing blind.
3. **Regression Prevention:** After fixing each bug, ALL previous tests stay green. This proves you didn't break something while fixing something else.
4. **Incremental Debugging:** Fixing one bug at a time with commits creates a clear audit trail. You can see exactly what each bug was and how it was fixed.
5. **Real-World Skill:** This mirrors production work—most code you touch is existing code with bugs, not greenfield TDD.
---
### 5. Mars Rover ✅
**Implementation:** [`lib/mars_rover.dart`](lib/mars_rover.dart)
**Tests:** [`test/mars_rover_test.dart`](test/mars_rover_test.dart)
A Command Pattern kata simulating a robotic rover navigating a plateau on Mars. Demonstrates clean separation of concerns, value objects, and command-based control.
#### Domain Context
A rover explores a rectangular plateau with coordinate-based navigation:
**Core Concepts:**
- **Position:** (x, y) coordinates on the plateau grid
- **Direction:** Cardinal directions (N, E, S, W)
- **Plateau:** Grid with defined boundaries that wrap around (toroidal topology)
**Commands:**
- `L` - Turn left 90 degrees (changes direction, not position)
- `R` - Turn right 90 degrees (changes direction, not position)
- `M` - Move forward one grid point in current direction
**Example Navigation:**
```
Starting: (0,0) facing North
Commands: "MMRMMLM"
- MM: Move to (0,2) facing North
- R: Turn to face East (still at 0,2)
- MM: Move to (2,2) facing East
- L: Turn to face North (still at 2,2)
- M: Move to (2,3) facing North
Result: (2,3) facing North
```
#### Key Design Decisions
**Direction Enum:** Encapsulates rotation logic using modular arithmetic. Each direction knows how to turn left/right, eliminating conditional branching.
**Value Objects:**
- `Position` is immutable—movement returns new position instances
- `Plateau` encapsulates boundary wrapping logic
- Prevents invalid states at the type level
**Command Pattern (Implicit):** The `execute()` method delegates to command handlers (`turnLeft()`, `turnRight()`, `moveForward()`). Each command is isolated and testable.
**Wrapping Logic:** Plateau boundaries wrap around (toroidal topology). Moving past edge (e.g., x=5→6 on 5x5 grid) wraps to opposite side (x=0).
#### Usage
```dart
import 'package:tdd_katas/mars_rover.dart';
// Create rover at position (1,2) facing North on 5x5 plateau
final rover = Rover(
x: 1,
y: 2,
direction: 'N',
plateauWidth: 5,
plateauHeight: 5,
);
rover.execute('LMLMLMLMM');
print('Position: (${rover.x}, ${rover.y})'); // Position: (1, 3)
print('Direction: ${rover.direction}'); // Direction: N
```
#### The "Aha!" Moments
1. **Enums as Behavior Carriers:** Direction enum doesn't just store values—it encapsulates rotation logic. Turning left/right becomes `direction.turnLeft()`, eliminating lookup tables.
2. **Value Objects Prevent Bugs:** Immutable `Position` means movement can't corrupt state. New position calculated, validated, then assigned. Boundary wrapping isolated in `Plateau`.
3. **Switch Expressions Shine:** Modern Dart's `switch` expression makes direction-based movement elegant and exhaustive. Compiler enforces handling all directions.
4. **Modular Arithmetic for Rotation:** `(index + 1) % 4` handles right rotation elegantly. No if-statements, no edge cases—math models the domain perfectly.
5. **Refactoring Without Fear:** Two refactoring commits drastically improved code structure. Tests stayed green throughout, proving behavior preservation.
---
## Running Tests
```bash
@ -229,6 +402,8 @@ dart test
dart test test/roman_numerals_test.dart
dart test test/bowling_game_test.dart
dart test test/gilded_rose_test.dart
dart test test/string_calculator_test.dart
dart test test/mars_rover_test.dart
# Run with coverage
dart test --coverage
@ -536,22 +711,23 @@ Decision: Skip refactor commit—code is already excellent.
## Comparing the Katas
### All Three Katas at a Glance
### All Five Katas at a Glance
| Aspect | Roman Numerals | Bowling Game | Gilded Rose |
|--------|----------------|--------------|-------------|
| **Complexity** | Beginner | Intermediate | Advanced |
| **Approach** | Greenfield TDD | Greenfield TDD | Legacy refactoring |
| **State** | Stateless | Stateful | Stateful |
| **Algorithm** | Table-driven lookup | Frame iteration | Strategy pattern |
| **Key Challenge** | Pattern recognition | State & bonuses | Refactoring safely |
| **Design Pattern** | Value Object | Implicit strategy | Explicit strategy |
| **Lines of Code** | ~45 production | ~30 production | ~120 production |
| **Test Count** | ~15 tests | ~10 tests | ~17 tests |
| **Aha! Moment** | Table = data structure | Simple → complex works | Refactoring = safety |
| Aspect | Roman Numerals | Bowling Game | Gilded Rose | String Calculator | Mars Rover |
|--------|----------------|--------------|-------------|-------------------|------------|
| **Complexity** | Beginner | Intermediate | Advanced | Beginner | Intermediate |
| **Approach** | Greenfield TDD | Greenfield TDD | Legacy refactoring | Bug hunting | Greenfield TDD |
| **State** | Stateless | Stateful | Stateful | Stateless | Stateful |
| **Algorithm** | Table-driven | Frame iteration | Strategy pattern | String parsing | Command pattern |
| **Key Challenge** | Pattern recognition | State & bonuses | Refactoring safely | Finding bugs | Navigation & wrapping |
| **Design Pattern** | Value Object | Implicit strategy | Explicit strategy | Filters & pipes | Command + Value Objects |
| **Lines of Code** | ~45 production | ~30 production | ~120 production | ~25 production | ~95 production |
| **Test Count** | ~15 tests | ~10 tests | ~17 tests | 6 tests | 23 tests |
| **Aha! Moment** | Table = data | Simple → complex | Refactor = safe | Tests find bugs | Enums carry behavior |
### What Each Kata Teaches
**Roman Numerals:**
**Roman Numerals:**
- Converting domain rules into data structures
- Value Objects for enforcing constraints
@ -561,22 +737,37 @@ Decision: Skip refactor commit—code is already excellent.
- State management without over-engineering
- Look-ahead logic in sequential data
- How correct abstractions scale beyond test cases
**Gilded Rose:**
- Safely refactoring legacy code with characterization tests
- Strategy pattern for eliminating conditional complexity
- Open-Closed Principle for extensibility
- Working effectively with code you didn't write
**String Calculator:**
- Using tests to expose bugs in existing code
- Bug hunting with RED-GREEN discipline
- Incremental debugging with clear commits
- Regression prevention through test accumulation
**Mars Rover:**
- Command pattern for behavior delegation
- Value Objects for domain modeling (Position, Plateau, Direction)
- Enums as behavior carriers, not just constants
- Coordinate systems and wrapping logic
- Progressive refactoring with confidence
### Progressive Learning Path
1. **Roman Numerals first:** Learn TDD fundamentals without state complexity
2. **Bowling Game second:** Apply TDD to stateful problems
3. **Gilded Rose third:** Master refactoring legacy code with tests as safety net
4. **Next kata:** Choose based on what you want to practice:
- **String Calculator:** Parsing, validation, error handling
- **Mars Rover:** Command pattern, multiple behaviors
- **Prime Factors:** Mathematical decomposition, algorithmic thinkingsts
- **Mars Rover:** Command pattern, multiple behaviors
3. **Mars Rover third:** Master Command pattern and value objects
4. **Gilded Rose fourth:** Refactor legacy code with tests as safety net
5. **String Calculator fifth:** Practice bug hunting and fixing with TDD
6. **Next kata:** Choose based on what you want to practice:
- **Prime Factors:** Mathematical decomposition, algorithmic thinking
- **Tennis Scoring:** State machines, domain language
- **FizzBuzz:** Classic conditional logic exercise
---

View file

@ -1,5 +1,3 @@
import 'package:tdd_katas/roman_numerals.dart' as roman_numerals;
void main(List<String> arguments) {
print('Hello world: ${roman_numerals.integerToRoman(1)}');
print('TDD Katas exercises, please read the README.md file.');
}

View file

@ -14,7 +14,6 @@ class Item {
/// Quality bounds - domain constraints
const int _minQuality = 0;
const int _maxQuality = 50;
const int _legendaryQuality = 80; // unused, just for documentation
/// Helper methods for quality management
void _degradeQuality(Item item, int amount) {

106
lib/mars_rover.dart Normal file
View file

@ -0,0 +1,106 @@
enum Direction {
north('N'),
east('E'),
south('S'),
west('W');
final String code;
const Direction(this.code);
static Direction fromCode(String code) {
return Direction.values.firstWhere((d) => d.code == code);
}
Direction turnLeft() {
return Direction.values[(index + 3) % 4];
}
Direction turnRight() {
return Direction.values[(index + 1) % 4];
}
}
class Position {
final int x;
final int y;
Position(this.x, this.y);
Position moveNorth() => Position(x, y + 1);
Position moveEast() => Position(x + 1, y);
Position moveSouth() => Position(x, y - 1);
Position moveWest() => Position(x - 1, y);
}
class Plateau {
final int width;
final int height;
Plateau(this.width, this.height);
Position wrap(Position position) {
final wrappedX = _wrapCoordinate(position.x, width);
final wrappedY = _wrapCoordinate(position.y, height);
return Position(wrappedX, wrappedY);
}
int _wrapCoordinate(int value, int max) {
final wrapped = value % (max + 1);
return wrapped < 0 ? wrapped + max + 1 : wrapped;
}
}
class Rover {
Position _position;
Direction _direction;
final Plateau plateau;
Rover({
required int x,
required int y,
required String direction,
int plateauWidth = 100,
int plateauHeight = 100,
}) : _position = Position(x, y),
_direction = Direction.fromCode(direction),
plateau = Plateau(plateauWidth, plateauHeight);
int get x => _position.x;
int get y => _position.y;
String get direction => _direction.code;
void turnLeft() {
_direction = _direction.turnLeft();
}
void turnRight() {
_direction = _direction.turnRight();
}
void moveForward() {
final newPosition = switch (_direction) {
Direction.north => _position.moveNorth(),
Direction.east => _position.moveEast(),
Direction.south => _position.moveSouth(),
Direction.west => _position.moveWest(),
};
_position = plateau.wrap(newPosition);
}
void execute(String commands) {
for (var i = 0; i < commands.length; i++) {
final command = commands[i];
switch (command) {
case 'L':
turnLeft();
break;
case 'R':
turnRight();
break;
case 'M':
moveForward();
break;
}
}
}
}

View file

@ -0,0 +1,55 @@
/// String Calculator - Buggy Implementation
/// This code has intentional bugs for the Bug Hunt Kata exercise
///
/// Requirements:
/// 1. Empty string returns 0
/// 2. Single number returns that number
/// 3. Two numbers comma-delimited returns sum
/// 4. Handle newlines as delimiters
/// 5. Support custom delimiters: "//[delimiter]\n[numbers]"
library;
class StringCalculator {
int add(String numbers) {
// Bug 1: Empty string handling
if (numbers.isEmpty) {
return 0; // Fixed: Return 0 for empty string
}
// Bug 2: Single number parsing
if (!numbers.contains(',') &&
!numbers.contains('\n') &&
!numbers.startsWith('//')) {
return int.parse(numbers); // Fixed: Removed off-by-one error
}
String delimiter = ',';
String numbersToProcess = numbers;
// Custom delimiter support
if (numbers.startsWith('//')) {
// Bug 4: Custom delimiter not actually used
final parts = numbers.split('\n');
delimiter = parts[0].substring(2); // Fixed: Extract custom delimiter
numbersToProcess = parts.skip(1).join('\n');
}
// Bug 3 & 4: Delimiter handling issues
final numList = numbersToProcess
.replaceAll('\n', delimiter)
.split(delimiter)
.where((s) => s.isNotEmpty)
.map((s) => int.parse(s))
.where((n) => n <= 1000) // Filter out numbers > 1000
.toList();
// Bug 3: Off-by-one in summation
int sum = 0;
for (int i = 0; i < numList.length; i++) {
// Fixed: Include last element
sum += numList[i];
}
return sum;
}
}

194
test/mars_rover_test.dart Normal file
View file

@ -0,0 +1,194 @@
import 'package:tdd_katas/mars_rover.dart';
import 'package:test/test.dart';
void main() {
group('Mars Rover:', () {
test('rover reports initial position and direction', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
expect(rover.x, equals(0));
expect(rover.y, equals(0));
expect(rover.direction, equals('N'));
});
group('Turning Left:', () {
test('from North faces West', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.turnLeft();
expect(rover.direction, equals('W'));
});
test('from West faces South', () {
final rover = Rover(x: 0, y: 0, direction: 'W');
rover.turnLeft();
expect(rover.direction, equals('S'));
});
test('from South faces East', () {
final rover = Rover(x: 0, y: 0, direction: 'S');
rover.turnLeft();
expect(rover.direction, equals('E'));
});
test('from East faces North', () {
final rover = Rover(x: 0, y: 0, direction: 'E');
rover.turnLeft();
expect(rover.direction, equals('N'));
});
});
group('Turning Right:', () {
test('from North faces East', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.turnRight();
expect(rover.direction, equals('E'));
});
test('from East faces South', () {
final rover = Rover(x: 0, y: 0, direction: 'E');
rover.turnRight();
expect(rover.direction, equals('S'));
});
test('from South faces West', () {
final rover = Rover(x: 0, y: 0, direction: 'S');
rover.turnRight();
expect(rover.direction, equals('W'));
});
test('from West faces North', () {
final rover = Rover(x: 0, y: 0, direction: 'W');
rover.turnRight();
expect(rover.direction, equals('N'));
});
});
group('Moving Forward:', () {
test('facing North increases Y', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.moveForward();
expect(rover.x, equals(0));
expect(rover.y, equals(1));
});
test('facing East increases X', () {
final rover = Rover(x: 0, y: 0, direction: 'E');
rover.moveForward();
expect(rover.x, equals(1));
expect(rover.y, equals(0));
});
test('facing South decreases Y', () {
final rover = Rover(x: 0, y: 5, direction: 'S');
rover.moveForward();
expect(rover.x, equals(0));
expect(rover.y, equals(4));
});
test('facing West decreases X', () {
final rover = Rover(x: 5, y: 0, direction: 'W');
rover.moveForward();
expect(rover.x, equals(4));
expect(rover.y, equals(0));
});
});
group('Executing Command Sequences:', () {
test('single command L', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.execute('L');
expect(rover.direction, equals('W'));
});
test('single command R', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.execute('R');
expect(rover.direction, equals('E'));
});
test('single command M', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.execute('M');
expect(rover.y, equals(1));
});
test('complex sequence MMRMMLM', () {
final rover = Rover(x: 0, y: 0, direction: 'N');
rover.execute('MMRMMLM');
expect(rover.x, equals(2));
expect(rover.y, equals(3));
expect(rover.direction, equals('N'));
});
test('example from Mars Rover kata', () {
final rover = Rover(x: 1, y: 2, direction: 'N');
rover.execute('LMLMLMLMM');
expect(rover.x, equals(1));
expect(rover.y, equals(3));
expect(rover.direction, equals('N'));
});
});
group('Plateau Boundaries:', () {
test('wraps around when moving North past boundary', () {
final rover = Rover(
x: 0,
y: 5,
direction: 'N',
plateauWidth: 5,
plateauHeight: 5,
);
rover.moveForward();
expect(rover.y, equals(0));
});
test('wraps around when moving East past boundary', () {
final rover = Rover(
x: 5,
y: 0,
direction: 'E',
plateauWidth: 5,
plateauHeight: 5,
);
rover.moveForward();
expect(rover.x, equals(0));
});
test('wraps around when moving South past boundary', () {
final rover = Rover(
x: 0,
y: 0,
direction: 'S',
plateauWidth: 5,
plateauHeight: 5,
);
rover.moveForward();
expect(rover.y, equals(5));
});
test('wraps around when moving West past boundary', () {
final rover = Rover(
x: 0,
y: 0,
direction: 'W',
plateauWidth: 5,
plateauHeight: 5,
);
rover.moveForward();
expect(rover.x, equals(5));
});
test('example with wrapping', () {
final rover = Rover(
x: 5,
y: 5,
direction: 'N',
plateauWidth: 5,
plateauHeight: 5,
);
rover.execute('MMM');
expect(rover.y, equals(2));
});
});
});
}

View file

@ -0,0 +1,41 @@
import 'package:tdd_katas/string_calculator.dart';
import 'package:test/test.dart';
void main() {
group('String Calculator - Bug Hunt', () {
late StringCalculator calculator;
setUp(() {
calculator = StringCalculator();
});
test('empty string returns 0', () {
expect(calculator.add(''), equals(0));
});
test('single number returns that number', () {
expect(calculator.add('5'), equals(5));
expect(calculator.add('42'), equals(42));
});
test('two comma-delimited numbers return sum', () {
expect(calculator.add('1,2'), equals(3));
expect(calculator.add('10,20'), equals(30));
});
test('multiple comma-delimited numbers return sum', () {
expect(calculator.add('1,2,3'), equals(6));
expect(calculator.add('5,10,15,20'), equals(50));
});
test('custom delimiter works correctly', () {
expect(calculator.add('//;\n1;2'), equals(3));
expect(calculator.add('//|\n10|20|30'), equals(60));
});
test('numbers greater than 1000 are ignored', () {
expect(calculator.add('2,1001'), equals(2));
expect(calculator.add('1000,1001,2'), equals(1002));
});
});
}