├── .editorconfig ├── .formatter.exs ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── assets └── svg │ └── github-sponsor.svg ├── examples └── scribe.sh ├── lib ├── docs │ └── modules │ │ └── behaviours │ │ ├── persona.ex │ │ └── persona_validator.ex ├── elixir_scribe.ex ├── elixir_scribe │ ├── behaviour_typed_contract.ex │ ├── generator │ │ ├── domain │ │ │ └── resource │ │ │ │ ├── build_action_files_paths │ │ │ │ └── build_action_files_paths_resource.ex │ │ │ │ ├── build_api_file_paths │ │ │ │ └── build_api_file_paths_resource.ex │ │ │ │ ├── build_domain_contract │ │ │ │ └── build_domain_resource_contract.ex │ │ │ │ ├── build_files_to_generate │ │ │ │ └── build_files_to_generate_resource.ex │ │ │ │ ├── build_test_action_files_paths │ │ │ │ └── build_test_action_files_paths_resource.ex │ │ │ │ ├── generate_actions │ │ │ │ └── generate_actions_resource.ex │ │ │ │ ├── generate_api │ │ │ │ └── generate_api_resource.ex │ │ │ │ ├── generate_new_files │ │ │ │ └── generate_new_files_resource.ex │ │ │ │ ├── generate_schema │ │ │ │ └── generate_schema_resource.ex │ │ │ │ ├── generate_test_fixture │ │ │ │ └── generate_test_fixture_resource.ex │ │ │ │ └── generate_tests │ │ │ │ └── generate_tests_resource.ex │ │ ├── domain_contract.ex │ │ ├── domain_resource_api.ex │ │ ├── schema │ │ │ └── resource │ │ │ │ ├── build_schema_contract │ │ │ │ └── build_schema_contract.ex │ │ │ │ └── schema_resource_helpers.ex │ │ ├── schema_contract.ex │ │ └── schema_resource_api.ex │ ├── mix │ │ ├── cli_command │ │ │ ├── parse │ │ │ │ └── parse_cli_command.ex │ │ │ └── parse_schema │ │ │ │ └── parse_schema_cli_command.ex │ │ ├── mix_api.ex │ │ └── shell │ │ │ └── prompt_for_file_conflicts │ │ │ └── prompt_for_file_conflicts_shell.ex │ ├── template │ │ ├── binding │ │ │ ├── build │ │ │ │ └── build_binding_template.ex │ │ │ └── rebuild │ │ │ │ └── rebuild_binding_template.ex │ │ ├── binding_api.ex │ │ ├── binding_api_contract_build_filename_for_action_file.ex │ │ ├── file │ │ │ ├── build_dir_path_for_html │ │ │ │ └── build_dir_path_for_html_file.ex │ │ │ ├── build_filename_for_action │ │ │ │ └── build_filename_for_action_file.ex │ │ │ └── inject │ │ │ │ ├── inject_content_before_module_end.ex │ │ │ │ └── inject_eex_template_before_module.ex │ │ ├── file_api.ex │ │ ├── module │ │ │ ├── build_embed_templates │ │ │ │ └── build_module_embed_templates.ex │ │ │ └── build_name │ │ │ │ ├── build_absolute_module_action_name.ex │ │ │ │ ├── build_absolute_module_action_name_aliases.ex │ │ │ │ ├── build_absolute_module_name.ex │ │ │ │ └── build_module_action_name.ex │ │ ├── module_api.ex │ │ ├── route │ │ │ └── scope │ │ │ │ └── scope_action_routes.ex │ │ └── route_api.ex │ └── utils │ │ ├── string │ │ ├── camel_case_to_sentence │ │ │ └── camel_case_to_sentence.ex │ │ ├── capitalize │ │ │ └── capitalize_string.ex │ │ ├── find_acronyms │ │ │ └── find_acronyms.ex │ │ ├── first_word │ │ │ └── first_word.ex │ │ └── human_capitalize │ │ │ └── human_capitalize_string.ex │ │ └── string_api.ex └── mix │ └── tasks │ ├── scribe.ex │ ├── scribe.gen.domain.ex │ ├── scribe.gen.ex │ └── scribe.gen.html.ex ├── mix.exs ├── mix.lock ├── priv └── templates │ ├── scribe.gen.domain │ ├── actions │ │ ├── action_module.ex │ │ ├── action_module_no_schema_access.ex │ │ ├── no_schema_access │ │ │ └── any_action.ex │ │ └── schema_access │ │ │ ├── create_schema.ex │ │ │ ├── default_schema.ex │ │ │ ├── delete_schema.ex │ │ │ ├── edit_schema.ex │ │ │ ├── list_schema.ex │ │ │ ├── new_schema.ex │ │ │ ├── read_schema.ex │ │ │ └── update_schema.ex │ ├── apis │ │ ├── api_function_no_schema_access.ex │ │ ├── api_module.ex │ │ ├── create_api_function.ex │ │ ├── default_api_function.ex │ │ ├── delete_api_function.ex │ │ ├── edit_api_function.ex │ │ ├── list_api_function.ex │ │ ├── new_api_function.ex │ │ ├── read_api_function.ex │ │ └── update_api_function.ex │ └── tests │ │ ├── actions │ │ ├── action_module_test.exs │ │ ├── action_module_test_no_schema_access.exs │ │ ├── no_schema_access │ │ │ └── module_any_action_test.exs │ │ └── schema_access │ │ │ ├── create_schema_test.exs │ │ │ ├── default_schema_test.exs │ │ │ ├── delete_schema_test.exs │ │ │ ├── edit_schema_test.exs │ │ │ ├── list_schema_test.exs │ │ │ ├── new_schema_test.exs │ │ │ ├── read_schema_test.exs │ │ │ └── update_schema_test.exs │ │ ├── fixtures.ex │ │ └── fixtures_module.ex │ └── scribe.gen.html │ ├── controllers │ ├── create_controller.ex │ ├── default_controller.ex │ ├── delete_controller.ex │ ├── edit_controller.ex │ ├── list_controller.ex │ ├── new_controller.ex │ ├── read_controller.ex │ └── update_controller.ex │ ├── html │ └── default │ │ ├── edit.html.heex │ │ ├── html.ex │ │ ├── list.html.heex │ │ ├── new.html.heex │ │ ├── read.html.heex │ │ └── resource_form.html.heex │ └── tests │ └── controllers │ ├── create_controller_test.exs │ ├── default_controller_test.exs │ ├── delete_controller_test.exs │ ├── edit_controller_test.exs │ ├── list_controller_test.exs │ ├── new_controller_test.exs │ ├── read_controller_test.exs │ └── update_controller_test.exs └── test ├── docs └── modules │ └── behaviours │ └── persona_validator_test.exs ├── elixir_scribe ├── behaviour_typed_contract_test.exs ├── generator │ ├── domain │ │ └── resource │ │ │ ├── build_action_files_paths │ │ │ └── build_action_files_paths_resource_test.exs │ │ │ ├── build_api_file_paths │ │ │ └── build_api_file_paths_resource_test.exs │ │ │ ├── build_domain_contract │ │ │ └── build_domain_resource_contract_test.exs │ │ │ ├── build_files_to_generate │ │ │ └── build_files_to_generate_resource_test.exs │ │ │ ├── build_test_action_files_paths │ │ │ └── build_test_action_files_paths_resource_test.exs │ │ │ ├── generate_actions │ │ │ └── generate_actions_resource_test.exs │ │ │ ├── generate_api │ │ │ └── generate_api_resource_test.exs │ │ │ ├── generate_new_files │ │ │ └── generate_new_files_resource_test.exs │ │ │ ├── generate_schema │ │ │ └── generate_schema_resource_test.exs │ │ │ ├── generate_test_fixture │ │ │ └── generate_test_fixture_resource_test.exs │ │ │ └── generate_tests │ │ │ └── generate_tests_resource_test.exs │ ├── domain_contract_test.exs │ ├── domain_resource_api_async_test.exs │ ├── domain_resource_api_sync_test.exs │ ├── schema │ │ └── resource │ │ │ └── build_schema_contract │ │ │ └── build_schema_contract_test.exs │ ├── schema_contract_test.exs │ └── schema_resource_api_test.exs ├── mix │ ├── cli_command │ │ └── parse │ │ │ └── parse_cli_command_test.exs │ ├── mix_api_test.exs │ └── shell │ │ └── prompt_for_file_conflicts │ │ └── prompt_for_file_conflicts_shell_test.exs ├── template │ ├── binding │ │ ├── build │ │ │ └── build_binding_template_test.exs │ │ └── rebuild │ │ │ └── rebuild_binding_template_test.exs │ ├── binding_api_test.exs │ ├── file │ │ ├── build_dir_path_for_html │ │ │ └── build_dir_path_for_html_file_test.exs │ │ ├── build_filename_for_action │ │ │ └── build_filename_for_action_file_test.exs │ │ └── inject │ │ │ ├── inject_content_before_module_end_test.exs │ │ │ └── inject_eex_template_before_module_end_test.exs │ ├── file_api_async_test.exs │ ├── file_api_sync_test.exs │ ├── module │ │ ├── build_embed_templates │ │ │ └── build_module_embed_templates_test.exs │ │ └── build_name │ │ │ ├── build_absolute_module_action_name_aliases_test.exs │ │ │ ├── build_absolute_module_action_name_test.exs │ │ │ ├── build_absolute_module_name_test.exs │ │ │ └── build_module_action_name_test.exs │ ├── module_api_test.exs │ └── route │ │ └── scope │ │ └── scope_action_routes_test.exs └── utils │ ├── string │ ├── camel_case_to_sentence │ │ └── camel_case_to_sentence_test.exs │ ├── capitalize │ │ └── capitalize_string_test.exs │ ├── find_acronyms │ │ └── find_acronyms_test.exs │ ├── first_word │ │ └── first_word_test.exs │ └── human_capitalize │ │ └── human_capitalize_string_test.exs │ └── string_api_test.exs ├── elixir_scribe_test.exs ├── mix └── tasks │ ├── scribe.gen.domain_async_test.exs │ ├── scribe.gen.domain_sync_test.exs │ ├── scribe.gen.html_async_test.exs │ ├── scribe.gen.html_sync_test.exs │ ├── scribe.gen_test.exs │ └── scribe_test.exs ├── mix_test_helper.exs ├── support ├── base_case.ex └── fixtures │ └── domain_generator_fixtures.ex └── test_helper.exs /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{md, markdown}] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["Exadra37"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | elixir_scribe-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Paulo Renato] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 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 THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/svg/github-sponsor.svg: -------------------------------------------------------------------------------- 1 | Sponsor: ❤Sponsor 2 | -------------------------------------------------------------------------------- /examples/scribe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | Maybe_Create_App() { 6 | local _app_dir="${TARGET_DIR}/${APP_DIRNAME}" 7 | 8 | if [ -d "${_app_dir}" ]; then 9 | cd "${_app_dir}" 10 | git stash -u 11 | else 12 | mkdir -p "${TARGET_DIR}" 13 | cd "${TARGET_DIR}" 14 | mix phx.new "${APP_DIRNAME}" --database sqlite3 15 | 16 | echo "-> Add the Elixir Scribe dependency." 17 | echo "-> Run mix deps.get." 18 | echo "-> Run mix compile." 19 | echo "-> Run mix ecto.setup." 20 | echo "-> Run git init and commit it all." 21 | echo "-> You can now use ./examples/.scribe.sh example-here" 22 | 23 | exit 0 24 | fi 25 | } 26 | 27 | Todo_App() { 28 | # sleep .5 is required to ensure each migration has a different datetime 29 | 30 | mix scribe.gen.html Todo Task tasks title:string done:boolean --no-schema 31 | sleep .5 32 | 33 | mix scribe.gen.html Todo Tag tags title:string desc:string 34 | sleep .5 35 | 36 | mix scribe.gen.html Acounts User users name:string email:string 37 | } 38 | 39 | Shop_App() { 40 | # sleep .5 is required to ensure each migration has a different datetime 41 | 42 | # Sales Catalog 43 | mix scribe.gen.html Sales.Catalog Category categories name:string desc:string 44 | sleep .5 45 | 46 | mix scribe.gen.html Sales.Catalog Product products sku:string name:string desc:string price:integer vat:integer --actions import,export 47 | sleep .5 48 | 49 | mix scribe.gen.html Sales.Catalog Cart carts total_amount:integer total_quantity:integer products_skus:array:string --actions report 50 | sleep .5 51 | 52 | # Sales Checkout 53 | mix scribe.gen.domain Sales.Checkout CheckoutProduct checkout_products sku:string name:string desc:string --no-default-actions --actions build 54 | sleep .5 55 | 56 | mix scribe.gen.html Sales.Checkout Order orders total_amount:integer total_quantity:integer products_skus:array:string cart_uuid:string shipping_uuid:string --actions report 57 | sleep .5 58 | 59 | # Warehouse Fulfillment 60 | mix scribe.gen.html Warehouse.Fulfillment FulfillmentProduct fulfillment_products sku:string label:string total_quantity:integer location:string --no-default-actions --actions build 61 | sleep .5 62 | 63 | mix scribe.gen.html Warehouse.Shipment Parcel parcels pickup_datetime:datetime label:string carrier_uuid:string 64 | } 65 | 66 | Main() { 67 | export MIX_ENV=dev 68 | 69 | local APP_DIRNAME="my_app" 70 | local TARGET_DIR=".local" 71 | 72 | Maybe_Create_App 73 | 74 | for input in "${@}"; do 75 | case "${input}" in 76 | --dir ) 77 | shift 1 78 | TARGET_DIR="${1}" 79 | ;; 80 | 81 | todo ) 82 | Todo_App 83 | exit $? 84 | ;; 85 | 86 | shop ) 87 | Shop_App 88 | exit $? 89 | ;; 90 | esac 91 | done 92 | } 93 | 94 | Main "${@}" 95 | -------------------------------------------------------------------------------- /lib/docs/modules/behaviours/persona.ex: -------------------------------------------------------------------------------- 1 | defmodule Persona do 2 | @moduledoc false 3 | 4 | require PersonaValidator 5 | 6 | @fields %{ 7 | required: [:name, :email], 8 | optional: [role: nil] 9 | } 10 | 11 | use ElixirScribe.Behaviour.TypedContract, fields: @fields 12 | 13 | @impl true 14 | def type_spec() do 15 | schema(%__MODULE__{ 16 | name: is_binary() |> spec(), 17 | email: PersonaValidator.corporate_email?() |> spec(), 18 | role: PersonaValidator.role?() |> spec() 19 | }) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/docs/modules/behaviours/persona_validator.ex: -------------------------------------------------------------------------------- 1 | defmodule PersonaValidator do 2 | @moduledoc false 3 | 4 | # A very simplistic set o validations used by the docs usage examples. 5 | 6 | @email_providers ["gmail.com", "yahoo.com", "hotmail.com"] 7 | def corporate_email?(email) when is_binary(email) do 8 | case String.split(email, "@", trim: true) do 9 | [_one_part] -> 10 | false 11 | 12 | [_, email_provider] when email_provider not in @email_providers -> 13 | true 14 | 15 | _ -> 16 | false 17 | end 18 | end 19 | 20 | def corporate_email?(_email), do: false 21 | 22 | # The role is optional, thus we return true 23 | def role?(role) when is_nil(role), do: true 24 | 25 | def role?(role) when is_binary(role) do 26 | role = role |> String.trim() 27 | String.length(role) >= 3 28 | end 29 | 30 | def role?(_role), do: false 31 | end 32 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/build_action_files_paths/build_action_files_paths_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildActionFilesPaths.BuildActionFilesPathsResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Template.FileAPI 7 | 8 | def build(%DomainContract{} = contract) do 9 | resource_actions = contract.opts |> Keyword.get(:resource_actions) 10 | 11 | for action <- resource_actions do 12 | source_path = build_source_path(contract.schema, action) 13 | target_path = build_target_path(contract, action) 14 | 15 | {:eex, :resource, source_path, target_path, action} 16 | end 17 | end 18 | 19 | defp build_target_path(contract, action) do 20 | plural_actions = ElixirScribe.resource_plural_actions() 21 | 22 | resource_name = 23 | (action in plural_actions && contract.resource_path_name_plural) || 24 | contract.resource_path_name_singular 25 | 26 | filename = "#{action}_" <> resource_name <> ".ex" 27 | 28 | Path.join([contract.lib_resource_dir, action, filename]) 29 | end 30 | 31 | defp build_source_path(schema, action) do 32 | resource_action_path = ElixirScribe.resource_actions_template_path() 33 | schema_folder = ElixirScribe.schema_template_folder_name(schema) 34 | action_template_filename = build_template_action_filename(action, schema.generate?) 35 | 36 | Path.join([resource_action_path, schema_folder, action_template_filename]) 37 | end 38 | 39 | defp build_template_action_filename(action, true) do 40 | attrs = %{action: action, action_suffix: "_", file_type: "schema", file_extension: ".ex"} 41 | 42 | contract = BuildFilenameForActionFileContract.new!(attrs) 43 | 44 | FileAPI.build_template_action_filename(contract) 45 | end 46 | 47 | defp build_template_action_filename(_action, false), do: "any_action.ex" 48 | end 49 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/build_api_file_paths/build_api_file_paths_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildAPIFilePaths.BuildAPIFilePathsResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | 6 | def build(%DomainContract{} = contract), do: build_api_file(contract) 7 | 8 | defp build_api_file(contract) do 9 | source_path = ElixirScribe.domain_api_template_path() |> Path.join("api_module.ex") 10 | target_path = contract.api_file 11 | 12 | {:eex, :api, source_path, target_path} 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/build_files_to_generate/build_files_to_generate_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildFilesToGenerate.BuildFilesToGenerateResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | 7 | def build(%DomainContract{} = contract) do 8 | api_file = [DomainResourceAPI.build_api_file_paths(contract)] 9 | resource_files = DomainResourceAPI.build_action_files_paths(contract) 10 | resource_test_files = DomainResourceAPI.build_test_action_files_paths(contract) 11 | schema = ElixirScribe.to_phoenix_schema(contract.schema) 12 | schema_files = Mix.Tasks.Phx.Gen.Schema.files_to_be_generated(schema) 13 | test_fixtures_file = build_test_fixture_file(contract) 14 | 15 | api_file 16 | |> Kernel.++(resource_files) 17 | |> Kernel.++(resource_test_files) 18 | |> Kernel.++(schema_files) 19 | |> Kernel.++(test_fixtures_file) 20 | end 21 | 22 | defp build_test_fixture_file(%DomainContract{test_fixtures_file: test_fixtures_file}) do 23 | source_path = ElixirScribe.domain_tests_template_path() |> Path.join("fixtures_module.ex") 24 | 25 | [{:eex, source_path, test_fixtures_file}] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/build_test_action_files_paths/build_test_action_files_paths_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildTestActionFilesPaths.BuildTestActionFilesPathsResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Template.FileAPI 7 | 8 | def build(%DomainContract{} = contract) do 9 | resource_actions = contract.opts |> Keyword.get(:resource_actions) 10 | 11 | for action <- resource_actions do 12 | source_path = build_source_path(contract.schema, action) 13 | target_path = build_target_path(contract, action) 14 | 15 | {:eex, :resource_test, source_path, target_path, action} 16 | end 17 | end 18 | 19 | defp build_target_path(contract, action) do 20 | plural_actions = ElixirScribe.resource_plural_actions() 21 | 22 | resource_name = 23 | (action in plural_actions && contract.resource_path_name_plural) || 24 | contract.resource_path_name_singular 25 | 26 | filename = "#{action}_" <> resource_name <> "_test.exs" 27 | 28 | Path.join([contract.test_resource_dir, action, filename]) 29 | end 30 | 31 | defp build_source_path(schema, action) do 32 | resource_action_path = ElixirScribe.resource_test_actions_template_path() 33 | schema_folder = ElixirScribe.schema_template_folder_name(schema) 34 | action_template_filename = build_template_action_filename(action, schema.generate?) 35 | 36 | Path.join([resource_action_path, schema_folder, action_template_filename]) 37 | end 38 | 39 | defp build_template_action_filename(action, true) do 40 | attrs = %{ 41 | action: action, 42 | action_suffix: "_", 43 | file_type: "schema", 44 | file_extension: "_test.exs" 45 | } 46 | 47 | contract = BuildFilenameForActionFileContract.new!(attrs) 48 | 49 | FileAPI.build_template_action_filename(contract) 50 | end 51 | 52 | defp build_template_action_filename(_action, false), do: "module_any_action_test.exs" 53 | end 54 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_actions/generate_actions_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateActions.GenerateActionsResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Template.FileAPI 6 | alias ElixirScribe.Generator.DomainResourceAPI 7 | alias ElixirScribe.Generator.DomainContract 8 | 9 | def generate(%DomainContract{} = contract) do 10 | base_template_paths = ElixirScribe.base_template_paths() 11 | 12 | binding = BindingAPI.build_binding_template(contract) 13 | 14 | for {:eex, :resource, source_path, target_path, action} <- 15 | DomainResourceAPI.build_action_files_paths(contract) do 16 | binding = BindingAPI.rebuild_binding_template(binding, action, file_type: :lib_core) 17 | 18 | target_path = Regex.replace(~r/^default/, target_path, action) 19 | 20 | # When the file already exists we are asked if we want to overwrite it. 21 | created_or_overwritten? = 22 | create_action_module_file( 23 | base_template_paths, 24 | target_path, 25 | binding, 26 | contract.schema.generate? 27 | ) 28 | 29 | if created_or_overwritten? do 30 | inject_action_function_into_module(base_template_paths, source_path, target_path, binding) 31 | end 32 | end 33 | 34 | contract 35 | end 36 | 37 | defp create_action_module_file(base_template_paths, target_path, binding, schema_generate?) do 38 | module_template_path = build_module_template_path(schema_generate?) 39 | content = Mix.Phoenix.eval_from(base_template_paths, module_template_path, binding) 40 | 41 | Mix.Generator.create_file(target_path, content) 42 | end 43 | 44 | defp build_module_template_path(true) do 45 | ElixirScribe.resource_actions_template_path() 46 | |> Path.join("action_module.ex") 47 | end 48 | 49 | defp build_module_template_path(false) do 50 | ElixirScribe.resource_actions_template_path() 51 | |> Path.join("action_module_no_schema_access.ex") 52 | end 53 | 54 | defp inject_action_function_into_module(base_template_paths, source_path, target_path, binding) do 55 | FileAPI.inject_eex_template_before_module_end( 56 | base_template_paths, 57 | source_path, 58 | target_path, 59 | binding 60 | ) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_api/generate_api_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateApi.GenerateApiResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Template.BindingAPI 6 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 7 | alias ElixirScribe.Generator.DomainContract 8 | alias ElixirScribe.Template.FileAPI 9 | alias ElixirScribe.Generator.DomainResourceAPI 10 | 11 | def generate(%DomainContract{} = contract) do 12 | base_template_paths = ElixirScribe.base_template_paths() 13 | binding = BindingAPI.build_binding_template(contract) 14 | 15 | contract 16 | |> ensure_api_file_exists(base_template_paths, binding) 17 | |> inject_api_functions(base_template_paths, binding) 18 | 19 | contract 20 | end 21 | 22 | defp build_api_binding(contract, binding) do 23 | Keyword.merge(binding, 24 | absolute_module_name: ModuleAPI.build_absolute_module_name(contract, file_type: :lib_core), 25 | aliases: 26 | ModuleAPI.build_absolute_module_action_name_aliases(contract, 27 | file_type: :lib_core 28 | ) 29 | ) 30 | end 31 | 32 | defp ensure_api_file_exists(contract, base_template_paths, binding) do 33 | binding = build_api_binding(contract, binding) 34 | 35 | unless File.exists?(contract.api_file) do 36 | {:eex, :api, source_path, target_path} = DomainResourceAPI.build_api_file_paths(contract) 37 | 38 | Mix.Generator.create_file( 39 | target_path, 40 | Mix.Phoenix.eval_from(base_template_paths, source_path, binding) 41 | ) 42 | end 43 | 44 | contract 45 | end 46 | 47 | defp inject_api_functions(contract, base_template_paths, binding) do 48 | resource_actions = contract.opts |> Keyword.get(:resource_actions) 49 | 50 | for action <- resource_actions do 51 | binding = 52 | build_api_binding(contract, binding) 53 | |> BindingAPI.rebuild_binding_template(action, file_type: :lib_core) 54 | 55 | api_action_template_path = build_api_action_template_path(action, contract.schema.generate?) 56 | 57 | FileAPI.inject_eex_template_before_module_end( 58 | base_template_paths, 59 | api_action_template_path, 60 | contract.api_file, 61 | binding 62 | ) 63 | end 64 | end 65 | 66 | defp build_api_action_template_path(action, true) do 67 | attrs = %{ 68 | action: action, 69 | action_suffix: "_", 70 | file_type: "api_function", 71 | file_extension: ".ex" 72 | } 73 | 74 | contract = BuildFilenameForActionFileContract.new!(attrs) 75 | 76 | action_template_filename = FileAPI.build_template_action_filename(contract) 77 | 78 | ElixirScribe.domain_api_template_path() |> Path.join(action_template_filename) 79 | end 80 | 81 | defp build_api_action_template_path(_action, false) do 82 | ElixirScribe.domain_api_template_path() 83 | |> Path.join("api_function_no_schema_access.ex") 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_new_files/generate_new_files_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateNewFiles.GenerateNewFilesResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.MixAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Generator.DomainResourceAPI 7 | 8 | def generate(%DomainContract{} = contract, opts) when is_list(opts) do 9 | prompt_for_conflicts? = Keyword.get(opts, :prompt_for_conflicts?, true) 10 | 11 | if prompt_for_conflicts? do 12 | prompt_for_conflicts(contract) 13 | end 14 | 15 | contract 16 | |> DomainResourceAPI.generate_api() 17 | |> DomainResourceAPI.generate_schema() 18 | |> DomainResourceAPI.generate_actions() 19 | |> DomainResourceAPI.generate_tests() 20 | |> DomainResourceAPI.generate_test_fixture() 21 | 22 | contract 23 | end 24 | 25 | defp prompt_for_conflicts(contract) do 26 | contract 27 | |> DomainResourceAPI.build_files_to_generate() 28 | |> MixAPI.prompt_for_file_conflicts() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_schema/generate_schema_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateSchema.GenerateSchemaResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | 7 | def generate(%DomainContract{schema: %{generate?: false}} = contract), do: contract 8 | 9 | def generate(%DomainContract{schema: %{generate?: true}} = contract) do 10 | schema = ElixirScribe.to_phoenix_schema(contract.schema) 11 | paths = ElixirScribe.base_template_paths() 12 | bindings = build_bindings(contract) 13 | 14 | Mix.Tasks.Phx.Gen.Schema.copy_new_files(schema, paths, bindings) 15 | 16 | contract 17 | end 18 | 19 | defp build_bindings(contract) do 20 | contract 21 | |> BindingAPI.build_binding_template() 22 | |> Keyword.merge(schema: contract.schema) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_test_fixture/generate_test_fixture_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateTestFixture.GenerateTestFixtureResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Template.FileAPI 7 | 8 | def generate(%DomainContract{} = contract) do 9 | base_template_paths = ElixirScribe.base_template_paths() 10 | binding = BindingAPI.build_binding_template(contract) 11 | 12 | contract 13 | |> ensure_test_fixtures_file_exists(base_template_paths, binding) 14 | |> inject_test_fixture(base_template_paths, binding) 15 | 16 | contract 17 | end 18 | 19 | defp ensure_test_fixtures_file_exists( 20 | %DomainContract{test_fixtures_file: test_fixtures_file} = contract, 21 | base_template_paths, 22 | binding 23 | ) do 24 | unless File.exists?(test_fixtures_file) do 25 | fixtures_module_template_path = 26 | ElixirScribe.domain_tests_template_path() |> Path.join("fixtures_module.ex") 27 | 28 | Mix.Generator.create_file( 29 | test_fixtures_file, 30 | Mix.Phoenix.eval_from(base_template_paths, fixtures_module_template_path, binding) 31 | ) 32 | end 33 | 34 | contract 35 | end 36 | 37 | defp inject_test_fixture( 38 | %DomainContract{test_fixtures_file: test_fixtures_file, opts: opts} = contract, 39 | base_template_paths, 40 | binding 41 | ) do 42 | no_default_actions = Keyword.get(opts, :no_default_actions, false) 43 | 44 | unless no_default_actions do 45 | fixtures_file_template_path = 46 | ElixirScribe.domain_tests_template_path() |> Path.join("fixtures.ex") 47 | 48 | FileAPI.inject_eex_template_before_module_end( 49 | base_template_paths, 50 | fixtures_file_template_path, 51 | test_fixtures_file, 52 | binding 53 | ) 54 | 55 | maybe_print_unimplemented_fixture_functions(contract) 56 | end 57 | end 58 | 59 | defp maybe_print_unimplemented_fixture_functions(%DomainContract{} = contract) do 60 | fixture_functions_needing_implementations = 61 | Enum.flat_map( 62 | contract.schema.fixture_unique_functions, 63 | fn 64 | {_field, {_function_name, function_def, true}} -> [function_def] 65 | {_field, {_function_name, _function_def, false}} -> [] 66 | end 67 | ) 68 | 69 | if Enum.any?(fixture_functions_needing_implementations) do 70 | Mix.shell().info(""" 71 | 72 | Some of the generated database columns are unique. Please provide 73 | unique implementations for the following fixture function(s) in 74 | #{contract.test_fixtures_file}: 75 | 76 | #{fixture_functions_needing_implementations |> Enum.map_join(&indent(&1, 2)) |> String.trim_trailing()} 77 | """) 78 | end 79 | 80 | contract 81 | end 82 | 83 | defp indent(string, spaces) do 84 | indent_string = String.duplicate(" ", spaces) 85 | 86 | string 87 | |> String.split("\n") 88 | |> Enum.map_join(fn line -> 89 | if String.trim(line) == "" do 90 | "\n" 91 | else 92 | indent_string <> line <> "\n" 93 | end 94 | end) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain/resource/generate_tests/generate_tests_resource.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateTests.GenerateTestsResource do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Generator.DomainResourceAPI 6 | alias ElixirScribe.Generator.DomainContract 7 | alias ElixirScribe.Template.FileAPI 8 | 9 | def generate(%DomainContract{} = contract) do 10 | base_template_paths = ElixirScribe.base_template_paths() 11 | binding = BindingAPI.build_binding_template(contract) 12 | 13 | for {:eex, :resource_test, source_path, target_path, action} <- 14 | DomainResourceAPI.build_test_action_files_paths(contract) do 15 | binding = 16 | BindingAPI.rebuild_binding_template(binding, action, file_type: :lib_core) 17 | 18 | # When the file already exists we are asked if we want to overwrite it. 19 | created_or_overwritten? = 20 | create_test_action_module_file( 21 | base_template_paths, 22 | target_path, 23 | binding, 24 | contract.schema.generate? 25 | ) 26 | 27 | if created_or_overwritten? do 28 | inject_action_function_into_module( 29 | base_template_paths, 30 | source_path, 31 | target_path, 32 | binding 33 | ) 34 | end 35 | end 36 | 37 | contract 38 | end 39 | 40 | defp create_test_action_module_file(base_template_paths, target_path, binding, schema_generate?) do 41 | module_template_path = build_module_template_path(schema_generate?) 42 | content = Mix.Phoenix.eval_from(base_template_paths, module_template_path, binding) 43 | 44 | Mix.Generator.create_file(target_path, content) 45 | end 46 | 47 | defp build_module_template_path(true) do 48 | ElixirScribe.resource_test_actions_template_path() 49 | |> Path.join("action_module_test.exs") 50 | end 51 | 52 | defp build_module_template_path(false) do 53 | ElixirScribe.resource_test_actions_template_path() 54 | |> Path.join("action_module_test_no_schema_access.exs") 55 | end 56 | 57 | defp inject_action_function_into_module(base_template_paths, source_path, target_path, binding) do 58 | FileAPI.inject_eex_template_before_module_end( 59 | base_template_paths, 60 | source_path, 61 | target_path, 62 | binding 63 | ) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain_contract.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.DomainContract do 2 | @moduledoc false 3 | 4 | # This contract mirrors the Mix.Phoenix.Context module from the Phoenix 5 | # Framework with some added fields to suite ElixirScribe needs. 6 | 7 | @required [ 8 | :name, 9 | :module, 10 | :resource_module, 11 | :resource_module_plural, 12 | :schema, 13 | :alias, 14 | :base_module, 15 | :web_module, 16 | :web_domain_module, 17 | :web_resource_module, 18 | :web_resource_module_plural, 19 | :basename, 20 | :api_file, 21 | :test_file, 22 | :test_fixtures_file, 23 | :lib_web_domain_dir, 24 | :lib_domain_dir, 25 | :lib_web_resource_dir, 26 | :lib_web_resource_dir_plural, 27 | :lib_resource_dir, 28 | :lib_resource_dir_plural, 29 | :test_web_domain_dir, 30 | :test_domain_dir, 31 | :test_web_resource_dir, 32 | :test_web_resource_dir_plural, 33 | :test_resource_dir, 34 | :test_resource_dir_plural, 35 | :context_app, 36 | :resource_path_name_singular, 37 | :resource_path_name_plural, 38 | :resource_actions 39 | ] 40 | 41 | @optional [ 42 | generate?: true, 43 | opts: [] 44 | ] 45 | 46 | use ElixirScribe.Behaviour.TypedContract, fields: %{required: @required, optional: @optional} 47 | 48 | @impl true 49 | def type_spec() do 50 | schema(%__MODULE__{ 51 | name: is_binary() |> spec(), 52 | module: is_atom() |> spec(), 53 | resource_module: is_atom() |> spec(), 54 | resource_module_plural: is_atom() |> spec(), 55 | schema: spec(is_struct() or is_nil()), 56 | alias: is_atom() |> spec(), 57 | base_module: is_atom() |> spec(), 58 | web_module: is_atom() |> spec(), 59 | web_domain_module: is_atom() |> spec(), 60 | web_resource_module: is_atom() |> spec(), 61 | web_resource_module_plural: is_atom() |> spec(), 62 | basename: is_binary() |> spec(), 63 | api_file: is_binary() |> spec(), 64 | test_file: is_binary() |> spec(), 65 | test_fixtures_file: is_binary() |> spec(), 66 | lib_web_domain_dir: is_binary() |> spec(), 67 | lib_domain_dir: is_binary() |> spec(), 68 | lib_web_resource_dir: is_binary() |> spec(), 69 | lib_web_resource_dir_plural: is_binary() |> spec(), 70 | lib_resource_dir: is_binary() |> spec(), 71 | lib_resource_dir_plural: is_binary() |> spec(), 72 | test_web_domain_dir: is_binary() |> spec(), 73 | test_domain_dir: is_binary() |> spec(), 74 | test_web_resource_dir: is_binary() |> spec(), 75 | test_web_resource_dir_plural: is_binary() |> spec(), 76 | test_resource_dir: is_binary() |> spec(), 77 | test_resource_dir_plural: is_binary() |> spec(), 78 | generate?: is_boolean() |> spec(), 79 | context_app: is_atom() |> spec(), 80 | resource_actions: is_list() |> spec(), 81 | resource_path_name_singular: is_binary() |> spec(), 82 | resource_path_name_plural: is_binary() |> spec(), 83 | opts: is_list() |> spec() 84 | }) 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/domain_resource_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.DomainResourceAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.Domain.Resource.GenerateSchema.GenerateSchemaResource 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Generator.Domain.Resource.BuildContract.BuildDomainResourceContract 7 | alias ElixirScribe.Generator.Domain.Resource.BuildFilesToGenerate.BuildFilesToGenerateResource 8 | alias ElixirScribe.Generator.Domain.Resource.BuildAPIFilePaths.BuildAPIFilePathsResource 9 | alias ElixirScribe.Generator.Domain.Resource.GenerateActions.GenerateActionsResource 10 | alias ElixirScribe.Generator.Domain.Resource.GenerateTests.GenerateTestsResource 11 | alias ElixirScribe.Generator.Domain.Resource.BuildActionFilesPaths.BuildActionFilesPathsResource 12 | 13 | alias ElixirScribe.Generator.Domain.Resource.BuildTestActionFilesPaths.BuildTestActionFilesPathsResource 14 | 15 | alias ElixirScribe.Generator.Domain.Resource.GenerateApi.GenerateApiResource 16 | alias ElixirScribe.Generator.Domain.Resource.GenerateTestFixture.GenerateTestFixtureResource 17 | alias ElixirScribe.Generator.Domain.Resource.GenerateNewFiles.GenerateNewFilesResource 18 | 19 | @doc """ 20 | Resource: Build DomainContract. 21 | """ 22 | def build_domain_resource_contract(args, opts) when is_list(args) and is_list(opts), 23 | do: BuildDomainResourceContract.build(args, opts) 24 | 25 | def build_domain_resource_contract!(args, opts) when is_list(args) and is_list(opts), 26 | do: BuildDomainResourceContract.build!(args, opts) 27 | 28 | @doc """ 29 | Resource: Files To Generate. 30 | """ 31 | def build_files_to_generate(%DomainContract{} = contract), 32 | do: BuildFilesToGenerateResource.build(contract) 33 | 34 | @doc """ 35 | Resource: Build Action Files To Generate 36 | """ 37 | def build_action_files_paths(%DomainContract{} = contract), 38 | do: BuildActionFilesPathsResource.build(contract) 39 | 40 | @doc """ 41 | Resource: Build Test Action Files To Generate 42 | """ 43 | def build_test_action_files_paths(%DomainContract{} = contract), 44 | do: BuildTestActionFilesPathsResource.build(contract) 45 | 46 | def build_api_file_paths(%DomainContract{} = contract), 47 | do: BuildAPIFilePathsResource.build(contract) 48 | 49 | @doc """ 50 | Resource: Generate Schema. 51 | """ 52 | def generate_schema(%DomainContract{} = contract), 53 | do: GenerateSchemaResource.generate(contract) 54 | 55 | @doc """ 56 | Resource: Generate Actions. 57 | """ 58 | def generate_actions(%DomainContract{} = contract), 59 | do: GenerateActionsResource.generate(contract) 60 | 61 | @doc """ 62 | Resource: Generate Tests. 63 | """ 64 | def generate_tests(%DomainContract{} = contract), 65 | do: GenerateTestsResource.generate(contract) 66 | 67 | @doc """ 68 | Resource: Generate Api. 69 | """ 70 | def generate_api(%DomainContract{} = contract), do: GenerateApiResource.generate(contract) 71 | 72 | @doc """ 73 | Resource: Generate Test Fixture. 74 | """ 75 | def generate_test_fixture(%DomainContract{} = contract), 76 | do: GenerateTestFixtureResource.generate(contract) 77 | 78 | @doc """ 79 | Resource: Generate New Files. 80 | """ 81 | def generate_new_files(%DomainContract{} = contract, opts \\ []) when is_list(opts), 82 | do: GenerateNewFilesResource.generate(contract, opts) 83 | end 84 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/schema/resource/schema_resource_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Schema.Resource.SchemaResourceHelpers do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.SchemaContract 5 | 6 | @valid_types [ 7 | :integer, 8 | :float, 9 | :decimal, 10 | :boolean, 11 | :map, 12 | :string, 13 | :array, 14 | :references, 15 | :text, 16 | :date, 17 | :time, 18 | :time_usec, 19 | :naive_datetime, 20 | :naive_datetime_usec, 21 | :utc_datetime, 22 | :utc_datetime_usec, 23 | :uuid, 24 | :binary, 25 | :enum 26 | ] 27 | 28 | def valid_types, do: @valid_types 29 | 30 | @doc """ 31 | Returns the string value of the default schema param. 32 | """ 33 | def default_param(%SchemaContract{} = schema, action) when is_atom(action) do 34 | schema.params 35 | |> Map.fetch!(action) 36 | |> Map.fetch!(schema.params.default_key) 37 | |> to_string() 38 | end 39 | 40 | @doc """ 41 | Converts the given value to map format when it is a date, time, datetime or naive_datetime. 42 | 43 | Since `form_component.html.heex` generated by the live generator uses selects for dates and/or 44 | times, fixtures must use map format for those fields in order to submit the live form. 45 | """ 46 | def live_form_value(%Date{} = date), do: Calendar.strftime(date, "%Y-%m-%d") 47 | 48 | def live_form_value(%Time{} = time), do: Calendar.strftime(time, "%H:%M") 49 | 50 | def live_form_value(%NaiveDateTime{} = naive) do 51 | NaiveDateTime.to_iso8601(naive) 52 | end 53 | 54 | def live_form_value(%DateTime{} = naive) do 55 | DateTime.to_iso8601(naive) 56 | end 57 | 58 | def live_form_value(value), do: value 59 | 60 | @doc """ 61 | Build an invalid value for `@invalid_attrs` which is nil by default. 62 | 63 | * In case the value is a list, this will return an empty array. 64 | * In case the value is date, datetime, naive_datetime or time, this will return an invalid date. 65 | * In case it is a boolean, we keep it as false 66 | """ 67 | def invalid_form_value(value) when is_list(value), do: [] 68 | 69 | def invalid_form_value(%{day: _day, month: _month, year: _year} = _date), 70 | do: "2022-00" 71 | 72 | def invalid_form_value(%{hour: _hour, minute: _minute}), do: %{hour: 14, minute: 00} 73 | def invalid_form_value(true), do: false 74 | def invalid_form_value(_value), do: nil 75 | 76 | @doc """ 77 | Generates an invalid error message according to the params present in the schema. 78 | """ 79 | def failed_render_change_message(_schema) do 80 | "can't be blank" 81 | end 82 | 83 | def type_for_migration({:enum, _}), do: :string 84 | def type_for_migration(other), do: other 85 | 86 | def format_fields_for_schema(schema) do 87 | Enum.map_join(schema.types, "\n", fn {k, v} -> 88 | " field #{inspect(k)}, #{type_and_opts_for_schema(v)}#{schema.defaults[k]}#{maybe_redact_field(k in schema.redacts)}" 89 | end) 90 | end 91 | 92 | @doc """ 93 | Return the required fields in the schema. Anything not in the `optionals` list 94 | is considered required. 95 | """ 96 | def required_fields(schema) do 97 | Enum.reject(schema.attrs, fn {key, _} -> key in schema.optionals end) 98 | end 99 | 100 | def type_and_opts_for_schema({:enum, opts}), 101 | do: ~s|Ecto.Enum, values: #{inspect(Keyword.get(opts, :values))}| 102 | 103 | def type_and_opts_for_schema(other), do: inspect(other) 104 | 105 | def maybe_redact_field(true), do: ", redact: true" 106 | def maybe_redact_field(false), do: "" 107 | 108 | @doc """ 109 | Returns the string value for use in EEx templates. 110 | """ 111 | def value(schema, field, value) do 112 | schema.types 113 | |> Map.fetch!(field) 114 | |> inspect_value(value) 115 | end 116 | 117 | defp inspect_value(:decimal, value), do: "Decimal.new(\"#{value}\")" 118 | defp inspect_value(_type, value), do: inspect(value) 119 | end 120 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/schema_contract.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.SchemaContract do 2 | @moduledoc false 3 | 4 | # This module was borrowed from the Phoenix Framework module 5 | # Mix.Phoenix.Schema and modified to suite ElixirScribe needs. 6 | 7 | @optional [ 8 | module: nil, 9 | repo: nil, 10 | repo_alias: nil, 11 | table: nil, 12 | collection: nil, 13 | embedded?: false, 14 | generate?: true, 15 | opts: [], 16 | alias: nil, 17 | alias_plural: nil, 18 | file: nil, 19 | attrs: [], 20 | string_attr: nil, 21 | plural: nil, 22 | singular: nil, 23 | uniques: [], 24 | redacts: [], 25 | assocs: [], 26 | types: [], 27 | indexes: [], 28 | defaults: [], 29 | human_singular: nil, 30 | human_plural: nil, 31 | binary_id: false, 32 | migration_defaults: nil, 33 | migration?: false, 34 | migration_dir: nil, 35 | params: %{}, 36 | optionals: [], 37 | sample_id: nil, 38 | web_path: nil, 39 | web_namespace: nil, 40 | context_app: nil, 41 | route_helper: nil, 42 | route_prefix: nil, 43 | api_route_prefix: nil, 44 | migration_module: nil, 45 | fixture_unique_functions: [], 46 | fixture_params: [], 47 | prefix: nil, 48 | timestamp_type: :naive_datetime 49 | ] 50 | 51 | use ElixirScribe.Behaviour.TypedContract, fields: %{required: [], optional: @optional} 52 | 53 | @impl true 54 | def type_spec() do 55 | schema(%__MODULE__{ 56 | module: is_atom() |> spec(), 57 | repo: is_atom() |> spec(), 58 | repo_alias: is_binary() |> spec(), 59 | table: is_binary() |> spec(), 60 | collection: is_binary() |> spec(), 61 | embedded?: is_boolean() |> spec(), 62 | generate?: is_boolean() |> spec(), 63 | opts: [], 64 | alias: is_atom() |> spec(), 65 | alias_plural: is_atom() |> spec(), 66 | file: is_binary() |> spec(), 67 | attrs: [], 68 | string_attr: is_atom() |> spec(), 69 | plural: is_binary() |> spec(), 70 | singular: is_binary() |> spec(), 71 | uniques: [], 72 | redacts: [], 73 | assocs: [], 74 | types: [], 75 | indexes: [], 76 | defaults: [], 77 | human_singular: is_binary() |> spec(), 78 | human_plural: is_binary() |> spec(), 79 | binary_id: is_boolean() |> spec(), 80 | migration_defaults: is_map() |> spec(), 81 | migration?: is_boolean() |> spec(), 82 | migration_dir: is_binary() |> spec(), 83 | params: %{}, 84 | optionals: [], 85 | sample_id: is_binary() |> spec(), 86 | web_path: is_binary() |> spec(), 87 | web_namespace: is_binary() |> spec(), 88 | context_app: is_atom() |> spec(), 89 | route_helper: is_binary() |> spec(), 90 | route_prefix: is_binary() |> spec(), 91 | api_route_prefix: is_binary() |> spec(), 92 | migration_module: is_atom() |> spec(), 93 | fixture_unique_functions: [], 94 | fixture_params: [], 95 | prefix: spec(is_binary() or is_nil()), 96 | timestamp_type: is_atom() |> spec() 97 | }) 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/elixir_scribe/generator/schema_resource_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.SchemaResourceAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.SchemaContract 5 | alias ElixirScribe.Generator.Schema.Resource.SchemaResourceHelpers 6 | alias ElixirScribe.Generator.Schema.Resource.BuildSchemaResourceContract 7 | 8 | def build_schema_resource_contract(args, opts) when is_list(args) and is_list(opts), 9 | do: BuildSchemaResourceContract.build(args, opts) 10 | 11 | def build_schema_resource_contract!(args, opts) when is_list(args) and is_list(opts), 12 | do: BuildSchemaResourceContract.build!(args, opts) 13 | 14 | def default_param_value(%SchemaContract{} = contract, action) when is_atom(action), 15 | do: SchemaResourceHelpers.default_param(contract, action) 16 | 17 | def live_form_value(%Date{} = date), do: SchemaResourceHelpers.live_form_value(date) 18 | def live_form_value(%Time{} = time), do: SchemaResourceHelpers.live_form_value(time) 19 | def live_form_value(%DateTime{} = datetime), do: SchemaResourceHelpers.live_form_value(datetime) 20 | 21 | def live_form_value(%NaiveDateTime{} = naive_datetime), 22 | do: SchemaResourceHelpers.live_form_value(naive_datetime) 23 | 24 | def live_form_value(value), do: SchemaResourceHelpers.live_form_value(value) 25 | end 26 | -------------------------------------------------------------------------------- /lib/elixir_scribe/mix/cli_command/parse/parse_cli_command.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Mix.CLICommand.Parse.ParseCLICommand do 2 | @moduledoc false 3 | 4 | @supported_options [ 5 | table: :string, 6 | web: :string, 7 | schema: :boolean, 8 | context_app: :string, 9 | merge_with_existing_context: :boolean, 10 | prefix: :string, 11 | live: :boolean, 12 | actions: :string, 13 | no_default_actions: :boolean, 14 | html_template: :string 15 | ] 16 | 17 | @default_opts [ 18 | schema: true, 19 | no_default_actions: false, 20 | actions: nil, 21 | html_template: "default" 22 | ] 23 | 24 | def parse(args) when is_list(args) do 25 | {opts, parsed_args, invalid_opts} = 26 | args 27 | |> extract_args_and_opts() 28 | 29 | all_opts = opts |> parse_options() 30 | 31 | {parsed_args, all_opts, invalid_opts} 32 | end 33 | 34 | defp extract_args_and_opts(args) do 35 | OptionParser.parse(args, strict: @supported_options) 36 | end 37 | 38 | defp parse_options(opts) when is_list(opts) do 39 | @default_opts 40 | |> Keyword.merge(opts) 41 | |> put_context_app(opts[:context_app]) 42 | |> put_resource_actions() 43 | end 44 | 45 | defp put_context_app(opts, nil), do: opts 46 | 47 | defp put_context_app(opts, string) do 48 | Keyword.put(opts, :context_app, String.to_atom(string)) 49 | end 50 | 51 | defp put_resource_actions(opts) do 52 | resource_actions = build_actions_from_options(opts) 53 | Keyword.put(opts, :resource_actions, resource_actions) 54 | end 55 | 56 | defp build_actions_from_options(opts) when is_list(opts) do 57 | no_default_actions = (opts[:no_default_actions] && true) || false 58 | 59 | if no_default_actions do 60 | actions(opts[:actions]) 61 | else 62 | (actions(opts[:actions]) ++ ElixirScribe.resource_actions()) 63 | |> Enum.uniq() 64 | end 65 | end 66 | 67 | defp actions(actions) when is_binary(actions) and byte_size(actions) > 0, 68 | do: actions |> String.split(",") 69 | 70 | defp actions(_actions), do: [] 71 | end 72 | -------------------------------------------------------------------------------- /lib/elixir_scribe/mix/cli_command/parse_schema/parse_schema_cli_command.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Mix.CLICommand.ParseSchema.ParseSchemaCLICommand do 2 | @moduledoc false 3 | 4 | @supported_options [ 5 | migration: :boolean, 6 | table: :string, 7 | web: :string, 8 | prefix: :string, 9 | repo: :string, 10 | migration_dir: :string 11 | ] 12 | 13 | @default_opts [ 14 | migration: true 15 | ] 16 | 17 | def parse(args) when is_list(args) do 18 | {opts, parsed_args, invalid_opts} = 19 | args 20 | |> extract_args_and_opts() 21 | 22 | all_opts = opts |> parse_options() 23 | 24 | {parsed_args, all_opts, invalid_opts} 25 | end 26 | 27 | defp extract_args_and_opts(args) do 28 | OptionParser.parse(args, strict: @supported_options) 29 | end 30 | 31 | defp parse_options(opts) when is_list(opts) do 32 | @default_opts 33 | |> Keyword.merge(opts) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/elixir_scribe/mix/mix_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.MixAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Mix.Shell.PromptForFileConflicts.PromptForFileConflictsShell 5 | alias ElixirScribe.Mix.CLICommand.Parse.ParseCLICommand 6 | alias ElixirScribe.Mix.CLICommand.ParseSchema.ParseSchemaCLICommand 7 | 8 | @doc """ 9 | Parses the provided list, that represents the CLI command, into one list of arguments and one list of options. The returned options will include all the options with their defaults. 10 | 11 | It returns a tuple in this format: `{parsed_args, all_opts, invalid}`. 12 | """ 13 | def parse_cli_command(args) when is_list(args), do: ParseCLICommand.parse(args) 14 | 15 | @doc """ 16 | Parses the provided list, that represents the Schema CLI command, into one list of arguments and one list of options. The returned options will include all the options with their defaults. 17 | 18 | It returns a tuple in this format: `{parsed_args, all_opts, invalid}`. 19 | """ 20 | def parse_schema_cli_command(args) when is_list(args), do: ParseSchemaCLICommand.parse(args) 21 | 22 | @doc """ 23 | Prompts to continue if any files exist. 24 | """ 25 | def prompt_for_file_conflicts(files) when is_list(files), 26 | do: PromptForFileConflictsShell.prompt(files) 27 | end 28 | -------------------------------------------------------------------------------- /lib/elixir_scribe/mix/shell/prompt_for_file_conflicts/prompt_for_file_conflicts_shell.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Mix.Shell.PromptForFileConflicts.PromptForFileConflictsShell do 2 | @moduledoc false 3 | 4 | # The function `prompt/1` was copied from the Phoenix Framework module at `/lib/mix/phoenix.ex`. 5 | # It was modified to use the `action` from the provided `generator_files`, 6 | # which is now a three elements tuple. 7 | 8 | @doc false 9 | def prompt(generator_files) when is_list(generator_files) do 10 | file_paths = 11 | Enum.flat_map(generator_files, fn 12 | {:new_eex, _, _path} -> [] 13 | {_kind, _source_path, target_path} -> [target_path] 14 | {_kind, _file_type, _source_path, target_path} -> [target_path] 15 | {_kind, _file_type, _source_path, target_path, _action} -> [target_path] 16 | end) 17 | 18 | case Enum.filter(file_paths, &File.exists?(&1)) do 19 | [] -> 20 | :ok 21 | 22 | conflicts -> 23 | Mix.shell().info(""" 24 | The following files conflict with new files to be generated: 25 | 26 | #{Enum.map_join(conflicts, "\n", &" * #{&1}")} 27 | 28 | """) 29 | 30 | unless Mix.shell().yes?("Proceed with interactive overwrite?") do 31 | System.halt() 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/binding/build/build_binding_template.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Binding.Build.BuildBindingTemplate do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Utils.StringAPI 7 | 8 | def build(%DomainContract{} = contract) do 9 | resource_actions = contract.resource_actions 10 | 11 | [contract: contract] 12 | |> add_resource_action_aliases(resource_actions) 13 | |> add_resource_action_aliases_capitalized(resource_actions) 14 | |> add_embeded_templates() 15 | end 16 | 17 | defp add_resource_action_aliases(binding, resource_actions) do 18 | new_bindings = 19 | resource_actions 20 | |> Keyword.new(fn action -> 21 | action_alias = ElixirScribe.resource_action_alias(action) 22 | action_key = String.to_atom("#{action}_action") 23 | {action_key, action_alias} 24 | end) 25 | 26 | Keyword.merge(binding, new_bindings) 27 | end 28 | 29 | defp add_resource_action_aliases_capitalized(binding, resource_actions) do 30 | new_bindings = 31 | resource_actions 32 | |> Keyword.new(&new_action_binding/1) 33 | 34 | Keyword.merge(binding, new_bindings) 35 | end 36 | 37 | defp new_action_binding(action) do 38 | action_alias_capitalized = 39 | action 40 | |> ElixirScribe.resource_action_alias() 41 | |> StringAPI.capitalize() 42 | 43 | action_key = String.to_atom("#{action}_action_capitalized") 44 | 45 | {action_key, action_alias_capitalized} 46 | end 47 | 48 | defp add_embeded_templates(binding) do 49 | Keyword.put(binding, :embeded_templates, ModuleAPI.build_embeded_templates()) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/binding/rebuild/rebuild_binding_template.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Binding.Rebuild.RebuildBindingTemplate do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Utils.StringAPI 6 | 7 | def rebuild(bindings, action, opts) 8 | when is_list(bindings) and is_binary(action) and is_list(opts) do 9 | contract = Keyword.get(bindings, :contract) 10 | 11 | Keyword.merge(bindings, 12 | action: action, 13 | action_first_word: StringAPI.first_word(action), 14 | action_capitalized: StringAPI.capitalize(action), 15 | action_human_capitalized: StringAPI.human_capitalize(action), 16 | module_action_name: ModuleAPI.build_module_action_name(contract, action), 17 | absolute_module_action_name: 18 | ModuleAPI.build_absolute_module_action_name(contract, action, opts) 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/binding_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.BindingAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | alias ElixirScribe.Template.Binding.Build.BuildBindingTemplate 6 | alias ElixirScribe.Template.Binding.Rebuild.RebuildBindingTemplate 7 | 8 | def build_binding_template(%DomainContract{} = contract), 9 | do: BuildBindingTemplate.build(contract) 10 | 11 | def rebuild_binding_template(binding, action, opts), 12 | do: RebuildBindingTemplate.rebuild(binding, action, opts) 13 | end 14 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/binding_api_contract_build_filename_for_action_file.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.BuildFilenameForActionFileContract do 2 | @moduledoc false 3 | 4 | @fields %{ 5 | required: [:action, :action_suffix, :file_type, :file_extension], 6 | optional: [] 7 | } 8 | 9 | use ElixirScribe.Behaviour.TypedContract, fields: @fields 10 | 11 | @impl true 12 | def type_spec() do 13 | schema(%__MODULE__{ 14 | action: is_binary() |> spec(), 15 | action_suffix: is_binary() |> spec(), 16 | file_type: is_binary() |> spec(), 17 | file_extension: is_binary() |> spec() 18 | }) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/file/build_dir_path_for_html/build_dir_path_for_html_file.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.BuildPathForHtml.BuildPathForHtmlFile do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | 6 | def build(%DomainContract{} = contract) do 7 | template = Keyword.get(contract.opts, :html_template, "default") 8 | 9 | ElixirScribe.html_template_path() 10 | |> Path.join(template) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/file/build_filename_for_action/build_filename_for_action_file.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.BuildFilenameForAction.BuildFilenameForActionFile do 2 | @moduledoc false 3 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 4 | 5 | def build(%BuildFilenameForActionFileContract{} = contract) do 6 | case contract.action in ElixirScribe.resource_actions() do 7 | true -> 8 | "#{contract.action}#{contract.action_suffix}#{contract.file_type}#{contract.file_extension}" 9 | 10 | false -> 11 | "default#{contract.action_suffix}#{contract.file_type}#{contract.file_extension}" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/file/inject/inject_content_before_module_end.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.Inject.InjectContentBeforeModuleEnd do 2 | @moduledoc false 3 | 4 | def inject(content_to_inject, file_path) 5 | when is_binary(content_to_inject) and is_binary(file_path) do 6 | file_content = File.read!(file_path) 7 | 8 | if String.contains?(file_content, content_to_inject) do 9 | {:noop, :content_to_inject_already_exists} 10 | else 11 | Mix.shell().info([:green, "* injecting ", :reset, Path.relative_to_cwd(file_path)]) 12 | 13 | file_content 14 | |> String.trim_trailing() 15 | |> String.trim_trailing("end") 16 | |> Kernel.<>(content_to_inject) 17 | |> Kernel.<>("end\n") 18 | |> write_file(file_path) 19 | end 20 | end 21 | 22 | defp write_file(content, file_path) do 23 | File.write!(file_path, content) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/file/inject/inject_eex_template_before_module.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.Inject.InjectEExTemplateBeforeModuleEnd do 2 | @moduledoc false 3 | alias ElixirScribe.Template.FileAPI 4 | 5 | def inject(base_template_paths, source_path, target_path, binding) 6 | when is_list(base_template_paths) and is_binary(source_path) and is_binary(target_path) and 7 | is_list(binding) do 8 | base_template_paths 9 | |> Mix.Phoenix.eval_from(source_path, binding) 10 | |> FileAPI.inject_content_before_module_end(target_path) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/file_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.FileAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Template.File.Inject.InjectContentBeforeModuleEnd 7 | alias ElixirScribe.Template.File.Inject.InjectEExTemplateBeforeModuleEnd 8 | alias ElixirScribe.Template.File.BuildFilenameForAction.BuildFilenameForActionFile 9 | alias ElixirScribe.Template.File.BuildPathForHtml.BuildPathForHtmlFile 10 | 11 | def build_dir_path_for_html_file(%DomainContract{} = contract), 12 | do: BuildPathForHtmlFile.build(contract) 13 | 14 | def build_template_action_filename(%BuildFilenameForActionFileContract{} = contract), 15 | do: BuildFilenameForActionFile.build(contract) 16 | 17 | def inject_content_before_module_end(content_to_inject, file_path) 18 | when is_binary(content_to_inject) and is_binary(file_path), 19 | do: InjectContentBeforeModuleEnd.inject(content_to_inject, file_path) 20 | 21 | def inject_eex_template_before_module_end( 22 | base_template_paths, 23 | source_path, 24 | target_path, 25 | binding 26 | ) 27 | when is_list(base_template_paths) and is_binary(source_path) and is_binary(target_path) and 28 | is_list(binding), 29 | do: 30 | InjectEExTemplateBeforeModuleEnd.inject( 31 | base_template_paths, 32 | source_path, 33 | target_path, 34 | binding 35 | ) 36 | end 37 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module/build_embed_templates/build_module_embed_templates.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Options.BuildEmbedTemplates.BuildModuleEmbedTemplates do 2 | @moduledoc false 3 | 4 | def build() do 5 | for action <- ElixirScribe.resource_html_actions(), reduce: "" do 6 | embeds -> embeds <> "\n embed_templates \"#{action}/*\"" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module/build_name/build_absolute_module_action_name.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionName do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | alias ElixirScribe.Utils.StringAPI 7 | 8 | def build(%DomainContract{} = _context, _action, file_type: :html), do: nil 9 | 10 | def build(%DomainContract{} = context, action, opts) when is_list(opts) do 11 | base_module = ModuleAPI.build_absolute_module_name(context, opts) |> inspect() 12 | module_action_name = ModuleAPI.build_module_action_name(context, action) 13 | action_capitalized = action |> StringAPI.capitalize() 14 | 15 | "#{base_module}.#{action_capitalized}.#{module_action_name}" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module/build_name/build_absolute_module_action_name_aliases.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionNameAliases do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | 7 | def build(%DomainContract{} = context, opts) when is_list(opts) do 8 | resource_actions = context.opts |> Keyword.get(:resource_actions) 9 | 10 | for action <- resource_actions, reduce: "" do 11 | aliases -> 12 | aliases <> 13 | "\n alias " <> 14 | ModuleAPI.build_absolute_module_action_name(context, action, opts) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module/build_name/build_absolute_module_name.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleName do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | 6 | def build(%DomainContract{} = _contract, file_type: :html), do: nil 7 | 8 | def build(%DomainContract{} = contract, opts) when is_list(opts), 9 | do: module_name(contract, opts) 10 | 11 | @core_files [:resource, :resource_test, :lib_core, :test_core] 12 | defp module_name(contract, file_type: type) when type in @core_files, 13 | do: contract.resource_module 14 | 15 | @web_files [:lib_web, :controller, :controller_test, :test_web] 16 | defp module_name(contract, file_type: type) when type in @web_files, 17 | do: contract.web_resource_module 18 | end 19 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module/build_name/build_module_action_name.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildModuleActionName do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | alias ElixirScribe.Utils.StringAPI 6 | 7 | def build(%DomainContract{} = context, action) when is_binary(action) do 8 | schema = 9 | (action in ElixirScribe.resource_plural_actions() && context.schema.alias_plural) || 10 | context.schema.alias 11 | 12 | action_capitalized = action |> StringAPI.capitalize() 13 | 14 | "#{action_capitalized}#{inspect(schema)}" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/module_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.ModuleAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | alias ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleName 6 | alias ElixirScribe.Template.Module.BuildName.BuildModuleActionName 7 | alias ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionName 8 | alias ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionNameAliases 9 | alias ElixirScribe.Template.Options.BuildEmbedTemplates.BuildModuleEmbedTemplates 10 | 11 | def build_embeded_templates(), do: BuildModuleEmbedTemplates.build() 12 | 13 | def build_absolute_module_action_name(%DomainContract{} = contract, action, opts) 14 | when is_list(opts), 15 | do: BuildAbsoluteModuleActionName.build(contract, action, opts) 16 | 17 | def build_absolute_module_action_name_aliases(%DomainContract{} = contract, opts) 18 | when is_list(opts), 19 | do: BuildAbsoluteModuleActionNameAliases.build(contract, opts) 20 | 21 | def build_absolute_module_name(%DomainContract{} = contract, opts) when is_list(opts), 22 | do: BuildAbsoluteModuleName.build(contract, opts) 23 | 24 | def build_module_action_name(%DomainContract{} = contract, action) when is_binary(action), 25 | do: BuildModuleActionName.build(contract, action) 26 | end 27 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/route/scope/scope_action_routes.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Route.Scope.ScopeActionRoutes do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Template.ModuleAPI 5 | alias ElixirScribe.Generator.DomainContract 6 | 7 | def scope(%DomainContract{schema: schema} = contract) do 8 | resource = schema.plural 9 | controller = inspect(contract.web_resource_module) 10 | scope_alias = String.replace(schema.web_path, "/", "_") <> "_" <> resource 11 | 12 | """ 13 | 14 | scope "/#{schema.web_path}/#{resource}", #{controller}, as: :#{scope_alias} do 15 | pipe_through :browser 16 | 17 | #{build_action_routes(contract)} 18 | end 19 | """ 20 | end 21 | 22 | defp build_action_routes(%DomainContract{} = contract) do 23 | resource_actions = contract.opts |> Keyword.get(:resource_actions) 24 | 25 | for action <- resource_actions, reduce: "" do 26 | routes -> 27 | routes <> build_route_action(action, contract) 28 | end 29 | |> String.trim_leading() 30 | end 31 | 32 | defp build_route_action("create", contract) do 33 | assemble_route_action("post", "create", contract) 34 | end 35 | 36 | defp build_route_action("update", contract) do 37 | assemble_route_action("patch", "update", contract) <> 38 | assemble_route_action("put", "update", contract) 39 | end 40 | 41 | defp build_route_action("delete", contract) do 42 | assemble_route_action("delete", "delete", contract) 43 | end 44 | 45 | defp build_route_action(action, contract) do 46 | assemble_route_action("get", action, contract) 47 | end 48 | 49 | defp assemble_route_action(method, action, contract) do 50 | action_alias = ElixirScribe.resource_action_alias(action) 51 | endpoint = build_endpoint(action, action_alias) 52 | controller = build_controller(contract, action, action_alias) 53 | 54 | "\n #{method} \"#{endpoint}\", #{controller}, :#{action_alias}" 55 | end 56 | 57 | @http_get_actions ["read", "new", "edit", "list"] 58 | @resource_id_actions ["read", "update", "delete"] 59 | 60 | defp build_endpoint("create", _action_alias), do: "/" 61 | defp build_endpoint("new", action_alias), do: "/#{action_alias}" 62 | defp build_endpoint("edit", action_alias), do: "/:id/#{action_alias}" 63 | 64 | defp build_endpoint(action, _action_alias) when action in @resource_id_actions, 65 | do: "/:id" 66 | 67 | defp build_endpoint(action, _action_alias) when action in @http_get_actions, 68 | do: "/" 69 | 70 | defp build_endpoint(_action, action_alias), do: "/#{action_alias}" 71 | 72 | defp build_controller(contract, action, action_alias) do 73 | action_capitalized = String.capitalize(action_alias) 74 | module_action_name = ModuleAPI.build_module_action_name(contract, action) 75 | 76 | "#{action_capitalized}.#{module_action_name}Controller" 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/elixir_scribe/template/route_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.RouteAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | alias ElixirScribe.Template.Route.Scope.ScopeActionRoutes 6 | 7 | def scope_routes(%DomainContract{} = contract), do: ScopeActionRoutes.scope(contract) 8 | end 9 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string/camel_case_to_sentence/camel_case_to_sentence.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.CamelCaseToSentence do 2 | @moduledoc false 3 | 4 | # Regex Components: 5 | # * (?<=[A-Z])(?=[A-Z][a-z]) - Matches a position after an uppercase letter 6 | # and before a sequence of an uppercase letter followed by a lowercase 7 | # letter. Example: In "ISBNFiles", this matches between "ISBN" and "Files". 8 | # * (?<=[a-z])(?=[A-Z]) - Matches a position after a lowercase letter and 9 | # before an uppercase letter. Example: In "MySuperVariable", this matches 10 | # between "My" and "Super". 11 | 12 | @doc """ 13 | Converts a camel case word to a capitalized sentence and preserves acronyms. 14 | 15 | ## Examples 16 | iex> CamelCaseToSentence.convert("ISBNFiles", :capitalized) 17 | "ISBN Files" 18 | 19 | iex> CamelCaseToSentence.convert("CamelCaseToSentence", :capitalized) 20 | "Camel Case To Sentence" 21 | 22 | iex> CamelCaseToSentence.convert("ABCD", :capitalized) 23 | "ABCD" 24 | """ 25 | def convert(camel_case_word, :capitalized) 26 | when is_binary(camel_case_word) and byte_size(camel_case_word) > 0 do 27 | camel_case_word 28 | |> String.replace(~r/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/, " ") 29 | end 30 | 31 | @doc """ 32 | Translates a camel case word into a sentence, preserving acronyms, with only 33 | the first word capitalized. 34 | 35 | ## Examples 36 | 37 | iex> CamelCaseToSentence.convert("HTTPRequestURLParser") 38 | "HTTP request URL parser" 39 | 40 | iex> CamelCaseToSentence.convert("XMLToJSON") 41 | "XML to JSON" 42 | 43 | iex> CamelCaseToSentence.convert("JSONAPIHandler") 44 | "JSONAPI handler" 45 | 46 | iex> CamelCaseToSentence.convert("SimpleExample") 47 | "Simple example" 48 | """ 49 | def convert(camel_case_word) 50 | when is_binary(camel_case_word) and byte_size(camel_case_word) > 0 do 51 | camel_case_word 52 | |> split_camel_case_into_words() 53 | |> format_words() 54 | |> Enum.join(" ") 55 | end 56 | 57 | defp split_camel_case_into_words(word) do 58 | # Regular expression for splitting camel case while preserving acronyms 59 | regex = ~r/(?<=[a-z])(?=[A-Z])|(?<=[A-Z]{2})(?=[A-Z][a-z])/ 60 | 61 | String.split(word, regex) 62 | end 63 | 64 | defp format_words(words) do 65 | words 66 | |> Enum.with_index() 67 | |> Enum.map(fn 68 | {first_word, 0} -> capitalize_sentence_first_word(first_word) 69 | {remaining_words, _} -> downcase_rest_of_sentence(remaining_words) 70 | end) 71 | end 72 | 73 | defp capitalize_sentence_first_word(word) do 74 | if acronym?(word), do: word, else: String.capitalize(word) 75 | end 76 | 77 | defp downcase_rest_of_sentence(word) do 78 | if acronym?(word), do: word, else: String.downcase(word) 79 | end 80 | 81 | defp acronym?(word) do 82 | String.upcase(word) == word 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string/capitalize/capitalize_string.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.Capitalize.CapitalizeString do 2 | @moduledoc false 3 | 4 | def capitalize(string, joiner \\ "") when is_binary(string) and is_binary(joiner) do 5 | string 6 | |> String.split(["_", "-", " "], trim: true) 7 | |> Enum.map(fn part -> part |> String.trim() |> String.capitalize() end) 8 | |> Enum.join(joiner) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string/find_acronyms/find_acronyms.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.FindAcronyms do 2 | @moduledoc false 3 | 4 | @doc """ 5 | Finds acronyms in a string. An acronym is defined as two or more consecutive uppercase letters. 6 | 7 | ## Examples 8 | 9 | iex> FindAcronyms.find("HTTPRequestHandler is a BooksAPI example") 10 | ["HTTP", "API"] 11 | 12 | iex> FindAcronyms.find("The ParseISBNHandler is an example") 13 | ["ISBN"] 14 | 15 | iex> FindAcronyms.find("JSONParser from file_OCAID") 16 | ["JSON", "OCAID"] 17 | 18 | iex> FindAcronyms.find("Find all TAR files from SQL_dumps dir") 19 | ["TAR", "SQL"] 20 | 21 | iex> FindAcronyms.find("Find all TXT files from dir some/JSON/folder") 22 | ["TXT", "JSON"] 23 | 24 | iex> FindAcronyms.find("This is a test with no acronyms") 25 | [] 26 | """ 27 | def find(string) when is_binary(string) and byte_size(string) > 0 do 28 | # Regex to match two or more uppercase letters not followed by a lowercase letter 29 | ~r/[A-Z]{2,}(?=[^a-z]|$)/ 30 | |> Regex.scan(string) 31 | |> List.flatten() 32 | end 33 | 34 | def find(_string), do: [] 35 | end 36 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string/first_word/first_word.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.FirstWord.FirstWordString do 2 | @moduledoc false 3 | 4 | def first(string, word_separators \\ ["_", "-", " "]) 5 | when is_binary(string) and is_list(word_separators) do 6 | string 7 | |> String.split(word_separators) 8 | |> List.first() 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string/human_capitalize/human_capitalize_string.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.HumanCapitalize.HumanCapitalizeString do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Utils.StringAPI 5 | 6 | def capitalize(string) when is_binary(string) do 7 | joiner = " " 8 | 9 | string 10 | |> StringAPI.capitalize(joiner) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/elixir_scribe/utils/string_api.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.StringAPI do 2 | @moduledoc false 3 | 4 | alias ElixirScribe.Utils.String.Capitalize.CapitalizeString 5 | alias ElixirScribe.Utils.String.HumanCapitalize.HumanCapitalizeString 6 | alias ElixirScribe.Utils.String.FirstWord.FirstWordString 7 | alias ElixirScribe.Utils.String.CamelCaseToSentence 8 | alias ElixirScribe.Utils.String.FindAcronyms 9 | 10 | def capitalize(string) when is_binary(string), do: CapitalizeString.capitalize(string) 11 | 12 | def capitalize(string, joiner) when is_binary(string) and is_binary(joiner), 13 | do: CapitalizeString.capitalize(string, joiner) 14 | 15 | def human_capitalize(string) when is_binary(string), 16 | do: HumanCapitalizeString.capitalize(string) 17 | 18 | def first_word(string) when is_binary(string), 19 | do: FirstWordString.first(string) 20 | 21 | def first_word(string, word_separators) when is_binary(string) when is_list(word_separators), 22 | do: FirstWordString.first(string, word_separators) 23 | 24 | def camel_case_to_sentence(camel_case_word) 25 | when is_binary(camel_case_word) and byte_size(camel_case_word) > 0, 26 | do: CamelCaseToSentence.convert(camel_case_word) 27 | 28 | def camel_case_to_sentence(camel_case_word, modifier) 29 | when is_binary(camel_case_word) and byte_size(camel_case_word) > 0 and is_atom(modifier), 30 | do: CamelCaseToSentence.convert(camel_case_word) 31 | 32 | def find_acronyms(string) when is_binary(string), do: FindAcronyms.find(string) 33 | end 34 | -------------------------------------------------------------------------------- /lib/mix/tasks/scribe.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Scribe do 2 | use Mix.Task 3 | 4 | @shortdoc "Prints Elixir Scribe help information" 5 | 6 | @moduledoc """ 7 | Prints Elixir Scribe tasks and their information. 8 | 9 | $ mix scribe 10 | 11 | To print the Phoenix version, pass `-v` or `--version`, for example: 12 | 13 | $ mix scribe --version 14 | 15 | """ 16 | 17 | @version Mix.Project.config()[:version] 18 | 19 | @impl true 20 | @doc false 21 | def run([version]) when version in ~w(-v --version) do 22 | Mix.shell().info("Elixir Scribe v#{@version}") 23 | end 24 | 25 | def run(args) do 26 | case args do 27 | [] -> help() 28 | _ -> Mix.raise("Invalid arguments, expected: mix scribe") 29 | end 30 | end 31 | 32 | defp help() do 33 | Application.ensure_all_started(:phoenix) 34 | Mix.shell().info("Elixir Scribe v#{Application.spec(:phoenix, :vsn)}") 35 | 36 | Mix.shell().info( 37 | "\nEnables craftsmanship through Clean Code in a Clean Software Architecture\n" 38 | ) 39 | 40 | Mix.shell().info("\n## Options\n") 41 | Mix.shell().info("-v, --version # Prints Elixir Scribe version\n") 42 | Mix.shell().info("\n## Tasks\n") 43 | Mix.Tasks.Help.run(["--search", "scribe."]) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mix/tasks/scribe.gen.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Scribe.Gen do 2 | # This module was borrowed from the Phoenix Framework module 3 | # Mix.Tasks.Phx.Gen and modified to suite ElixirScribe needs. 4 | 5 | use Mix.Task 6 | 7 | @shortdoc "Lists all available Scribe generators" 8 | 9 | @moduledoc """ 10 | Lists all available Scribe generators: 11 | 12 | ```console 13 | mix scribe.gen 14 | ``` 15 | """ 16 | 17 | @doc false 18 | def run(_args) do 19 | Mix.Task.run("help", ["--search", "scribe.gen."]) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.MixProject do 2 | use Mix.Project 3 | 4 | # The Elixir version here needs to be equal or greater then the one 5 | # used by the Phoenix Framework and Phoenix Installer. 6 | @elixir_requirement "~> 1.14" 7 | @scm_url "https://github.com/exadra37/elixir-scribe" 8 | 9 | @description """ 10 | Elixir Scribe - A Mix Task to Generate Elixir and Phoenix Projects 11 | 12 | The Elixir Scribe tool aims to help developers to more easily write clean code in a clean software architecture for enhanced developer experience and productivity. 13 | """ 14 | 15 | def project do 16 | [ 17 | app: :elixir_scribe, 18 | name: "Elixir Scribe", 19 | version: "0.3.0", 20 | elixir: @elixir_requirement, 21 | elixirc_paths: elixirc_paths(Mix.env()), 22 | start_permanent: Mix.env() == :prod, 23 | homepage_url: @scm_url, 24 | source_url: @scm_url, 25 | description: @description, 26 | package: package(), 27 | docs: docs(), 28 | aliases: aliases(), 29 | deps: deps() 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | extra_applications: [:logger] 37 | ] 38 | end 39 | 40 | # Specifies which paths to compile per environment. 41 | defp elixirc_paths(:test), do: ["lib", "test/support"] 42 | defp elixirc_paths(_), do: ["lib"] 43 | 44 | defp package do 45 | [ 46 | maintainers: ["Paulo Renato (Exadra37)"], 47 | licenses: ["MIT"], 48 | links: %{"GitHub" => @scm_url}, 49 | files: ~w(lib priv LICENSE.md mix.exs README.md .formatter.exs), 50 | exclude_patterns: [".local"] 51 | ] 52 | end 53 | 54 | defp docs() do 55 | [ 56 | main: "readme", 57 | extras: ["README.md"] 58 | # groups_for_extras: [ 59 | # Examples: ~r"test/examples" 60 | # ], 61 | ] 62 | end 63 | 64 | defp aliases do 65 | [docs: ["docs", ©_images/1]] 66 | end 67 | 68 | defp copy_images(_) do 69 | File.cp_r!("assets", "doc/assets") 70 | end 71 | 72 | # Run "mix help deps" to learn about dependencies. 73 | defp deps do 74 | [ 75 | # {:dep_from_hexpm, "~> 0.3.0"}, 76 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 77 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, 78 | {:phoenix, ">= 1.7.0"}, 79 | {:phoenix_ecto, "~> 4.4"}, 80 | {:norm, "~> 0.13"}, 81 | {:assertions, "0.19.0", only: [:test]} 82 | ] 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/action_module.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %> do 2 | @moduledoc false 3 | 4 | end 5 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/action_module_no_schema_access.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %> do 2 | @moduledoc false 3 | end 4 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex: -------------------------------------------------------------------------------- 1 | 2 | def <%= action_first_word %>() do 3 | raise "TODO: Implement the action `<%= action_first_word %>` for the module `<%= module_action_name %>`" 4 | end 5 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/create_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | alias <%= inspect contract.schema.module %>API 3 | 4 | def <%= action_first_word %>(%{} = attrs) when attrs !== %{} do 5 | attrs 6 | |> <%= inspect contract.schema.alias %>API.new() 7 | |> Repo.insert() 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/default_schema.ex: -------------------------------------------------------------------------------- 1 | # alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | # alias <%= inspect contract.schema.module %> 3 | 4 | def <%= action_first_word %>() do 5 | raise "TOOO: Implement business logic for #{__MODULE__}" 6 | end 7 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/delete_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | alias <%= inspect contract.schema.module %>API 3 | 4 | def <%= action_first_word %>(uuid) when is_binary(uuid) do 5 | uuid 6 | |> <%= inspect(contract.schema.alias) %>API.<%= read_action %>!() 7 | |> Repo.delete() 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/edit_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.module %> 2 | alias <%= inspect contract.schema.module %>API 3 | 4 | def <%= action_first_word %>(uuid, attrs \\ %{}) when is_binary(uuid) and is_map(attrs) do 5 | uuid 6 | |> <%= inspect contract.schema.alias %>API.<%= read_action %>!() 7 | |> <%= inspect contract.schema.alias %>.changeset(attrs) 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/list_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | alias <%= inspect contract.schema.module %> 3 | 4 | def <%= action_first_word %>() do 5 | Repo.all(<%= inspect contract.schema.alias %>) 6 | end 7 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/new_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.module %> 2 | 3 | def <%= action_first_word %>(attrs \\ %{}) when is_map(attrs) do 4 | <%= inspect contract.schema.alias %>.changeset(%<%= inspect contract.schema.alias %>{}, attrs) 5 | end 6 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/read_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | alias <%= inspect contract.schema.module %> 3 | 4 | def <%= action_first_word %>!(uuid) when is_binary(uuid), do: Repo.get!(<%= inspect contract.schema.alias %>, uuid) 5 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/actions/schema_access/update_schema.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.repo %><%= contract.schema.repo_alias %> 2 | alias <%= inspect contract.schema.module %> 3 | alias <%= inspect contract.schema.module %>API 4 | 5 | def <%= action_first_word %>(uuid, %{} = attrs) when is_binary(uuid) and is_map(attrs) do 6 | uuid 7 | |> <%= inspect(contract.schema.alias) %>API.<%= read_action %>!() 8 | |> <%= inspect(contract.schema.alias) %>.changeset(attrs) 9 | |> Repo.update() 10 | end 11 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/api_function_no_schema_access.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | <%= contract.schema.human_singular %>: <%= action_human_capitalized %>. 4 | """ 5 | def <%= action %>(), do: <%= module_action_name %>.<%= action_first_word %>() 6 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/api_module.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect absolute_module_name %>API do 2 | @moduledoc """ 3 | The <%= inspect contract.name %> <%= contract.schema.human_singular %> API. 4 | """ 5 | <%= aliases %> 6 | end 7 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/create_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Creates a <%= contract.schema.singular %> from the given `attrs`. 4 | 5 | ## Examples 6 | 7 | iex> <%= inspect contract.schema.alias %>API.<%= action %>(%{field: value}) 8 | {:ok, %<%= inspect contract.schema.alias %>{}} 9 | 10 | iex> <%= inspect contract.schema.alias %>API.<%= action %>(%{field: bad_value}) 11 | {:error, %Ecto.Changeset{}} 12 | 13 | """ 14 | def <%= action %>(attrs) when is_map(attrs), do: <%= module_action_name %>.<%= action_first_word %>(attrs) 15 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/default_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | def <%= action %>(), do: <%= module_action_name %>.<%= action_first_word %>() 3 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/delete_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Deletes the <%= contract.schema.singular %> for the given `uuid`. 4 | 5 | ## Examples 6 | 7 | iex> <%= inspect contract.schema.alias %>API.<%= action %>("38cd0012-79dc-4838-acc0-94d4143c4f2c") 8 | {:ok, %<%= inspect contract.schema.alias %>{}} 9 | 10 | iex> <%= inspect contract.schema.alias %>API.<%= action %>("815af9955-19ab-2567-bdd1-49e4143c4g3d") 11 | {:error, %Ecto.Changeset{}} 12 | 13 | """ 14 | def <%= action %>(uuid) when is_binary(uuid), do: <%= module_action_name %>.<%= action_first_word %>(uuid) 15 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/edit_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Returns an `%Ecto.Changeset{}` with a `%<%= inspect contract.schema.alias %>{}` for tracking changes for the given <%= contract.schema.singular %> `uuid`. 4 | 5 | ## Examples 6 | 7 | iex> <%= inspect contract.schema.alias %>API.<%= action %>("38cd0012-79dc-4838-acc0-94d4143c4f2c") 8 | %Ecto.Changeset{data: %<%= inspect contract.schema.alias %>{id: uuid}} 9 | 10 | """ 11 | def <%= action %>(uuid, attrs \\ %{}) when is_binary(uuid) and is_map(attrs), do: <%= module_action_name %>.<%= action_first_word %>(uuid, attrs) 12 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/list_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Returns the list of <%= contract.schema.plural %>. 4 | 5 | ## Examples 6 | 7 | iex> <%=inspect contract.schema.alias %>API.<%= action %>() 8 | [%<%= inspect contract.schema.alias %>{}, ...] 9 | 10 | """ 11 | def <%= action %>(), do: <%= module_action_name %>.<%= action_first_word %>() 12 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/new_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Returns an `%Ecto.Changeset{}` with a default `%<%= inspect contract.schema.alias %>{}` for tracking <%= contract.schema.singular %> changes. 4 | 5 | ## Examples 6 | 7 | iex> <%= inspect contract.schema.alias %>API.<%= action %>() 8 | %Ecto.Changeset{data: %<%= inspect contract.schema.alias %>{}} 9 | 10 | """ 11 | def <%= action %>(attrs \\ %{}) when is_map(attrs), do: <%= module_action_name %>.<%= action_first_word %>(attrs) 12 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/read_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Gets the `<%= inspect contract.schema.alias %>` for the given `uuid`. 4 | 5 | Raises `Ecto.NoResultsError` if the `<%= inspect contract.schema.alias %>` does not exist. 6 | 7 | ## Examples 8 | 9 | iex> <%= inspect contract.schema.alias %>API.<%= action %>!("38cd0012-79dc-4838-acc0-94d4143c4f2c") 10 | %<%= inspect contract.schema.alias %>{} 11 | 12 | iex> <%= inspect contract.schema.alias %>API.<%= action %>!("815af9955-19ab-2567-bdd1-49e4143c4g3d") 13 | ** (Ecto.NoResultsError) 14 | 15 | """ 16 | def <%= action %>!(uuid) when is_binary(uuid), do: <%= module_action_name %>.<%= action_first_word %>!(uuid) 17 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/apis/update_api_function.ex: -------------------------------------------------------------------------------- 1 | 2 | @doc """ 3 | Updates the `<%= inspect contract.schema.alias %>` for the given `uuid` with the provided `attrs`. 4 | 5 | ## Examples 6 | 7 | iex> <%= inspect contract.schema.alias %>API.<%= action %>("38cd0012-79dc-4838-acc0-94d4143c4f2c", %{field: new_value}) 8 | {:ok, %<%= inspect contract.schema.alias %>{}} 9 | 10 | iex> <%= inspect contract.schema.alias %>API.<%= action %>("38cd0012-79dc-4838-acc0-94d4143c4f2c", %{field: bad_value}) 11 | {:error, %Ecto.Changeset{}} 12 | 13 | """ 14 | def <%= action %>(uuid, attrs) when is_binary(uuid) and is_map(attrs), do: <%= module_action_name %>.<%= action_first_word %>(uuid, attrs) 15 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/action_module_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Test do 2 | use <%= inspect contract.base_module %>.DataCase 3 | 4 | end 5 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/action_module_test_no_schema_access.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %> do 2 | @moduledoc false 3 | end 4 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/no_schema_access/module_any_action_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Test do 2 | use <%= inspect contract.base_module %>.DataCase 3 | 4 | alias <%= module_action_name %> 5 | 6 | test "<%= action_first_word %> works as expected" do 7 | raise "TODO: Implement test for action `<%= action_first_word %>` at `<%= module_action_name %>`" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/create_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | alias <%= inspect contract.schema.module %> 3 | 4 | @invalid_attrs <%= Mix.Phoenix.to_text for {key, _} <- contract.schema.params.create, into: %{}, do: {key, nil} %> 5 | 6 | test "<%= action_first_word %>/1 with valid data creates a <%= contract.schema.singular %>" do 7 | valid_attrs = <%= Mix.Phoenix.to_text contract.schema.params.create %> 8 | 9 | assert {:ok, %<%= inspect contract.schema.alias %>{} = <%= contract.schema.singular %>} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>(valid_attrs) 10 | <%= for {field, value} <- contract.schema.params.create do %> 11 | assert <%= contract.schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(contract.schema, field, value) %><% end %> 12 | end 13 | 14 | test "<%= action_first_word %>/1 with invalid data returns error changeset" do 15 | assert {:error, %Ecto.Changeset{}} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>(@invalid_attrs) 16 | end 17 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/default_schema_test.exs: -------------------------------------------------------------------------------- 1 | # alias <%= module_action_name %> 2 | # alias <%= inspect contract.schema.module %> 3 | 4 | # import <%= inspect contract.schema.module %>Fixtures 5 | 6 | test "<%= action_first_word %>" do 7 | raise "TOOO: Implement test for #{__MODULE__}" 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/delete_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | alias <%= inspect contract.schema.module %> 3 | alias <%= inspect contract.schema.module %><%= "." <> read_action_capitalized <> "." <> read_action_capitalized <> inspect(contract.schema.alias) %> 4 | 5 | import <%= inspect contract.schema.module %>Fixtures 6 | 7 | test "<%= action_first_word %>/1 deletes the <%= contract.schema.singular %>" do 8 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 9 | assert {:ok, %<%= inspect contract.schema.alias %>{}} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>(<%= contract.schema.singular %>.id) 10 | assert_raise Ecto.NoResultsError, fn -> <%= read_action_capitalized %><%= inspect(contract.schema.alias) %>.<%= read_action %>!(<%= contract.schema.singular %>.id) end 11 | end 12 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/edit_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | 3 | import <%= inspect contract.schema.module %>Fixtures 4 | 5 | test "<%= action_first_word %>/1 returns a <%= contract.schema.singular %> changeset" do 6 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 7 | assert %Ecto.Changeset{} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>(<%= contract.schema.singular %>.id) 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/list_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | 3 | import <%= inspect contract.schema.module %>Fixtures 4 | 5 | test "<%= action_first_word %>/0 returns all <%= contract.schema.plural %>" do 6 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 7 | assert <%= action_capitalized <> inspect(contract.schema.alias_plural) <> "." <> action_first_word %>() == [<%= contract.schema.singular %>] 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/new_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | 3 | test "<%= action_first_word %>/0 returns a <%= contract.schema.singular %> changeset" do 4 | assert %Ecto.Changeset{} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>() 5 | end 6 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/read_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | 3 | import <%= inspect contract.schema.module %>Fixtures 4 | 5 | test "<%= action_first_word %>!/1 returns the <%= contract.schema.singular %> with given id" do 6 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 7 | assert <%= action_capitalized %><%= inspect(contract.schema.alias) <> "." <> action_first_word %>!(<%= contract.schema.singular %>.id) == <%= contract.schema.singular %> 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/actions/schema_access/update_schema_test.exs: -------------------------------------------------------------------------------- 1 | alias <%= absolute_module_action_name %> 2 | alias <%= inspect contract.schema.module %> 3 | alias <%= inspect contract.schema.module %><%= "." <> read_action_capitalized <> "." <> read_action_capitalized <> inspect(contract.schema.alias) %> 4 | 5 | import <%= inspect contract.schema.module %>Fixtures 6 | 7 | @invalid_attrs <%= Mix.Phoenix.to_text for {key, _} <- contract.schema.params.create, into: %{}, do: {key, nil} %> 8 | 9 | test "<%= action_first_word %>/2 with valid data updates the <%= contract.schema.singular %>" do 10 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 11 | update_attrs = <%= Mix.Phoenix.to_text contract.schema.params.update%> 12 | 13 | assert {:ok, %<%= inspect contract.schema.alias %>{} = <%= contract.schema.singular %>} = <%= action_capitalized %><%= inspect(contract.schema.alias) %>.<%= action_first_word %>(<%= contract.schema.singular %>.id, update_attrs) 14 | <%= for {field, value} <- contract.schema.params.update do %> 15 | assert <%= contract.schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(contract.schema, field, value) %><% end %> 16 | end 17 | 18 | test "<%= action_first_word %>/2 with invalid data returns error changeset" do 19 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 20 | assert {:error, %Ecto.Changeset{}} = <%= action_capitalized %><%= inspect(contract.schema.alias) <> "." <> action_first_word %>(<%= contract.schema.singular %>.id, @invalid_attrs) 21 | assert <%= contract.schema.singular %> == <%= read_action_capitalized %><%= inspect(contract.schema.alias) <> "." <> read_action %>!(<%= contract.schema.singular %>.id) 22 | end 23 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/fixtures.ex: -------------------------------------------------------------------------------- 1 | alias <%= inspect contract.schema.module %>API 2 | 3 | <%= for {attr, {_function_name, function_def, _needs_impl?}} <- contract.schema.fixture_unique_functions do %> @doc """ 4 | Generate a unique <%= contract.schema.singular %> <%= attr %>. 5 | """ 6 | <%= function_def %> 7 | <% end %> @doc """ 8 | Generate a <%= contract.schema.singular %>. 9 | """ 10 | def <%= contract.schema.singular %>_fixture(attrs \\ %{}) do 11 | {:ok, <%= contract.schema.singular %>} = 12 | attrs 13 | |> Enum.into(%{ 14 | <%= contract.schema.fixture_params |> Enum.map(fn {key, code} -> " #{key}: #{code}" end) |> Enum.join(",\n") %> 15 | }) 16 | |> <%= inspect(contract.schema.alias) <> "API." <> create_action %>() 17 | 18 | <%= contract.schema.singular %> 19 | end 20 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.domain/tests/fixtures_module.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect contract.schema.module %>Fixtures do 2 | @moduledoc """ 3 | This module defines test helpers for creating 4 | entities via the `<%= inspect contract.module %>` contract. 5 | """ 6 | end 7 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/create_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, %{<%= inspect contract.schema.singular %> => <%= contract.schema.singular %>_params}) do 9 | case <%= inspect(contract.schema.alias) %>API.<%= action %>(<%= contract.schema.singular %>_params) do 10 | {:ok, <%= contract.schema.singular %>} -> 11 | conn 12 | |> put_flash(:info, "<%= contract.schema.human_singular %> created successfully.") 13 | |> redirect(to: ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}") 14 | 15 | {:error, %Ecto.Changeset{} = changeset} -> 16 | render(conn, "<%= new_action %>_<%= contract.schema.singular %>.html", changeset: changeset) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/default_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | # alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(_conn, _params) do 9 | raise "TOOO: Implement controller logic for #{__MODULE__}" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/delete_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, %{"id" => id}) do 9 | {:ok, _<%= contract.schema.singular %>} = <%= inspect(contract.schema.alias) %>API.<%= action %>(id) 10 | 11 | conn 12 | |> put_flash(:info, "<%= contract.schema.human_singular %> deleted successfully.") 13 | |> redirect(to: ~p"<%= contract.schema.route_prefix %>") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/edit_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, %{"id" => id}) do 9 | changeset = <%= inspect(contract.schema.alias) %>API.<%= action %>(id) 10 | render(conn, "<%= action %>_<%= contract.schema.singular %>.html", <%= contract.schema.singular %>: changeset.data, changeset: changeset) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/list_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, _params) do 9 | <%= contract.schema.plural %> = <%= inspect(contract.schema.alias) %>API.<%= action %>() 10 | render(conn, "<%= action %>_<%= contract.schema.plural %>.html", <%= contract.schema.collection %>: <%= contract.schema.plural %>) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/new_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, _params) do 9 | changeset = <%= inspect(contract.schema.alias) %>API.<%= action %>() 10 | render(conn, "<%= action %>_<%= contract.schema.singular %>.html", changeset: changeset) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/read_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, %{"id" => id}) do 9 | <%= contract.schema.singular %> = <%= inspect(contract.schema.alias) %>API.<%= action %>!(id) 10 | render(conn, "<%= action %>_<%= contract.schema.singular %>.html", <%= contract.schema.singular %>: <%= contract.schema.singular %>) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/controllers/update_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>Controller do 2 | use <%= inspect contract.web_module %>, :controller 3 | 4 | alias <%= inspect contract.schema.module %>API 5 | 6 | plug :put_view, html: <%= inspect contract.web_module %>.<%= contract.schema.web_namespace %>.<%= inspect contract.schema.alias %>HTML 7 | 8 | def <%= action %>(conn, %{"id" => id, <%= inspect contract.schema.singular %> => <%= contract.schema.singular %>_params}) do 9 | case <%= inspect(contract.schema.alias) %>API.<%= action %>(id, <%= contract.schema.singular %>_params) do 10 | {:ok, <%= contract.schema.singular %>} -> 11 | conn 12 | |> put_flash(:info, "<%= contract.schema.human_singular %> updated successfully.") 13 | |> redirect(to: ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}") 14 | 15 | {:error, %Ecto.Changeset{} = changeset} -> 16 | render(conn, "<%= edit_action %>_<%= contract.schema.singular %>.html", <%= contract.schema.singular %>: changeset.data, changeset: changeset) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/edit.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | Edit <%= contract.schema.human_singular %> <%%= @<%= contract.schema.singular %>.id %> 3 | <:subtitle>Use this form to manage <%= contract.schema.singular %> records in your database. 4 | 5 | 6 | <.<%= contract.schema.singular %>_form changeset={@changeset} action={~p"<%= contract.schema.route_prefix %>/#{@<%= contract.schema.singular %>}"} /> 7 | 8 | <.back navigate={~p"<%= contract.schema.route_prefix %>"}>Back to <%= contract.schema.plural %> 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/html.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect contract.web_module %>.<%= inspect Module.concat(contract.schema.web_namespace, contract.schema.alias) %>HTML do 2 | use <%= inspect contract.web_module %>, :html 3 | <%= embeded_templates %> 4 | embed_templates "<%= contract.schema.singular %>_form.html" 5 | 6 | @doc """ 7 | Renders a <%= contract.schema.singular %> form. 8 | """ 9 | attr :changeset, Ecto.Changeset, required: true 10 | attr :action, :string, required: true 11 | 12 | def <%= contract.schema.singular %>_form(assigns) 13 | end 14 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/list.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | Listing <%= contract.schema.human_plural %> 3 | <:actions> 4 | <.link href={~p"<%= contract.schema.route_prefix %>/new"}> 5 | <.button>New <%= contract.schema.human_singular %> 6 | 7 | 8 | 9 | 10 | <.table id="<%= contract.schema.plural %>" rows={@<%= contract.schema.collection %>} row_click={&JS.navigate(~p"<%= contract.schema.route_prefix %>/#{&1}")}><%= for {k, _} <- contract.schema.attrs do %> 11 | <:col :let={<%= contract.schema.singular %>} label="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"><%%= <%= contract.schema.singular %>.<%= k %> %><% end %> 12 | <:action :let={<%= contract.schema.singular %>}> 13 |
14 | <.link navigate={~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}"}>Show 15 |
16 | <.link navigate={~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}/edit"}>Edit 17 | 18 | <:action :let={<%= contract.schema.singular %>}> 19 | <.link href={~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}"} method="delete" data-confirm="Are you sure?"> 20 | Delete 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/new.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | New <%= contract.schema.human_singular %> 3 | <:subtitle>Use this form to manage <%= contract.schema.singular %> records in your database. 4 | 5 | 6 | <.<%= contract.schema.singular %>_form changeset={@changeset} action={~p"<%= contract.schema.route_prefix %>"} /> 7 | 8 | <.back navigate={~p"<%= contract.schema.route_prefix %>"}>Back to <%= contract.schema.plural %> 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/read.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | <%= contract.schema.human_singular %> <%%= @<%= contract.schema.singular %>.id %> 3 | <:subtitle>This is a <%= contract.schema.singular %> record from your database. 4 | <:actions> 5 | <.link href={~p"<%= contract.schema.route_prefix %>/#{@<%= contract.schema.singular %>}/edit"}> 6 | <.button>Edit <%= contract.schema.singular %> 7 | 8 | 9 | 10 | 11 | <.list><%= for {k, _} <- contract.schema.attrs do %> 12 | <:item title="<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"><%%= @<%= contract.schema.singular %>.<%= k %> %><% end %> 13 | 14 | 15 | <.back navigate={~p"<%= contract.schema.route_prefix %>"}>Back to <%= contract.schema.plural %> 16 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/html/default/resource_form.html.heex: -------------------------------------------------------------------------------- 1 | <.simple_form :let={f} for={@changeset} action={@action}> 2 | <.error :if={@changeset.action}> 3 | Oops, something went wrong! Please check the errors below. 4 | 5 | <%= Mix.Tasks.Phx.Gen.Html.indent_inputs(inputs, 2) %> 6 | <:actions> 7 | <.button>Save <%= contract.schema.human_singular %> 8 | 9 | 10 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/create_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | @create_attrs <%= Mix.Phoenix.to_text contract.schema.params.create %> 5 | @invalid_attrs <%= Mix.Phoenix.to_text (for {key, _} <- contract.schema.params.create, into: %{}, do: {key, nil}) %> 6 | 7 | test "<%= action %> <%= contract.schema.singular %> redirects to show when data is valid", %{conn: conn} do 8 | conn = post(conn, ~p"<%= contract.schema.route_prefix %>", <%= contract.schema.singular %>: @create_attrs) 9 | 10 | assert %{id: id} = redirected_params(conn) 11 | assert redirected_to(conn) == ~p"<%= contract.schema.route_prefix %>/#{id}" 12 | 13 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>/#{id}") 14 | assert html_response(conn, 200) =~ "<%= contract.schema.human_singular %> #{id}" 15 | end 16 | 17 | test "<%= action %> <%= contract.schema.singular %> renders errors when data is invalid", %{conn: conn} do 18 | conn = post(conn, ~p"<%= contract.schema.route_prefix %>", <%= contract.schema.singular %>: @invalid_attrs) 19 | assert html_response(conn, 200) =~ "<%= new_action_capitalized %> <%= contract.schema.human_singular %>" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/default_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | test "<%= action %> <%= contract.schema.singular %>", %{conn: _conn} do 5 | raise "Test not implemented yet..." 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/delete_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | import <%= inspect contract.schema.module %>Fixtures 5 | 6 | setup do 7 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 8 | %{<%= contract.schema.singular %>: <%= contract.schema.singular %>} 9 | end 10 | 11 | test "deletes chosen <%= contract.schema.singular %>", %{conn: conn, <%= contract.schema.singular %>: <%= contract.schema.singular %>} do 12 | conn = delete(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}") 13 | assert redirected_to(conn) == ~p"<%= contract.schema.route_prefix %>" 14 | 15 | assert_error_sent 404, fn -> 16 | get(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}") 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/edit_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | import <%= inspect contract.schema.module %>Fixtures 5 | 6 | setup do 7 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 8 | %{<%= contract.schema.singular %>: <%= contract.schema.singular %>} 9 | end 10 | 11 | test "<%= action %> <%= contract.schema.singular %> renders form for editing chosen <%= contract.schema.singular %>", %{conn: conn, <%= contract.schema.singular %>: <%= contract.schema.singular %>} do 12 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}/<%= action %>") 13 | assert html_response(conn, 200) =~ "<%= edit_action_capitalized %> <%= contract.schema.human_singular %>" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/list_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | import <%= inspect contract.schema.module %>Fixtures 5 | 6 | setup do 7 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 8 | %{<%= contract.schema.singular %>: <%= contract.schema.singular %>} 9 | end 10 | 11 | test "lists all <%= contract.schema.plural %>", %{conn: conn} do 12 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>") 13 | assert html_response(conn, 200) =~ "Listing <%= contract.schema.human_plural %>" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/new_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | test "<%= action %> <%= contract.schema.singular %> renders form", %{conn: conn} do 5 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>/<%= action %>") 6 | assert html_response(conn, 200) =~ "<%= action_capitalized %> <%= contract.schema.human_singular %>" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/read_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | import <%= inspect contract.schema.module %>Fixtures 5 | 6 | setup do 7 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 8 | %{<%= contract.schema.singular %>: <%= contract.schema.singular %>} 9 | end 10 | 11 | test "<%= action %> <%= contract.schema.singular %> renders form", %{conn: conn, <%= contract.schema.singular %>: <%= contract.schema.singular %>} do 12 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>.id}") 13 | assert html_response(conn, 200) =~ "<%= contract.schema.human_singular %>" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/templates/scribe.gen.html/tests/controllers/update_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= absolute_module_action_name %>ControllerTest do 2 | use <%= inspect contract.web_module %>.ConnCase 3 | 4 | import <%= inspect contract.schema.module %>Fixtures 5 | 6 | @update_attrs <%= Mix.Phoenix.to_text contract.schema.params.update %> 7 | @invalid_attrs <%= Mix.Phoenix.to_text (for {key, _} <- contract.schema.params.create, into: %{}, do: {key, nil}) %> 8 | 9 | setup do 10 | <%= contract.schema.singular %> = <%= contract.schema.singular %>_fixture() 11 | %{<%= contract.schema.singular %>: <%= contract.schema.singular %>} 12 | end 13 | 14 | test "<%= action %> <%= contract.schema.singular %> redirects when data is valid", %{conn: conn, <%= contract.schema.singular %>: <%= contract.schema.singular %>} do 15 | conn = put(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}", <%= contract.schema.singular %>: @update_attrs) 16 | assert redirected_to(conn) == ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}" 17 | 18 | conn = get(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}")<%= if contract.schema.string_attr do %> 19 | assert html_response(conn, 200) =~ <%= inspect ElixirScribe.Generator.SchemaResourceAPI.default_param_value(contract.schema, :update) %><% else %> 20 | assert html_response(conn, 200)<% end %> 21 | end 22 | 23 | test "renders errors when data is invalid", %{conn: conn, <%= contract.schema.singular %>: <%= contract.schema.singular %>} do 24 | conn = put(conn, ~p"<%= contract.schema.route_prefix %>/#{<%= contract.schema.singular %>}", <%= contract.schema.singular %>: @invalid_attrs) 25 | assert html_response(conn, 200) =~ "<%= edit_action_capitalized %> <%= contract.schema.human_singular %>" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/docs/modules/behaviours/persona_validator_test.exs: -------------------------------------------------------------------------------- 1 | defmodule PersonaValidatorTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | # This validator is used as part of the Persona module in the docs for the 5 | # typed contract, therefore not a strict validator that needs to be exhaustively tested. 6 | 7 | describe "corporate_email?/1" do 8 | test "returns true with valid corporate email" do 9 | assert PersonaValidator.corporate_email?("me@company.com") 10 | end 11 | 12 | test "returns false with valid personal email" do 13 | refute PersonaValidator.corporate_email?("me@gmail.com") 14 | end 15 | 16 | test "returns false with invalid email format" do 17 | refute PersonaValidator.corporate_email?("me@") 18 | refute PersonaValidator.corporate_email?("megmail.com") 19 | end 20 | 21 | test "returns false when the email isn't a string" do 22 | refute PersonaValidator.corporate_email?(["me@company.com"]) 23 | end 24 | end 25 | 26 | describe "role?/1" do 27 | test "it returns true when the optional role attribute it's nil" do 28 | assert PersonaValidator.role?(nil) 29 | end 30 | 31 | test "it returns true when the role is a string with at least three characters" do 32 | assert PersonaValidator.role?("dev") 33 | end 34 | 35 | test "it returns false when the role is a string with less then three characters" do 36 | refute PersonaValidator.role?("dv") 37 | end 38 | 39 | test "it returns false when the role isn't a string" do 40 | refute PersonaValidator.role?(:dev) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/elixir_scribe/behaviour_typed_contract_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Behaviour.TypedContractTest do 2 | use ExUnit.Case, async: true 3 | doctest ElixirScribe.Behaviour.TypedContract 4 | 5 | defmodule CarContract do 6 | @moduledoc false 7 | 8 | @fields %{ 9 | required: [:brand, :model], 10 | optional: [year: nil] 11 | } 12 | 13 | use ElixirScribe.Behaviour.TypedContract, fields: @fields 14 | 15 | @impl true 16 | def type_spec() do 17 | schema(%__MODULE__{ 18 | brand: is_binary() |> spec(), 19 | model: is_binary() |> spec(), 20 | year: spec(is_integer() or is_nil()) 21 | }) 22 | end 23 | end 24 | 25 | test "it can create a typed contract with :required and :optional fields" do 26 | attrs = %{ 27 | brand: "Toyota", 28 | model: "Supra", 29 | year: 2000 30 | } 31 | 32 | assert {:ok, %CarContract{} = contract} = CarContract.new(attrs) 33 | assert ^contract = CarContract.new!(attrs) 34 | end 35 | 36 | defmodule ProductContract do 37 | @moduledoc false 38 | 39 | @fields %{ 40 | required: [:title, :description] 41 | } 42 | 43 | use ElixirScribe.Behaviour.TypedContract, fields: @fields 44 | 45 | @impl true 46 | def type_spec() do 47 | schema(%__MODULE__{ 48 | title: is_binary() |> spec(), 49 | description: is_binary() |> spec() 50 | }) 51 | end 52 | end 53 | 54 | test "it can create a typed contract without the :optional key" do 55 | attrs = %{ 56 | title: "Penguin Laptop", 57 | description: "Awesome Linux machine" 58 | } 59 | 60 | assert {:ok, %ProductContract{} = contract} = ProductContract.new(attrs) 61 | assert ^contract = ProductContract.new!(attrs) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/build_action_files_paths/build_action_files_paths_resource_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildActionFilesPaths.BuildActionFilesPathsResourceTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | 6 | test "returns a list of all resource action files paths" do 7 | expected_files = [ 8 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/list_schema.ex", 9 | "lib/elixir_scribe/domain/site/blog/post/list/list_posts.ex", "list"}, 10 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/new_schema.ex", 11 | "lib/elixir_scribe/domain/site/blog/post/new/new_post.ex", "new"}, 12 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/read_schema.ex", 13 | "lib/elixir_scribe/domain/site/blog/post/read/read_post.ex", "read"}, 14 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/edit_schema.ex", 15 | "lib/elixir_scribe/domain/site/blog/post/edit/edit_post.ex", "edit"}, 16 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/create_schema.ex", 17 | "lib/elixir_scribe/domain/site/blog/post/create/create_post.ex", "create"}, 18 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/update_schema.ex", 19 | "lib/elixir_scribe/domain/site/blog/post/update/update_post.ex", "update"}, 20 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/delete_schema.ex", 21 | "lib/elixir_scribe/domain/site/blog/post/delete/delete_post.ex", "delete"} 22 | ] 23 | 24 | contract = domain_contract_fixture() 25 | 26 | assert DomainResourceAPI.build_action_files_paths(contract) === expected_files 27 | end 28 | 29 | test "it returns the expected source template path when the flag --no-schema is used" do 30 | expected_files = [ 31 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 32 | "lib/elixir_scribe/domain/blog/post/list/list_posts.ex", "list"}, 33 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 34 | "lib/elixir_scribe/domain/blog/post/new/new_post.ex", "new"}, 35 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 36 | "lib/elixir_scribe/domain/blog/post/read/read_post.ex", "read"}, 37 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 38 | "lib/elixir_scribe/domain/blog/post/edit/edit_post.ex", "edit"}, 39 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 40 | "lib/elixir_scribe/domain/blog/post/create/create_post.ex", "create"}, 41 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 42 | "lib/elixir_scribe/domain/blog/post/update/update_post.ex", "update"}, 43 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/no_schema_access/any_action.ex", 44 | "lib/elixir_scribe/domain/blog/post/delete/delete_post.ex", "delete"} 45 | ] 46 | 47 | args = ["Blog", "Post", "posts", "title:string", "desc:string", "--no-schema"] 48 | contract = domain_contract_fixture(args) 49 | 50 | assert DomainResourceAPI.build_action_files_paths(contract) === expected_files 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/build_api_file_paths/build_api_file_paths_resource_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildAPIFilePaths.BuildAPIFilePathsResourceTest do 2 | alias ElixirScribe.Generator.DomainResourceAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | test "returns the API file paths" do 6 | expected_files = 7 | {:eex, :api, "priv/templates/scribe.gen.domain/apis/api_module.ex", 8 | "lib/elixir_scribe/domain/site/blog/post_api.ex"} 9 | 10 | contract = domain_contract_fixture() 11 | 12 | assert DomainResourceAPI.build_api_file_paths(contract) === expected_files 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/build_files_to_generate/build_files_to_generate_resource_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildFilesToGenerate.BuildFilesToGenerateResourceTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | 6 | test "files_to_generate/1 works as expected" do 7 | expected_files = [ 8 | {:eex, :api, "priv/templates/scribe.gen.domain/apis/api_module.ex", 9 | "lib/elixir_scribe/domain/site/blog/post_api.ex"}, 10 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/list_schema.ex", 11 | "lib/elixir_scribe/domain/site/blog/post/list/list_posts.ex", "list"}, 12 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/new_schema.ex", 13 | "lib/elixir_scribe/domain/site/blog/post/new/new_post.ex", "new"}, 14 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/read_schema.ex", 15 | "lib/elixir_scribe/domain/site/blog/post/read/read_post.ex", "read"}, 16 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/edit_schema.ex", 17 | "lib/elixir_scribe/domain/site/blog/post/edit/edit_post.ex", "edit"}, 18 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/create_schema.ex", 19 | "lib/elixir_scribe/domain/site/blog/post/create/create_post.ex", "create"}, 20 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/update_schema.ex", 21 | "lib/elixir_scribe/domain/site/blog/post/update/update_post.ex", "update"}, 22 | {:eex, :resource, "priv/templates/scribe.gen.domain/actions/schema_access/delete_schema.ex", 23 | "lib/elixir_scribe/domain/site/blog/post/delete/delete_post.ex", "delete"}, 24 | {:eex, :resource_test, 25 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/list_schema_test.exs", 26 | "test/elixir_scribe/domain/site/blog/post/list/list_posts_test.exs", "list"}, 27 | {:eex, :resource_test, 28 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/new_schema_test.exs", 29 | "test/elixir_scribe/domain/site/blog/post/new/new_post_test.exs", "new"}, 30 | {:eex, :resource_test, 31 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/read_schema_test.exs", 32 | "test/elixir_scribe/domain/site/blog/post/read/read_post_test.exs", "read"}, 33 | {:eex, :resource_test, 34 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/edit_schema_test.exs", 35 | "test/elixir_scribe/domain/site/blog/post/edit/edit_post_test.exs", "edit"}, 36 | {:eex, :resource_test, 37 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/create_schema_test.exs", 38 | "test/elixir_scribe/domain/site/blog/post/create/create_post_test.exs", "create"}, 39 | {:eex, :resource_test, 40 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/update_schema_test.exs", 41 | "test/elixir_scribe/domain/site/blog/post/update/update_post_test.exs", "update"}, 42 | {:eex, :resource_test, 43 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/delete_schema_test.exs", 44 | "test/elixir_scribe/domain/site/blog/post/delete/delete_post_test.exs", "delete"}, 45 | {:eex, "schema.ex", "lib/elixir_scribe/domain/site/blog/post/post_schema.ex"}, 46 | {:eex, "priv/templates/scribe.gen.domain/tests/fixtures_module.ex", 47 | "test/support/fixtures/domain/site/blog/post_fixtures.ex"} 48 | ] 49 | 50 | domain_contract = domain_contract_fixture() 51 | 52 | assert DomainResourceAPI.build_files_to_generate(domain_contract) === expected_files 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/build_test_action_files_paths/build_test_action_files_paths_resource_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.Domain.Resource.BuildTestActionFilesPaths.BuildTestActionFilesPathsResourceTest do 2 | alias ElixirScribe.Generator.DomainResourceAPI 3 | 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | test "returns a list of all resource action test files paths" do 7 | expected_files = [ 8 | {:eex, :resource_test, 9 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/list_schema_test.exs", 10 | "test/elixir_scribe/domain/site/blog/post/list/list_posts_test.exs", "list"}, 11 | {:eex, :resource_test, 12 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/new_schema_test.exs", 13 | "test/elixir_scribe/domain/site/blog/post/new/new_post_test.exs", "new"}, 14 | {:eex, :resource_test, 15 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/read_schema_test.exs", 16 | "test/elixir_scribe/domain/site/blog/post/read/read_post_test.exs", "read"}, 17 | {:eex, :resource_test, 18 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/edit_schema_test.exs", 19 | "test/elixir_scribe/domain/site/blog/post/edit/edit_post_test.exs", "edit"}, 20 | {:eex, :resource_test, 21 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/create_schema_test.exs", 22 | "test/elixir_scribe/domain/site/blog/post/create/create_post_test.exs", "create"}, 23 | {:eex, :resource_test, 24 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/update_schema_test.exs", 25 | "test/elixir_scribe/domain/site/blog/post/update/update_post_test.exs", "update"}, 26 | {:eex, :resource_test, 27 | "priv/templates/scribe.gen.domain/tests/actions/schema_access/delete_schema_test.exs", 28 | "test/elixir_scribe/domain/site/blog/post/delete/delete_post_test.exs", "delete"} 29 | ] 30 | 31 | contract = domain_contract_fixture() 32 | 33 | assert DomainResourceAPI.build_test_action_files_paths(contract) === expected_files 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/generate_actions/generate_actions_resource_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateActions.GenerateActionsResourceTest do 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | use ElixirScribe.BaseCase 6 | 7 | import MixTestHelper 8 | 9 | setup do 10 | Mix.Task.clear() 11 | :ok 12 | end 13 | 14 | test "with flag --no-schema the resource action file is generated without the logic to access the schema", 15 | config do 16 | in_tmp_project(config.test, fn -> 17 | args = [ 18 | "Blog", 19 | "Post", 20 | "posts", 21 | "slug:unique", 22 | "title:string", 23 | "--no-schema" 24 | ] 25 | 26 | domain_contract = domain_contract_fixture(args) 27 | DomainResourceAPI.generate_actions(domain_contract) 28 | 29 | assert_file("lib/elixir_scribe/domain/blog/post/list/list_posts.ex", fn file -> 30 | assert file =~ "defmodule ElixirScribe.Blog.Post.List.ListPosts do" 31 | assert file =~ "def list()" 32 | assert file =~ "raise \"TODO: Implement the action `list` for the module `ListPosts`" 33 | end) 34 | end) 35 | end 36 | 37 | test "generates a file for each Resource Action", config do 38 | in_tmp_project(config.test, fn -> 39 | args = [ 40 | "Blog", 41 | "Post", 42 | "posts", 43 | "slug:unique", 44 | "secret:redact", 45 | "title:string", 46 | "--actions", 47 | "export,import" 48 | ] 49 | 50 | contract = domain_contract_fixture(args) 51 | 52 | DomainResourceAPI.generate_actions(contract) 53 | 54 | assert_file("lib/elixir_scribe/domain/blog/post/list/list_posts.ex", fn file -> 55 | assert file =~ "defmodule ElixirScribe.Blog.Post.List.ListPosts do" 56 | assert file =~ "def list()" 57 | end) 58 | 59 | assert_file("lib/elixir_scribe/domain/blog/post/new/new_post.ex", fn file -> 60 | assert file =~ "defmodule ElixirScribe.Blog.Post.New.NewPost do" 61 | assert file =~ "def new(attrs \\\\ %{})" 62 | end) 63 | 64 | assert_file("lib/elixir_scribe/domain/blog/post/read/read_post.ex", fn file -> 65 | assert file =~ "defmodule ElixirScribe.Blog.Post.Read.ReadPost do" 66 | assert file =~ "def read!(uuid)" 67 | end) 68 | 69 | assert_file("lib/elixir_scribe/domain/blog/post/edit/edit_post.ex", fn file -> 70 | assert file =~ "defmodule ElixirScribe.Blog.Post.Edit.EditPost do" 71 | assert file =~ "def edit(uuid, attrs \\\\ %{})" 72 | end) 73 | 74 | assert_file("lib/elixir_scribe/domain/blog/post/create/create_post.ex", fn file -> 75 | assert file =~ "defmodule ElixirScribe.Blog.Post.Create.CreatePost do" 76 | assert file =~ "def create(%{} = attrs)" 77 | end) 78 | 79 | assert_file("lib/elixir_scribe/domain/blog/post/update/update_post.ex", fn file -> 80 | assert file =~ "defmodule ElixirScribe.Blog.Post.Update.UpdatePost do" 81 | assert file =~ "def update(uuid, %{} = attrs)" 82 | end) 83 | 84 | assert_file("lib/elixir_scribe/domain/blog/post/delete/delete_post.ex", fn file -> 85 | assert file =~ "defmodule ElixirScribe.Blog.Post.Delete.DeletePost do" 86 | assert file =~ "def delete(uuid)" 87 | end) 88 | end) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/generate_schema/generate_schema_resource_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test//mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateSchema.GenerateSchemaResourceTest do 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | 6 | use ElixirScribe.BaseCase 7 | import MixTestHelper 8 | 9 | setup do 10 | Mix.Task.clear() 11 | :ok 12 | end 13 | 14 | test "generates the Resource Schema file", config do 15 | in_tmp_project(config.test, fn -> 16 | contract = domain_contract_fixture() 17 | DomainResourceAPI.generate_schema(contract) 18 | 19 | assert_file("lib/elixir_scribe/domain/site/blog/post/post_schema.ex", fn file -> 20 | assert file =~ "field :name, :string" 21 | assert file =~ "field :desc, :string" 22 | end) 23 | end) 24 | end 25 | 26 | test "doesn't generate the Resource Schema file when the flag --no-schema is used", config do 27 | in_tmp_project(config.test, fn -> 28 | args = ["Blog", "Post", "posts", "title:string", "desc:string", "--no-schema"] 29 | 30 | contract = domain_contract_fixture(args) 31 | 32 | DomainResourceAPI.generate_schema(contract) 33 | 34 | refute File.exists?("lib/elixir_scribe/domain/site/blog/post/post_schema.ex") 35 | end) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/generate_test_fixture/generate_test_fixture_resource_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateTestFixture.GenerateTestFixtureResourceTest do 4 | use ElixirScribe.BaseCase 5 | 6 | alias ElixirScribe.Generator.DomainContract 7 | alias ElixirScribe.Generator.DomainResourceAPI 8 | 9 | import MixTestHelper 10 | 11 | setup do 12 | Mix.Task.clear() 13 | :ok 14 | end 15 | 16 | test "generates the file for the resource test fixture", config do 17 | in_tmp_project(config.test, fn -> 18 | args = [ 19 | "Blog", 20 | "Post", 21 | "posts", 22 | "slug:unique", 23 | "secret:redact", 24 | "title:string", 25 | "--actions", 26 | "export,import" 27 | ] 28 | 29 | contract = domain_contract_fixture(args) 30 | 31 | assert %DomainContract{} = DomainResourceAPI.generate_test_fixture(contract) 32 | 33 | assert_file("test/support/fixtures/domain/blog/post_fixtures.ex", fn file -> 34 | assert file =~ "defmodule ElixirScribe.Blog.PostFixtures do" 35 | assert file =~ "def post_fixture(attrs \\\\ %{})" 36 | assert file =~ "title: \"some title\"" 37 | end) 38 | end) 39 | end 40 | 41 | test "generates the file for the resource test fixture with unique schema fields", config do 42 | in_tmp_project(config.test, fn -> 43 | args = ~w(Blog Post posts 44 | slug:string:unique 45 | subject:unique 46 | body:text:unique 47 | order:integer:unique 48 | price:decimal:unique 49 | published_at:utc_datetime:unique 50 | author:references:users:unique 51 | published?:boolean 52 | ) 53 | 54 | contract = domain_contract_fixture(args) 55 | 56 | assert %DomainContract{} = DomainResourceAPI.generate_test_fixture(contract) 57 | 58 | assert_received {:mix_shell, :info, 59 | [ 60 | """ 61 | 62 | Some of the generated database columns are unique. Please provide 63 | unique implementations for the following fixture function(s) in 64 | test/support/fixtures/domain/blog/post_fixtures.ex: 65 | 66 | def unique_post_price do 67 | raise "implement the logic to generate a unique post price" 68 | end 69 | 70 | def unique_post_published_at do 71 | raise "implement the logic to generate a unique post published_at" 72 | end 73 | """ 74 | ]} 75 | 76 | assert_file("test/support/fixtures/domain/blog/post_fixtures.ex", fn file -> 77 | assert file =~ "defmodule ElixirScribe.Blog.PostFixtures do" 78 | assert file =~ ~S|def unique_post_order, do: System.unique_integer([:positive])| 79 | 80 | assert file =~ 81 | ~S|def unique_post_slug, do: "some slug#{System.unique_integer([:positive])}"| 82 | 83 | assert file =~ 84 | ~S|def unique_post_body, do: "some body#{System.unique_integer([:positive])}"| 85 | 86 | assert file =~ 87 | ~S|def unique_post_subject, do: "some subject#{System.unique_integer([:positive])}"| 88 | 89 | refute file =~ ~S|def unique_post_author| 90 | 91 | assert file =~ """ 92 | def unique_post_price do 93 | raise "implement the logic to generate a unique post price" 94 | end 95 | """ 96 | 97 | assert file =~ """ 98 | body: unique_post_body(), 99 | order: unique_post_order(), 100 | price: unique_post_price(), 101 | published?: true, 102 | published_at: unique_post_published_at(), 103 | slug: unique_post_slug(), 104 | subject: unique_post_subject() 105 | """ 106 | end) 107 | end) 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain/resource/generate_tests/generate_tests_resource_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Generator.Domain.Resource.GenerateTests.GenerateTestsResourceTest do 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | use ElixirScribe.BaseCase 6 | 7 | import MixTestHelper 8 | 9 | setup do 10 | Mix.Task.clear() 11 | :ok 12 | end 13 | 14 | test "with flag --no-schema the test file is generated without tests being implemented ", 15 | config do 16 | in_tmp_project(config.test, fn -> 17 | args = [ 18 | "Blog", 19 | "Post", 20 | "posts", 21 | "slug:unique", 22 | "title:string", 23 | "--no-schema" 24 | ] 25 | 26 | domain_contract = domain_contract_fixture(args) 27 | DomainResourceAPI.generate_tests(domain_contract) 28 | 29 | assert_file("test/elixir_scribe/domain/blog/post/list/list_posts_test.exs", fn file -> 30 | assert file =~ "defmodule ElixirScribe.Blog.Post.List.ListPostsTest do" 31 | assert file =~ "test \"list" 32 | assert file =~ "raise \"TODO: Implement test for action `list` at `ListPosts`" 33 | end) 34 | end) 35 | end 36 | 37 | test "generates a test file for each Resource Action", config do 38 | in_tmp_project(config.test, fn -> 39 | args = [ 40 | "Blog", 41 | "Post", 42 | "posts", 43 | "slug:unique", 44 | "secret:redact", 45 | "title:string", 46 | "--actions", 47 | "export,import" 48 | ] 49 | 50 | contract = domain_contract_fixture(args) 51 | 52 | DomainResourceAPI.generate_tests(contract) 53 | 54 | assert_file("test/elixir_scribe/domain/blog/post/list/list_posts_test.exs", fn file -> 55 | assert file =~ "defmodule ElixirScribe.Blog.Post.List.ListPostsTest do" 56 | assert file =~ "test \"list/0" 57 | end) 58 | 59 | assert_file("test/elixir_scribe/domain/blog/post/new/new_post_test.exs", fn file -> 60 | assert file =~ "defmodule ElixirScribe.Blog.Post.New.NewPostTest do" 61 | assert file =~ "test \"new/0" 62 | end) 63 | 64 | assert_file("test/elixir_scribe/domain/blog/post/read/read_post_test.exs", fn file -> 65 | assert file =~ "defmodule ElixirScribe.Blog.Post.Read.ReadPostTest do" 66 | assert file =~ "test \"read!/1" 67 | end) 68 | 69 | assert_file("test/elixir_scribe/domain/blog/post/edit/edit_post_test.exs", fn file -> 70 | assert file =~ "defmodule ElixirScribe.Blog.Post.Edit.EditPostTest do" 71 | assert file =~ "test \"edit/1" 72 | end) 73 | 74 | assert_file("test/elixir_scribe/domain/blog/post/create/create_post_test.exs", fn file -> 75 | assert file =~ "defmodule ElixirScribe.Blog.Post.Create.CreatePostTest do" 76 | assert file =~ "test \"create/1" 77 | end) 78 | 79 | assert_file("test/elixir_scribe/domain/blog/post/update/update_post_test.exs", fn file -> 80 | assert file =~ "defmodule ElixirScribe.Blog.Post.Update.UpdatePostTest do" 81 | assert file =~ "test \"update/2" 82 | end) 83 | 84 | assert_file("test/elixir_scribe/domain/blog/post/delete/delete_post_test.exs", fn file -> 85 | assert file =~ "defmodule ElixirScribe.Blog.Post.Delete.DeletePostTest do" 86 | assert file =~ "test \"delete/1" 87 | end) 88 | end) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain_contract_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.DomainContractTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | alias ElixirScribe.Generator.DomainContract 5 | 6 | setup do 7 | contract_attrs = %{ 8 | alias: Blog, 9 | api_file: "lib/elixir_scribe/domain/blog/post_api.ex", 10 | base_module: ElixirScribe, 11 | basename: "blog", 12 | context_app: :elixir_scribe, 13 | generate?: true, 14 | lib_domain_dir: "lib/elixir_scribe/domain/blog", 15 | lib_resource_dir: "lib/elixir_scribe/domain/blog/post", 16 | lib_resource_dir_plural: "lib/elixir_scribe/domain/blog/posts", 17 | lib_web_domain_dir: "lib/elixir_scribe_web/domain/blog", 18 | lib_web_resource_dir: "lib/elixir_scribe_web/domain/blog/post", 19 | lib_web_resource_dir_plural: "lib/elixir_scribe_web/domain/blog/posts", 20 | module: ElixirScribe.Blog, 21 | name: "Blog", 22 | opts: [ 23 | {:web, "Blog"}, 24 | {:resource_actions, ["list", "new", "read", "edit", "create", "update", "delete"]}, 25 | {:schema, true}, 26 | {:context, true}, 27 | {:no_default_actions, false}, 28 | {:actions, nil}, 29 | {:binary_id, true} 30 | ], 31 | resource_actions: ["list", "new", "read", "edit", "create", "update", "delete"], 32 | resource_module: ElixirScribe.Blog.Post, 33 | resource_module_plural: ElixirScribe.Blog.Posts, 34 | resource_path_name_plural: "posts", 35 | resource_path_name_singular: "post", 36 | schema: %ElixirScribe.Generator.SchemaContract{ 37 | alias: Post, 38 | api_route_prefix: "/api/blog/posts", 39 | assocs: [], 40 | attrs: [{:title, :string}, {:desc, :string}], 41 | binary_id: true, 42 | collection: "posts", 43 | context_app: :elixir_scribe, 44 | defaults: %{title: "", desc: ""}, 45 | embedded?: false, 46 | file: "lib/elixir_scribe/domain/blog/post/post_schema.ex", 47 | fixture_params: [{:desc, "\"some desc\""}, {:title, "\"some title\""}], 48 | fixture_unique_functions: [], 49 | generate?: true, 50 | human_plural: "Posts", 51 | human_singular: "Post", 52 | indexes: [], 53 | migration?: true, 54 | migration_defaults: %{title: "", desc: ""}, 55 | migration_module: Ecto.Migration, 56 | module: ElixirScribe.Blog.Post, 57 | optionals: [], 58 | opts: [ 59 | {:web, "Blog"}, 60 | {:resource_actions, ["list", "new", "read", "edit", "create", "update", "delete"]}, 61 | {:schema, true}, 62 | {:context, true}, 63 | {:no_default_actions, false}, 64 | {:actions, nil}, 65 | {:binary_id, true} 66 | ], 67 | params: %{ 68 | create: %{title: "some title", desc: "some desc"}, 69 | default_key: :title, 70 | update: %{title: "some updated title", desc: "some updated desc"} 71 | }, 72 | plural: "posts", 73 | prefix: nil, 74 | redacts: [], 75 | repo: ElixirScribe.Repo, 76 | repo_alias: "", 77 | route_helper: "blog_post", 78 | route_prefix: "/blog/posts", 79 | sample_id: "11111111-1111-1111-1111-111111111111", 80 | self: ElixirScribe.Generator.SchemaContract, 81 | singular: "post", 82 | string_attr: :title, 83 | table: "posts", 84 | timestamp_type: :naive_datetime, 85 | types: %{title: :string, desc: :string}, 86 | uniques: [], 87 | web_namespace: "Blog", 88 | web_path: "blog" 89 | }, 90 | self: ElixirScribe.Generator.DomainContract, 91 | test_domain_dir: "test/elixir_scribe/domain/blog", 92 | test_file: "test/elixir_scribe/domain/blog_test.exs", 93 | test_fixtures_file: "test/support/fixtures/domain/blog/post_fixtures.ex", 94 | test_resource_dir: "test/elixir_scribe/domain/blog/post", 95 | test_resource_dir_plural: "test/elixir_scribe/domain/blog/posts", 96 | test_web_domain_dir: "test/elixir_scribe_web/domain/blog", 97 | test_web_resource_dir: "test/elixir_scribe_web/domain/blog/post", 98 | test_web_resource_dir_plural: "test/elixir_scribe_web/domain/blog/posts", 99 | web_domain_module: ElixirScribeWeb.Blog, 100 | web_module: ElixirScribeWeb, 101 | web_resource_module: ElixirScribeWeb.Blog.Post, 102 | web_resource_module_plural: ElixirScribeWeb.Blog.Posts 103 | } 104 | 105 | %{contract_attrs: contract_attrs} 106 | end 107 | 108 | describe "Builds the contract successfully with" do 109 | test "new/1", %{contract_attrs: contract_attrs} do 110 | assert {:ok, domain_contract = %DomainContract{}} = DomainContract.new(contract_attrs) 111 | 112 | expected_fields = Map.keys(contract_attrs) 113 | 114 | assert_maps_equal(contract_attrs, domain_contract, expected_fields) 115 | end 116 | 117 | test "new!/1", %{contract_attrs: contract_attrs} do 118 | assert domain_contract = %DomainContract{} = DomainContract.new!(contract_attrs) 119 | 120 | expected_fields = Map.keys(contract_attrs) 121 | 122 | assert_maps_equal(contract_attrs, domain_contract, expected_fields) 123 | end 124 | end 125 | 126 | describe "When it fails to build the contract" do 127 | test "new/1 returns an :error tuple" do 128 | assert {:error, _reason} = DomainContract.new(%{}) 129 | end 130 | 131 | test "new!/1 raises" do 132 | assert_raise Norm.MismatchError, 133 | ~r/^Could not conform input:.*$/s, 134 | fn -> DomainContract.new!(%{}) end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain_resource_api_async_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.DomainResourceAPIAsyncTest do 2 | alias ElixirScribe.MixAPI 3 | alias ElixirScribe.Generator.DomainContract 4 | alias ElixirScribe.Generator.DomainResourceAPI 5 | use ElixirScribe.BaseCase, async: true 6 | 7 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 8 | 9 | describe "build_domain_resource_contract/2" do 10 | test "can be invoked with the correct arguments types (list, list) and returns the expected tuple ({:ok, %DomainContract{}})" do 11 | args = ["Blog", "Post", "posts", "title:string", "desc:string"] 12 | {valid_args, opts, _invalid_args} = args |> MixAPI.parse_cli_command() 13 | 14 | assert {:ok, %DomainContract{}} = 15 | DomainResourceAPI.build_domain_resource_contract(valid_args, opts) 16 | end 17 | 18 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (list)" do 19 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 20 | DomainResourceAPI.build_domain_resource_contract(%{}, []) 21 | end 22 | end 23 | 24 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 25 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 26 | DomainResourceAPI.build_domain_resource_contract([], %{}) 27 | end 28 | end 29 | end 30 | 31 | describe "build_files_to_generate/1" do 32 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (list)" do 33 | domain_contract = domain_contract_fixture() 34 | 35 | assert DomainResourceAPI.build_files_to_generate(domain_contract) |> is_list() 36 | end 37 | 38 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 39 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 40 | DomainResourceAPI.build_files_to_generate(%{}) 41 | end 42 | end 43 | end 44 | 45 | describe "build_action_files_paths/1" do 46 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (list)" do 47 | domain_contract = domain_contract_fixture() 48 | 49 | assert DomainResourceAPI.build_action_files_paths(domain_contract) |> is_list() 50 | end 51 | 52 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 53 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 54 | DomainResourceAPI.build_action_files_paths(%{}) 55 | end 56 | end 57 | end 58 | 59 | describe "build_test_action_files_paths/1" do 60 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (list)" do 61 | domain_contract = domain_contract_fixture() 62 | 63 | assert DomainResourceAPI.build_test_action_files_paths(domain_contract) |> is_list() 64 | end 65 | 66 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 67 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 68 | DomainResourceAPI.build_test_action_files_paths(%{}) 69 | end 70 | end 71 | end 72 | 73 | # The SYNC tests for `generate_actions/1` at test/elixir_scribe/generator/domain_resource_api_sync_test.exs 74 | describe "generate_actions/1" do 75 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 76 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 77 | DomainResourceAPI.generate_actions(%{}) 78 | end 79 | end 80 | end 81 | 82 | # The SYNC tests for `generate_tests/1` at test/elixir_scribe/generator/domain_resource_api_sync_test.exs 83 | describe "generate_tests/1" do 84 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 85 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 86 | DomainResourceAPI.generate_tests(%{}) 87 | end 88 | end 89 | end 90 | 91 | # The SYNC tests for `generate_api/1` at test/elixir_scribe/generator/domain_resource_api_sync_test.exs 92 | describe "generate_api/1" do 93 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 94 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 95 | DomainResourceAPI.generate_api(%{}) 96 | end 97 | end 98 | end 99 | 100 | # The SYNC tests for `generate_test_fixture/1` at test/elixir_scribe/generator/domain_resource_api_sync_test.exs 101 | describe "generate_test_fixture/1" do 102 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 103 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 104 | DomainResourceAPI.generate_test_fixture(%{}) 105 | end 106 | end 107 | end 108 | 109 | # The SYNC tests for `generate_new_files/1` at test/elixir_scribe/generator/domain_resource_api_sync_test.exs 110 | describe "generate_new_files/1" do 111 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 112 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 113 | DomainResourceAPI.generate_new_files(%{}) 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/domain_resource_api_sync_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("../../mix_test_helper.exs", __DIR__) 2 | 3 | defmodule ElixirScribe.Generator.DomainResourceAPISyncTest do 4 | alias ElixirScribe.Generator.DomainContract 5 | alias ElixirScribe.Generator.DomainResourceAPI 6 | use ElixirScribe.BaseCase 7 | 8 | import MixTestHelper 9 | 10 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 11 | 12 | setup do 13 | Mix.Task.clear() 14 | :ok 15 | end 16 | 17 | # Check the ASYNC tests for `generate_actions/1` at test/elixir_scribe/generator/domain_resource_api_async_test.exs 18 | describe "generate_actions/1" do 19 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (%DomainContract{})", 20 | config do 21 | in_tmp_project(config.test, fn -> 22 | domain_contract = domain_contract_fixture() 23 | 24 | assert %DomainContract{} = DomainResourceAPI.generate_actions(domain_contract) 25 | end) 26 | end 27 | end 28 | 29 | # Check the ASYNC tests for `generate_tests/1` at test/elixir_scribe/generator/domain_resource_api_async_test.exs 30 | describe "generate_tests/1" do 31 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (%DomainContract{})", 32 | config do 33 | in_tmp_project(config.test, fn -> 34 | domain_contract = domain_contract_fixture() 35 | 36 | assert %DomainContract{} = DomainResourceAPI.generate_tests(domain_contract) 37 | end) 38 | end 39 | end 40 | 41 | # Check the ASYNC tests for `generate_api/1` at test/elixir_scribe/generator/domain_resource_api_async_test.exs 42 | describe "generate_api/1" do 43 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (%DomainContract{})", 44 | config do 45 | in_tmp_project(config.test, fn -> 46 | domain_contract = domain_contract_fixture() 47 | 48 | assert %DomainContract{} = DomainResourceAPI.generate_api(domain_contract) 49 | end) 50 | end 51 | end 52 | 53 | # Check the ASYNC tests for `generate_test_fixture/1` at test/elixir_scribe/generator/domain_resource_api_async_test.exs 54 | describe "generate_test_fixture/1" do 55 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (%DomainContract{})", 56 | config do 57 | in_tmp_project(config.test, fn -> 58 | domain_contract = domain_contract_fixture() 59 | 60 | assert %DomainContract{} = DomainResourceAPI.generate_test_fixture(domain_contract) 61 | end) 62 | end 63 | end 64 | 65 | # Check the ASYNC tests for `generate_new_files/1` at test/elixir_scribe/generator/domain_resource_api_async_test.exs 66 | describe "generate_new_files/1" do 67 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (%DomainContract{})", 68 | config do 69 | in_tmp_project(config.test, fn -> 70 | domain_contract = domain_contract_fixture() 71 | 72 | assert %DomainContract{} = DomainResourceAPI.generate_new_files(domain_contract) 73 | end) 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/elixir_scribe/generator/schema_resource_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.SchemaResourceAPITest do 2 | alias ElixirScribe.MixAPI 3 | alias ElixirScribe.Generator.SchemaResourceAPI 4 | alias ElixirScribe.Generator.SchemaContract 5 | use ElixirScribe.BaseCase, async: true 6 | 7 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 8 | 9 | describe "build_schema_resource_contract/2" do 10 | test "can be invoked with the correct arguments types (list, list) and returns the expected tuple ({:ok, %SchemaContract{}})" do 11 | args = ["Blog", "Post", "posts", "title:string", "desc:string"] 12 | 13 | {valid_args, opts, _invalid_args} = args |> MixAPI.parse_cli_command() 14 | 15 | assert {:ok, %SchemaContract{} = contract} = 16 | SchemaResourceAPI.build_schema_resource_contract(valid_args, opts) 17 | 18 | assert ^contract = SchemaResourceAPI.build_schema_resource_contract!(valid_args, opts) 19 | end 20 | 21 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (list)" do 22 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 23 | SchemaResourceAPI.build_schema_resource_contract(%{}, []) 24 | end 25 | end 26 | 27 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 28 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 29 | SchemaResourceAPI.build_schema_resource_contract([], %{}) 30 | end 31 | end 32 | end 33 | 34 | describe "default_param_value/2" do 35 | test "can be invoked with the correct arguments types (%SchemaContract{}, atom) and returns the expected string" do 36 | contract = domain_contract_fixture() 37 | 38 | assert SchemaResourceAPI.default_param_value(contract.schema, :create) === "some name" 39 | end 40 | 41 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (%SchemaContract{})" do 42 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 43 | SchemaResourceAPI.default_param_value(%{}, []) 44 | end 45 | end 46 | 47 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (atom)" do 48 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 49 | contract = domain_contract_fixture() 50 | SchemaResourceAPI.default_param_value(contract, []) 51 | end 52 | end 53 | end 54 | 55 | describe "live_form_value/2" do 56 | test "can be invoked with the correct first argument type (%Date{}) and returns the expected string format %Y-%m-%d" do 57 | date = Date.new!(2024, 9, 6) 58 | 59 | assert SchemaResourceAPI.live_form_value(date) === "2024-09-06" 60 | end 61 | 62 | test "can be invoked with the correct first argument type (%Time{}) and returns the expected string format %H:%M" do 63 | time = Time.new!(9, 12, 25) 64 | 65 | assert SchemaResourceAPI.live_form_value(time) === "09:12" 66 | end 67 | 68 | test "can be invoked with the correct first argument type (%DateTime{}) and returns the expected string format %Y-%m-%d %H:%MZ" do 69 | date = Date.new!(2024, 9, 6) 70 | time = Time.new!(9, 12, 25) 71 | datetime = DateTime.new!(date, time) 72 | 73 | assert SchemaResourceAPI.live_form_value(datetime) === "2024-09-06T09:12:25Z" 74 | end 75 | 76 | test "can be invoked with the correct first argument type (%NaiveDateTime{}) and returns the expected string format %Y-%m-%dT%H:%M" do 77 | naive_datetime = NaiveDateTime.new!(2024, 9, 6, 9, 12, 25) 78 | 79 | assert SchemaResourceAPI.live_form_value(naive_datetime) === "2024-09-06T09:12:25" 80 | end 81 | 82 | test "when isn't invoked with the correct first argument type (%Date{}, %Time{}, %DateTime{} or %NaiveDateTime{}) it returns the same unchanged value" do 83 | assert SchemaResourceAPI.live_form_value("whatever") === "whatever" 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/elixir_scribe/mix/mix_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.MixAPITest do 2 | alias ElixirScribe.MixAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | # @INFO: Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 6 | 7 | describe "parse_cli_command/1" do 8 | test "can be invoked with the correct argument type (list) and returns the expected type ({list, list, list})" do 9 | assert {parsed_args, opts, invalid_args} = MixAPI.parse_cli_command([]) 10 | assert is_list(parsed_args) 11 | assert is_list(opts) 12 | assert is_list(invalid_args) 13 | end 14 | 15 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (list)" do 16 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 17 | MixAPI.parse_cli_command(%{}) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/elixir_scribe/mix/shell/prompt_for_file_conflicts/prompt_for_file_conflicts_shell_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Mix.Shell.PromptForFileConflicts.PromptForFileConflictsShellTest do 4 | alias ElixirScribe.MixAPI 5 | use ElixirScribe.BaseCase 6 | 7 | import MixTestHelper 8 | 9 | setup do 10 | Mix.Task.clear() 11 | :ok 12 | end 13 | 14 | test "it prompts for file conflicts when the file already exists", config do 15 | in_tmp_project(config.test, fn -> 16 | files = [ 17 | {:eex, "from/schema_template", "schema.ex"}, 18 | {:eex, :api, "from/api_template_file", "api_file.ex"}, 19 | {:eex, :resource, "from/resource_template_file", "create_resource.ex", "create"} 20 | ] 21 | 22 | File.touch("schema.ex") 23 | File.touch("api_file.ex") 24 | File.touch("create_resource.ex") 25 | 26 | expected_info = """ 27 | The following files conflict with new files to be generated: 28 | 29 | * schema.ex 30 | * api_file.ex 31 | * create_resource.ex 32 | 33 | """ 34 | 35 | send(self(), {:mix_shell_input, :yes?, true}) 36 | MixAPI.prompt_for_file_conflicts(files) 37 | 38 | assert_received {:mix_shell, :info, [^expected_info]} 39 | 40 | assert_received {:mix_shell, :yes?, ["Proceed with interactive overwrite?" <> _]} 41 | end) 42 | end 43 | 44 | test "doesn't prompt for file conflicts when the file is :new_eex", config do 45 | in_tmp_project(config.test, fn -> 46 | files = [ 47 | {:new_eex, "from/some_template", "new_eex_ignored.ex"} 48 | ] 49 | 50 | File.touch("new_eex_ignored.ex") 51 | 52 | expected_info = """ 53 | The following files conflict with new files to be generated: 54 | 55 | * new_eex_ignored.ex 56 | 57 | """ 58 | 59 | send(self(), {:mix_shell_input, :yes?, true}) 60 | MixAPI.prompt_for_file_conflicts(files) 61 | 62 | refute_received {:mix_shell, :info, [^expected_info]} 63 | 64 | refute_received {:mix_shell, :yes?, ["Proceed with interactive overwrite?" <> _]} 65 | end) 66 | end 67 | 68 | test "doesn't prompt for file conflicts when the file doesn't exist", config do 69 | in_tmp_project(config.test, fn -> 70 | file = "new.ex" 71 | 72 | MixAPI.prompt_for_file_conflicts([{:ex, :any, file}]) 73 | 74 | refute_received {:mix_shell, :info, 75 | ["The following files conflict with new files to be generated:" <> _]} 76 | 77 | refute_received {:mix_shell_input, :yes?, true} 78 | end) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/binding/build/build_binding_template_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Binding.Build.BuildBindingTemplateTest do 2 | alias ElixirScribe.Template.BindingAPI 3 | 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | test "it builds the template bindings" do 7 | contract = domain_contract_fixture() 8 | 9 | expected_embeds = 10 | """ 11 | 12 | embed_templates "read/*" 13 | embed_templates "new/*" 14 | embed_templates "edit/*" 15 | embed_templates "list/*" 16 | """ 17 | |> String.trim_trailing() 18 | 19 | expected_bindings = [ 20 | embeded_templates: expected_embeds, 21 | contract: contract, 22 | list_action: "list", 23 | new_action: "new", 24 | read_action: "read", 25 | edit_action: "edit", 26 | create_action: "create", 27 | update_action: "update", 28 | delete_action: "delete", 29 | list_action_capitalized: "List", 30 | new_action_capitalized: "New", 31 | read_action_capitalized: "Read", 32 | edit_action_capitalized: "Edit", 33 | create_action_capitalized: "Create", 34 | update_action_capitalized: "Update", 35 | delete_action_capitalized: "Delete" 36 | ] 37 | 38 | assert BindingAPI.build_binding_template(contract) === expected_bindings 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/binding/rebuild/rebuild_binding_template_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Binding.Rebuild.RebuildBindingTemplateTest do 2 | alias ElixirScribe.Template.BindingAPI 3 | 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | test "it rebuilds the bindings for the template" do 7 | contract = domain_contract_fixture() 8 | 9 | bindings = [ 10 | contract: contract, 11 | action: "build_report", 12 | action_first_word: "build", 13 | action_capitalized: "BuildReport", 14 | action_human_capitalized: "Build Report", 15 | module_action_name: "BuildReportPost", 16 | absolute_module_action_name: "ElixirScribe.Site.Blog.Post.BatchUpdate.BatchUpdatePost" 17 | ] 18 | 19 | expected_bindings = [ 20 | contract: contract, 21 | action: "batch_update", 22 | action_first_word: "batch", 23 | action_capitalized: "BatchUpdate", 24 | action_human_capitalized: "Batch Update", 25 | module_action_name: "BatchUpdatePost", 26 | absolute_module_action_name: "ElixirScribe.Site.Blog.Post.BatchUpdate.BatchUpdatePost" 27 | ] 28 | 29 | assert BindingAPI.rebuild_binding_template(bindings, "batch_update", file_type: :resource) === 30 | expected_bindings 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/binding_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.BindingAPITest do 2 | alias ElixirScribe.Template.BindingAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 6 | 7 | describe "build_binding_template/1" do 8 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (list)" do 9 | contract = domain_contract_fixture() 10 | 11 | assert BindingAPI.build_binding_template(contract) |> is_list() 12 | end 13 | 14 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 15 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 16 | BindingAPI.build_binding_template(%{}) 17 | end 18 | end 19 | end 20 | 21 | describe "rebuild_binding_template/1" do 22 | test "can be invoked with the correct arguments type (list, string, list) and returns the expected type (list)" do 23 | contract = domain_contract_fixture() 24 | 25 | binding = BindingAPI.build_binding_template(contract) 26 | 27 | assert BindingAPI.rebuild_binding_template( 28 | binding, 29 | "whatever_action", 30 | file_type: :lib_core 31 | ) 32 | |> is_list() 33 | end 34 | 35 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (list)" do 36 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 37 | BindingAPI.rebuild_binding_template(%{}, "", []) 38 | end 39 | end 40 | 41 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 42 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 43 | BindingAPI.rebuild_binding_template([], 1, []) 44 | end 45 | end 46 | 47 | test "raises a FunctionClauseError when isn't invoked with the correct third argument type (list)" do 48 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 49 | BindingAPI.rebuild_binding_template([], "", %{}) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file/build_dir_path_for_html/build_dir_path_for_html_file_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.BuildPathForHtml.BuildPathForHtmlFileTest do 2 | alias ElixirScribe.Template.FileAPI 3 | 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | test "it builds the directory path for the default HTML template" do 7 | args = ["Site.Blog", "Post", "posts", "name:string", "desc:string"] 8 | contract = domain_contract_fixture(args) 9 | 10 | assert FileAPI.build_dir_path_for_html_file(contract) === 11 | "priv/templates/scribe.gen.html/html/default" 12 | end 13 | 14 | test "it builds the directory path for a custom HTML template" do 15 | args = [ 16 | "Site.Blog", 17 | "Post", 18 | "posts", 19 | "name:string", 20 | "desc:string", 21 | "--html-template", 22 | "online_shop" 23 | ] 24 | 25 | contract = domain_contract_fixture(args) 26 | 27 | assert FileAPI.build_dir_path_for_html_file(contract) === 28 | "priv/templates/scribe.gen.html/html/online_shop" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file/build_filename_for_action/build_filename_for_action_file_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.File.BuildFilenameForAction.BuildFilenameForActionFileTest do 2 | alias ElixirScribe.Template.FileAPI 3 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 4 | 5 | use ElixirScribe.BaseCase, async: true 6 | 7 | test "it builds the template filename for an action in the default resource actions" do 8 | attrs = %{ 9 | action: "read", 10 | action_suffix: "_", 11 | file_type: "schema", 12 | file_extension: ".ex" 13 | } 14 | 15 | contract = BuildFilenameForActionFileContract.new!(attrs) 16 | 17 | assert FileAPI.build_template_action_filename(contract) === "read_schema.ex" 18 | end 19 | 20 | test "it builds the default template filename for an action not in the the default resource actions" do 21 | attrs = %{ 22 | action: "report", 23 | action_suffix: "_", 24 | file_type: "schema", 25 | file_extension: ".ex" 26 | } 27 | 28 | contract = BuildFilenameForActionFileContract.new!(attrs) 29 | 30 | assert FileAPI.build_template_action_filename(contract) === "default_schema.ex" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file/inject/inject_content_before_module_end_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Template.File.Inject.InjectContentBeforeModuleEndTest do 4 | alias ElixirScribe.Template.FileAPI 5 | use ElixirScribe.BaseCase 6 | 7 | import MixTestHelper 8 | 9 | setup do 10 | Mix.Task.clear() 11 | :ok 12 | end 13 | 14 | test "it injects content in the module before the final end tag", config do 15 | in_tmp(config.test, fn -> 16 | api_content = """ 17 | defmodule API do 18 | def whatever(), do: :whatever 19 | end 20 | """ 21 | 22 | File.write!("api.ex", api_content) 23 | 24 | content_to_inject = """ 25 | 26 | def something(), do: :something 27 | """ 28 | 29 | expected_api_content = """ 30 | defmodule API do 31 | def whatever(), do: :whatever 32 | 33 | def something(), do: :something 34 | end 35 | """ 36 | 37 | assert :ok = FileAPI.inject_content_before_module_end(content_to_inject, "api.ex") 38 | 39 | assert_received {:mix_shell, :info, ["* injecting api.ex"]} 40 | 41 | assert_file("api.ex", fn file -> 42 | assert file === expected_api_content 43 | end) 44 | end) 45 | end 46 | 47 | test "doesn't inject the content when already present in the module", config do 48 | in_tmp(config.test, fn -> 49 | content_to_inject = """ 50 | 51 | def something(), do: :something 52 | """ 53 | 54 | expected_api_content = """ 55 | defmodule API do 56 | def whatever(), do: :whatever 57 | 58 | def something(), do: :something 59 | end 60 | """ 61 | 62 | File.write!("api.ex", expected_api_content) 63 | 64 | assert {:noop, :content_to_inject_already_exists} = 65 | FileAPI.inject_content_before_module_end(content_to_inject, "api.ex") 66 | 67 | refute_received {:mix_shell, :info, ["* injecting api.ex"]} 68 | 69 | assert_file("api.ex", fn file -> 70 | assert file === expected_api_content 71 | end) 72 | end) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file/inject/inject_eex_template_before_module_end_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.Template.File.Inject.InjectEExTemplateBeforeModuleEndTest do 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Template.FileAPI 6 | use ElixirScribe.BaseCase 7 | 8 | import MixTestHelper 9 | 10 | setup do 11 | Mix.Task.clear() 12 | :ok 13 | end 14 | 15 | test "it injects an EEX template before the module end tag", config do 16 | in_tmp_project(config.test, fn -> 17 | api_content = """ 18 | defmodule API do 19 | end 20 | """ 21 | 22 | File.write!("api_existing_module.ex", api_content) 23 | 24 | function_template = """ 25 | def <%= action %>(), do: :<%= action %> 26 | """ 27 | 28 | File.write!("api_function_template.ex", function_template) 29 | 30 | expected_api_content = """ 31 | defmodule API do 32 | def create(), do: :create 33 | end 34 | """ 35 | 36 | base_template_paths = ElixirScribe.base_template_paths() 37 | 38 | binding = 39 | domain_contract_fixture() 40 | |> BindingAPI.build_binding_template() 41 | |> BindingAPI.rebuild_binding_template("create", 42 | file_type: :lib_core 43 | ) 44 | 45 | assert :ok = 46 | FileAPI.inject_eex_template_before_module_end( 47 | base_template_paths, 48 | "api_function_template.ex", 49 | "api_existing_module.ex", 50 | binding 51 | ) 52 | 53 | assert_received {:mix_shell, :info, ["* injecting api_existing_module.ex"]} 54 | 55 | assert_file("api_existing_module.ex", fn file -> 56 | assert file === expected_api_content 57 | end) 58 | end) 59 | end 60 | 61 | test "doesn't inject an EEX template when already exists in the module", config do 62 | in_tmp_project(config.test, fn -> 63 | function_template = """ 64 | def <%= action %>(), do: :<%= action %> 65 | """ 66 | 67 | File.write!("api_function_template.ex", function_template) 68 | 69 | expected_api_content = """ 70 | defmodule API do 71 | def create(), do: :create 72 | end 73 | """ 74 | 75 | File.write!("api_existing_module.ex", expected_api_content) 76 | 77 | base_template_paths = ElixirScribe.base_template_paths() 78 | 79 | binding = 80 | domain_contract_fixture() 81 | |> BindingAPI.build_binding_template() 82 | |> BindingAPI.rebuild_binding_template("create", 83 | file_type: :lib_core 84 | ) 85 | 86 | assert {:noop, :content_to_inject_already_exists} = 87 | FileAPI.inject_eex_template_before_module_end( 88 | base_template_paths, 89 | "api_function_template.ex", 90 | "api_existing_module.ex", 91 | binding 92 | ) 93 | 94 | refute_received {:mix_shell, :info, ["* injecting api_existing_module.ex"]} 95 | 96 | assert_file("api_existing_module.ex", fn file -> 97 | assert file === expected_api_content 98 | end) 99 | end) 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file_api_async_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.TemplateFileAPIAsyncTest do 2 | alias ElixirScribe.Template.BuildFilenameForActionFileContract 3 | alias ElixirScribe.Template.FileAPI 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 7 | 8 | describe "build_dir_path_for_html_file/1" do 9 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (string)" do 10 | contract = domain_contract_fixture() 11 | 12 | assert FileAPI.build_dir_path_for_html_file(contract) |> is_binary() 13 | end 14 | 15 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%DomainContract{})" do 16 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 17 | FileAPI.build_dir_path_for_html_file(%{}) 18 | end 19 | end 20 | end 21 | 22 | describe "build_template_action_filename/1" do 23 | test "can be invoked with the correct argument type (%BuildFilenameForActionFileContract{}) and returns the expected type (string)" do 24 | attrs = %{ 25 | action: "read", 26 | action_suffix: "_", 27 | file_type: "schema", 28 | file_extension: ".ex" 29 | } 30 | 31 | contract = BuildFilenameForActionFileContract.new!(attrs) 32 | 33 | assert FileAPI.build_template_action_filename(contract) |> is_binary() 34 | end 35 | 36 | test "raises a FunctionClauseError when isn't invoked with the correct argument type (%BuildFilenameForActionFileContract{})" do 37 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 38 | FileAPI.build_template_action_filename(%{}) 39 | end 40 | end 41 | end 42 | 43 | # Check the SYNC tests at test/elixir_scribe/template/template_binding_api_sync_test.exs 44 | describe "inject_content_before_module_end/2" do 45 | test "raises a FunctionClauseError when the first argument isn't a string" do 46 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 47 | FileAPI.inject_content_before_module_end(["whatever"], "api.ex") 48 | end 49 | end 50 | 51 | test "raises a FunctionClauseError when the second argument isn't a string" do 52 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 53 | FileAPI.inject_content_before_module_end("whatever", ["api.ex"]) 54 | end 55 | end 56 | end 57 | 58 | describe "inject_eex_template_before_module_end/4" do 59 | test "raises a FunctionClauseError when the first argument isn't a list" do 60 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 61 | FileAPI.inject_eex_template_before_module_end(%{}, "source.ex", "target.ex", []) 62 | end 63 | end 64 | 65 | test "raises a FunctionClauseError when the second argument isn't a string" do 66 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 67 | FileAPI.inject_eex_template_before_module_end([], ["source.ex"], "target.ex", []) 68 | end 69 | end 70 | 71 | test "raises a FunctionClauseError when the third argument isn't a string" do 72 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 73 | FileAPI.inject_eex_template_before_module_end([], "source.ex", ["target.ex"], []) 74 | end 75 | end 76 | 77 | test "raises a FunctionClauseError when the fourth argument isn't a list" do 78 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 79 | FileAPI.inject_eex_template_before_module_end([], "source.ex", "target.ex", "") 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/file_api_sync_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule ElixirScribe.TemplateFileAPISyncTest do 4 | alias ElixirScribe.Template.BindingAPI 5 | alias ElixirScribe.Template.FileAPI 6 | use ElixirScribe.BaseCase 7 | 8 | import MixTestHelper 9 | 10 | setup do 11 | Mix.Task.clear() 12 | :ok 13 | end 14 | 15 | # Tests in the API module only care about testing the function can be invoked and that the API contract is respected for guards, pattern matching and expected return types. The unit tests for the functionality are done in their respective modules. 16 | 17 | # Check the ASYNC tests at test/elixir_scribe/template/template_binding_api_async_test.exs 18 | describe "inject_content_before_module_end/2" do 19 | test "can be invoked with the correct arguments types (string, string) and returns the expected atom (:ok) for a successful operation", 20 | config do 21 | in_tmp_project(config.test, fn -> 22 | module_content = """ 23 | defmodule API do 24 | end 25 | """ 26 | 27 | File.write!("api.ex", module_content) 28 | 29 | content_to_inject = """ 30 | def whatever, do: :whatever 31 | """ 32 | 33 | assert :ok = FileAPI.inject_content_before_module_end(content_to_inject, "api.ex") 34 | end) 35 | end 36 | 37 | test "returns the tuple {:noop, reason} for an unsuccessful operation", config do 38 | in_tmp_project(config.test, fn -> 39 | module_content = """ 40 | defmodule API do 41 | def whatever, do: :whatever 42 | end 43 | """ 44 | 45 | File.write!("api.ex", module_content) 46 | 47 | content_to_inject = """ 48 | def whatever, do: :whatever 49 | """ 50 | 51 | assert {:noop, :content_to_inject_already_exists} = 52 | FileAPI.inject_content_before_module_end(content_to_inject, "api.ex") 53 | end) 54 | end 55 | end 56 | 57 | describe "inject_eex_template_before_module_end/4" do 58 | test "can be invoked with the correct arguments types (list, string, string, list) and returns the expected atom (:ok) for a successful operation", 59 | config do 60 | in_tmp_project(config.test, fn -> 61 | api_content = """ 62 | defmodule API do 63 | end 64 | """ 65 | 66 | File.write!("api_existing_module.ex", api_content) 67 | 68 | function_template = """ 69 | def <%= action %>(), do: :<%= action %> 70 | """ 71 | 72 | File.write!("api_function_template.ex", function_template) 73 | 74 | base_template_paths = ElixirScribe.base_template_paths() 75 | 76 | binding = 77 | domain_contract_fixture() 78 | |> BindingAPI.build_binding_template() 79 | |> BindingAPI.rebuild_binding_template( 80 | "read", 81 | file_type: :lib_core 82 | ) 83 | 84 | assert :ok = 85 | FileAPI.inject_eex_template_before_module_end( 86 | base_template_paths, 87 | "api_function_template.ex", 88 | "api_existing_module.ex", 89 | binding 90 | ) 91 | end) 92 | end 93 | 94 | test "returns the tuple {:noop, reason} for an unsuccessful operation", config do 95 | in_tmp_project(config.test, fn -> 96 | function_template = """ 97 | def <%= action %>(), do: :<%= action %> 98 | """ 99 | 100 | File.write!("api_function_template.ex", function_template) 101 | 102 | expected_api_content = """ 103 | defmodule API do 104 | def create(), do: :create 105 | end 106 | """ 107 | 108 | File.write!("api_existing_module.ex", expected_api_content) 109 | 110 | base_template_paths = ElixirScribe.base_template_paths() 111 | 112 | binding = 113 | domain_contract_fixture() 114 | |> BindingAPI.build_binding_template() 115 | |> BindingAPI.rebuild_binding_template("create", 116 | file_type: :lib_core 117 | ) 118 | 119 | assert {:noop, :content_to_inject_already_exists} = 120 | FileAPI.inject_eex_template_before_module_end( 121 | base_template_paths, 122 | "api_function_template.ex", 123 | "api_existing_module.ex", 124 | binding 125 | ) 126 | end) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module/build_embed_templates/build_module_embed_templates_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Options.BuildEmbedTemplates.BuildModuleEmbedTemplatesTest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | test "it builds an build_embeded_templates statement for each HTML resource action" do 6 | expected_embeds = 7 | """ 8 | 9 | embed_templates "read/*" 10 | embed_templates "new/*" 11 | embed_templates "edit/*" 12 | embed_templates "list/*" 13 | """ 14 | |> String.trim_trailing() 15 | 16 | assert ModuleAPI.build_embeded_templates() == expected_embeds 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module/build_name/build_absolute_module_action_name_aliases_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionNameAliasesTest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | 4 | use ElixirScribe.BaseCase, async: true 5 | 6 | test "it builds the module alias for each resource action" do 7 | contract = domain_contract_fixture() 8 | 9 | expected_aliases = 10 | """ 11 | 12 | alias ElixirScribe.Site.Blog.Post.List.ListPosts 13 | alias ElixirScribe.Site.Blog.Post.New.NewPost 14 | alias ElixirScribe.Site.Blog.Post.Read.ReadPost 15 | alias ElixirScribe.Site.Blog.Post.Edit.EditPost 16 | alias ElixirScribe.Site.Blog.Post.Create.CreatePost 17 | alias ElixirScribe.Site.Blog.Post.Update.UpdatePost 18 | alias ElixirScribe.Site.Blog.Post.Delete.DeletePost 19 | """ 20 | |> String.trim_trailing() 21 | 22 | assert ModuleAPI.build_absolute_module_action_name_aliases(contract, 23 | file_type: :lib_core 24 | ) === expected_aliases 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module/build_name/build_absolute_module_action_name_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleActionNameTest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | test "it builds the absolute module name for the given action" do 6 | contract = domain_contract_fixture() 7 | 8 | assert ModuleAPI.build_absolute_module_action_name(contract, "read", file_type: :lib_core) === 9 | "ElixirScribe.Site.Blog.Post.Read.ReadPost" 10 | end 11 | 12 | test "it returns nil when the file type is HTML" do 13 | contract = domain_contract_fixture() 14 | 15 | assert ModuleAPI.build_absolute_module_action_name(contract, "read", file_type: :html) === nil 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module/build_name/build_absolute_module_name_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildAbsoluteModuleNameTest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | describe "absolute module name for core file" do 6 | test "builds correctly for `file_type: :resource`" do 7 | contract = domain_contract_fixture() 8 | 9 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :resource) === 10 | ElixirScribe.Site.Blog.Post 11 | end 12 | 13 | test "builds correctly for `file_type: :resource_test`" do 14 | contract = domain_contract_fixture() 15 | 16 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :resource_test) === 17 | ElixirScribe.Site.Blog.Post 18 | end 19 | 20 | test "builds correctly for `file_type: :lib_core`" do 21 | contract = domain_contract_fixture() 22 | 23 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :lib_core) === 24 | ElixirScribe.Site.Blog.Post 25 | end 26 | 27 | test "builds correctly for `file_type: :test_core`" do 28 | contract = domain_contract_fixture() 29 | 30 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :test_core) === 31 | ElixirScribe.Site.Blog.Post 32 | end 33 | 34 | test "it returns nil for when the file type is HTML" do 35 | contract = domain_contract_fixture() 36 | 37 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :html) === nil 38 | end 39 | end 40 | 41 | describe "absolute module name for web file" do 42 | test "builds correctly for `file_type: :lib_web`" do 43 | contract = domain_contract_fixture() 44 | 45 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :lib_web) === 46 | ElixirScribeWeb.Site.Blog.Post 47 | end 48 | 49 | test "builds correctly for `file_type: :controller`" do 50 | contract = domain_contract_fixture() 51 | 52 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :controller) === 53 | ElixirScribeWeb.Site.Blog.Post 54 | end 55 | 56 | test "builds correctly for `file_type: :controller_test`" do 57 | contract = domain_contract_fixture() 58 | 59 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :controller_test) === 60 | ElixirScribeWeb.Site.Blog.Post 61 | end 62 | 63 | test "builds correctly for `file_type: :test_web`" do 64 | contract = domain_contract_fixture() 65 | 66 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :test_web) === 67 | ElixirScribeWeb.Site.Blog.Post 68 | end 69 | end 70 | 71 | test "it returns nil for `file_type: :test_web`" do 72 | contract = domain_contract_fixture() 73 | 74 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :html) === nil 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module/build_name/build_module_action_name_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Module.BuildName.BuildModuleActionNameTest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | test "it builds the module action name for a singular action" do 6 | contract = domain_contract_fixture() 7 | 8 | assert ModuleAPI.build_module_action_name(contract, "read") === "ReadPost" 9 | end 10 | 11 | test "it builds the module action name for a plural action" do 12 | contract = domain_contract_fixture() 13 | 14 | assert ModuleAPI.build_module_action_name(contract, "list") === "ListPosts" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/module_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.ModuleAPITest do 2 | alias ElixirScribe.Template.ModuleAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | describe "build_embeded_templates/0" do 6 | test "can be invoked and returns the expected type (string)" do 7 | assert ModuleAPI.build_embeded_templates() |> is_binary() 8 | end 9 | end 10 | 11 | describe "build_absolute_module_action_name/3" do 12 | test "can be invoked with the correct arguments type (%DomainContract{}, string, list) and returns the expected type (string)" do 13 | contract = domain_contract_fixture() 14 | 15 | assert ModuleAPI.build_absolute_module_action_name(contract, "read", file_type: :lib_core) 16 | |> is_binary() 17 | end 18 | 19 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (%DomainContract{})" do 20 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 21 | ModuleAPI.build_absolute_module_action_name(%{}, "action", []) 22 | end 23 | end 24 | 25 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (%DomainContract{})" do 26 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 27 | ModuleAPI.build_absolute_module_action_name(%{}, ["action"], []) 28 | end 29 | end 30 | 31 | test "raises a FunctionClauseError when isn't invoked with the correct third argument type (%DomainContract{})" do 32 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 33 | ModuleAPI.build_absolute_module_action_name(%{}, "action", "") 34 | end 35 | end 36 | end 37 | 38 | describe "build_absolute_module_action_name_aliases/2" do 39 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (string)" do 40 | contract = domain_contract_fixture() 41 | 42 | assert ModuleAPI.build_absolute_module_action_name_aliases(contract, file_type: :lib_core) 43 | |> is_binary() 44 | end 45 | 46 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (%DomainContract{})" do 47 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 48 | ModuleAPI.build_absolute_module_action_name_aliases(%{}, []) 49 | end 50 | end 51 | 52 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 53 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 54 | domain_contract_fixture() 55 | |> ModuleAPI.build_absolute_module_action_name_aliases(%{}) 56 | end 57 | end 58 | end 59 | 60 | describe "build_absolute_module_name/2" do 61 | test "can be invoked with the correct argument type (%DomainContract{}) and returns the expected type (atom)" do 62 | contract = domain_contract_fixture() 63 | 64 | assert ModuleAPI.build_absolute_module_name(contract, file_type: :lib_core) |> is_atom() 65 | end 66 | 67 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (%DomainContract{})" do 68 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 69 | ModuleAPI.build_absolute_module_name(%{}, []) 70 | end 71 | end 72 | 73 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 74 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 75 | domain_contract_fixture() 76 | |> ModuleAPI.build_absolute_module_name("") 77 | end 78 | end 79 | end 80 | 81 | describe "build_module_action_name/2" do 82 | test "can be invoked with the correct arguments type (%DomainContract{}, string) and returns the expected type (string)" do 83 | contract = domain_contract_fixture() 84 | 85 | assert ModuleAPI.build_module_action_name(contract, "action") |> is_binary() 86 | end 87 | 88 | test "raises a FunctionClauseError when isn't invoked with the correct first argument type (%DomainContract{})" do 89 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 90 | ModuleAPI.build_module_action_name(%{}, "action") 91 | end 92 | end 93 | 94 | test "raises a FunctionClauseError when isn't invoked with the correct second argument type (list)" do 95 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 96 | domain_contract_fixture() 97 | |> ModuleAPI.build_module_action_name(:whatever) 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/elixir_scribe/template/route/scope/scope_action_routes_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Template.Route.Scope.ScopeActionRoutesTest do 2 | alias ElixirScribe.Template.RouteAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | test "it builds the routes scope for all resource actions" do 6 | contract = domain_contract_fixture() 7 | 8 | expected_scope = 9 | """ 10 | 11 | scope "/site/blog/posts", ElixirScribeWeb.Site.Blog.Post, as: :site_blog_posts do 12 | pipe_through :browser 13 | 14 | get "/", List.ListPostsController, :list 15 | get "/new", New.NewPostController, :new 16 | get "/:id", Read.ReadPostController, :read 17 | get "/:id/edit", Edit.EditPostController, :edit 18 | post "/", Create.CreatePostController, :create 19 | patch "/:id", Update.UpdatePostController, :update 20 | put "/:id", Update.UpdatePostController, :update 21 | delete "/:id", Delete.DeletePostController, :delete 22 | end 23 | """ 24 | 25 | assert RouteAPI.scope_routes(contract) == expected_scope 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string/camel_case_to_sentence/camel_case_to_sentence_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.CamelCaseToSentenceTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias ElixirScribe.Utils.String.CamelCaseToSentence 5 | 6 | doctest ElixirScribe.Utils.String.CamelCaseToSentence 7 | end 8 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string/capitalize/capitalize_string_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.Capitalize.CapitalizeStringTest do 2 | use ExUnit.Case 3 | 4 | alias ElixirScribe.Utils.StringAPI 5 | 6 | describe "capitalize/1" do 7 | test "capitalizes a string" do 8 | assert " string " |> StringAPI.capitalize() === "String" 9 | end 10 | 11 | test "capitalizes a snake_case string" do 12 | assert "some_string" |> StringAPI.capitalize() === "SomeString" 13 | end 14 | 15 | test "capitalizes a kebab-case string" do 16 | assert "some-string" |> StringAPI.capitalize() === "SomeString" 17 | end 18 | end 19 | 20 | describe "capitalize/2" do 21 | test "capitalizes a string with the given joiner" do 22 | joiner = " " 23 | 24 | assert "some-string" |> StringAPI.capitalize(joiner) === "Some String" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string/find_acronyms/find_acronyms_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.FindAcronymsTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias ElixirScribe.Utils.String.FindAcronyms 5 | 6 | doctest ElixirScribe.Utils.String.FindAcronyms 7 | end 8 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string/first_word/first_word_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.FirstWord.FirstWordStringTest do 2 | use ExUnit.Case 3 | 4 | alias ElixirScribe.Utils.StringAPI 5 | 6 | describe "first_word/2" do 7 | test "returns first word in a string with more then one word" do 8 | assert "This is a string" |> StringAPI.first_word() === "This" 9 | end 10 | 11 | test "returns the same string when it only has one word" do 12 | assert "string" |> StringAPI.first_word() === "string" 13 | end 14 | 15 | test "returns an empty string when an empty string is given" do 16 | assert "" |> StringAPI.first_word() === "" 17 | end 18 | 19 | test "returns an empty string when a string with white spaces is given" do 20 | assert " " |> StringAPI.first_word() === "" 21 | end 22 | 23 | test "returns the first word in a string when word separators are given as the second argument" do 24 | assert "This|is a string" |> StringAPI.first_word(["|"]) === "This" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string/human_capitalize/human_capitalize_string_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.String.HumanCapitalize.HumanCapitalizeStringTest do 2 | use ExUnit.Case 3 | 4 | alias ElixirScribe.Utils.StringAPI 5 | 6 | describe "human_capitalize/1" do 7 | test "capitalizes a string to be human readable" do 8 | assert " string " |> StringAPI.human_capitalize() === "String" 9 | end 10 | 11 | test "capitalizes a snake_case string to be human readable" do 12 | assert "some_string" |> StringAPI.human_capitalize() === "Some String" 13 | end 14 | 15 | test "capitalizes a kebab-case string to be human readable" do 16 | assert "some-string" |> StringAPI.human_capitalize() === "Some String" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/elixir_scribe/utils/string_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Utils.StringAPITest do 2 | alias ElixirScribe.Utils.StringAPI 3 | use ElixirScribe.BaseCase, async: true 4 | 5 | # INFO: Tests in the API module only care about testing the function can be invoked and that the API contract is respected (for now only guards and pattern matching). The unit tests for the functionality are done in their respective modules 6 | 7 | describe "capitalize/1" do 8 | test "can be invoked and returns a string" do 9 | assert StringAPI.capitalize("whatever") |> is_binary() 10 | end 11 | 12 | test "raises a FunctionClauseError when the argument isn't a string" do 13 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 14 | StringAPI.capitalize(123) 15 | end 16 | end 17 | end 18 | 19 | describe "capitalize/2" do 20 | test "can be invoked and returns a string" do 21 | assert StringAPI.capitalize("whatever word", "") |> is_binary() 22 | end 23 | 24 | test "raises a FunctionClauseError when the first arguments isn't a string" do 25 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 26 | StringAPI.capitalize(:whatever, "") 27 | end 28 | end 29 | 30 | test "raises a FunctionClauseError when the joiner (2nd arg) isn't a string" do 31 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 32 | StringAPI.capitalize("whatever word", :bad_joiner) 33 | end 34 | end 35 | end 36 | 37 | describe "human_capitalize/1" do 38 | test "can be invoked and returns a string" do 39 | assert StringAPI.capitalize("whatever word") |> is_binary() 40 | end 41 | 42 | test "raises a FunctionClauseError when the argument isn't a string" do 43 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 44 | StringAPI.capitalize(:whatever) 45 | end 46 | end 47 | end 48 | 49 | describe "first_word/1" do 50 | test "can be invoked and returns a string" do 51 | assert StringAPI.first_word("whatever word") |> is_binary() 52 | end 53 | 54 | test "raises a FunctionClauseError when the argument isn't a string" do 55 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 56 | StringAPI.first_word(:whatever) 57 | end 58 | end 59 | end 60 | 61 | describe "first_word/2" do 62 | test "can be invoked and returns a string" do 63 | assert StringAPI.first_word("whatever_word") |> is_binary() 64 | end 65 | 66 | test "raises a FunctionClauseError when the first argument isn't a string" do 67 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 68 | StringAPI.first_word(:whatever_word) 69 | end 70 | end 71 | 72 | test "raises a FunctionClauseError when the second argument (word_separators) isn't a list" do 73 | assert_raise FunctionClauseError, ~r/^no function clause matching in.*$/s, fn -> 74 | StringAPI.first_word(:whatever_word, "") 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/elixir_scribe_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribeTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | doctest ElixirScribe 5 | 6 | describe "base_template_paths/0" do 7 | test "returns a list of base paths in the expected order" do 8 | assert ElixirScribe.base_template_paths() === [".", :elixir_scribe, :phoenix] 9 | end 10 | end 11 | 12 | describe "app_name/0" do 13 | test "return the app name as a string" do 14 | assert ElixirScribe.app_name() === "elixir_scribe" 15 | end 16 | end 17 | 18 | describe "app_path/1" do 19 | test "returns app path for :lib_core" do 20 | assert ElixirScribe.app_path(:lib_core) === "lib/elixir_scribe" 21 | end 22 | 23 | test "returns app path for :test_core" do 24 | assert ElixirScribe.app_path(:test_core) === "test/elixir_scribe" 25 | end 26 | 27 | test "returns app path for :lib_web" do 28 | assert ElixirScribe.app_path(:lib_web) === "lib/elixir_scribe_web" 29 | end 30 | 31 | test "returns app path for :test_web" do 32 | assert ElixirScribe.app_path(:test_web) === "test/elixir_scribe_web" 33 | end 34 | end 35 | 36 | describe "web_template_path/0" do 37 | test "returns the web template path" do 38 | assert ElixirScribe.web_template_path() === "priv/templates/scribe.gen.html" 39 | end 40 | end 41 | 42 | describe "html_template_path/0" do 43 | test "returns the html template path" do 44 | assert ElixirScribe.html_template_path() === "priv/templates/scribe.gen.html/html" 45 | end 46 | end 47 | 48 | describe "controller_template_path/0" do 49 | test "returns the controller template path" do 50 | assert ElixirScribe.controller_template_path() === 51 | "priv/templates/scribe.gen.html/controllers" 52 | end 53 | end 54 | 55 | describe "controller_test_template_path/0" do 56 | test "returns the controller test template path" do 57 | assert ElixirScribe.controller_test_template_path() === 58 | "priv/templates/scribe.gen.html/tests/controllers" 59 | end 60 | end 61 | 62 | describe "domain_template_path/0" do 63 | test "returns the domain template path" do 64 | assert ElixirScribe.domain_template_path() === "priv/templates/scribe.gen.domain" 65 | end 66 | end 67 | 68 | describe "domain_tests_template_path/0" do 69 | test "returns the default domain tests template path" do 70 | assert ElixirScribe.domain_tests_template_path() === 71 | "priv/templates/scribe.gen.domain/tests" 72 | end 73 | end 74 | 75 | describe "domain_api_template_path/0" do 76 | test "returns the default domain api template path" do 77 | assert ElixirScribe.domain_api_template_path() === "priv/templates/scribe.gen.domain/apis" 78 | end 79 | end 80 | 81 | describe "resource_actions_template_path/0" do 82 | test "returns the default domain actions template path" do 83 | assert ElixirScribe.resource_actions_template_path() === 84 | "priv/templates/scribe.gen.domain/actions" 85 | end 86 | end 87 | 88 | describe "resource_actions/0" do 89 | test "returns the resource actions in the expected order" do 90 | assert ElixirScribe.resource_actions() === [ 91 | "list", 92 | "new", 93 | "read", 94 | "edit", 95 | "create", 96 | "update", 97 | "delete" 98 | ] 99 | end 100 | end 101 | 102 | describe "resource_actions_aliases/0" do 103 | test "returns an empty map when no aliases are configured in the app config" do 104 | assert ElixirScribe.resource_actions_aliases() === %{} 105 | end 106 | end 107 | 108 | describe "resource_action_alias/1" do 109 | test "returns the same action when no alias is configured for action in the app config" do 110 | assert ElixirScribe.resource_action_alias("create") === "create" 111 | end 112 | end 113 | 114 | describe "resource_html_actions/0" do 115 | test "returns the resource html actions in the expected order" do 116 | assert ElixirScribe.resource_html_actions() === ["read", "new", "edit", "list"] 117 | end 118 | end 119 | 120 | describe "resource_plural_actions/0" do 121 | test "returns the resource plural actions" do 122 | assert ElixirScribe.resource_plural_actions() === ["index", "list"] 123 | end 124 | end 125 | 126 | describe "schema_template_folder_name/1" do 127 | test "returns `schema_access` as the folder template name when schema.generate? is true" do 128 | domain_contract = domain_contract_fixture() 129 | 130 | assert ElixirScribe.schema_template_folder_name(domain_contract.schema) === "schema_access" 131 | end 132 | 133 | test "returns `no_schema_access` as the folder template name when schema.generate? is false" do 134 | domain_contract = domain_contract_fixture() 135 | schema = Map.put(domain_contract.schema, :generate?, false) 136 | 137 | assert ElixirScribe.schema_template_folder_name(schema) === "no_schema_access" 138 | end 139 | end 140 | 141 | describe "app_file_extensions/0" do 142 | test "it returns a list with all app file extensions" do 143 | assert ElixirScribe.app_file_extensions() === [".ex", ".exs", "html.heex"] 144 | end 145 | end 146 | 147 | describe "app_file_types/0" do 148 | test "it returns a list with all app file types" do 149 | assert ElixirScribe.app_file_types() === ["", "controller", "controller_test", "test"] 150 | end 151 | end 152 | 153 | describe "app_path_types/0" do 154 | test "it returns a list with all file path types" do 155 | assert ElixirScribe.app_path_types() === [:lib_core, :lib_web, :test_core, :test_web] 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /test/mix/tasks/scribe.gen.domain_async_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Scribe.Gen.DomainAsyncTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | test "It raises when no arguments are provided" do 5 | assert_raise Mix.Error, ~r/No arguments were provided.*$/s, fn -> 6 | Mix.Tasks.Scribe.Gen.Domain.run([]) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/mix/tasks/scribe.gen.domain_sync_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("test/mix_test_helper.exs") 2 | 3 | defmodule Mix.Tasks.Scribe.Gen.DomainSyncTest do 4 | use ElixirScribe.BaseCase 5 | 6 | import MixTestHelper 7 | 8 | setup do 9 | Mix.Task.clear() 10 | :ok 11 | end 12 | 13 | # test "it raises when inside in umbrella project", config do 14 | # in_tmp_umbrella_project(config.test, fn -> 15 | # assert_raise Mix.Error, ~r/No arguments were provided.*$/s, fn -> 16 | # Mix.Tasks.Scribe.Gen.Domain.run(~w(blog Post posts title:string)) 17 | # end 18 | # end) 19 | # end 20 | 21 | test "it generates the domain resource", config do 22 | in_tmp_project(config.test, fn -> 23 | assert :ok = Mix.Tasks.Scribe.Gen.Domain.run(~w(Blog Post posts title:string)) 24 | 25 | assert_received {:mix_shell, :info, 26 | [ 27 | "\nRemember to update your repository by running migrations:\n\n $ mix ecto.migrate\n" 28 | ]} 29 | end) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/mix/tasks/scribe.gen.html_async_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Scribe.Gen.HtmlAsyncTest do 2 | use ElixirScribe.BaseCase, async: true 3 | 4 | test "It raises when no arguments are provided" do 5 | assert_raise Mix.Error, ~r/No arguments were provided.*$/s, fn -> 6 | Mix.Tasks.Scribe.Gen.Html.run([]) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/mix/tasks/scribe.gen_test.exs: -------------------------------------------------------------------------------- 1 | # Get Mix output sent to the current 2 | # process to avoid polluting tests. 3 | Mix.shell(Mix.Shell.Process) 4 | 5 | defmodule Mix.Tasks.Scribe.GenTest do 6 | use ElixirScribe.BaseCase 7 | 8 | test "It list all Elixir Scribe generators" do 9 | Mix.Tasks.Scribe.Gen.run([]) 10 | assert_received {:mix_shell, :info, ["mix scribe.gen.html" <> _]} 11 | assert_received {:mix_shell, :info, ["mix scribe.gen.domain" <> _]} 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/mix/tasks/scribe_test.exs: -------------------------------------------------------------------------------- 1 | # Get Mix output sent to the current 2 | # process to avoid polluting tests. 3 | Mix.shell(Mix.Shell.Process) 4 | 5 | defmodule Mix.Tasks.ScribeTest do 6 | use ElixirScribe.BaseCase 7 | 8 | test "It list all Elixir Scribe tasks" do 9 | Mix.Tasks.Scribe.run([]) 10 | assert_received {:mix_shell, :info, ["mix scribe.gen" <> _]} 11 | assert_received {:mix_shell, :info, ["mix scribe.gen.html" <> _]} 12 | assert_received {:mix_shell, :info, ["mix scribe.gen.domain" <> _]} 13 | end 14 | 15 | test "expects no arguments" do 16 | assert_raise Mix.Error, fn -> 17 | Mix.Tasks.Scribe.run(["invalid"]) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/mix_test_helper.exs: -------------------------------------------------------------------------------- 1 | # Get Mix output sent to the current 2 | # process to avoid polluting tests. 3 | Mix.shell(Mix.Shell.Process) 4 | 5 | defmodule MixTestHelper do 6 | import ExUnit.Assertions 7 | import ExUnit.CaptureIO 8 | 9 | def tmp_path do 10 | Path.expand("../tmp", __DIR__) 11 | end 12 | 13 | def random_tmp_path(which) do 14 | test_name = to_string(which) |> String.trim() |> String.replace(" ", "_") 15 | Path.join([tmp_path(), random_string(10) <> "_" <> test_name]) 16 | end 17 | 18 | defp random_string(len) do 19 | len |> :crypto.strong_rand_bytes() |> Base.encode64() |> binary_part(0, len) 20 | end 21 | 22 | def in_tmp(which, function) do 23 | path = random_tmp_path(which) 24 | 25 | try do 26 | File.rm_rf!(path) 27 | File.mkdir_p!(path) 28 | File.cd!(path, function) 29 | after 30 | File.rm_rf!(path) 31 | end 32 | end 33 | 34 | def in_tmp_project(which, function) do 35 | conf_before = Application.get_env(:phoenix, :generators) || [] 36 | path = random_tmp_path(which) 37 | 38 | try do 39 | File.rm_rf!(path) 40 | File.mkdir_p!(path) 41 | 42 | File.cd!(path, fn -> 43 | File.touch!("mix.exs") 44 | 45 | File.mkdir_p!("lib/elixir_scribe_web/") 46 | 47 | File.write!("lib/elixir_scribe_web/router.ex", """ 48 | defmodule ElixirScribeAppWeb.Router do 49 | use ElixirScribeAppWeb, :router 50 | 51 | end 52 | """) 53 | 54 | File.write!(".formatter.exs", """ 55 | [ 56 | import_deps: [:phoenix, :ecto, :ecto_sql], 57 | inputs: ["*.exs"] 58 | ] 59 | """) 60 | 61 | function.() 62 | end) 63 | after 64 | File.rm_rf!(path) 65 | Application.put_env(:phoenix, :generators, conf_before) 66 | end 67 | end 68 | 69 | def in_tmp_umbrella_project(which, function) do 70 | conf_before = Application.get_env(:phoenix, :generators) || [] 71 | path = random_tmp_path(which) 72 | 73 | try do 74 | apps_path = Path.join(path, "apps") 75 | config_path = Path.join(path, "config") 76 | File.rm_rf!(path) 77 | File.mkdir_p!(path) 78 | File.mkdir_p!(apps_path) 79 | File.mkdir_p!(config_path) 80 | File.touch!(Path.join(path, "mix.exs")) 81 | 82 | for file <- ~w(config.exs dev.exs test.exs prod.exs) do 83 | File.write!(Path.join(config_path, file), "import Config\n") 84 | end 85 | 86 | File.cd!(apps_path, function) 87 | after 88 | Application.put_env(:phoenix, :generators, conf_before) 89 | File.rm_rf!(path) 90 | end 91 | end 92 | 93 | def in_project(app, path, fun) do 94 | %{name: name, file: file} = Mix.Project.pop() 95 | 96 | try do 97 | capture_io(:stderr, fn -> 98 | Mix.Project.in_project(app, path, [prune_code_paths: false], fun) 99 | end) 100 | after 101 | Mix.Project.push(name, file) 102 | end 103 | end 104 | 105 | def assert_file(file) do 106 | assert File.regular?(file), "Expected #{file} to exist, but does not" 107 | end 108 | 109 | def refute_file(file) do 110 | refute File.regular?(file), "Expected #{file} to not exist, but it does" 111 | end 112 | 113 | def assert_file(file, match) do 114 | cond do 115 | is_list(match) -> 116 | assert_file(file, &Enum.each(match, fn m -> assert &1 =~ m end)) 117 | 118 | is_binary(match) or is_struct(match, Regex) -> 119 | assert_file(file, &assert(&1 =~ match)) 120 | 121 | is_function(match, 1) -> 122 | assert_file(file) 123 | match.(File.read!(file)) 124 | 125 | true -> 126 | raise inspect({file, match}) 127 | end 128 | end 129 | 130 | def modify_file(path, function) when is_binary(path) and is_function(function, 1) do 131 | path 132 | |> File.read!() 133 | |> function.() 134 | |> write_file!(path) 135 | end 136 | 137 | defp write_file!(content, path) do 138 | File.write!(path, content) 139 | end 140 | 141 | def with_generator_env(app_name \\ :phoenix, new_env, fun) do 142 | config_before = Application.fetch_env(app_name, :generators) 143 | Application.put_env(app_name, :generators, new_env) 144 | 145 | try do 146 | fun.() 147 | after 148 | case config_before do 149 | {:ok, config} -> Application.put_env(app_name, :generators, config) 150 | :error -> Application.delete_env(app_name, :generators) 151 | end 152 | end 153 | end 154 | 155 | def umbrella_mixfile_contents do 156 | """ 157 | defmodule Umbrella.MixProject do 158 | use Mix.Project 159 | 160 | def project do 161 | [ 162 | apps_path: "apps", 163 | deps: deps() 164 | ] 165 | end 166 | 167 | defp deps do 168 | [] 169 | end 170 | end 171 | """ 172 | end 173 | 174 | def flush do 175 | receive do 176 | _ -> flush() 177 | after 178 | 0 -> :ok 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/support/base_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.BaseCase do 2 | @moduledoc false 3 | 4 | use ExUnit.CaseTemplate 5 | 6 | using do 7 | quote do 8 | import ElixirScribe.Generator.DomainFixtures 9 | import Assertions 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/support/fixtures/domain_generator_fixtures.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirScribe.Generator.DomainFixtures do 2 | @moduledoc """ 3 | This module defines test helpers for creating 4 | entities via the `ElixirScribe.Generator.Domain` context. 5 | """ 6 | 7 | alias ElixirScribe.MixAPI 8 | alias ElixirScribe.Generator.DomainResourceAPI 9 | 10 | @default_args ["Site.Blog", "Post", "posts", "name:string", "desc:string"] 11 | def domain_contract_fixture(args \\ @default_args) do 12 | {valid_args, opts, _invalid_args} = args |> MixAPI.parse_cli_command() 13 | 14 | DomainResourceAPI.build_domain_resource_contract!(valid_args, opts) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------