Repository: phoenixframework/phoenix
Pull request #3051 (2018-09-05 18:54:38)

Update CONTRIBUTING.md
Updated mailing list link noting it's recent depreciation in favor the Phoenix Forum.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2b2d14aeb..6a0128722 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,7 +13,8 @@ Use the issues tracker for:
 
 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)
+* [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).
diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js
index 4145e8dea..109a8fd0c 100644
--- a/assets/js/phoenix.js
+++ b/assets/js/phoenix.js
@@ -185,7 +185,7 @@
  * @module phoenix
  */
 
-const global = typeof(self) !== "undefined" ? self : window
+const global = typeof self !== "undefined" ? self : window
 const VSN = "2.0.0"
 const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}
 const DEFAULT_TIMEOUT = 10000
@@ -218,7 +218,7 @@ const TRANSPORTS = {
 
 // wraps value in closure or returns closure
 let closure = (value) => {
-  if(typeof(value) === "function"){
+  if(typeof value === "function"){
     return value
   } else {
     let closure = function(){ return value }
diff --git a/assets/package.json b/assets/package.json
index a40f57790..8a310f8e3 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -23,6 +23,7 @@
     "webpack": "4.0.0",
     "webpack-cli": "^2.0.10"
   },
+  "sideEffects": false,
   "scripts": {
     "test": "mocha ./test/**/*.js --compilers js:babel-register -r jsdom-global/register",
     "docs": "documentation build js/phoenix.js -f html -o ../doc/js",
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index beba8dce3..db3cb9296 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -559,7 +559,7 @@ defmodule Phoenix.Controller do
   @doc """
   Retrieves the current layout.
   """
-  @spec layout(Plug.Conn.t) :: {atom, String.t} | false
+  @spec layout(Plug.Conn.t) :: {atom, String.t | atom} | false
   def layout(conn), do: conn.private |> Map.get(:phoenix_layout, false)
 
   @doc """
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index dc052dcab..9868c3493 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -871,7 +871,10 @@ defmodule Phoenix.Endpoint do
 
       For example:
 
-        connect_info: [:peer_data, :x_headers, :uri]
+          socket "/socket", AppWeb.UserSocket,
+            websocket: [
+              connect_info: [:peer_data, :x_headers, :uri]
+            ]
 
   ## Websocket configuration
 
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index 8df6867a4..af260cd73 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -775,9 +775,7 @@ defmodule Phoenix.Router do
     router_opts = Keyword.put(router_opts, :as, nil)
 
     quote unquote: true, bind_quoted: [path: path, plug: plug] do
-      plug = Scope.expand_alias(__MODULE__, plug)
-      path_segments = Route.forward_path_segments(path, plug, @phoenix_forwards)
-      @phoenix_forwards Map.put(@phoenix_forwards, plug, path_segments)
+      plug = Scope.register_forwards(__MODULE__, path, plug)
       unquote(add_route(:forward, :*, path, plug, plug_opts, router_opts))
     end
   end
diff --git a/lib/phoenix/router/scope.ex b/lib/phoenix/router/scope.ex
index 47e4fef38..32ca4000f 100644
--- a/lib/phoenix/router/scope.ex
+++ b/lib/phoenix/router/scope.ex
@@ -1,5 +1,5 @@
 defmodule Phoenix.Router.Scope do
-  alias Phoenix.Router.Scope
+  alias Phoenix.Router.{Scope, Route}
   @moduledoc false
 
   @stack :phoenix_router_scopes
@@ -117,9 +117,22 @@ defmodule Phoenix.Router.Scope do
   def inside_scope?(module), do: length(get_stack(module)) > 1
 
   @doc """
-  Expands alias within scoped block.
+  Add a forward to the router.
   """
-  def expand_alias(module, alias) do
+  def register_forwards(module, path, plug) when is_atom(plug) do
+    plug = expand_alias(module, plug)
+    phoenix_forwards = Module.get_attribute(module, :phoenix_forwards)
+    path_segments = Route.forward_path_segments(path, plug, phoenix_forwards)
+    phoenix_forwards = Map.put(phoenix_forwards, plug, path_segments)
+    Module.put_attribute(module, :phoenix_forwards, phoenix_forwards)
+    plug
+  end
+
+  def register_forwards(_, _, plug) do
+    raise ArgumentError, "forward expects a module as the second argument, #{inspect plug} given"
+  end
+
+  defp expand_alias(module, alias) do
     if inside_scope?(module) do
       module
       |> get_stack()
diff --git a/lib/phoenix/view.ex b/lib/phoenix/view.ex
index e52636abd..f3fb09a23 100644
--- a/lib/phoenix/view.ex
+++ b/lib/phoenix/view.ex
@@ -241,23 +241,32 @@ defmodule Phoenix.View do
     |> render_within(module, template)
   end
 
-  defp render_within({{layout_mod, layout_tpl}, assigns}, inner_mod, inner_tpl) do
-    assigns = Map.merge(assigns, %{view_module: inner_mod,
-                                   view_template: inner_tpl})
-
-    render_layout(layout_mod, layout_tpl, assigns)
-  end
-
   defp render_within({false, assigns}, module, template) do
     assigns = Map.merge(assigns, %{view_module: module,
                                    view_template: template})
     module.render(template, assigns)
   end
 
-  defp render_layout(layout_mod, layout_tpl, assigns) do
+  defp render_within({layout, assigns}, inner_mod, inner_tpl) do
+    assigns = Map.merge(assigns, %{view_module: inner_mod, view_template: inner_tpl})
+    render_layout(layout, assigns)
+  end
+
+  defp render_layout({layout_mod, layout_tpl}, assigns)
+       when is_atom(layout_mod) and is_binary(layout_tpl) do
     layout_mod.render(layout_tpl, assigns)
   end
 
+  defp render_layout(layout, _assigns) do
+    raise ArgumentError, """
+    invalid value for reserved key :layout in View.render/3 assigns
+
+    :layout accepts a tuple of the form {LayoutModule, "template.extension"}
+
+    got: #{inspect(layout)}
+    """
+  end
+
   @doc """
   Renders a template only if it exists.
 
diff --git a/mix.exs b/mix.exs
index ab890b391..e46037ab6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -144,6 +144,7 @@ defmodule Phoenix.MixProject do
     # Phoenix
     # Phoenix.Channel
     # Phoenix.Controller
+    # Phoenix.Endpoint
     # Phoenix.Naming
     # Phoenix.Param
     # Phoenix.Presence
@@ -152,20 +153,19 @@ defmodule Phoenix.MixProject do
     # Phoenix.View
 
     [
-      "Endpoint And Plugs": [
+      "Testing": [
+        Phoenix.ChannelTest,
+        Phoenix.ConnTest,
+      ],
+
+      "Adapters and Plugs": [
         Phoenix.CodeReloader,
-        Phoenix.Endpoint,
         Phoenix.Endpoint.CowboyAdapter,
         Phoenix.Endpoint.Cowboy2Adapter,
         Phoenix.Logger,
       ],
 
-      "Testing": [
-        Phoenix.ChannelTest,
-        Phoenix.ConnTest,
-      ],
-
-      "Socket And Transport": [
+      "Socket and Transport": [
         Phoenix.Socket,
         Phoenix.Socket.Broadcast,
         Phoenix.Socket.Message,


Test patch:
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index f75391f0d..8a8db80a5 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -89,7 +89,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       end
       assert_file web_path(@app, "lib/#{@app}_web.ex"), fn file ->
         assert file =~ "defmodule PhxUmbWeb do"
-        assert file =~ "use Phoenix.View, root: \"lib/phx_umb_web/templates\""
+        assert file =~ ~r/use Phoenix.View,\s+root: "lib\/phx_umb_web\/templates"/
       end
       assert_file web_path(@app, "lib/#{@app}_web/endpoint.ex"), ~r/defmodule PhxUmbWeb.Endpoint do/
       assert_file web_path(@app, "test/#{@app}_web/controllers/page_controller_test.exs")
@@ -119,7 +119,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file web_path(@app, "assets/webpack.config.js"), "js/app.js"
       assert_file web_path(@app, "assets/.babelrc"), "env"
       assert_file web_path(@app, "config/dev.exs"), fn file ->
-        assert file =~ "watchers: [node:"
+        assert file =~ ~r/watchers: \[\s+node:/
         assert file =~ "lib/#{@app}_web/views/.*(ex)"
         assert file =~ "lib/#{@app}_web/templates/.*(eex)"
       end
@@ -231,7 +231,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file web_path(@app, "config/test.exs"), &refute(&1 =~ config)
       assert_file web_path(@app, "config/prod.secret.exs"), &refute(&1 =~ config)
 
-      assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/Supervisor.start_link\(\[\]/
+      assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/Supervisor.start_link\(/
 
       # No HTML
       assert File.exists?(web_path(@app, "test/#{@app}_web/controllers"))
@@ -493,7 +493,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
         assert_file "another/.gitignore",  ~r/\n$/
         assert_file "another/assets/webpack.config.js", "js/app.js"
         assert_file "another/assets/.babelrc", "env"
-        assert_file "another/config/dev.exs", "watchers: [node:"
+        assert_file "another/config/dev.exs", ~r/watchers: \[\s+node:/
         assert_file "another/assets/static/favicon.ico"
         assert_file "another/assets/static/images/phoenix.png"
         assert_file "another/assets/css/app.css"
diff --git a/test/phoenix/router/forward_test.exs b/test/phoenix/router/forward_test.exs
index 84869b3c5..58c44de8e 100644
--- a/test/phoenix/router/forward_test.exs
+++ b/test/phoenix/router/forward_test.exs
@@ -142,4 +142,16 @@ defmodule Phoenix.Router.ForwardTest do
     assert conn.resp_body == "health"
     assert conn.private[ApiRouter] == {[], %{Phoenix.Test.HealthController => []}}
   end
+
+  test "forwards raises if using the plug to arguments" do
+    error_message = ~r/expects a module/
+    assert_raise(ArgumentError, error_message, fn ->
+      defmodule BrokenRouter do
+        use Phoenix.Router
+        scope "/" do
+          forward "/health", to: HealthController
+        end
+      end
+    end)
+  end
 end
diff --git a/test/phoenix/view_test.exs b/test/phoenix/view_test.exs
index 02b4404ec..66ba16fc4 100644
--- a/test/phoenix/view_test.exs
+++ b/test/phoenix/view_test.exs
@@ -64,6 +64,16 @@ defmodule Phoenix.ViewTest do
                       [["" | "<div>Show! "] | "Hello world"] | "</div>\n"] | "</html>\n"]}
   end
 
+  test "validates explicitly passed layout" do
+    assert_raise ArgumentError, fn ->
+      render(MyApp.View, "show.html",
+        title: "Test",
+        message: "Hello world",
+        layout: {"not a layout", "app.html"}
+      )
+    end
+  end
+
   test "converts assigns to maps and removes :layout" do
     html = render_to_iodata(MyApp.UserView, "edit.html",
       title: "Test",