├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── config └── config.exs ├── currency.exs ├── elixir ├── exerciseLICENSE ├── formatter.exs ├── lib ├── autocomplete.ex ├── chores.ex ├── credit_card.ex ├── deck.ex ├── fib.ex ├── fizz_buzz.ex ├── github.ex ├── list_utils.ex ├── search.ex └── tic_tac_toe.ex ├── mix.exs ├── mix.lock ├── package.json ├── renovate.json ├── scripts └── test.sh ├── test ├── begin_test.exs ├── credit_card_test.exs ├── deck_test.exs ├── exercise_13_test.exs ├── fib_test.exs ├── fizz_buzz_test.exs ├── github_test.exs ├── list_utils_bounds_test.exs ├── list_utils_test.exs ├── search_test.exs ├── test_helper.exs └── tic_tac_toe_test.exs ├── words.txt └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something broke while you were progressing through the workshop 4 | 5 | --- 6 | 7 | 8 | 11 | 12 | - [ ] **System Information** 13 | - [ ] Browser type and version 14 | - [ ] OS type and version 15 | - [ ] WINDOWS: be sure to indicate which terminal you're using -- (i.e., cmd.exe, powershell, git- bash, cygwin, Ubuntu via windows subsystem for linux, etc...) 16 | - [ ] Node version 17 | - [ ] Any error messages that may be in the console where you ran npm start 18 | - [ ] Any error messages in the JS console 19 | 20 | - [ ] **Describe the bug** 21 | 22 | 23 | - [ ] **To Reproduce** 24 | Steps to reproduce the behavior: 25 | 1. Go to '...' 26 | 2. Click on '....' 27 | 3. Scroll down to '....' 28 | 4. See error 29 | 30 | - [ ] **Expected behavior** 31 | A clear and concise description of what you expected to happen. 32 | 33 | - [ ] **Screenshots (optional)** 34 | If applicable, add screenshots to help explain your problem. 35 | 36 | - [ ] **Additional context (optional)** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # The directory Mix will write compiled artifacts to. 3 | /_build 4 | 5 | # If you run "mix test --cover", coverage assets end up here. 6 | /cover 7 | 8 | # The directory Mix downloads your dependencies sources to. 9 | /deps 10 | 11 | # Where 3rd-party dependencies like ExDoc output generated docs. 12 | /doc 13 | 14 | # Ignore .fetch files in case you like to edit your project deps locally. 15 | /.fetch 16 | 17 | # If the VM crashes, it generates a dump, let's ignore it too. 18 | erl_crash.dump 19 | 20 | # Also ignore archive artifacts (built via "mix archive.build"). 21 | *.ez 22 | .elixir_ls 23 | scratch.* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.6.0 4 | otp_release: 5 | - 18.2 6 | sudo: false 7 | 8 | script: ./scripts/test.sh 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "mix_task", 9 | "name": "mix (Default task)", 10 | "request": "launch", 11 | "projectDir": "${workspaceRoot}" 12 | }, 13 | { 14 | "type": "mix_task", 15 | "name": "mix test", 16 | "request": "launch", 17 | "task": "test", 18 | "taskArgs": [ 19 | "--trace" 20 | ], 21 | "startApps": true, 22 | "projectDir": "${workspaceRoot}", 23 | "requireFiles": [ 24 | "test/**/test_helper.exs", 25 | "test/**/*_test.exs" 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/_build": true, 4 | "**/.elixir_ls": true, 5 | "**/deps": true, 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Mike Works, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |

7 | 8 | 9 | 10 |

11 | 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

23 |

24 | This is the example project used for the Mike.Works Elixir Fundamentals course. 25 |

