mirror of
https://github.com/rjNemo/exercism-elixir
synced 2026-06-06 02:16:48 +00:00
chessboard
This commit is contained in:
parent
3b7a535a6e
commit
c6177f9612
34 changed files with 1151 additions and 0 deletions
27
all-your-base/.exercism/config.json
Normal file
27
all-your-base/.exercism/config.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.",
|
||||
"authors": [
|
||||
"ananthamapod"
|
||||
],
|
||||
"contributors": [
|
||||
"angelikatyborska",
|
||||
"ChristianTovar",
|
||||
"Cohen-Carlisle",
|
||||
"devonestes",
|
||||
"neenjaw",
|
||||
"parkerl",
|
||||
"sotojuan",
|
||||
"ybod"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"lib/all_your_base.ex"
|
||||
],
|
||||
"test": [
|
||||
"test/all_your_base_test.exs"
|
||||
],
|
||||
"example": [
|
||||
".meta/example.ex"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
all-your-base/.exercism/metadata.json
Normal file
1
all-your-base/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"track":"elixir","exercise":"all-your-base","id":"7674c05295ec47618e00a878143e5422","url":"https://exercism.org/tracks/elixir/exercises/all-your-base","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||
4
all-your-base/.formatter.exs
Normal file
4
all-your-base/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
24
all-your-base/.gitignore
vendored
Normal file
24
all-your-base/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
all_your_base-*.tar
|
||||
|
||||
75
all-your-base/HELP.md
Normal file
75
all-your-base/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Help
|
||||
|
||||
## Running the tests
|
||||
|
||||
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||
|
||||
```bash
|
||||
$ mix test
|
||||
```
|
||||
|
||||
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||
|
||||
Documentation:
|
||||
|
||||
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||
|
||||
## Pending tests
|
||||
|
||||
In test suites of practice exercises, all but the first test have been tagged to be skipped.
|
||||
|
||||
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
|
||||
|
||||
For example:
|
||||
|
||||
```elixir
|
||||
# @tag :pending
|
||||
test "shouting" do
|
||||
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
|
||||
end
|
||||
```
|
||||
|
||||
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||
|
||||
```bash
|
||||
$ mix test --include pending
|
||||
```
|
||||
|
||||
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||
|
||||
```elixir
|
||||
# ExUnit.configure(exclude: :pending, trace: true)
|
||||
```
|
||||
|
||||
## Useful `mix test` options
|
||||
|
||||
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||
* `--failed` - runs only tests that failed the last time they ran
|
||||
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||
is reached
|
||||
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||
in the same order they were defined in
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
You can submit your solution using the `exercism submit lib/all_your_base.ex` command.
|
||||
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||
|
||||
It's possible to submit an incomplete solution which allows you to:
|
||||
|
||||
- See how others have completed the exercise
|
||||
- Request help from a mentor
|
||||
|
||||
## Need to get help?
|
||||
|
||||
If you'd like help solving the exercise, check the following pages:
|
||||
|
||||
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||
|
||||
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||
|
||||
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||
54
all-your-base/README.md
Normal file
54
all-your-base/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# All Your Base
|
||||
|
||||
Welcome to All Your Base on Exercism's Elixir Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
|
||||
## Instructions
|
||||
|
||||
Convert a number, represented as a sequence of digits in one base, to any other base.
|
||||
|
||||
Implement general base conversion. Given a number in base **a**,
|
||||
represented as a sequence of digits, convert it to base **b**.
|
||||
|
||||
## Note
|
||||
|
||||
- Try to implement the conversion yourself.
|
||||
Do not use something else to perform the conversion for you.
|
||||
|
||||
## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation)
|
||||
|
||||
In positional notation, a number in base **b** can be understood as a linear
|
||||
combination of powers of **b**.
|
||||
|
||||
The number 42, *in base 10*, means:
|
||||
|
||||
(4 \* 10^1) + (2 \* 10^0)
|
||||
|
||||
The number 101010, *in base 2*, means:
|
||||
|
||||
(1 \* 2^5) + (0 \* 2^4) + (1 \* 2^3) + (0 \* 2^2) + (1 \* 2^1) + (0 \* 2^0)
|
||||
|
||||
The number 1120, *in base 3*, means:
|
||||
|
||||
(1 \* 3^3) + (1 \* 3^2) + (2 \* 3^1) + (0 \* 3^0)
|
||||
|
||||
I think you got the idea!
|
||||
|
||||
*Yes. Those three numbers above are exactly the same. Congratulations!*
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @ananthamapod
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @angelikatyborska
|
||||
- @ChristianTovar
|
||||
- @Cohen-Carlisle
|
||||
- @devonestes
|
||||
- @neenjaw
|
||||
- @parkerl
|
||||
- @sotojuan
|
||||
- @ybod
|
||||
27
all-your-base/lib/all_your_base.ex
Normal file
27
all-your-base/lib/all_your_base.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
defmodule AllYourBase do
|
||||
@doc """
|
||||
Given a number in input base, represented as a sequence of digits, converts it to output base,
|
||||
or returns an error tuple if either of the bases are less than 2
|
||||
"""
|
||||
|
||||
@spec convert(list, integer, integer) :: {:ok, list} | {:error, String.t()}
|
||||
def convert(digits, input_base, 10), do: {:ok, [to_decimal(digits, input_base)]}
|
||||
|
||||
def convert(digits, input_base, output_base) do
|
||||
number = to_decimal(digits, input_base)
|
||||
do_convert(digits, input_base, output_base,[rem(number, output_base)])
|
||||
end
|
||||
|
||||
defp do_convert(digits,input_base, output_base,converted) do
|
||||
|
||||
|
||||
end
|
||||
|
||||
defp to_decimal(digits, input_base) do
|
||||
n = length(digits)
|
||||
|
||||
for {d, i} <- Enum.with_index(digits), reduce: 0 do
|
||||
acc -> d * input_base ** (n - i - 1) + acc
|
||||
end
|
||||
end
|
||||
end
|
||||
28
all-your-base/mix.exs
Normal file
28
all-your-base/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
defmodule AllYourBase.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :all_your_base,
|
||||
version: "0.1.0",
|
||||
# elixir: "~> 1.8",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
end
|
||||
end
|
||||
107
all-your-base/test/all_your_base_test.exs
Normal file
107
all-your-base/test/all_your_base_test.exs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
defmodule AllYourBaseTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "convert single bit one to decimal" do
|
||||
assert AllYourBase.convert([1], 2, 10) == {:ok, [1]}
|
||||
end
|
||||
|
||||
test "convert binary to single decimal" do
|
||||
assert AllYourBase.convert([1, 0, 1], 2, 10) == {:ok, [5]}
|
||||
end
|
||||
|
||||
test "convert single decimal to binary" do
|
||||
assert AllYourBase.convert([5], 10, 2) == {:ok, [1, 0, 1]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert binary to multiple decimal" do
|
||||
assert AllYourBase.convert([1, 0, 1, 0, 1, 0], 2, 10) == {:ok, [4, 2]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert decimal to binary" do
|
||||
assert AllYourBase.convert([4, 2], 10, 2) == {:ok, [1, 0, 1, 0, 1, 0]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert trinary to hexadecimal" do
|
||||
assert AllYourBase.convert([1, 1, 2, 0], 3, 16) == {:ok, [2, 10]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert hexadecimal to trinary" do
|
||||
assert AllYourBase.convert([2, 10], 16, 3) == {:ok, [1, 1, 2, 0]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert 15-bit integer" do
|
||||
assert AllYourBase.convert([3, 46, 60], 97, 73) == {:ok, [6, 10, 45]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert empty list" do
|
||||
assert AllYourBase.convert([], 2, 10) == {:ok, [0]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert single zero" do
|
||||
assert AllYourBase.convert([0], 10, 2) == {:ok, [0]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert multiple zeros" do
|
||||
assert AllYourBase.convert([0, 0, 0], 10, 2) == {:ok, [0]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert leading zeros" do
|
||||
assert AllYourBase.convert([0, 6, 0], 7, 10) == {:ok, [4, 2]}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert first base is one" do
|
||||
assert AllYourBase.convert([0], 1, 10) == {:error, "input base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert first base is zero" do
|
||||
assert AllYourBase.convert([], 0, 10) == {:error, "input base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert first base is negative" do
|
||||
assert AllYourBase.convert([1], -2, 10) == {:error, "input base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert negative digit" do
|
||||
assert AllYourBase.convert([1, -1, 1, 0, 1, 0], 2, 10) ==
|
||||
{:error, "all digits must be >= 0 and < input base"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert invalid positive digit" do
|
||||
assert AllYourBase.convert([1, 2, 1, 0, 1, 0], 2, 10) ==
|
||||
{:error, "all digits must be >= 0 and < input base"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert second base is one" do
|
||||
assert AllYourBase.convert([1, 0, 1, 0, 1, 0], 2, 1) == {:error, "output base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert second base is zero" do
|
||||
assert AllYourBase.convert([7], 10, 0) == {:error, "output base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert second base is negative" do
|
||||
assert AllYourBase.convert([1], 2, -7) == {:error, "output base must be >= 2"}
|
||||
end
|
||||
|
||||
@tag :pending
|
||||
test "convert both bases are negative" do
|
||||
assert AllYourBase.convert([1], -2, -7) == {:error, "output base must be >= 2"}
|
||||
end
|
||||
end
|
||||
2
all-your-base/test/test_helper.exs
Normal file
2
all-your-base/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ExUnit.start()
|
||||
ExUnit.configure(exclude: :pending, trace: true)
|
||||
21
chessboard/.exercism/config.json
Normal file
21
chessboard/.exercism/config.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"blurb": "Learn about ranges by generating a chessboard.",
|
||||
"authors": [
|
||||
"angelikatyborska"
|
||||
],
|
||||
"contributors": [
|
||||
"neenjaw"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"lib/chessboard.ex"
|
||||
],
|
||||
"test": [
|
||||
"test/chessboard_test.exs"
|
||||
],
|
||||
"exemplar": [
|
||||
".meta/exemplar.ex"
|
||||
]
|
||||
},
|
||||
"language_versions": ">=1.10"
|
||||
}
|
||||
1
chessboard/.exercism/metadata.json
Normal file
1
chessboard/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"track":"elixir","exercise":"chessboard","id":"5ffb191bbfc94a8eadae73a4626d115e","url":"https://exercism.org/tracks/elixir/exercises/chessboard","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||
4
chessboard/.formatter.exs
Normal file
4
chessboard/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
24
chessboard/.gitignore
vendored
Normal file
24
chessboard/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
match_binary-*.tar
|
||||
|
||||
75
chessboard/HELP.md
Normal file
75
chessboard/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Help
|
||||
|
||||
## Running the tests
|
||||
|
||||
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||
|
||||
```bash
|
||||
$ mix test
|
||||
```
|
||||
|
||||
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||
|
||||
Documentation:
|
||||
|
||||
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||
|
||||
## Pending tests
|
||||
|
||||
In test suites of practice exercises, all but the first test have been tagged to be skipped.
|
||||
|
||||
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
|
||||
|
||||
For example:
|
||||
|
||||
```elixir
|
||||
# @tag :pending
|
||||
test "shouting" do
|
||||
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
|
||||
end
|
||||
```
|
||||
|
||||
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||
|
||||
```bash
|
||||
$ mix test --include pending
|
||||
```
|
||||
|
||||
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||
|
||||
```elixir
|
||||
# ExUnit.configure(exclude: :pending, trace: true)
|
||||
```
|
||||
|
||||
## Useful `mix test` options
|
||||
|
||||
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||
* `--failed` - runs only tests that failed the last time they ran
|
||||
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||
is reached
|
||||
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||
in the same order they were defined in
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
You can submit your solution using the `exercism submit lib/chessboard.ex` command.
|
||||
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||
|
||||
It's possible to submit an incomplete solution which allows you to:
|
||||
|
||||
- See how others have completed the exercise
|
||||
- Request help from a mentor
|
||||
|
||||
## Need to get help?
|
||||
|
||||
If you'd like help solving the exercise, check the following pages:
|
||||
|
||||
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||
|
||||
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||
|
||||
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||
32
chessboard/HINTS.md
Normal file
32
chessboard/HINTS.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Hints
|
||||
|
||||
## General
|
||||
|
||||
- Read the official documentation for [ranges][range].
|
||||
|
||||
## 1. Define the rank range
|
||||
|
||||
- There is a [special operator][range-creation-operator] for creating ranges.
|
||||
|
||||
## 2. Define the file range
|
||||
|
||||
- There is a [special operator][range-creation-operator] for creating ranges.
|
||||
- There is a [special syntax][unicode-code-points] to write a character code point without explicitly knowing its value.
|
||||
|
||||
## 3. Transform the rank range into a list of ranks
|
||||
|
||||
- Ranges implement the `Enumerable` protocol.
|
||||
- There is a [built-in function][enum-to-list] to change an enumerable data structure to a list.
|
||||
|
||||
## 4. Transform the file range into a list of files
|
||||
|
||||
- Ranges implement the `Enumerable` protocol.
|
||||
- There is a [built-in function][enum-map] to change an enumerable data structure to a list while modifying its elements.
|
||||
- The [bitstring special form][bitstring-special-form] can be used to turn a code point into a string.
|
||||
|
||||
[range]: https://hexdocs.pm/elixir/Range.html
|
||||
[range-creation-operator]: https://hexdocs.pm/elixir/Kernel.html#../2
|
||||
[unicode-code-points]: https://hexdocs.pm/elixir/syntax-reference.html#integers-in-other-bases-and-unicode-code-points
|
||||
[enum-to-list]: https://hexdocs.pm/elixir/Enum.html#to_list/1
|
||||
[enum-map]: https://hexdocs.pm/elixir/Enum.html#map/2
|
||||
[bitstring-special-form]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1
|
||||
71
chessboard/README.md
Normal file
71
chessboard/README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Chessboard
|
||||
|
||||
Welcome to Chessboard on Exercism's Elixir Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||
|
||||
## Introduction
|
||||
|
||||
## Ranges
|
||||
|
||||
Ranges represent a sequence of one or many consecutive integers. They are created by connecting two integers with `..`.
|
||||
|
||||
```elixir
|
||||
1..5
|
||||
```
|
||||
|
||||
Ranges can be ascending or descending. They are always inclusive of the first and last values.
|
||||
|
||||
A range implements the _Enumerable protocol_, which means functions in the `Enum` module can be used to work with ranges.
|
||||
|
||||
## Instructions
|
||||
|
||||
As a chess enthusiast, you would like to write your own version of the game. Yes, there maybe plenty of implementations of chess available online already, but yours will be unique!
|
||||
|
||||
But before you can let your imagination run wild, you need to take care of the basics. Let's start by generating the board.
|
||||
|
||||
Each square of the chessboard is identified by a letter-number pair. The vertical columns of squares, called files, are labeled A through H. The horizontal rows of squares, called ranks, are numbered 1 to 8.
|
||||
|
||||
## 1. Define the rank range
|
||||
|
||||
Implement the `rank_range/0` function. It should return a range of integers, from 1 to 8.
|
||||
|
||||
```elixir
|
||||
Chessboard.rank_range()
|
||||
```
|
||||
|
||||
## 2. Define the file range
|
||||
|
||||
Implement the `file_range/0` function. It should return a range of integers, from the code point of the uppercase letter A, to the code point of the uppercase letter H.
|
||||
|
||||
```elixir
|
||||
Chessboard.file_range()
|
||||
```
|
||||
|
||||
## 3. Transform the rank range into a list of ranks
|
||||
|
||||
Implement the `ranks/0` function. It should return a list of integers, from 1 to 8. Do not write the list by hand, generate it from the range returned by the `rank_range/0` function.
|
||||
|
||||
```elixir
|
||||
Chessboard.ranks()
|
||||
# => [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
```
|
||||
|
||||
## 4. Transform the file range into a list of files
|
||||
|
||||
Implement the `files/0` function. It should return a list of letters (strings), from "A" to "H". Do not write the list by hand, generate it from the range returned by the `file_range/0` function.
|
||||
|
||||
```elixir
|
||||
Chessboard.files()
|
||||
# => ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @angelikatyborska
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @neenjaw
|
||||
11
chessboard/lib/chessboard.ex
Normal file
11
chessboard/lib/chessboard.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Chessboard do
|
||||
def rank_range, do: 1..8
|
||||
|
||||
def file_range, do: ?A..?H
|
||||
|
||||
def ranks, do: Enum.to_list(rank_range())
|
||||
|
||||
def files do
|
||||
file_range() |> Enum.map(&<<&1>>)
|
||||
end
|
||||
end
|
||||
28
chessboard/mix.exs
Normal file
28
chessboard/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
defmodule Chessboard.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :chessboard,
|
||||
version: "0.1.0",
|
||||
# elixir: "~> 1.10",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
end
|
||||
end
|
||||
23
chessboard/test/chessboard_test.exs
Normal file
23
chessboard/test/chessboard_test.exs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
defmodule ChessboardTest do
|
||||
use ExUnit.Case
|
||||
|
||||
@tag task_id: 1
|
||||
test "rank_range is a range from 1 to 8" do
|
||||
assert Chessboard.rank_range() == 1..8
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "file_range is a range from ?A to ?H" do
|
||||
assert Chessboard.file_range() == ?A..?H
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "ranks is a list of integers from 1 to 8" do
|
||||
assert Chessboard.ranks() == [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
end
|
||||
|
||||
@tag task_id: 4
|
||||
test "files is a list of letters (strings) from A to H" do
|
||||
assert Chessboard.files() == ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
end
|
||||
end
|
||||
2
chessboard/test/test_helper.exs
Normal file
2
chessboard/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ExUnit.start()
|
||||
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
||||
21
newsletter/.exercism/config.json
Normal file
21
newsletter/.exercism/config.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"blurb": "Learn about working with files by sending out a newsletter.",
|
||||
"authors": [
|
||||
"angelikatyborska"
|
||||
],
|
||||
"contributors": [
|
||||
"neenjaw"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"lib/newsletter.ex"
|
||||
],
|
||||
"test": [
|
||||
"test/newsletter_test.exs"
|
||||
],
|
||||
"exemplar": [
|
||||
".meta/exemplar.ex"
|
||||
]
|
||||
},
|
||||
"language_versions": ">=1.10"
|
||||
}
|
||||
1
newsletter/.exercism/metadata.json
Normal file
1
newsletter/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"track":"elixir","exercise":"newsletter","id":"ce997462e3674d26b979803a6147c540","url":"https://exercism.org/tracks/elixir/exercises/newsletter","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||
4
newsletter/.formatter.exs
Normal file
4
newsletter/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
24
newsletter/.gitignore
vendored
Normal file
24
newsletter/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
match_binary-*.tar
|
||||
|
||||
75
newsletter/HELP.md
Normal file
75
newsletter/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Help
|
||||
|
||||
## Running the tests
|
||||
|
||||
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||
|
||||
```bash
|
||||
$ mix test
|
||||
```
|
||||
|
||||
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||
|
||||
Documentation:
|
||||
|
||||
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||
|
||||
## Pending tests
|
||||
|
||||
In test suites of practice exercises, all but the first test have been tagged to be skipped.
|
||||
|
||||
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
|
||||
|
||||
For example:
|
||||
|
||||
```elixir
|
||||
# @tag :pending
|
||||
test "shouting" do
|
||||
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
|
||||
end
|
||||
```
|
||||
|
||||
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||
|
||||
```bash
|
||||
$ mix test --include pending
|
||||
```
|
||||
|
||||
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||
|
||||
```elixir
|
||||
# ExUnit.configure(exclude: :pending, trace: true)
|
||||
```
|
||||
|
||||
## Useful `mix test` options
|
||||
|
||||
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||
* `--failed` - runs only tests that failed the last time they ran
|
||||
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||
is reached
|
||||
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||
in the same order they were defined in
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
You can submit your solution using the `exercism submit lib/newsletter.ex` command.
|
||||
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||
|
||||
It's possible to submit an incomplete solution which allows you to:
|
||||
|
||||
- See how others have completed the exercise
|
||||
- Request help from a mentor
|
||||
|
||||
## Need to get help?
|
||||
|
||||
If you'd like help solving the exercise, check the following pages:
|
||||
|
||||
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||
|
||||
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||
|
||||
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||
40
newsletter/HINTS.md
Normal file
40
newsletter/HINTS.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Hints
|
||||
|
||||
## 1. General
|
||||
|
||||
- Read about files in the official [Getting Started guide][getting-started-file].
|
||||
- Read about files on [joyofelixir.com][joy-of-elixir-file].
|
||||
- Take a look at the [documentation of the `File` module][file].
|
||||
|
||||
## 1. Read email addresses from a file
|
||||
|
||||
- There is a [built-in function][file-read] for reading the contents of a file all at once.
|
||||
|
||||
## 2. Open a log file for writing
|
||||
|
||||
- There is a [built-in function][file-open] for opening a file.
|
||||
- The second argument of that function is a list of modes which allows specifying that the file should be opened for writing.
|
||||
|
||||
## 3. Log a sent email
|
||||
|
||||
- Functions for reading and writing to a file opened with [`File.open!/1`][file-open] can be found in the [`IO`][io] module.
|
||||
- There is a [built-in function][io-puts] for writing a string to a file, followed by a newline.
|
||||
|
||||
## 4. Close the log file
|
||||
|
||||
- There is a [built-in function][file-close] for closing a file.
|
||||
|
||||
## 5. Send the newsletter
|
||||
|
||||
- All the necessary operations on files were already implemented in the previous steps.
|
||||
- Before writing to a file, the file must be opened.
|
||||
- After all write operations to a file finished, the file should be closed.
|
||||
|
||||
[getting-started-file]: https://elixir-lang.org/getting-started/io-and-the-file-system.html#the-file-module
|
||||
[joy-of-elixir-file]: https://joyofelixir.com/11-files/
|
||||
[file]: https://hexdocs.pm/elixir/File.html
|
||||
[file-read]: https://hexdocs.pm/elixir/File.html#read!/1
|
||||
[file-open]: https://hexdocs.pm/elixir/File.html#open!/1
|
||||
[file-close]: https://hexdocs.pm/elixir/File.html#close/1
|
||||
[io]: https://hexdocs.pm/elixir/IO.html
|
||||
[io-puts]: https://hexdocs.pm/elixir/IO.html#puts/2
|
||||
90
newsletter/README.md
Normal file
90
newsletter/README.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Newsletter
|
||||
|
||||
Welcome to Newsletter on Exercism's Elixir Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||
|
||||
## Introduction
|
||||
|
||||
## File
|
||||
|
||||
Functions for working with files are provided by the `File` module.
|
||||
|
||||
To read a whole file, use `File.read/1`. To write to a file, use `File.write/2`.
|
||||
|
||||
Every time a file is written to with `File.write/2`, a file descriptor is opened and a new Elixir process is spawned. For this reason, writing to a file in a loop using `File.write/2` should be avoided.
|
||||
|
||||
Instead, a file can be opened using `File.open/2`. The second argument to `File.open/2` is a list of modes, which allows you to specify if you want to open the file for reading or for writing.
|
||||
|
||||
`File.open/2` returns a PID of a process that handles the file. To read and write to the file, use functions from the `IO` module and pass this PID as the IO device.
|
||||
|
||||
When you're finished working with the file, close it with `File.close/1`.
|
||||
|
||||
All the mentioned functions from the `File` module also have a `!` variant that raises an error instead of returning an error tuple (e.g. `File.read!/1`). Use that variant if you don't intend to handle errors such as missing files or lack of permissions.
|
||||
|
||||
## Instructions
|
||||
|
||||
You're a big model train enthusiast and have decided to share your passion with the world by starting a newsletter. You'll start by sending the first issue of your newsletter to your friends and acquaintances that share your hobby. You have a text file with a list of their email addresses.
|
||||
|
||||
## 1. Read email addresses from a file
|
||||
|
||||
Implement the `Newsletter.read_emails/1` function. It should take a file path. The file is a text file that contains email addresses separated by newlines. The function should return a list of the email addresses from the file.
|
||||
|
||||
```elixir
|
||||
Newsletter.read_emails("/home/my_user/documents/model_train_friends_emails.txt")
|
||||
# => ["rick@example.com", "choochoo42@example.com", "anna@example.com"]
|
||||
```
|
||||
|
||||
## 2. Open a log file for writing
|
||||
|
||||
Sending an email is a task that might fail for many unpredictable reasons, like a typo in the email address or temporary network issues. To ensure that you can retry sending the emails to all your friends without sending duplicates, you need to log the email addresses that already received the email. For this, you'll need a log file.
|
||||
|
||||
Implement the `Newsletter.open_log/1` function. It should take a file path, open the file for writing, and return the PID of the process that handles the file.
|
||||
|
||||
```elixir
|
||||
Newsletter.open_log("/home/my_user/documents/newsletter_issue1_log.txt")
|
||||
# => #PID<0.145.0>
|
||||
```
|
||||
|
||||
## 3. Log a sent email
|
||||
|
||||
Implement the `Newsletter.log_sent_email/2` function. It should take a PID of the process that handles the file and a string with the email address. It should write the email address to the file, followed by a newline.
|
||||
|
||||
```elixir
|
||||
Newsletter.log_sent_email(pid, "joe@example.com")
|
||||
# => :ok
|
||||
```
|
||||
|
||||
## 4. Close the log file
|
||||
|
||||
Implement the `Newsletter.close_log/1` function. It should take a PID of the process that handles the file and close the file.
|
||||
|
||||
```elixir
|
||||
Newsletter.close_log(pid)
|
||||
# => :ok
|
||||
```
|
||||
|
||||
## 5. Send the newsletter
|
||||
|
||||
Now that you have all of the building blocks of the email sending procedure, you need to combine them together in a single function.
|
||||
|
||||
Implement the `Newsletter.send_newsletter/3` function. It should take a path of the file with email addresses, a path of a log file, and an anonymous function that sends an email to a given email address. It should read all the email addresses from the given file and attempt to send an email to every one of them. If the anonymous function that sends the email returns `:ok`, write the email address to the log file, followed by a new line. Make sure to do it as soon as the email is sent. Afterwards, close the log file.
|
||||
|
||||
```elixir
|
||||
Newsletter.send_newsletter(
|
||||
"model_train_friends_emails.txt",
|
||||
"newsletter_issue1_log.txt",
|
||||
fn email -> :ok end
|
||||
)
|
||||
# => :ok
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @angelikatyborska
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @neenjaw
|
||||
4
newsletter/assets/emails.txt
Normal file
4
newsletter/assets/emails.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
alice@example.com
|
||||
bob@example.com
|
||||
charlie@example.com
|
||||
dave@example.com
|
||||
0
newsletter/assets/empty.txt
Normal file
0
newsletter/assets/empty.txt
Normal file
15
newsletter/lib/newsletter.ex
Normal file
15
newsletter/lib/newsletter.ex
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
defmodule Newsletter do
|
||||
def read_emails(path), do: File.read!(path) |> String.split("\n", trim: true)
|
||||
def open_log(path), do: File.open!(path, [:write])
|
||||
def log_sent_email(pid, email), do: IO.puts(pid, email)
|
||||
def close_log(pid), do: File.close(pid)
|
||||
|
||||
def send_newsletter(emails_path, log_path, send_fun) do
|
||||
pid = open_log(log_path)
|
||||
|
||||
read_emails(emails_path)
|
||||
|> Enum.each(&(send_fun.(&1) == :ok and log_sent_email(pid, &1)))
|
||||
|
||||
close_log(pid)
|
||||
end
|
||||
end
|
||||
28
newsletter/mix.exs
Normal file
28
newsletter/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
defmodule Newsletter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :newsletter,
|
||||
version: "0.1.0",
|
||||
# elixir: "~> 1.10",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
end
|
||||
end
|
||||
206
newsletter/test/newsletter_test.exs
Normal file
206
newsletter/test/newsletter_test.exs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
defmodule NewsletterTest do
|
||||
# run test synchronously to be able to use the same file path for all tests without write conflicts
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@temp_file_path Path.join(["assets", "temp.txt"])
|
||||
|
||||
setup do
|
||||
File.write!(@temp_file_path, "")
|
||||
on_exit(fn -> File.rm!(@temp_file_path) end)
|
||||
end
|
||||
|
||||
describe "read_emails" do
|
||||
@tag task_id: 1
|
||||
test "returns a list of all lines in a file" do
|
||||
emails_file_path = Path.join(["assets", "emails.txt"])
|
||||
|
||||
assert Newsletter.read_emails(emails_file_path) == [
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
"charlie@example.com",
|
||||
"dave@example.com"
|
||||
]
|
||||
end
|
||||
|
||||
@tag task_id: 1
|
||||
test "returns an empty list if the file is empty" do
|
||||
empty_file_path = Path.join(["assets", "empty.txt"])
|
||||
assert Newsletter.read_emails(empty_file_path) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "open_log" do
|
||||
@tag task_id: 2
|
||||
test "returns a pid" do
|
||||
file = Newsletter.open_log(@temp_file_path)
|
||||
assert is_pid(file)
|
||||
File.close(file)
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "opens the file for writing" do
|
||||
file = Newsletter.open_log(@temp_file_path)
|
||||
assert IO.write(file, "hello") == :ok
|
||||
assert File.read!(@temp_file_path) == "hello"
|
||||
File.close(file)
|
||||
end
|
||||
end
|
||||
|
||||
describe "log_sent_email" do
|
||||
@tag task_id: 3
|
||||
test "returns ok" do
|
||||
file = File.open!(@temp_file_path, [:write])
|
||||
assert Newsletter.log_sent_email(file, "janice@example.com") == :ok
|
||||
File.close(file)
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "writes the email address to the given file" do
|
||||
file = File.open!(@temp_file_path, [:write])
|
||||
Newsletter.log_sent_email(file, "joe@example.com")
|
||||
assert File.read!(@temp_file_path) == "joe@example.com\n"
|
||||
File.close(file)
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "writes many email addresses to the given file" do
|
||||
file = File.open!(@temp_file_path, [:write])
|
||||
Newsletter.log_sent_email(file, "joe@example.com")
|
||||
Newsletter.log_sent_email(file, "kathrine@example.com")
|
||||
Newsletter.log_sent_email(file, "lina@example.com")
|
||||
|
||||
assert File.read!(@temp_file_path) ==
|
||||
"joe@example.com\nkathrine@example.com\nlina@example.com\n"
|
||||
|
||||
File.close(file)
|
||||
end
|
||||
end
|
||||
|
||||
describe "close_log" do
|
||||
@tag task_id: 4
|
||||
test "returns ok" do
|
||||
file = File.open!(@temp_file_path, [:write])
|
||||
assert Newsletter.close_log(file) == :ok
|
||||
end
|
||||
|
||||
@tag task_id: 4
|
||||
test "closes the file" do
|
||||
file = File.open!(@temp_file_path, [:read])
|
||||
assert Newsletter.close_log(file) == :ok
|
||||
assert IO.read(file, :all) == {:error, :terminated}
|
||||
end
|
||||
end
|
||||
|
||||
describe "send_newsletter" do
|
||||
@tag task_id: 5
|
||||
test "returns ok" do
|
||||
send_fun = fn _ -> :ok end
|
||||
|
||||
assert Newsletter.send_newsletter(
|
||||
Path.join(["assets", "emails.txt"]),
|
||||
@temp_file_path,
|
||||
send_fun
|
||||
) == :ok
|
||||
end
|
||||
|
||||
@tag task_id: 5
|
||||
test "calls send function for every email from the emails file" do
|
||||
send_fun = fn email -> send(self(), {:send, email}) && :ok end
|
||||
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
|
||||
assert_received {:send, "alice@example.com"}
|
||||
assert_received {:send, "bob@example.com"}
|
||||
assert_received {:send, "charlie@example.com"}
|
||||
assert_received {:send, "dave@example.com"}
|
||||
end
|
||||
|
||||
@tag task_id: 5
|
||||
test "logs emails that were sent" do
|
||||
send_fun = fn _ -> :ok end
|
||||
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
|
||||
assert File.read!(@temp_file_path) ==
|
||||
"""
|
||||
alice@example.com
|
||||
bob@example.com
|
||||
charlie@example.com
|
||||
dave@example.com
|
||||
"""
|
||||
end
|
||||
|
||||
@tag task_id: 5
|
||||
test "does not log emails that could not be sent" do
|
||||
send_fun = fn
|
||||
"bob@example.com" -> :error
|
||||
"charlie@example.com" -> :error
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
|
||||
assert File.read!(@temp_file_path) == """
|
||||
alice@example.com
|
||||
dave@example.com
|
||||
"""
|
||||
end
|
||||
|
||||
@tag task_id: 5
|
||||
test "sending the same newsletter twice resets the log" do
|
||||
send_fun = fn _ -> :ok end
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
|
||||
assert File.read!(@temp_file_path) ==
|
||||
"""
|
||||
alice@example.com
|
||||
bob@example.com
|
||||
charlie@example.com
|
||||
dave@example.com
|
||||
"""
|
||||
end
|
||||
|
||||
@tag task_id: 5
|
||||
test "logs the email immediately after it was sent" do
|
||||
send_fun = fn email ->
|
||||
case email do
|
||||
"alice@example.com" ->
|
||||
:ok
|
||||
|
||||
"bob@example.com" ->
|
||||
assert File.read!(@temp_file_path) == """
|
||||
alice@example.com
|
||||
"""
|
||||
|
||||
:ok
|
||||
|
||||
"charlie@example.com" ->
|
||||
assert File.read!(@temp_file_path) == """
|
||||
alice@example.com
|
||||
bob@example.com
|
||||
"""
|
||||
|
||||
:error
|
||||
|
||||
"dave@example.com" ->
|
||||
assert File.read!(@temp_file_path) == """
|
||||
alice@example.com
|
||||
bob@example.com
|
||||
"""
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
Newsletter.send_newsletter(Path.join(["assets", "emails.txt"]), @temp_file_path, send_fun)
|
||||
|
||||
assert File.read!(@temp_file_path) ==
|
||||
"""
|
||||
alice@example.com
|
||||
bob@example.com
|
||||
dave@example.com
|
||||
"""
|
||||
end
|
||||
end
|
||||
end
|
||||
2
newsletter/test/test_helper.exs
Normal file
2
newsletter/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ExUnit.start()
|
||||
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
||||
Loading…
Reference in a new issue