Merge pest/main
This commit is contained in:
commit
05916ad08c
23 changed files with 4169 additions and 0 deletions
31
php/pest/.github/workflows/main.yml
vendored
Normal file
31
php/pest/.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
phpunit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Configure PHP ${{ matrix.php-version }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-composer-${{ hashFiles('composer.json') }}
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
8
php/pest/.gitignore
vendored
Normal file
8
php/pest/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
*
|
||||
!/*
|
||||
!/.github/**
|
||||
!/composer.*
|
||||
!/phpunit.xml
|
||||
!/README.md
|
||||
!/src/**
|
||||
!/tests/**
|
19
php/pest/Makefile
Normal file
19
php/pest/Makefile
Normal file
|
@ -0,0 +1,19 @@
|
|||
CLEAN_PATHS=vendor
|
||||
PEST_PATH=vendor/bin/pest
|
||||
|
||||
all: test
|
||||
|
||||
clean:
|
||||
@for dir in $(CLEAN_PATHS); do \
|
||||
rm -fr $$dir; \
|
||||
done
|
||||
|
||||
pest: vendor
|
||||
@$(PEST_PATH)
|
||||
|
||||
test: pest
|
||||
|
||||
vendor: composer.json composer.lock
|
||||
@composer install
|
||||
|
||||
.PHONY: all clean pest test
|
17
php/pest/README.md
Normal file
17
php/pest/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# PHP katas with Pest PHP
|
||||
|
||||
PHP code katas, tested with [Pest PHP][]. Based on exercises from [Exercism.io][].
|
||||
|
||||
Includes:
|
||||
|
||||
- **Anagrams**: select an anagram for a word from a list of condidates.
|
||||
- **Flatten Array**: take a nested list and return a single flattened list with all values except nil/null.
|
||||
- **Bob**: returns different responses based on input.
|
||||
- **Bowling**: calculate the score for a game of bowling.
|
||||
- **Grade School**: given students' names along with the grade that they are in, create a roster for the school.
|
||||
- **Pangram** - determine if a sentence is a pangram (every letter of the alphabet is used at least once).
|
||||
- **Rock, paper, scissors**
|
||||
- **Roman numerals**: convert a number into its roman numeral value.
|
||||
|
||||
[exercism.io]: https://exercism.io
|
||||
[pest php]: https://pestphp.com
|
24
php/pest/composer.json
Normal file
24
php/pest/composer.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"beberlei/assert": "^3.2",
|
||||
"tightenco/collect": "^7.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest": "^0.2.3",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/FlattenArray.php",
|
||||
"src/Pangram.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
3244
php/pest/composer.lock
generated
Normal file
3244
php/pest/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
php/pest/phpunit.xml
Normal file
12
php/pest/phpunit.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
27
php/pest/src/Anagram.php
Normal file
27
php/pest/src/Anagram.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
final class Anagram
|
||||
{
|
||||
private static function sortLettersInWord(string $word): string
|
||||
{
|
||||
return (new Collection(str_split($word)))
|
||||
->sort()
|
||||
->implode('');
|
||||
}
|
||||
|
||||
public static function forWord(string $word, array $candidates): Collection
|
||||
{
|
||||
$word = static::sortLettersInWord($word);
|
||||
|
||||
return (new Collection($candidates))
|
||||
->filter(fn (string $candidate): bool =>
|
||||
static::sortLettersInWord($candidate) == $word)
|
||||
->values();
|
||||
}
|
||||
}
|
57
php/pest/src/Bob.php
Normal file
57
php/pest/src/Bob.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
final class Bob
|
||||
{
|
||||
public const RESPONSE_DEFAULT = 'Whatever.';
|
||||
public const RESPONSE_SAY_NOTHING = 'Fine. Be that way!';
|
||||
public const RESPONSE_QUESTION = 'Sure.';
|
||||
public const RESPONSE_YELLING = 'Whoa, chill out!';
|
||||
public const RESPONSE_YELLING_QUESTION = 'Calm down, I know what I\'m doing!';
|
||||
|
||||
private string $input;
|
||||
|
||||
public static function saySomethingTo(string $input): self
|
||||
{
|
||||
return new self($input);
|
||||
}
|
||||
|
||||
public function getResponse(): string
|
||||
{
|
||||
if ($this->input === '') {
|
||||
return self::RESPONSE_SAY_NOTHING;
|
||||
}
|
||||
|
||||
if ($this->isQuestion() && $this->isYelling()) {
|
||||
return self::RESPONSE_YELLING_QUESTION;
|
||||
}
|
||||
|
||||
if ($this->isQuestion()) {
|
||||
return self::RESPONSE_QUESTION;
|
||||
}
|
||||
|
||||
if ($this->isYelling()) {
|
||||
return self::RESPONSE_YELLING;
|
||||
}
|
||||
|
||||
return self::RESPONSE_DEFAULT;
|
||||
}
|
||||
|
||||
final private function __construct(string $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
private function isQuestion(): bool
|
||||
{
|
||||
return (bool) preg_match('/.+\?/', $this->input);
|
||||
}
|
||||
|
||||
private function isYelling(): bool
|
||||
{
|
||||
return (bool) preg_match('/.+!/', $this->input);
|
||||
}
|
||||
}
|
88
php/pest/src/BowlingGame.php
Normal file
88
php/pest/src/BowlingGame.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Assert\Assert;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
final class BowlingGame
|
||||
{
|
||||
private const MAX_PINS_PER_FRAME = 10;
|
||||
private const NUMBER_OF_FRAMES = 10;
|
||||
|
||||
private Collection $rolls;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->rolls = new Collection();
|
||||
}
|
||||
|
||||
public function roll(int $pins): void
|
||||
{
|
||||
Assert::that($pins)
|
||||
->greaterOrEqualThan(0,
|
||||
'You cannot knock down a negative number of pins. Knocked down %d.')
|
||||
->lessOrEqualThan(self::MAX_PINS_PER_FRAME,
|
||||
'You can only knock down a maximum of 10 pins in a roll. Knocked down 12.');
|
||||
|
||||
$this->rolls->push($pins);
|
||||
}
|
||||
|
||||
public function getScore(): int
|
||||
{
|
||||
$score = 0;
|
||||
$roll = 0;
|
||||
|
||||
foreach (range(1, self::NUMBER_OF_FRAMES) as $frame) {
|
||||
if ($this->isStrike($roll)) {
|
||||
$score += $this->rolls[$roll];
|
||||
$score += $this->bonusForStrike($roll);
|
||||
|
||||
$roll += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isSpare($roll)) {
|
||||
$score += $this->defaultFrameScore($roll);
|
||||
$score += $this->bonusForSpare($roll);
|
||||
|
||||
$roll += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
$score += $this->defaultFrameScore($roll);
|
||||
|
||||
$roll += 2;
|
||||
}
|
||||
|
||||
return $score;
|
||||
}
|
||||
|
||||
private function bonusForSpare(int $roll): int
|
||||
{
|
||||
return $this->rolls->get($roll + 2);
|
||||
}
|
||||
|
||||
private function bonusForStrike(int $roll): int
|
||||
{
|
||||
return $this->rolls[$roll + 1] + $this->rolls[$roll + 2];
|
||||
}
|
||||
|
||||
private function defaultFrameScore(int $roll): int
|
||||
{
|
||||
return $this->rolls[$roll] + $this->rolls[$roll + 1];
|
||||
}
|
||||
|
||||
private function isSpare(int $roll): bool
|
||||
{
|
||||
return $this->defaultFrameScore($roll)
|
||||
== self::MAX_PINS_PER_FRAME;
|
||||
}
|
||||
|
||||
private function isStrike(int $roll): bool
|
||||
{
|
||||
return $this->rolls[$roll] == self::MAX_PINS_PER_FRAME;
|
||||
}
|
||||
}
|
19
php/pest/src/FlattenArray.php
Normal file
19
php/pest/src/FlattenArray.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
function flattenArray(array $input, array &$output = []): array
|
||||
{
|
||||
foreach ($input as $item) {
|
||||
if (!is_array($item)) {
|
||||
$output[] = $item;
|
||||
continue;
|
||||
}
|
||||
|
||||
flattenArray($item, $output);
|
||||
}
|
||||
|
||||
return array_values(array_filter($output));
|
||||
}
|
49
php/pest/src/GradeSchool.php
Normal file
49
php/pest/src/GradeSchool.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Assert\Assert;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
final class GradeSchool
|
||||
{
|
||||
private Collection $students;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->students = new Collection();
|
||||
}
|
||||
|
||||
public function addStudentToGrade(string $name, int $grade): void
|
||||
{
|
||||
Assert::that($name)->notEmpty('Name cannot be blank');
|
||||
|
||||
Assert::that($grade)
|
||||
->greaterOrEqualThan(1, 'Grade must be greater than or equal to 1');
|
||||
|
||||
$this->students->push(['name' => $name, 'grade' => $grade]);
|
||||
}
|
||||
|
||||
public function getAllStudents(): Collection
|
||||
{
|
||||
return $this->students->pluck('grade')
|
||||
->unique()
|
||||
->map(fn (int $grade): Collection => new Collection([
|
||||
'grade' => $grade,
|
||||
'students' => $this->getStudentsByGrade($grade),
|
||||
]))
|
||||
->sortBy('grade')
|
||||
->values();
|
||||
}
|
||||
|
||||
public function getStudentsByGrade(int $grade): Collection
|
||||
{
|
||||
return $this->students
|
||||
->filter(fn (array $student): bool => $grade == $student['grade'])
|
||||
->pluck('name')
|
||||
->sort()
|
||||
->values();
|
||||
}
|
||||
}
|
14
php/pest/src/Pangram.php
Normal file
14
php/pest/src/Pangram.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
function isPangram(string $input): bool
|
||||
{
|
||||
$split = str_split($input);
|
||||
$lower = array_map('strtolower', $split);
|
||||
$filtered = array_filter($lower, fn(string $letter) => $letter != ' ');
|
||||
|
||||
return count(array_unique($filtered)) == 26;
|
||||
}
|
77
php/pest/src/RockPaperScissorsGame.php
Normal file
77
php/pest/src/RockPaperScissorsGame.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Assert\Assert;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class RockPaperScissorsGame
|
||||
{
|
||||
private const MOVE_ROCK = 'rock';
|
||||
private const MOVE_PAPER = 'paper';
|
||||
private const MOVE_SCISSORS = 'scissors';
|
||||
|
||||
private static array $possibleMoves = [
|
||||
[
|
||||
'moves' => [self::MOVE_ROCK, self::MOVE_PAPER],
|
||||
'winner' => self::MOVE_PAPER,
|
||||
],
|
||||
[
|
||||
'moves' => [self::MOVE_ROCK, self::MOVE_SCISSORS],
|
||||
'winner' => self::MOVE_SCISSORS,
|
||||
],
|
||||
[
|
||||
'moves' => [self::MOVE_PAPER, self::MOVE_SCISSORS],
|
||||
'winner' => self::MOVE_SCISSORS,
|
||||
],
|
||||
];
|
||||
|
||||
private Collection $moves;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->moves = new Collection();
|
||||
}
|
||||
|
||||
public function firstMove(string $move): self
|
||||
{
|
||||
$this->validateMoveIsValid($move);
|
||||
$this->moves[0] = $move;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function secondMove(string $move): self
|
||||
{
|
||||
$this->validateMoveIsValid($move);
|
||||
$this->moves[1] = $move;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWinner(): ?string
|
||||
{
|
||||
if ($this->moves[0] == $this->moves[1]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$move = (new Collection(self::$possibleMoves))
|
||||
->first(function (array $move): bool {
|
||||
// Determine if the played moves match this combination of
|
||||
// possible moves.
|
||||
return $this->moves->diff($move['moves'])->isEmpty();
|
||||
});
|
||||
|
||||
return $move['winner'];
|
||||
}
|
||||
|
||||
private function validateMoveIsValid(string $move): void
|
||||
{
|
||||
Assert::that($move)
|
||||
->notEmpty('You must specify a move.')
|
||||
->inArray(
|
||||
[self::MOVE_ROCK, self::MOVE_PAPER, self::MOVE_SCISSORS],
|
||||
'You must enter a valid move. %s given.'
|
||||
);
|
||||
}
|
||||
}
|
50
php/pest/src/RomanNumeralsConverter.php
Normal file
50
php/pest/src/RomanNumeralsConverter.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Assert\Assert;
|
||||
|
||||
final class RomanNumeralsConverter
|
||||
{
|
||||
private static $map = [
|
||||
1000 => 'M',
|
||||
900 => 'CM',
|
||||
500 => 'D',
|
||||
400 => 'CD',
|
||||
100 => 'C',
|
||||
90 => 'XC',
|
||||
50 => 'L',
|
||||
40 => 'XL',
|
||||
10 => 'X',
|
||||
9 => 'IX',
|
||||
5 => 'V',
|
||||
4 => 'IV',
|
||||
3 => 'III',
|
||||
2 => 'II',
|
||||
1 => 'I',
|
||||
];
|
||||
|
||||
public static function convert(int $input): string
|
||||
{
|
||||
Assert::that($input)
|
||||
->greaterOrEqualThan(0, 'Cannot convert negative numbers');
|
||||
|
||||
$letters = '';
|
||||
|
||||
while ($input > 0) {
|
||||
foreach (static::$map as $number => $letter) {
|
||||
if ($input >= $number) {
|
||||
// Add the appropriate numeral and reduce the value of
|
||||
// $input accordingly.
|
||||
$letters .= $letter;
|
||||
$input = ($input - $number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $letters;
|
||||
}
|
||||
}
|
14
php/pest/tests/AnagramTest.php
Normal file
14
php/pest/tests/AnagramTest.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Anagram;
|
||||
|
||||
it('selects the correct anagrams for a word', function () {
|
||||
$matches = Anagram::forWord(
|
||||
'listen',
|
||||
['enlists', 'google', 'inlets', 'banana']
|
||||
);
|
||||
|
||||
assertSame(['inlets'], $matches->toArray());
|
||||
});
|
40
php/pest/tests/BobTest.php
Normal file
40
php/pest/tests/BobTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Bob;
|
||||
|
||||
it('replies with a standard response', function (): void {
|
||||
$input = 'Foo bar';
|
||||
$expected = Bob::RESPONSE_DEFAULT;
|
||||
|
||||
assertSame($expected, Bob::saySomethingTo($input)->getResponse());
|
||||
});
|
||||
|
||||
it('replies if you address him without saying anything', function (): void {
|
||||
$input = '';
|
||||
$expected = Bob::RESPONSE_SAY_NOTHING;
|
||||
|
||||
assertSame($expected, Bob::saySomethingTo($input)->getResponse());
|
||||
});
|
||||
|
||||
it('replies to a question', function (): void {
|
||||
$input = 'How are you?';
|
||||
$expected = Bob::RESPONSE_QUESTION;
|
||||
|
||||
assertSame($expected, Bob::saySomethingTo($input)->getResponse());
|
||||
});
|
||||
|
||||
it('replies to yelling', function (): void {
|
||||
$input = 'Go to bed!';
|
||||
$expected = Bob::RESPONSE_YELLING;
|
||||
|
||||
assertSame($expected, Bob::saySomethingTo($input)->getResponse());
|
||||
});
|
||||
|
||||
it('replies to yelling a question', function (): void {
|
||||
$input = 'Are you OK?!';
|
||||
$expected = Bob::RESPONSE_YELLING_QUESTION;
|
||||
|
||||
assertSame($expected, Bob::saySomethingTo($input)->getResponse());
|
||||
});
|
134
php/pest/tests/BowlingGameTest.php
Normal file
134
php/pest/tests/BowlingGameTest.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\BowlingGame;
|
||||
use Assert\Assert;
|
||||
use Assert\AssertionFailedException;
|
||||
|
||||
beforeEach(function (): void {
|
||||
$this->game = new BowlingGame();
|
||||
});
|
||||
|
||||
it('counts an empty game', function (): void {
|
||||
foreach (range(1, 20) as $roll) {
|
||||
$this->game->roll(0);
|
||||
}
|
||||
|
||||
assertSame(0, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('counts all single pins', function (): void {
|
||||
foreach (range(1, 20) as $roll) {
|
||||
$this->game->roll(1);
|
||||
}
|
||||
|
||||
assertSame(20, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('adds a roll one bonus for a spare', function (): void {
|
||||
$this->game->roll(5);
|
||||
$this->game->roll(5); // Spare
|
||||
|
||||
$this->game->roll(2);
|
||||
|
||||
foreach (range(1, 17) as $roll) {
|
||||
$this->game->roll(0);
|
||||
}
|
||||
|
||||
assertSame(14, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('adds a two roll bonus for a strike', function (): void {
|
||||
$this->game->roll(10); // Strike
|
||||
|
||||
$this->game->roll(3);
|
||||
$this->game->roll(6);
|
||||
|
||||
foreach (range(1, 16) as $roll) {
|
||||
$this->game->roll(0);
|
||||
}
|
||||
|
||||
assertSame(28, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('adds an extra ball for a spare on the final frame', function (): void {
|
||||
foreach (range(1, 18) as $roll) {
|
||||
$this->game->roll(0);
|
||||
}
|
||||
|
||||
$this->game->roll(7);
|
||||
$this->game->roll(3); // Spare
|
||||
|
||||
$this->game->roll(5);
|
||||
|
||||
assertSame(15, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('adds an two extra balls for a strike on the final frame', function (): void {
|
||||
foreach (range(1, 18) as $roll) {
|
||||
$this->game->roll(0);
|
||||
}
|
||||
|
||||
$this->game->roll(10); // Strike
|
||||
|
||||
$this->game->roll(4);
|
||||
$this->game->roll(2);
|
||||
|
||||
assertSame(16, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('scores a perfect game', function (): void {
|
||||
foreach (range(1, 12) as $roll) {
|
||||
$this->game->roll(10);
|
||||
}
|
||||
|
||||
assertSame(300, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('scores a normal game', function (): void {
|
||||
$this->game->roll(4);
|
||||
$this->game->roll(3);
|
||||
|
||||
$this->game->roll(10);
|
||||
|
||||
$this->game->roll(4);
|
||||
$this->game->roll(1);
|
||||
|
||||
$this->game->roll(0);
|
||||
$this->game->roll(2);
|
||||
|
||||
$this->game->roll(3);
|
||||
$this->game->roll(7);
|
||||
|
||||
$this->game->roll(8);
|
||||
$this->game->roll(1);
|
||||
|
||||
$this->game->roll(10);
|
||||
|
||||
$this->game->roll(0);
|
||||
$this->game->roll(7);
|
||||
|
||||
$this->game->roll(1);
|
||||
$this->game->roll(5);
|
||||
|
||||
$this->game->roll(10);
|
||||
$this->game->roll(6);
|
||||
$this->game->roll(1);
|
||||
|
||||
assertSame(103, $this->game->getScore());
|
||||
});
|
||||
|
||||
it('cannot knock down a negative pins', function (): void {
|
||||
$this->game->roll(-1);
|
||||
})->throws(
|
||||
AssertionFailedException::class,
|
||||
'You cannot knock down a negative number of pins. Knocked down -1.'
|
||||
);
|
||||
|
||||
it('cannot knock down more than 10 pins in a roll', function (): void {
|
||||
$this->game->roll(12);
|
||||
})->throws(
|
||||
AssertionFailedException::class,
|
||||
'You can only knock down a maximum of 10 pins in a roll. Knocked down 12.'
|
||||
);
|
31
php/pest/tests/FlattenArrayTest.php
Normal file
31
php/pest/tests/FlattenArrayTest.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function App\flattenArray;
|
||||
|
||||
it('flattens an array', function (
|
||||
array $input,
|
||||
array $expected
|
||||
): void {
|
||||
$result = flattenArray($input);
|
||||
|
||||
assertSame($expected, $result);
|
||||
})->with([
|
||||
[
|
||||
'input' => [1, 2, 3],
|
||||
'expected' => [1, 2, 3],
|
||||
],
|
||||
[
|
||||
'input' => [1, [2, 3, null, 4], [null], 5],
|
||||
'expected' => [1, 2, 3, 4, 5],
|
||||
],
|
||||
[
|
||||
'input' => [1, [2, [[3]], [4, [[5]]], 6, 7], 8],
|
||||
'expected' => [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
],
|
||||
[
|
||||
'input' => [null, [[[null]]], null, null, [[null, null], null], null],
|
||||
'expected' => [],
|
||||
]
|
||||
]);
|
83
php/pest/tests/GradeSchoolTest.php
Normal file
83
php/pest/tests/GradeSchoolTest.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\GradeSchool;
|
||||
use Assert\AssertionFailedException;
|
||||
|
||||
beforeEach(function (): void {
|
||||
$this->school = new GradeSchool();
|
||||
});
|
||||
|
||||
it('gets all students', function (): void {
|
||||
$this->school->addStudentToGrade('Anna', 1);
|
||||
$this->school->addStudentToGrade('Jim', 2);
|
||||
$this->school->addStudentToGrade('Charlie', 3);
|
||||
|
||||
$expected = [
|
||||
['grade' => 1, 'students' => ['Anna']],
|
||||
['grade' => 2, 'students' => ['Jim']],
|
||||
['grade' => 3, 'students' => ['Charlie']],
|
||||
];
|
||||
|
||||
assertSame($expected, $this->school->getAllStudents()->toArray());
|
||||
});
|
||||
|
||||
it('can get students by grade', function (): void {
|
||||
$this->school->addStudentToGrade('Jim', 2);
|
||||
|
||||
assertSame(['Jim'], $this->school->getStudentsByGrade(2)->toArray());
|
||||
});
|
||||
|
||||
it('should order grades numerically', function (): void {
|
||||
$this->school->addStudentToGrade('Anna', 1);
|
||||
$this->school->addStudentToGrade('Charlie', 3);
|
||||
$this->school->addStudentToGrade('Jim', 2);
|
||||
$this->school->addStudentToGrade('Harry', 4);
|
||||
|
||||
$expected = [
|
||||
['grade' => 1, 'students' => ['Anna']],
|
||||
['grade' => 2, 'students' => ['Jim']],
|
||||
['grade' => 3, 'students' => ['Charlie']],
|
||||
['grade' => 4, 'students' => ['Harry']],
|
||||
];
|
||||
|
||||
assertSame($expected, $this->school->getAllStudents()->toArray());
|
||||
});
|
||||
|
||||
it('names within a grade are ordered alphabetically', function (): void {
|
||||
$this->school->addStudentToGrade('Charlie', 1);
|
||||
$this->school->addStudentToGrade('Barb', 1);
|
||||
$this->school->addStudentToGrade('Anna', 1);
|
||||
|
||||
$this->school->addStudentToGrade('Peter', 2);
|
||||
$this->school->addStudentToGrade('Alex', 2);
|
||||
$this->school->addStudentToGrade('Zoe', 2);
|
||||
|
||||
$expected = [
|
||||
[
|
||||
'grade' => 1,
|
||||
'students' => ['Anna', 'Barb', 'Charlie'],
|
||||
],
|
||||
[
|
||||
'grade' => 2,
|
||||
'students' => ['Alex', 'Peter', 'Zoe'],
|
||||
]
|
||||
];
|
||||
|
||||
assertSame($expected, $this->school->getAllStudents()->toArray());
|
||||
});
|
||||
|
||||
test('name cannot be empty', function (): void {
|
||||
$this->school->addStudentToGrade('', 1);
|
||||
})->throws(
|
||||
AssertionFailedException::class,
|
||||
'Name cannot be blank'
|
||||
);
|
||||
|
||||
test('grade cannot be zero or negative', function (int $grade): void {
|
||||
$this->school->addStudentToGrade('Fred', $grade);
|
||||
})->with([0, -1])->throws(
|
||||
AssertionFailedException::class,
|
||||
'Grade must be greater than or equal to 1'
|
||||
);
|
19
php/pest/tests/PangramTest.php
Normal file
19
php/pest/tests/PangramTest.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
use function App\isPangram;
|
||||
|
||||
it('determines if a string is a pangram', function (
|
||||
string $input,
|
||||
bool $expected
|
||||
) {
|
||||
assertSame($expected, isPangram($input));
|
||||
})->with([
|
||||
[
|
||||
'input' => 'hello word',
|
||||
'expected' => false,
|
||||
],
|
||||
[
|
||||
'input' => 'The quick brown fox jumps over the lazy dog',
|
||||
'expected' => true,
|
||||
]
|
||||
]);
|
72
php/pest/tests/RockPaperScissorsTest.php
Normal file
72
php/pest/tests/RockPaperScissorsTest.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
use Assert\AssertionFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->game = new App\RockPaperScissorsGame();
|
||||
});
|
||||
|
||||
it('throws an exception for an empty first move', function () {
|
||||
$this->game->firstMove('');
|
||||
})->throws(AssertionFailedException::class, 'You must specify a move.');
|
||||
|
||||
it('throws an exception for an empty second move', function () {
|
||||
$this->game->secondMove('');
|
||||
})->throws(AssertionFailedException::class, 'You must specify a move.');
|
||||
|
||||
it('throws an exception if an invalid first move is entered', function () {
|
||||
$this->game->firstMove('banana');
|
||||
})->throws(AssertionFailedException::class, 'You must enter a valid move. banana given.');
|
||||
|
||||
it('throws an exception if an invalid second move is entered', function () {
|
||||
$this->game->secondMove('apple');
|
||||
})->throws(AssertionFailedException::class, 'You must enter a valid move. apple given.');
|
||||
|
||||
it('returns the result of two different valid moves', function (
|
||||
string $firstMove,
|
||||
string $secondMove,
|
||||
string $winner
|
||||
) {
|
||||
$this->game
|
||||
->firstMove($firstMove)
|
||||
->secondMove($secondMove);
|
||||
|
||||
assertSame($winner, $this->game->getWinner());
|
||||
})->with([
|
||||
[
|
||||
'firstMove' => 'rock',
|
||||
'secondMove' => 'paper',
|
||||
'winner' => 'paper',
|
||||
],
|
||||
[
|
||||
'firstMove' => 'paper',
|
||||
'secondMove' => 'rock',
|
||||
'winner' => 'paper',
|
||||
],
|
||||
[
|
||||
'firstMove' => 'rock',
|
||||
'secondMove' => 'scissors',
|
||||
'winner' => 'scissors',
|
||||
],
|
||||
[
|
||||
'firstMove' => 'scissors',
|
||||
'secondMove' => 'rock',
|
||||
'winner' => 'scissors',
|
||||
],
|
||||
[
|
||||
'firstMove' => 'scissors',
|
||||
'secondMove' => 'paper',
|
||||
'winner' => 'scissors',
|
||||
],
|
||||
[
|
||||
'firstMove' => 'paper',
|
||||
'secondMove' => 'scissors',
|
||||
'winner' => 'scissors',
|
||||
],
|
||||
]);
|
||||
|
||||
test('it returns null if there is a tie', function (string $move) {
|
||||
$this->game->firstMove($move)->secondMove($move);
|
||||
|
||||
assertNull($this->game->getWinner());
|
||||
})->with(['rock', 'paper', 'scissors']);
|
40
php/pest/tests/RomanNumeralsConverterTest.php
Normal file
40
php/pest/tests/RomanNumeralsConverterTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\RomanNumeralsConverter;
|
||||
use Assert\AssertionFailedException;
|
||||
|
||||
it('converts numbers to roman numerals', function (int $number, string $expected): void {
|
||||
assertSame($expected, RomanNumeralsConverter::convert($number));
|
||||
})->with([
|
||||
[1, 'I'],
|
||||
[2, 'II'],
|
||||
[3, 'III'],
|
||||
[4, 'IV'],
|
||||
[5, 'V'],
|
||||
[9, 'IX'],
|
||||
[10, 'X'],
|
||||
[15, 'XV'],
|
||||
[19, 'XIX'],
|
||||
[20, 'XX'],
|
||||
[21, 'XXI'],
|
||||
[40, 'XL'],
|
||||
[50, 'L'],
|
||||
[80, 'LXXX'],
|
||||
[90, 'XC'],
|
||||
[100, 'C'],
|
||||
[110, 'CX'],
|
||||
[400, 'CD'],
|
||||
[500, 'D'],
|
||||
[700, 'DCC'],
|
||||
[900, 'CM'],
|
||||
[1000, 'M'],
|
||||
[1986, 'MCMLXXXVI'],
|
||||
[1990, 'MCMXC'],
|
||||
[2020, 'MMXX'],
|
||||
]);
|
||||
|
||||
it('cannot convert negative numbers', function (): void {
|
||||
RomanNumeralsConverter::convert(-1);
|
||||
})->throws(AssertionFailedException::class, 'Cannot convert negative numbers');
|
Loading…
Add table
Add a link
Reference in a new issue