Repository: phoenixframework/phoenix
Pull request #4174 (2021-01-25 08:15:49)

Update example to match with generated layout
The custom error pages guide has HTML example for custom error page but it's outdated so different from the generated layout a lot.

This PR is to align the HTML example with the generated layout. Please note that it still does not use Routes helper (like the existing example)


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f58ab774f..9bd0b2fdd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,16 +8,16 @@ Also make sure you read our [Code of Conduct](CODE_OF_CONDUCT.md) that outlines
 
 Use the issues tracker for:
 
-* [bug reports](#bug-reports)
-* [submitting pull requests](#pull-requests)
+* [Bug reports](#bug-reports)
+* [Submitting pull requests](#pull-requests)
 
 Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
 
-* [the phoenix-talk mailing list](http://groups.google.com/group/phoenix-talk) (closed in favor of forum, but archive is still online)
+* The [phoenix-talk mailing list](http://groups.google.com/group/phoenix-talk) (closed in favor of forum, but archive is still online)
 * [The Phoenix subforum on the Elixir forum](https://elixirforum.com/c/phoenix-forum)
 * **[#elixir-lang](irc://chat.freenode.net/elixir-lang)** IRC channel on [chat.freenode.net](http://www.freenode.net/)
 
-Development issues can be discussed on [the phoenix-core mailing list](http://groups.google.com/group/phoenix-core).
+Development issues can be discussed on the [phoenix-core mailing list](http://groups.google.com/group/phoenix-core).
 
 We do our best to keep the issue tracker tidy and organized, making it useful
 for everyone. For example, we classify open issues per perceived difficulty,
@@ -65,7 +65,7 @@ Example:
 
 ## Feature requests
 
-Feature requests are welcome and should be discussed on [the phoenix-core mailing list](http://groups.google.com/group/phoenix-core). But take a moment to find
+Feature requests are welcome and should be discussed on the [phoenix-core mailing list](http://groups.google.com/group/phoenix-core). But take a moment to find
 out whether your idea fits with the scope and aims of the project. It's up to *you*
 to make a strong case to convince the community of the merits of this feature.
 Please provide as much detail and context as possible.
diff --git a/guides/authentication/mix_phx_gen_auth.md b/guides/authentication/mix_phx_gen_auth.md
index 1fcb91f9d..bf8d8fd2e 100644
--- a/guides/authentication/mix_phx_gen_auth.md
+++ b/guides/authentication/mix_phx_gen_auth.md
@@ -36,7 +36,7 @@ The following are notes about the generated authentication system.
 
 ### Password hashing
 
-The password hashing mechanism defaults to `bcrypt` for Unix systems and `pbkdf2` for Windows systems. Both systems use [the Comeonin interface](https://hexdocs.pm/comeonin/).
+The password hashing mechanism defaults to `bcrypt` for Unix systems and `pbkdf2` for Windows systems. Both systems use the [Comeonin interface](https://hexdocs.pm/comeonin/).
 
 ### Forbidding access
 
diff --git a/guides/contexts.md b/guides/contexts.md
index 52ef7b499..52a53fa9e 100644
--- a/guides/contexts.md
+++ b/guides/contexts.md
@@ -1,10 +1,10 @@
 # Contexts
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
 
-> **Requirement**: This guide expects that you have gone through [the Ecto guide](ecto.html).
+> **Requirement**: This guide expects that you have gone through the [Ecto guide](ecto.html).
 
 So far, we've built pages, wired up controller actions through our routers, and learned how Ecto allows data to be validated and persisted. Now it's time to tie it all together by writing web-facing features that interact with our greater Elixir application.
 
@@ -194,7 +194,7 @@ defmodule Hello.Accounts do
 end
 ```
 
-This module will be the public API for all account functionality in our system. For example, in addition to user account management, we may also handle user login credentials, account preferences, and password reset generation. If we look at the `list_users/0` function, we can see the private details of user fetching. And it's super simple. We have a call to `Repo.all(User)`. We saw how Ecto repo queries worked in [the Ecto guide](ecto.html), so this call should look familiar. Our `list_users` function is a generalized function specifying the *intent* of our code – namely to list users. The details of that intent where we use our Repo to fetch the users from our PostgreSQL database is hidden from our callers. This is a common theme we'll see re-iterated as we use the Phoenix generators. Phoenix will push us to think about where we have different responsibilities in our application, and then to wrap up those different areas behind well-named modules and functions that make the intent of our code clear, while encapsulating the details.
+This module will be the public API for all account functionality in our system. For example, in addition to user account management, we may also handle user login credentials, account preferences, and password reset generation. If we look at the `list_users/0` function, we can see the private details of user fetching. And it's super simple. We have a call to `Repo.all(User)`. We saw how Ecto repo queries worked in the [Ecto guide](ecto.html), so this call should look familiar. Our `list_users` function is a generalized function specifying the *intent* of our code – namely to list users. The details of that intent where we use our Repo to fetch the users from our PostgreSQL database is hidden from our callers. This is a common theme we'll see re-iterated as we use the Phoenix generators. Phoenix will push us to think about where we have different responsibilities in our application, and then to wrap up those different areas behind well-named modules and functions that make the intent of our code clear, while encapsulating the details.
 
 Now we know how data is fetched, but how are users persisted? Let's take a look at the `Accounts.create_user/1` function:
 
diff --git a/guides/controllers.md b/guides/controllers.md
index e1d166531..1b5abd7d6 100644
--- a/guides/controllers.md
+++ b/guides/controllers.md
@@ -1,8 +1,8 @@
 # Controllers
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
 
 Phoenix controllers act as intermediary modules. Their functions - called actions - are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response.
 
@@ -416,7 +416,7 @@ Fortunately, our application layout, `lib/hello_web/templates/layout/app.html.ee
 
 When we reload the [Welcome Page](http://localhost:4000/), our messages should appear just above "Welcome to Phoenix!"
 
-The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we re-use the redirect action from the previous section, we can do:
+The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we reuse the redirect action from the previous section, we can do:
 
 ```elixir
   def index(conn, _params) do
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index f2ecc020d..a41ae6c52 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -21,7 +21,7 @@ Heroku is a great platform and Elixir performs well on it. However, you may run
 - In-memory state such as those in [Agents](https://elixir-lang.org/getting-started/mix-otp/agent.html), [GenServers](https://elixir-lang.org/getting-started/mix-otp/genserver.html), and [ETS](https://elixir-lang.org/getting-started/mix-otp/ets.html) will be lost every 24 hours.
   - Heroku [restarts dynos](https://devcenter.heroku.com/articles/dynos#restarting) every 24 hours regardless of whether the node is healthy.
 
-- [The built-in Observer](https://elixir-lang.org/getting-started/debugging.html#observer) can't be used with Heroku.
+- [The built-in observer](https://elixir-lang.org/getting-started/debugging.html#observer) can't be used with Heroku.
   - Heroku does allow for connection into your dyno, but you won't be able to use the observer to watch the state of your dyno.
 
 If you are just getting started or you don't expect to use the features above, Heroku should be enough for your needs. For instance, if you are migrating an existing application running on Heroku to Phoenix, keeping a similar set of features, Elixir will perform just as well or even better than your current stack.
@@ -351,7 +351,7 @@ build:
 
 ### Set up releases and Dockerfile
 
-Now we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow [the releases docs](releases.html). At the end of the guide, there is a sample Dockerfile file you can use.
+Now we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow the [releases docs](releases.html). At the end of the guide, there is a sample Dockerfile file you can use.
 
 Once you have the image definition set up, you can push your app to heroku and you can see it starts building the image and deploy it.
 
diff --git a/guides/directory_structure.md b/guides/directory_structure.md
index 51690c150..d678f78bc 100644
--- a/guides/directory_structure.md
+++ b/guides/directory_structure.md
@@ -1,6 +1,6 @@
 # Directory structure
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
 When we use `mix phx.new` to generate a new Phoenix application, it builds a top-level directory structure like this:
 
@@ -20,7 +20,7 @@ When we use `mix phx.new` to generate a new Phoenix application, it builds a top
 
 We will go over those directories one by one:
 
-  * `_build` - a directory created by the `mix` command line tool that ships as part of Elixir that holds all compilation artifacts. As we have seen in "Up and Running", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch
+  * `_build` - a directory created by the `mix` command line tool that ships as part of Elixir that holds all compilation artifacts. As we have seen in "[Up and Running](up_and_running.html)", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch
 
   * `assets` - a directory that keeps everything related to front-end assets, such as JavaScript, CSS, static images and more. It is typically handled by the `npm` tool. Phoenix developers typically only need to run `npm install` inside the assets directory. Everything else is managed by Phoenix
 
@@ -65,7 +65,7 @@ If it is your first time with Phoenix, you don't need to worry about the details
 
 You can learn more about applications in [Elixir's official docs for Application](https://hexdocs.pm/elixir/Application.html).
 
-In the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default), you will see something like this:
+In the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default database), you will see something like this:
 
 ```elixir
 defmodule Hello.Repo do
@@ -103,13 +103,13 @@ lib/hello_web
 └── telemetry.ex
 ```
 
-All of the files which are currently in the `controllers`, `templates`, and `views` directories are there to create the "Welcome to Phoenix!" page we saw in the "Up and running" guide.
+All of the files which are currently in the `controllers`, `templates`, and `views` directories are there to create the "Welcome to Phoenix!" page we saw in the "[Up and running](up_and_running.html)" guide.
 The `channels` directory is where we will add code related to building real-time Phoenix applications.
 
 By looking at `templates` and `views` directories, we can see Phoenix provides features for handling layouts and error pages out of the box.
 
-Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses `http://localhost:4000`, the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "Request life-cycle" guide coming next.
+Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses [http://localhost:4000](http://localhost:4000), the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "[Request life-cycle](request_lifecycle.html)" guide coming next.
 
-Through _telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).
+Through _Telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).
 
 Finally, there is a `lib/hello_web/gettext.ex` file which provides internationalization through [Gettext](https://hexdocs.pm/gettext/Gettext.html). If you are not worried about internationalization, you can safely skip this file and its contents.
diff --git a/guides/ecto.md b/guides/ecto.md
index 957b5c073..b306c435b 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -1,6 +1,6 @@
 # Ecto
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
 Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have Ecto to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!
 
@@ -518,6 +518,6 @@ $ mix ecto.migrate
 
 ## Other options
 
-While Phoenix uses [the Ecto project](https://hexdocs.pm/ecto) to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) and [DETS](http://www.erlang.org/doc/man/dets.html) are key value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
+While Phoenix uses the [Ecto project](https://hexdocs.pm/ecto) to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) and [DETS](http://www.erlang.org/doc/man/dets.html) are key value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
 
 The data world is your oyster, but we won't be covering these options in these guides.
diff --git a/guides/howto/custom_error_pages.md b/guides/howto/custom_error_pages.md
index 8cf997e7b..9a935b1aa 100644
--- a/guides/howto/custom_error_pages.md
+++ b/guides/howto/custom_error_pages.md
@@ -67,35 +67,31 @@ Phoenix generates an `ErrorView` for us, but it doesn't give us a `lib/hello_web
 <!DOCTYPE html>
 <html lang="en">
   <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <meta name="description" content="">
-    <meta name="author" content="">
-
+    <meta charset="utf-8"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <title>Welcome to Phoenix!</title>
-    <link rel="stylesheet" href="/css/app.css">
+    <link rel="stylesheet" href="/css/app.css"/>
+    <script defer type="text/javascript" src="/js/app.js"></script>
   </head>
-
   <body>
-    <div class="container">
-      <div class="header">
-        <ul class="nav nav-pills pull-right">
-          <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
-        </ul>
-        <span class="logo"></span>
-      </div>
-
-      <div class="phx-hero">
+    <header>
+      <section class="container">
+        <nav role="navigation">
+          <ul>
+            <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
+          </ul>
+        </nav>
+        <a href="https://phoenixframework.org/" class="phx-logo">
+          <img src="/images/phoenix.png" alt="Phoenix Framework Logo"/>
+        </a>
+      </section>
+    </header>
+    <main role="main" class="container">
+      <section class="phx-hero">
         <p>Sorry, the page you are looking for does not exist.</p>
-      </div>
-
-      <div class="footer">
-        <p><a href="https://phoenixframework.org">phoenixframework.org</a></p>
-      </div>
-
-    </div> <!-- /container -->
-    <script src="/js/app.js"></script>
+      </section>
+    </main>
   </body>
 </html>
 ```
diff --git a/guides/plug.md b/guides/plug.md
index 8a8dd6ec9..49bda8c2a 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -1,8 +1,8 @@
 # Plug
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
 
 Plug lives at the heart of Phoenix's HTTP layer, and Phoenix puts Plug front and center. We interact with plugs at every step of the request life-cycle, and the core Phoenix components like Endpoints, Routers, and Controllers are all just Plugs internally. Let's jump in and find out just what makes Plug so special.
 
@@ -72,7 +72,7 @@ Module plugs are another type of Plug that let us define a connection transforma
 - `init/1` which initializes any arguments or options to be passed to `call/2`
 - `call/2` which carries out the connection transformation. `call/2` is just a function plug that we saw earlier
 
-To see this in action, let's write a module plug that puts the `:locale` key and value into the connection assign for downstream use in other plugs, controller actions, and our views. Put the contents above to a file named "lib/hello_web/plugs/locale.ex":
+To see this in action, let's write a module plug that puts the `:locale` key and value into the connection assign for downstream use in other plugs, controller actions, and our views. Put the contents above to a file named `lib/hello_web/plugs/locale.ex`:
 
 ```elixir
 defmodule HelloWeb.Plugs.Locale do
@@ -111,7 +111,7 @@ defmodule HelloWeb.Router do
 
 We are able to add this module plug to our browser pipeline via `plug HelloWeb.Plugs.Locale, "en"`. In the `init/1` callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple `call/2` function heads to validate the locale in the params, and fall back to "en" if there is no match.
 
-To see the assign in action, go to the layout in "lib/hello_web/templates/layout/app.html.eex" and add the following close to the main container:
+To see the assign in action, go to the layout in `lib/hello_web/templates/layout/app.html.eex` and add the following close to the main container:
 
 ```html
 <main role="main" class="container">
@@ -195,14 +195,14 @@ defmodule HelloWeb.Router do
 
 Routes are defined inside scopes and scopes may pipe through multiple pipelines. Once a route matches, Phoenix invokes all plugs defined in all pipelines associated to that route. For example, accessing "/" will pipe through the `:browser` pipeline, consequently invoking all of its plugs.
 
-As we will see in [the Routing guide](routing.html), the pipelines themselves are plugs. There we will also discuss all plugs in the `:browser` pipeline.
+As we will see in the [Routing guide](routing.html), the pipelines themselves are plugs. There we will also discuss all plugs in the `:browser` pipeline.
 
 ### Controller plugs
 
 Finally, controllers are plugs too, so we can do:
 
 ```elixir
-defmodule HelloWeb.HelloController do
+defmodule HelloWeb.PageController do
   use HelloWeb, :controller
 
   plug HelloWeb.Plugs.Locale, "en"
@@ -211,7 +211,7 @@ defmodule HelloWeb.HelloController do
 In particular, controller plugs provide a feature that allows us to execute plugs only within certain actions. For example, you can do:
 
 ```elixir
-defmodule HelloWeb.HelloController do
+defmodule HelloWeb.PageController do
   use HelloWeb, :controller
 
   plug HelloWeb.Plugs.Locale, "en" when action in [:index]
@@ -293,4 +293,4 @@ To make this all work, we converted the nested blocks of code and used `halt(con
 
 At the end of the day, by replacing the nested blocks of code with a flattened series of plug transformations, we are able to achieve the same functionality in a much more composable, clear, and reusable way.
 
-To learn more about Plugs, see the documentation for [the Plug project](https://hexdocs.pm/plug), which provides many built-in plugs and functionalities.
+To learn more about Plugs, see the documentation for the [Plug project](https://hexdocs.pm/plug), which provides many built-in plugs and functionalities.
diff --git a/guides/realtime/channels.md b/guides/realtime/channels.md
index 82f581d88..b2b2a790a 100644
--- a/guides/realtime/channels.md
+++ b/guides/realtime/channels.md
@@ -1,6 +1,6 @@
 # Channels
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
 Channels are an exciting part of Phoenix that enable soft real-time communication with and between millions of connected clients.
 
diff --git a/guides/realtime/presence.md b/guides/realtime/presence.md
index 80bdb0b1b..028faa582 100644
--- a/guides/realtime/presence.md
+++ b/guides/realtime/presence.md
@@ -1,8 +1,8 @@
 # Presence
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Channels guide](channels.html).
+> **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html).
 
 Phoenix Presence is a feature which allows you to register process information on a topic and replicate it transparently across a cluster. It's a combination of both a server-side and client-side library which makes it simple to implement. A simple use-case would be showing which users are currently online in an application.
 
diff --git a/guides/request_lifecycle.md b/guides/request_lifecycle.md
index a27ff073d..64811d726 100644
--- a/guides/request_lifecycle.md
+++ b/guides/request_lifecycle.md
@@ -1,6 +1,6 @@
 # Request life-cycle
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
 The goal of this guide is to talk about Phoenix's request life-cycle. This guide will take a practical approach where we will learn by doing: we will add two new pages to our Phoenix project and comment on how the pieces fit together along the way.
 
@@ -68,7 +68,7 @@ end
 
 ```
 
-For now, we'll ignore the pipelines and the use of `scope` here and just focus on adding a route. We will discuss those in [the Routing guide](routing.html).
+For now, we'll ignore the pipelines and the use of `scope` here and just focus on adding a route. We will discuss those in the [Routing guide](routing.html).
 
 Let's add a new route to the router that maps a `GET` request for `/hello` to the `index` action of a soon-to-be-created `HelloWeb.HelloController` inside the `scope "/" do` block of the router:
 
@@ -101,13 +101,13 @@ We'll save a discussion of `use HelloWeb, :controller` for the [Controllers Guid
 
 All controller actions take two arguments. The first is `conn`, a struct which holds a ton of data about the request. The second is `params`, which are the request parameters. Here, we are not using `params`, and we avoid compiler warnings by adding the leading `_`.
 
-The core of this action is `render(conn, "index.html")`. It tells Phoenix to render "index.html". The modules responsible for rendering are called views. By default, Phoenix views are named after the controller, so Phoenix is expecting a `HelloWeb.HelloView` to exist and handle "index.html" for us.
+The core of this action is `render(conn, "index.html")`. It tells Phoenix to render `"index.html"`. The modules responsible for rendering are called views. By default, Phoenix views are named after the controller, so Phoenix is expecting a `HelloWeb.HelloView` to exist and handle `"index.html"` for us.
 
 > Note: Using an atom as the template name also works `render(conn, :index)`. In these cases, the template will be chosen based off the Accept headers, e.g. `"index.html"` or `"index.json"`.
 
 ### A new View
 
-Phoenix views act as the presentation layer. For example, we expect the output of rendering "index.html" to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages.
+Phoenix views act as the presentation layer. For example, we expect the output of rendering `"index.html"` to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages.
 
 Let's create a new view. Create `lib/hello_web/views/hello_view.ex` and make it look like this:
 
@@ -119,7 +119,7 @@ end
 
 Now in order to add templates to this view, we simply need to add files to the `lib/hello_web/templates/hello` directory. Note the controller name (`HelloController`), the view name (`HelloView`), and the template directory (`hello`) all follow the same naming convention and are named after each other.
 
-A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, we will create a "index.html.eex" file at "lib/hello_web/templates/hello/index.html.eex". ".eex" stands for `EEx`, which is a library for embedding Elixir that ships as part of Elixir itself. Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part.
+A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, we will create a `index.html.eex` file at `lib/hello_web/templates/hello/index.html.eex`. ".eex" stands for `EEx`, which is a library for embedding Elixir that ships as part of Elixir itself. Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part.
 
 Create `lib/hello_web/templates/hello/index.html.eex` and make it look like this:
 
@@ -188,7 +188,7 @@ As we did last time, the first thing we'll do is create a new route.
 
 ### Another new Route
 
-For this exercise, we're going to re-use the `HelloController` we just created and just add a new `show` action. We'll add a line just below our last route, like this:
+For this exercise, we're going to reuse the `HelloController` we just created and just add a new `show` action. We'll add a line just below our last route, like this:
 
 ```elixir
 scope "/", HelloWeb do
diff --git a/guides/routing.md b/guides/routing.md
index 7ae6d3a88..05c82d661 100644
--- a/guides/routing.md
+++ b/guides/routing.md
@@ -1,8 +1,8 @@
 # Routing
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
 
 Routers are the main hubs of Phoenix applications. They match HTTP requests to controller actions, wire up real-time channel handlers, and define a series of pipeline transformations scoped to a set of routes.
 
diff --git a/guides/views.md b/guides/views.md
index 0339f5fc7..9268722c7 100644
--- a/guides/views.md
+++ b/guides/views.md
@@ -1,8 +1,8 @@
 # Views and templates
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
 
 A Phoenix view's main job is to render the body of the response which gets sent back to browsers and API clients. Most of the time, we use templates to build these responses, but we can also craft them by hand. We will learn how.
 
@@ -220,7 +220,7 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The "index.json" and "show.json" are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
+In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The `"index.json"` and `"show.json"` are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
 
 ```javascript
 {
@@ -245,7 +245,7 @@ And the `render/2` matching `"show.json"`:
 }
 ```
 
-This works because both "index.json" and "show.json" build themselves on top of an internal "page.json" clause.
+This works because both `index.json` and `show.json` build themselves on top of an internal `page.json` clause.
 
 The `render_many/3` function takes the data we want to respond with (`pages`), a view, and a string to pattern match on the `render/2` function defined on view. It will map over each item in `pages` and call `PageView.render("page.json", %{page: page})`. `render_one/3` follows the same signature, ultimately using the `render/2` matching `page.json` to specify what each `page` looks like.
 
@@ -278,4 +278,4 @@ end
 
 ## Error pages
 
-Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of the `ErrorView` is to handle errors in a general way, from one centralized location.  Similar to the views we built in this guide, error views can return both HTML and JSON responses. See [the Custom Error Pages How-To](custom_error_pages.html) for more information.
+Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of the `ErrorView` is to handle errors in a general way, from one centralized location.  Similar to the views we built in this guide, error views can return both HTML and JSON responses. See the [Custom Error Pages How-To](custom_error_pages.html) for more information.
diff --git a/lib/mix/tasks/phx.gen.auth.ex b/lib/mix/tasks/phx.gen.auth.ex
index a079c1672..52c27a510 100644
--- a/lib/mix/tasks/phx.gen.auth.ex
+++ b/lib/mix/tasks/phx.gen.auth.ex
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
 
   The password hashing mechanism defaults to `bcrypt` for
   Unix systems and `pbkdf2` for Windows systems. Both
-  systems use [the Comeonin interface](https://hexdocs.pm/comeonin/).
+  systems use the [Comeonin interface](https://hexdocs.pm/comeonin/).
 
   The password hashing mechanism can be overridden with the
   `--hashing-lib` option. The following values are supported:
@@ -85,7 +85,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
              table: :string, merge_with_existing_context: :boolean]
 
   @doc false
-  def run(args) do
+  def run(args, test_opts \\ []) do
     if Mix.Project.umbrella?() do
       Mix.raise("mix phx.gen.auth can only be run inside an application directory")
     end
@@ -99,16 +99,21 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
     {context, schema} = Gen.Context.build(context_args, __MODULE__)
     Gen.Context.prompt_for_code_injection(context)
 
-    # Needed so we can get the ecto adapter and ensure other
-    # libraries are loaded.
-    #
-    # As far as I can tell, everything after this must be tested with an
-    # integration test.
-    Mix.Task.run("compile")
+    if Keyword.get(test_opts, :validate_dependencies?, true) do
+      # Needed so we can get the ecto adapter and ensure other
+      # libraries are loaded.
+      Mix.Task.run("compile")
 
-    validate_required_dependencies!()
+      validate_required_dependencies!()
+    end
+
+    ecto_adapter =
+      Keyword.get_lazy(
+        test_opts,
+        :ecto_adapter,
+        fn -> get_ecto_adapter!(schema) end
+      )
 
-    ecto_adapter = get_ecto_adapter!(schema)
     migration = Migration.build(ecto_adapter)
 
     binding = [
@@ -311,7 +316,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
       {:error, :unable_to_inject} ->
         Mix.shell().info("""
 
-        Add your #{inspect(mix_dependency)} dependency to #{file_path}:
+        Add your #{mix_dependency} dependency to #{file_path}:
 
             defp deps do
               [
diff --git a/mix.exs b/mix.exs
index c513708e6..08c3f221b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -79,6 +79,7 @@ defmodule Phoenix.MixProject do
       {:telemetry_metrics, "~> 0.4", only: :docs},
 
       # Test dependencies
+      {:phx_new, path: "./installer", only: :test},
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
     ]
   end


Test patch:
diff --git a/guides/testing/testing.md b/guides/testing/testing.md
index 8d3fc2da5..7bf19ac96 100644
--- a/guides/testing/testing.md
+++ b/guides/testing/testing.md
@@ -1,6 +1,6 @@
 # Introduction to Testing
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
 Testing has become integral to the software development process, and the ability to easily write meaningful tests is an indispensable feature for any modern web framework. Phoenix takes this seriously, providing support files to make all the major components of the framework easy to test. It also generates test modules with real-world examples alongside any generated modules to help get us going.
 
diff --git a/guides/testing/testing_channels.md b/guides/testing/testing_channels.md
index 7a0a398cb..39b3fbedc 100644
--- a/guides/testing/testing_channels.md
+++ b/guides/testing/testing_channels.md
@@ -1,10 +1,10 @@
 # Testing Channels
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Introduction to Testing guide](testing.html).
+> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).
 
-> **Requirement**: This guide expects that you have gone through [the Channels guide](channels.html).
+> **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html).
 
 In the Channels guide, we saw that a "Channel" is a layered system with different components. Given this, there would be cases when writing unit tests for our Channel functions may not be enough. We may want to verify that its different moving parts are working together as we expect. This integration testing would assure us that we correctly defined our channel route, the channel module, and its callbacks; and that the lower-level layers such as the PubSub and Transport are configured correctly and are working as intended.
 
@@ -77,7 +77,7 @@ setup do
 end
 ```
 
-The `setup` block sets up a `Phoenix.Socket` based on the `UserSocket` module, which you can find at "lib/hello_web/channels/user_socket.ex". Then it says we want to subscribe and join the `RoomChannel`, accessible as "room:lobby" in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can re-use it on every test.
+The `setup` block sets up a `Phoenix.Socket` based on the `UserSocket` module, which you can find at `lib/hello_web/channels/user_socket.ex`. Then it says we want to subscribe and join the `RoomChannel`, accessible as `"room:lobby"` in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can reuse it on every test.
 
 In a nutshell, `subscribe_and_join/3` emulates the client joining a channel and subscribes the test process to the given topic. This is a necessary step since clients need to join a channel before they can send and receive events on that channel.
 
diff --git a/guides/testing/testing_contexts.md b/guides/testing/testing_contexts.md
index 632ebdec5..dd8d3e449 100644
--- a/guides/testing/testing_contexts.md
+++ b/guides/testing/testing_contexts.md
@@ -1,10 +1,10 @@
 # Testing Contexts
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Introduction to Testing guide](testing.html).
+> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).
 
-> **Requirement**: This guide expects that you have gone through [the Contexts guide](contexts.html).
+> **Requirement**: This guide expects that you have gone through the [Contexts guide](contexts.html).
 
 At the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command:
 
diff --git a/guides/testing/testing_controllers.md b/guides/testing/testing_controllers.md
index 798cf56bd..44f692fc0 100644
--- a/guides/testing/testing_controllers.md
+++ b/guides/testing/testing_controllers.md
@@ -1,8 +1,8 @@
 # Testing Controllers
 
-> **Requirement**: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.
+> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through [the Introduction to Testing guide](testing.html).
+> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).
 
 At the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command:
 
@@ -101,7 +101,7 @@ describe "create post" do
 end
 ```
 
-The first test starts with a `post/2` request. That's because once the form in the "/posts/new" page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like "/posts/ID", where ID is the identifier of the post in the database.
+The first test starts with a `post/2` request. That's because once the form in the `/posts/new` page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like `/posts/ID`, where ID is the identifier of the post in the database.
 
 We then use `redirected_params(conn)` to get the ID of the post and then match that we indeed redirected to the show action. Finally, we do request a `get` request to the page we redirected to, allowing us to verify that the post was indeed created.
 
@@ -185,7 +185,7 @@ end
   1. An exception was raised
   2. The exception has a status code equivalent to 404 (which stands for Not Found)
 
-This pretty much mimics how Phoenix handles exceptions. For example, when we access "/posts/12345" where 12345 is an ID that does not exist, we will invoke our `show` action:
+This pretty much mimics how Phoenix handles exceptions. For example, when we access `/posts/12345` where `12345` is an ID that does not exist, we will invoke our `show` action:
 
 ```elixir
 def show(conn, %{"id" => id}) do
@@ -255,7 +255,7 @@ def index(conn, _params) do
 end
 ```
 
-The action gets all articles and renders "index.json". Since we are talking about JSON, we don't have a "index.json.eex" template. Instead, the code that converts "articles" into JSON can be found directly in the ArticleView module, defined at `lib/hello_web/views/article_view.ex` like this:
+The action gets all articles and renders `index.json`. Since we are talking about JSON, we don't have a `index.json.eex` template. Instead, the code that converts `articles` into JSON can be found directly in the ArticleView module, defined at `lib/hello_web/views/article_view.ex` like this:
 
 ```elixir
 defmodule HelloWeb.ArticleView do
diff --git a/installer/test/mix_helper.exs b/installer/test/mix_helper.exs
index fc06436b9..3513896d5 100644
--- a/installer/test/mix_helper.exs
+++ b/installer/test/mix_helper.exs
@@ -98,12 +98,27 @@ defmodule MixHelper do
     end
   end
 
-  def with_generator_env(new_env, fun) do
-    Application.put_env(:phoenix, :generators, new_env)
+  def modify_file(path, function) when is_binary(path) and is_function(function, 1) do
+    path
+    |> File.read!()
+    |> function.()
+    |> write_file!(path)
+  end
+
+  defp write_file!(content, path) do
+    File.write!(path, content)
+  end
+
+  def with_generator_env(app_name \\ :phoenix, new_env, fun) do
+    config_before = Application.fetch_env(app_name, :generators)
+    Application.put_env(app_name, :generators, new_env)
     try do
       fun.()
     after
-      Application.delete_env(:phoenix, :generators)
+      case config_before do
+        {:ok, config} -> Application.put_env(app_name, :generators, config)
+        :error -> Application.delete_env(app_name, :generators)
+      end
     end
   end
 
diff --git a/test/mix/tasks/phx.gen.auth_test.exs b/test/mix/tasks/phx.gen.auth_test.exs
index 8890c8e37..1eac38a1d 100644
--- a/test/mix/tasks/phx.gen.auth_test.exs
+++ b/test/mix/tasks/phx.gen.auth_test.exs
@@ -11,8 +11,26 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
     :ok
   end
 
+  defp in_tmp_phx_project(test, additional_args \\ [], func) do
+    in_tmp(test, fn ->
+      Mix.Tasks.Phx.New.run(~w(my_app --no-install) ++ additional_args)
+      in_project(:my_app, "my_app", fn _module ->
+        func.()
+      end)
+    end)
+  end
+
+  defp in_tmp_phx_umbrella_project(test, func) do
+    in_tmp(test, fn ->
+      Mix.Tasks.Phx.New.run(~w(my_app --umbrella --no-install))
+      File.cd!("my_app_umbrella", fn ->
+        func.()
+      end)
+    end)
+  end
+
   test "invalid mix arguments", config do
-    in_tmp_project(config.test, fn ->
+    in_tmp_phx_project(config.test, fn ->
       assert_raise Mix.Error, ~r/Expected the context, "accounts", to be a valid module name.*phx\.gen\.auth/s, fn ->
         Gen.Auth.run(~w(accounts User users))
       end
@@ -25,12 +43,12 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
         Gen.Auth.run(~w(User User users))
       end
 
-      assert_raise Mix.Error, ~r/Cannot generate context Phoenix because it has the same name as the application/, fn ->
-        Gen.Auth.run(~w(Phoenix User users))
+      assert_raise Mix.Error, ~r/Cannot generate context MyApp because it has the same name as the application/, fn ->
+        Gen.Auth.run(~w(MyApp User users))
       end
 
-      assert_raise Mix.Error, ~r/Cannot generate schema Phoenix because it has the same name as the application/, fn ->
-        Gen.Auth.run(~w(Accounts Phoenix users))
+      assert_raise Mix.Error, ~r/Cannot generate schema MyApp because it has the same name as the application/, fn ->
+        Gen.Auth.run(~w(Accounts MyApp users))
       end
 
       assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
@@ -54,4 +72,804 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
       end
     end)
   end
+
+  test "generates with defaults", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "config/test.exs", fn file ->
+        assert file =~ "config :bcrypt_elixir, :log_rounds, 1"
+      end
+
+      assert_file "lib/my_app/accounts.ex"
+      assert_file "lib/my_app/accounts/user.ex"
+      assert_file "lib/my_app/accounts/user_token.ex"
+      assert_file "lib/my_app/accounts/user_notifier.ex"
+      assert_file "test/my_app/accounts_test.exs"
+      assert_file "test/support/fixtures/accounts_fixtures.ex"
+      assert_file "lib/my_app_web/controllers/user_auth.ex"
+      assert_file "test/my_app_web/controllers/user_auth_test.exs"
+      assert_file "lib/my_app_web/views/user_confirmation_view.ex"
+      assert_file "lib/my_app_web/templates/user_confirmation/new.html.eex"
+      assert_file "lib/my_app_web/controllers/user_confirmation_controller.ex"
+      assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs"
+      assert_file "lib/my_app_web/templates/layout/_user_menu.html.eex"
+      assert_file "lib/my_app_web/controllers/user_registration_controller.ex"
+      assert_file "lib/my_app_web/views/user_registration_view.ex"
+      assert_file "test/my_app_web/controllers/user_registration_controller_test.exs"
+      assert_file "lib/my_app_web/controllers/user_reset_password_controller.ex"
+      assert_file "lib/my_app_web/templates/user_reset_password/edit.html.eex"
+      assert_file "lib/my_app_web/templates/user_reset_password/new.html.eex"
+      assert_file "lib/my_app_web/views/user_reset_password_view.ex"
+      assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs"
+      assert_file "lib/my_app_web/controllers/user_session_controller.ex"
+      assert_file "lib/my_app_web/templates/user_session/new.html.eex"
+      assert_file "test/my_app_web/controllers/user_session_controller_test.exs"
+      assert_file "lib/my_app_web/views/user_session_view.ex"
+      assert_file "lib/my_app_web/controllers/user_settings_controller.ex"
+      assert_file "lib/my_app_web/templates/user_settings/edit.html.eex"
+      assert_file "lib/my_app_web/views/user_settings_view.ex"
+      assert_file "test/my_app_web/controllers/user_settings_controller_test.exs"
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users) do"
+        assert file =~ "create table(:users_tokens) do"
+      end
+
+      assert_file "mix.exs", fn file ->
+        assert file =~ ~s|{:bcrypt_elixir, "~> 2.0"},|
+      end
+
+      assert_file "lib/my_app_web/router.ex", fn file ->
+        assert file =~ "import MyAppWeb.UserAuth"
+        assert file =~ "plug :fetch_current_user"
+
+        assert file =~ """
+          ## Authentication routes
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser, :redirect_if_user_is_authenticated]
+
+            get "/users/register", UserRegistrationController, :new
+            post "/users/register", UserRegistrationController, :create
+            get "/users/log_in", UserSessionController, :new
+            post "/users/log_in", UserSessionController, :create
+            get "/users/reset_password", UserResetPasswordController, :new
+            post "/users/reset_password", UserResetPasswordController, :create
+            get "/users/reset_password/:token", UserResetPasswordController, :edit
+            put "/users/reset_password/:token", UserResetPasswordController, :update
+          end
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser, :require_authenticated_user]
+
+            get "/users/settings", UserSettingsController, :edit
+            put "/users/settings", UserSettingsController, :update
+            get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
+          end
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser]
+
+            delete "/users/log_out", UserSessionController, :delete
+            get "/users/confirm", UserConfirmationController, :new
+            post "/users/confirm", UserConfirmationController, :create
+            get "/users/confirm/:token", UserConfirmationController, :confirm
+          end
+        """
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "test/support/conn_case.ex", fn file ->
+        assert file =~ "def register_and_log_in_user(%{conn: conn})"
+        assert file =~ "def log_in_user(conn, user)"
+      end
+
+      assert_received {:mix_shell, :info, ["""
+
+      Please re-fetch your dependencies with the following command:
+
+          mix deps.get
+      """]}
+
+      assert_received {:mix_shell, :info, ["""
+
+      Remember to update your repository by running migrations:
+
+        $ mix ecto.migrate
+      """]}
+    end)
+  end
+
+  test "works with apps generated with --live", config do
+    in_tmp_phx_project(config.test, ~w(--live), fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app_web/templates/layout/root.html.leex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        refute file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+    end)
+  end
+
+  test "generates with --web option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --web warehouse),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts.ex"
+      assert_file "lib/my_app/accounts/user.ex"
+      assert_file "lib/my_app/accounts/user_token.ex"
+      assert_file "lib/my_app/accounts/user_notifier.ex"
+      assert_file "test/my_app/accounts_test.exs"
+      assert_file "test/support/fixtures/accounts_fixtures.ex"
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_auth.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserAuth do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_auth_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserAuthTest do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_confirmation_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationView do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_confirmation/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for :user, Routes.warehouse_user_confirmation_path(@conn, :create), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_confirmation_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationController do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_confirmation_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/templates/layout/_user_menu.html.eex", fn file ->
+        assert file =~ ~S|<%= link "Settings", to: Routes.warehouse_user_settings_path(@conn, :edit) %>|
+        assert file =~ ~S|<%= link "Log out", to: Routes.warehouse_user_session_path(@conn, :delete), method: :delete %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_registration_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationController do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_registration_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_registration_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_reset_password_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_reset_password/edit.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @changeset, Routes.warehouse_user_reset_password_path(@conn, :update, @token), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_reset_password/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for :user, Routes.warehouse_user_reset_password_path(@conn, :create), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_reset_password_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_reset_password_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_session_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_session/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @conn, Routes.warehouse_user_session_path(@conn, :create), [as: :user], fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Forgot your password?", to: Routes.warehouse_user_reset_password_path(@conn, :new) %>|
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_session_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_session_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionView do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_settings_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_settings/edit.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @email_changeset, Routes.warehouse_user_settings_path(@conn, :update), fn f -> %>|
+        assert file =~ ~S|<%= form_for @password_changeset, Routes.warehouse_user_settings_path(@conn, :update), fn f -> %>|
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_settings_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_settings_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsControllerTest do"
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users) do"
+        assert file =~ "create table(:users_tokens) do"
+      end
+
+      assert_file "lib/my_app_web/router.ex", fn file ->
+        assert file =~ "import MyAppWeb.Warehouse.UserAuth"
+        assert file =~ "plug :fetch_current_user"
+
+        assert file =~ """
+          ## Authentication routes
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser, :redirect_if_user_is_authenticated]
+
+            get "/users/register", UserRegistrationController, :new
+            post "/users/register", UserRegistrationController, :create
+            get "/users/log_in", UserSessionController, :new
+            post "/users/log_in", UserSessionController, :create
+            get "/users/reset_password", UserResetPasswordController, :new
+            post "/users/reset_password", UserResetPasswordController, :create
+            get "/users/reset_password/:token", UserResetPasswordController, :edit
+            put "/users/reset_password/:token", UserResetPasswordController, :update
+          end
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser, :require_authenticated_user]
+
+            get "/users/settings", UserSettingsController, :edit
+            put "/users/settings", UserSettingsController, :update
+            get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
+          end
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser]
+
+            delete "/users/log_out", UserSessionController, :delete
+            get "/users/confirm", UserConfirmationController, :new
+            post "/users/confirm", UserConfirmationController, :create
+            get "/users/confirm/:token", UserConfirmationController, :confirm
+          end
+        """
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "test/support/conn_case.ex", fn file ->
+        assert file =~ "def register_and_log_in_user(%{conn: conn})"
+        assert file =~ "def log_in_user(conn, user)"
+      end
+    end)
+  end
+
+  describe "--database option" do
+    test "when the database is postgres", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          assert file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :citext, null: false$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+      end)
+    end
+
+    test "when the database is mysql", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.MyXQL, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          refute file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :string, null: false, size: 160$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+      end)
+    end
+
+    test "when the database is mssql", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.TDS, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          refute file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :string, null: false, size: 160$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+      end)
+    end
+  end
+
+  test "supports --binary_id option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --binary-id),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts/user.ex", fn file ->
+        assert file =~ "@primary_key {:id, :binary_id, autogenerate: true}"
+        assert file =~ "@foreign_key_type :binary_id"
+      end
+
+      assert_file "lib/my_app/accounts/user_token.ex", fn file ->
+        assert file =~ "@primary_key {:id, :binary_id, autogenerate: true}"
+        assert file =~ "@foreign_key_type :binary_id"
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users, primary_key: false)"
+        assert file =~ "create table(:users_tokens, primary_key: false)"
+        assert file =~ "add :id, :binary_id, primary_key: true"
+      end
+    end)
+  end
+
+  describe "--hashing-lib option" do
+    test "when bcrypt", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib bcrypt),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:bcrypt_elixir, "~> 2.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ "config :bcrypt_elixir, :log_rounds, 1"
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Bcrypt.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+
+    test "when pbkdf2", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib pbkdf2),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:pbkdf2_elixir, "~> 1.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ "config :pbkdf2_elixir, :rounds, 1"
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Pbkdf2.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+
+    test "when argon2", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib argon2),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:argon2_elixir, "~> 2.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ """
+          config :argon2_elixir,
+            t_cost: 1,
+            m_cost: 8
+          """
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Argon2.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+  end
+
+  test "with --table option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --table my_users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts/user.ex", fn file ->
+        assert file =~ ~S|schema "my_users" do|
+      end
+
+      assert_file "lib/my_app/accounts/user_token.ex", fn file ->
+        assert file =~ ~S|schema "my_users_tokens" do|
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_my_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:my_users) do"
+        assert file =~ "create table(:my_users_tokens) do"
+      end
+    end)
+  end
+
+  describe "inside umbrella" do
+    test "without context_app generators config uses web dir", config do
+      in_tmp_phx_umbrella_project(config.test, fn ->
+        in_project(:my_app, "apps/my_app", fn _module ->
+          with_generator_env(:my_app_web, [context_app: nil], fn ->
+            Gen.Auth.run(
+              ~w(Accounts User users),
+              [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+            )
+          end)
+        end)
+
+        assert_file "apps/my_app/lib/my_app/accounts.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_token.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_notifier.ex"
+        assert_file "apps/my_app/test/my_app/accounts_test.exs"
+        assert_file "apps/my_app/test/support/fixtures/accounts_fixtures.ex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_auth.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_auth_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/views/user_confirmation_view.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_confirmation/new.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_confirmation_controller.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_confirmation_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/templates/layout/_user_menu.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_registration_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_registration_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_registration_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_reset_password_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_reset_password/edit.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_reset_password/new.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_reset_password_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_reset_password_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_session_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_session/new.html.eex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_session_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/views/user_session_view.ex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_settings_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_settings/edit.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_settings_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_settings_controller_test.exs"
+      end)
+    end
+
+    test "with context_app generators config does not use web dir", config do
+      in_tmp_phx_umbrella_project(config.test, fn ->
+        in_project(:my_app_web, "apps/my_app_web", fn _module ->
+          with_generator_env(:my_app_web, [context_app: :my_app], fn ->
+            Gen.Auth.run(
+              ~w(Accounts User users),
+              [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+            )
+          end)
+        end)
+
+        assert_file "apps/my_app/lib/my_app/accounts.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_token.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_notifier.ex"
+        assert_file "apps/my_app/test/my_app/accounts_test.exs"
+        assert_file "apps/my_app/test/support/fixtures/accounts_fixtures.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_auth.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_auth_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_confirmation_view.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_confirmation/new.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_confirmation_controller.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_confirmation_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/layout/_user_menu.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_registration_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_registration_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_registration_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_reset_password_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_reset_password/edit.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_reset_password/new.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_reset_password_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_reset_password_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_session_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_session/new.html.eex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_session_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_session_view.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_settings_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_settings/edit.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_settings_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_settings_controller_test.exs"
+      end)
+    end
+
+    test "raises with false context_app", config do
+      in_tmp_phx_umbrella_project config.test, fn ->
+        in_project(:my_app_web, "apps/my_app_web", fn _module ->
+          with_generator_env(:my_app_web, [context_app: :false], fn ->
+            assert_raise Mix.Error, ~r/no context_app configured/, fn ->
+              Gen.Auth.run(
+                ~w(Accounts User users),
+                [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+              )
+              end
+          end)
+        end)
+      end
+    end
+  end
+
+  describe "user prompts" do
+    test "when unable to inject dependencies in mix.exs", config do
+      in_tmp_phx_project(config.test, fn ->
+        File.write!("mix.exs", "")
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add your {:bcrypt_elixir, "~> 2.0"} dependency to mix.exs:
+
+            defp deps do
+              [
+                {:bcrypt_elixir, "~> 2.0"},
+                ...
+              ]
+            end
+        """]}
+      end)
+    end
+
+    test "when unable to inject authentication import into router.ex", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/router.ex", fn file ->
+          String.replace(file, "use MyAppWeb, :router", "")
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add your MyAppWeb.UserAuth import to lib/my_app_web/router.ex:
+
+            defmodule MyAppWeb.Router do
+              use MyAppWeb, :router
+
+              # Import authentication plugs
+              import MyAppWeb.UserAuth
+
+              ...
+            end
+
+        """]}
+      end)
+    end
+
+    test "when unable to inject plugs into router.ex", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/router.ex", fn file ->
+          String.replace(file, "plug :put_secure_browser_headers\n", "")
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add the :fetch_current_user plug to the :browser pipeline in lib/my_app_web/router.ex:
+
+            pipeline :browser do
+              ...
+              plug :put_secure_browser_headers
+              plug :fetch_current_user
+            end
+
+        """]}
+      end)
+    end
+
+    test "when layout file is not found", config do
+      in_tmp_phx_project(config.test, fn ->
+        File.rm!("lib/my_app_web/templates/layout/app.html.eex")
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :error, ["""
+
+        Unable to find an application layout file to inject a render
+        call for "_user_menu.html".
+
+        Missing files:
+
+          * lib/my_app_web/templates/layout/root.html.leex
+          * lib/my_app_web/templates/layout/app.html.eex
+
+        Please ensure this phoenix app was not generated with
+        --no-html. If you have changed the name of your application
+        layout file, please add the following code to it where you'd
+        like "_user_menu.html" to be rendered.
+
+            <%= render "_user_menu.html", assigns %>
+        """]}
+      end)
+    end
+
+    test "when user menu can't be injected into layout", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/templates/layout/app.html.eex", fn _file ->
+          ""
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add a render call for "_user_menu.html" to lib/my_app_web/templates/layout/app.html.eex:
+
+          <nav role="navigation">
+            <%= render "_user_menu.html", assigns %>
+          </nav>
+
+        """]}
+      end)
+    end
+  end
+
+  test "allows templates to be overridden", config do
+    in_tmp_phx_project(config.test, fn ->
+      File.mkdir_p!("priv/templates/phx.gen.auth")
+      File.write!("priv/templates/phx.gen.auth/_menu.html.eex", """
+      <ul>
+        <%%= if @current_<%= schema.singular %> do %>
+          You're logged in
+        <%% end %>
+      </ul>
+      """)
+
+      Gen.Auth.run(
+        ~w(Accounts Admin admins),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app_web/templates/layout/_admin_menu.html.eex", fn file ->
+        assert file =~ ~S|<%= if @current_admin do %>|
+            assert file =~ ~S|You're logged in|
+          end
+      end)
+  end
+
 end