26 | 27 | # Course outline and slides 28 | * [View course outline here](https://mike.works/course/elixir-fundamentals-2b07030) 29 | * [View slides here](https://docs.mike.works/elixir-fundamentals) 30 | 31 | # Setting Up 32 | * First, you'll need to ensure you have [Elixir](http://elixir-lang.org/install.html) installed on your computer. This workshop requires that you have version `1.4` installed, which requires Erlang `v18`. 33 | 34 | * Next, clone this project 35 | ```sh 36 | git clone https://github.com/mike-works/elixir-fundamentals.git myelixir 37 | ``` 38 | * Then, go into the project and install dependencies 39 | ```sh 40 | cd myelixir 41 | mix deps.get 42 | ``` 43 | 44 | ## Development Environment 45 | 46 | Setup a development environment that includes Elixir syntax hilighting. I recommend [Visual Studio Code](https://code.visualstudio.com/) with the following plugins 47 | * [vscode-elixir](https://marketplace.visualstudio.com/items?itemName=mjmcloug.vscode-elixir) - for syntax hilighting and intellisense (autocomplete) of Elixir and Erlang core libraries. 48 | * [Code Runner](https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner) - for quick execution of code snippets 49 | * [Dark+ Elixir (color theme)](https://marketplace.visualstudio.com/items?itemName=kkalita.dark-plus-elixir) - highlights some important parts of the language syntax more prominently than many default themes 50 | 51 | 52 | # License 53 | While the general license for this project is the BSD 3-clause, the exercises 54 | themselves are proprietary and are licensed on a per-individual basis, usually 55 | as a result of purchasing a ticket to a public workshop, or being a participant 56 | in a private training. 57 | 58 | Here are some guidelines for things that are **OK** and **NOT OK**, based on our 59 | understanding of how these licenses work: 60 | 61 | ### OK 62 | * Using everything in this project other than the exercises (or accompanying tests) 63 | to build a project used for your own free or commercial training material 64 | * Copying code from build scripts, configuration files, tests and development 65 | harnesses that are not part of the exercises specifically, for your own projects 66 | * As an owner of an individual license, using code from tests, exercises, or 67 | exercise solutions for your own non-training-related project. 68 | 69 | ### NOT OK (without express written consent) 70 | * Using this project, or any subset of 71 | exercises contained within this project to run your own workshops 72 | * Writing a book that uses the code for these exercises 73 | * Recording a screencast that contains one or more of this project's exercises 74 | 75 | 76 | # Copyright 77 | 78 | © 2018 [Mike.Works](https://mike.works), All Rights Reserved 79 | 80 | ###### This material may not be used for workshops, training, or any other form of instructing or teaching developers, without express written consent 81 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | config :elixir_fundamentals, 6 | github_api: "https://api.github.com", 7 | github_token: "4223f2b92ca96cb0241f5befa9ed984dfda93235" 8 | 9 | # This configuration is loaded before any dependency and is restricted 10 | # to this project. If another project depends on this project, this 11 | # file won't be loaded nor affect the parent project. For this reason, 12 | # if you want to provide default values for your application for 13 | # 3rd-party users, it should be done in your "mix.exs" file. 14 | 15 | # You can configure for your application as: 16 | # 17 | # config :elixir_fundamentals, key: :value 18 | # 19 | # And access this configuration in your application as: 20 | # 21 | # Application.get_env(:elixir_fundamentals, :key) 22 | # 23 | # Or configure a 3rd-party app: 24 | # 25 | # config :logger, level: :info 26 | # 27 | 28 | # It is also possible to import configuration files, relative to this 29 | # directory. For example, you can emulate configuration per environment 30 | # by uncommenting the line below and defining dev.exs, test.exs and such. 31 | # Configuration from the imported file will override the ones defined 32 | # here (which is why it is important to import them last). 33 | # 34 | # import_config "#{Mix.env}.exs" 35 | -------------------------------------------------------------------------------- /currency.exs: -------------------------------------------------------------------------------- 1 | 2 | formatter = fn(symbol) -> 3 | # TODO 4 | end 5 | 6 | ##### TESTS ##### 7 | test = fn to_run, expected, description -> 8 | require Logger 9 | result = to_run.() 10 | try do 11 | ^expected = result 12 | Logger.info "PASSED: #{description}" 13 | rescue 14 | _ in MatchError -> 15 | Logger.error "FAILED: #{description}\n\tFOUND: #{result}\n\tEXPECTED: #{expected}" 16 | end 17 | end 18 | 19 | test.(fn -> is_function(formatter.("$")) end, true, "formatter should return a function") 20 | test.(fn -> formatter.("$").(0) end, "0", "0 should be formatted as \"0\"") 21 | test.(fn -> formatter.("€").(125) end, "€ 125", "€ formatter should print 125 formatted as \"€ 125\"") 22 | test.(fn -> formatter.("$").(-1125) end, "($ 1125)", "$ formatter should print -1125 formatted as \"($ 1125)\"") 23 | -------------------------------------------------------------------------------- /elixir: -------------------------------------------------------------------------------- 1 | elixir -------------------------------------------------------------------------------- /exerciseLICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Mike Works, Inc. All rights reserved. 2 | 3 | This training material (all exercises and accompanying tests) is licensed to 4 | the individual who purchased it. We don't copy-protect it because that would 5 | limit your ability to use it for your own purposes. Please don't break this 6 | trust - don't allow others to use these exercises without purchasing their own 7 | license. Thanks. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are not permitted provided without an individual license. It may 11 | not be used to create training material, courses, books, articles, and the like. 12 | Contact us if you are in doubt. We make no guarantees that this code is 13 | fit for any purpose. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /formatter.exs: -------------------------------------------------------------------------------- 1 | 2 | formatter = fn 3 | _ -> "placeholder" 4 | end 5 | 6 | 7 | 8 | ##### TESTS ##### 9 | test = fn to_run, expected, description -> 10 | require Logger 11 | result = to_run.() 12 | try do 13 | ^expected = result 14 | Logger.info "PASSED: #{description}" 15 | rescue 16 | _ in MatchError -> 17 | Logger.error "FAILED: #{description}\n\tFOUND: #{result}\n\tEXPECTED: #{expected}" 18 | end 19 | 20 | end 21 | 22 | test.(fn -> formatter.(0) end, "0", "0 should be formatted as \"0\"") 23 | test.(fn -> formatter.(99) end, "99", "99 should be formatted as \"99\"") 24 | test.(fn -> formatter.(1000) end, "1.0K", "1000 should be formatted as \"1.0K\"") 25 | test.(fn -> formatter.(-1300) end, "-1.3K", "-1300 should be formatted as \"-1.3K\"") 26 | test.(fn -> formatter.(1800) end, "1.8K", "1800 should be formatted as \"1.8K\"") 27 | test.(fn -> formatter.(7_200_000) end, "7.2M", "72000000 should be formatted as \"7.2M\"") 28 | -------------------------------------------------------------------------------- /lib/autocomplete.ex: -------------------------------------------------------------------------------- 1 | defmodule Autocomplete do 2 | require Logger 3 | 4 | def get_completions(hint) do 5 | # Switch this to use get_completions_good 6 | {time, result} = measure(fn -> get_completions_bad(hint) end) 7 | Logger.info "Completed search for \"#{hint}\" in #{time}s" 8 | result 9 | end 10 | 11 | ''' 12 | This is the enum (non-stream) version of the autocomplete function. 13 | ''' 14 | defp get_completions_bad(hint) when is_bitstring(hint) and byte_size(hint) > 2 do 15 | lower_hint = String.downcase(hint) 16 | 17 | "words.txt" 18 | |> File.read! 19 | |> String.split("\n") 20 | |> Enum.filter(fn w -> String.starts_with?(w, lower_hint) end) 21 | end 22 | 23 | ''' 24 | Implement something similar here, using streams 25 | ''' 26 | defp get_completions_good(hint) when is_bitstring(hint) and byte_size(hint) > 2 do 27 | lower_hint = String.downcase(hint) 28 | # PUT EXERCISE 10 SOLUTION HERE 29 | end 30 | 31 | # a function used to benchmark the autocomplete 32 | defp measure(function) do 33 | parts = function 34 | |> :timer.tc 35 | 36 | time = elem(parts, 0) 37 | |> Kernel./(1_000_000) 38 | {time, elem(parts, 1)} 39 | end 40 | 41 | end -------------------------------------------------------------------------------- /lib/chores.ex: -------------------------------------------------------------------------------- 1 | defmodule Chores do 2 | def start, do: exit(:explode) 3 | def run do 4 | Process.flag(:trap_exit, true) 5 | pid = spawn_link(Chores, :start, []) 6 | receive do 7 | {:EXIT, pid, reason} -> 8 | IO.puts "Exited because: #{reason}" 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /lib/credit_card.ex: -------------------------------------------------------------------------------- 1 | defmodule CreditCard do 2 | # defstruct 3 | end -------------------------------------------------------------------------------- /lib/deck.ex: -------------------------------------------------------------------------------- 1 | defmodule Deck do 2 | def get_cards(num) when num > 0 do 3 | [] # TODO: deal some cards 4 | end 5 | end -------------------------------------------------------------------------------- /lib/fib.ex: -------------------------------------------------------------------------------- 1 | defmodule Fib do 2 | # NOTE: Enum.take([1, 2, 3, 4], -2) returns [3, 4] 3 | 4 | def normal(x), do: [x] 5 | def reverse(x), do: [x] 6 | 7 | end -------------------------------------------------------------------------------- /lib/fizz_buzz.ex: -------------------------------------------------------------------------------- 1 | defmodule FizzBuzz do 2 | def play_game(number) when number >= 1 do 3 | 1..number 4 | |> Enum.map(&player_turn/1) 5 | |> Enum.join(", ") 6 | end 7 | 8 | defp player_turn(number) do 9 | number # Replace with your real answer 10 | end 11 | end -------------------------------------------------------------------------------- /lib/github.ex: -------------------------------------------------------------------------------- 1 | defmodule Github do 2 | def org_url(_org_name), do: "TODO: org_url" 3 | def repo_url(_owner_name, _repo_name), do: "TODO: repo_url" 4 | def org(org_name), do: "TODO: org" 5 | def repo(owner_name, repo_name), do: "TODO: repo" 6 | end -------------------------------------------------------------------------------- /lib/list_utils.ex: -------------------------------------------------------------------------------- 1 | defmodule ListUtils do 2 | # Exercise 7 3 | def reduce(list, reducer, accumulator) do 4 | 5 | end 6 | 7 | def map(original, transform_fn, transformed \\ []) do 8 | 9 | end 10 | 11 | # Exercise 8 12 | def bounds(list), do: {nil, nil} 13 | 14 | # Exercise 13 15 | def pmap(list, func) do 16 | # Add your exercise 13 solution here 17 | end 18 | end -------------------------------------------------------------------------------- /lib/search.ex: -------------------------------------------------------------------------------- 1 | defmodule Search do 2 | def start do 3 | 4 | end 5 | def run(term) do 6 | import Logger 7 | # Kick off process 8 | Logger.info "Kicking off" 9 | search_pid = spawn(Search, :start, []) 10 | 11 | # Send it a message 12 | to_send = {:complete_me, term} 13 | Logger.info "Sending" 14 | send(search_pid, {self(), to_send}) 15 | 16 | Logger.info "Listening" 17 | # Listen for a response 18 | receive do 19 | {search_pid, {:completions, items}} -> 20 | Logger.info "Received response" 21 | items 22 | |> Enum.join(", ") 23 | |> IO.puts 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/tic_tac_toe.ex: -------------------------------------------------------------------------------- 1 | defmodule TicTacToe do 2 | def new do 3 | 4 | end 5 | def make_move(game, player, {x, y}) do 6 | 7 | end 8 | def get_board(game) do 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirFundamentals.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :elixir_fundamentals, 6 | version: "0.1.0", 7 | elixir: "~> 1.6", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | # Specify extra applications you'll use from Erlang/Elixir 18 | [extra_applications: [:logger, :httpoison]] 19 | end 20 | 21 | # Dependencies can be Hex packages: 22 | # 23 | # {:my_dep, "~> 0.3.0"} 24 | # 25 | # Or git/path repositories: 26 | # 27 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 28 | # 29 | # Type "mix help deps" for more examples and options 30 | defp deps do 31 | [ 32 | {:poison, "4.0.1"}, 33 | {:httpoison, "== 1.8.2"} 34 | ] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, 3 | "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, 4 | "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, 5 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 6 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 7 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 8 | "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, 9 | "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, 10 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, 11 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@mike-works/js-lib-renovate-config": "2.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@mike-works/js-lib-renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$TRAVIS_BRANCH" ] 3 | then 4 | git_branch=$(git symbolic-ref --short -q HEAD) 5 | else 6 | git_branch=$TRAVIS_BRANCH 7 | fi 8 | echo "Git Branch: $git_branch" 9 | 10 | if [ $1 ] 11 | then 12 | filter=$1 13 | else 14 | filter='BEGIN' 15 | fi 16 | echo "Test Filter: $filter" 17 | 18 | if [[ $git_branch = 'solutions' ]] 19 | then 20 | mix test 21 | else 22 | mix test --only $filter 23 | fi -------------------------------------------------------------------------------- /test/begin_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BeginTest do 2 | use ExUnit.Case 3 | @moduletag :begin 4 | 5 | test "true = true" do 6 | assert true === true 7 | end 8 | end -------------------------------------------------------------------------------- /test/credit_card_test.exs: -------------------------------------------------------------------------------- 1 | struct_info = CreditCard.module_info(:exports) 2 | |> Keyword.get_values(:__struct__) 3 | |> length 4 | cond do 5 | struct_info > 0 -> 6 | defmodule CreditCardTest do 7 | use ExUnit.Case 8 | @moduletag :exercise5 9 | 10 | test "passing a regular map (not a struct) to CreditCard.process should fail" do 11 | assert_raise(FunctionClauseError, fn -> 12 | CreditCard.process(%{type: "visa", expire_year: 2021}) 13 | end) 14 | end 15 | 16 | test "passing a card with type: \"discover\" to CreditCard.process should fail" do 17 | assert_raise(FunctionClauseError, fn -> 18 | CreditCard.process(%CreditCard{type: "discover"}) 19 | end) 20 | end 21 | 22 | test "passing a card with type: \"visa\" that expires in 2021 to CreditCard.process should be ok" do 23 | assert "OK!" === %CreditCard{ 24 | type: "visa", 25 | expire_year: 2021 26 | } 27 | |> CreditCard.process 28 | end 29 | 30 | test "passing a card that expired in 2015 to CreditCard.process should fail" do 31 | assert_raise(FunctionClauseError, fn -> 32 | CreditCard.process(%CreditCard{expire_year: 2015, type: "visa"}) 33 | end) 34 | end 35 | end 36 | true -> 37 | defmodule CreditCardTest do 38 | use ExUnit.Case 39 | @moduletag :exercise5 40 | end 41 | end -------------------------------------------------------------------------------- /test/deck_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DeckTest do 2 | use ExUnit.Case 3 | @moduletag :exercise11 4 | 5 | test "Deck.get_cards(5) should return five cards" do 6 | assert length(Deck.get_cards(5)) === 5 7 | end 8 | 9 | test "Deck.get_cards(3) should return five cards" do 10 | assert length(Deck.get_cards(3)) === 3 11 | end 12 | 13 | test "Deck.get_cards(99) should return 52 cards" do 14 | assert length(Deck.get_cards(99)) === 52 15 | end 16 | 17 | test "Cards should be two characters long" do 18 | [card|_] = Deck.get_cards(1) 19 | assert String.length(card) === 2 20 | end 21 | test "Cards should begin with a card type, and end with a suit" do 22 | [card|_] = Deck.get_cards(1) 23 | Regex.match?(~r/[CDSH]{1}[1-90JQKA]{1}$/, card) 24 | end 25 | 26 | end -------------------------------------------------------------------------------- /test/exercise_13_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Exercise13Test do 2 | use ExUnit.Case 3 | @moduletag :exercise13 4 | 5 | test "Map to square items" do 6 | assert [4, 9, 16] === ListUtils.pmap([2, 3, 4], fn item -> 7 | item * item 8 | end) 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /test/fib_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FibTest do 2 | use ExUnit.Case 3 | @moduletag :exercise6 4 | 5 | test "Fib.normal(5) should return [1, 1, 2, 3, 5]" do 6 | assert Fib.normal(5) === [1, 1, 2, 3, 5] 7 | end 8 | 9 | test "Fib.reverse(5) should return [5, 3, 2, 1, 1]" do 10 | assert Fib.reverse(5) === [5, 3, 2, 1, 1] 11 | end 12 | 13 | test "Fib.reverse(0) should return []" do 14 | assert Fib.reverse(0) === [] 15 | end 16 | 17 | test "Fib.normal(0) should return []" do 18 | assert Fib.normal(0) === [] 19 | end 20 | 21 | test "Fib.normal(-5) should return nil" do 22 | assert Fib.normal(-5) === nil 23 | end 24 | 25 | test "Fib.reverse(-5) should return nil" do 26 | assert Fib.reverse(-5) === nil 27 | end 28 | end -------------------------------------------------------------------------------- /test/fizz_buzz_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FizzBuzzTest do 2 | use ExUnit.Case 3 | @moduletag :exercise9 4 | 5 | test "Playing up to 3" do 6 | assert FizzBuzz.play_game(3) === "1, 2, Fizz" 7 | end 8 | 9 | test "Playing up to 5" do 10 | assert FizzBuzz.play_game(5) === "1, 2, Fizz, 4, Buzz" 11 | end 12 | 13 | test "Playing up to 15" do 14 | assert FizzBuzz.play_game(15) === "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz" 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /test/github_test.exs: -------------------------------------------------------------------------------- 1 | defmodule GithubTest do 2 | use ExUnit.Case 3 | @moduletag :exercise3 4 | 5 | @api_base Application.get_env(:elixir_fundamentals, :github_api) || "https://api.github.com" 6 | 7 | test "org URL building is done properly for string org names" do 8 | assert Github.org_url("jquery") === "#{@api_base}/orgs/jquery" 9 | end 10 | 11 | test "org URL building is guarded against (is_bitstring) for non-string org names" do 12 | assert_raise(FunctionClauseError, fn -> 13 | Github.org_url(0) 14 | end) 15 | end 16 | 17 | test "repo URL building is done properly for string owner and repo names" do 18 | assert Github.repo_url("mike-works", "elixir-fundamentals-workshop") === "#{@api_base}/repos/mike-works/elixir-fundamentals-workshop" 19 | end 20 | 21 | test "repo URL building is guarded against for non-string org and repo names" do 22 | assert_raise(FunctionClauseError, fn -> 23 | Github.repo_url("mike-works", 6) 24 | end) 25 | assert_raise(FunctionClauseError, fn -> 26 | Github.repo_url(6, "elixir-fundamentals-workshop") 27 | end) 28 | end 29 | 30 | test "org(\"jquery\") data fetching returns a tuple (size 2) with :ok as first item" do 31 | jquery_org_result = Github.org("jquery") 32 | assert is_tuple(jquery_org_result) === true 33 | {jquery_status, _} = jquery_org_result 34 | assert jquery_status === :ok 35 | end 36 | 37 | test "org(\"jquerrrrrry\") data fetching returns a tuple (size: 2) with :error as first item" do 38 | jquerrrrrry_org_result = Github.org("jquery") 39 | assert is_tuple(jquerrrrrry_org_result) === true 40 | {jquerrrrrry_status, _} = Github.org("jquerrrrrry") 41 | assert jquerrrrrry_status === :error 42 | end 43 | 44 | test "org() guards against non-string org names" do 45 | assert_raise(FunctionClauseError, fn -> 46 | Github.org(0) 47 | end) 48 | end 49 | 50 | 51 | test "repo(\"mike-works\", \"elixir-fundamentals\") returns a tuple, with :ok as first item" do 52 | repo_result = Github.repo("mike-works", "elixir-fundamentals") 53 | assert is_tuple(repo_result) 54 | {workshop_status, _body} = repo_result 55 | assert workshop_status === :ok 56 | end 57 | 58 | test "repo(\"mike-works\", \"elixxxxir-fundamentals-workshop\") returns a tuple, with :error as first item" do 59 | repo_result = Github.repo("mike-works", "elixxxxir-fundamentals-workshop") 60 | assert is_tuple(repo_result) 61 | {bad_status, _body} = repo_result 62 | assert bad_status === :error 63 | end 64 | 65 | test "repo() guards against non-string org and repo names" do 66 | 67 | assert_raise(FunctionClauseError, fn -> 68 | Github.repo("mike-works", 6) 69 | end) 70 | assert_raise(FunctionClauseError, fn -> 71 | Github.repo(6, "elixir-fundamentals-workshop") 72 | end) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/list_utils_bounds_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ListUtilsBoundsTest do 2 | use ExUnit.Case 3 | @moduletag :exercise8 4 | 5 | test "Bounds of a list of positive integers" do 6 | assert {1, 3} === ListUtils.bounds([1, 2, 3]) 7 | end 8 | 9 | test "Bounds of a list of mixed integers" do 10 | assert {-71, 99} === ListUtils.bounds([1, 2, -71, 3, 99]) 11 | end 12 | 13 | test "Bounds of an empty list" do 14 | assert {nil, nil} === ListUtils.bounds([]) 15 | end 16 | 17 | test "Bounds of a one-item list" do 18 | assert {3, 3} === ListUtils.bounds([3]) 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /test/list_utils_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ListUtilsTest do 2 | use ExUnit.Case 3 | @moduletag :exercise7 4 | 5 | test "Reduce to sum up items" do 6 | assert 6 === ListUtils.reduce([1, 2, 3], fn item, acc -> 7 | acc + item 8 | end, 0) 9 | end 10 | 11 | test "Reduce to count items" do 12 | assert 3 === ListUtils.reduce([1, 2, 3], fn _item, acc -> 13 | acc + 1 14 | end, 0) 15 | end 16 | 17 | test "Map to square items" do 18 | assert [4, 9, 16] === ListUtils.map([2, 3, 4], fn item -> 19 | item * item 20 | end) 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /test/search_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SearchTest do 2 | use ExUnit.Case 3 | @moduletag :exercise12 4 | 5 | test "Sending Search a message should result in a message being sent back" do 6 | pid = spawn(Search, :start, []) 7 | send(pid, {self(), {:complete_me, "comp"}}) 8 | assert_receive {pid, {:completions, _}} , 3_000 9 | end 10 | 11 | test "Sending Search a message should result in a non-empty LIST being sent back" do 12 | pid = spawn(Search, :start, []) 13 | send(pid, {self(), {:complete_me, "app"}}) 14 | assert_receive {pid, {:completions, [head|tail]}} , 3_000 15 | assert length(tail) > 0 16 | end 17 | 18 | test "Sending Search repeated messages should get repeated results" do 19 | pid = spawn(Search, :start, []) 20 | 21 | send(pid, {self(), {:complete_me, "ruby"}}) 22 | assert_receive {pid, {:completions, [h1|t1]}} , 3_000 23 | assert length(t1) > 0 24 | 25 | send(pid, {self(), {:complete_me, "elixi"}}) 26 | assert_receive {pid, {:completions, [h2|t2]}} , 3_000 27 | assert length(t2) > 0 28 | end 29 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/tic_tac_toe_test.exs: -------------------------------------------------------------------------------- 1 | defmodule TicTacToeTest do 2 | use ExUnit.Case 3 | @moduletag :exercise14 4 | 5 | test "Starting a game should return a PID" do 6 | game = TicTacToe.new 7 | assert is_pid(game) === true 8 | end 9 | 10 | test "Board is full of nils before game starts" do 11 | game = TicTacToe.new 12 | 13 | board_before = game |> TicTacToe.get_board 14 | assert board_before[1][1] === nil 15 | end 16 | 17 | test "Making a move should change game state" do 18 | game = TicTacToe.new 19 | 20 | board_before = game |> TicTacToe.get_board 21 | game 22 | |> TicTacToe.make_move("X", {1, 1}) 23 | 24 | board_after = game |> TicTacToe.get_board 25 | assert board_after[1][1] === "X" 26 | end 27 | 28 | 29 | end -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@mike-works/js-lib-renovate-config@^2.0.0": 6 | version "2.0.0" 7 | resolved "https://registry.yarnpkg.com/@mike-works/js-lib-renovate-config/-/js-lib-renovate-config-2.0.0.tgz#eb9b1cb4b68e38ba4e59ae5ef853def84d329397" 8 | integrity sha512-hzw/xYMwE1jcKK+pAaqIo3brzzn5ktR73odlPn3SUJJ1MxSuoxlfgWpYrbxuvkwfuS8qVBhVlDJIibCot+Ad6A== 9 | --------------------------------------------------------------------------------