@@ -1817,6 +1817,150 @@ defmodule Phoenix.LiveViewTest do
18171817 end
18181818 end
18191819
1820+ @ doc """
1821+ Asserts the LiveView process will receive a message within `timeout`.
1822+ The default `timeout` is [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html#configure/1)'s
1823+ `assert_receive_timeout` (100 ms).
1824+
1825+ This requires Erlang/OTP 27 or later.
1826+
1827+ A zero-arity setup function may be passed as the third argument. It is
1828+ invoked after tracing is enabled and before waiting for the message. To
1829+ customize the timeout, pass it as the fourth argument.
1830+
1831+ ## Examples
1832+
1833+ assert_will_receive view, {:test_message, num}
1834+ assert num == 1
1835+
1836+ assert_will_receive view, {:test_message, num}, fn ->
1837+ send(view.pid, {:test_message, 1})
1838+ end
1839+
1840+ assert_will_receive view, {:test_message, num}, fn ->
1841+ send(view.pid, {:test_message, 1})
1842+ end, 1000
1843+ """
1844+ defmacro assert_will_receive (
1845+ view ,
1846+ pattern ,
1847+ fun \\ quote ( do: fn -> nil end ) ,
1848+ timeout \\ Application . fetch_env! ( :ex_unit , :assert_receive_timeout )
1849+ ) do
1850+ { pattern , guard } = extract_guard ( pattern )
1851+ ref = Macro . unique_var ( :ref , __MODULE__ )
1852+ tracer = Macro . unique_var ( :tracer , __MODULE__ )
1853+ received = Macro . unique_var ( :received , __MODULE__ )
1854+ message = Macro . unique_var ( :message , __MODULE__ )
1855+ generated_pattern = mark_vars_as_generated ( pattern )
1856+ generated_guard = guard && mark_vars_as_generated ( guard )
1857+
1858+ trace_pattern =
1859+ quote do
1860+ { ^ unquote ( ref ) , unquote ( generated_pattern ) }
1861+ end
1862+ |> apply_guard ( generated_guard )
1863+
1864+ quote do
1865+ { unquote ( ref ) , unquote ( tracer ) } =
1866+ Phoenix.LiveViewTest . __start_assert_will_receive__ ( unquote ( view ) )
1867+
1868+ unquote ( received ) =
1869+ try do
1870+ unquote ( fun ) . ( )
1871+ assert_receive unquote ( trace_pattern ) , unquote ( timeout )
1872+ after
1873+ Phoenix.LiveViewTest . __stop_assert_will_receive__ ( unquote ( ref ) , unquote ( tracer ) )
1874+ end
1875+
1876+ { ^ unquote ( ref ) , unquote ( message ) } = unquote ( received )
1877+ unquote ( pattern ) = unquote ( message )
1878+ unquote ( message )
1879+ end
1880+ end
1881+
1882+ defp extract_guard ( { :when , _ , [ pattern , guard ] } ) , do: { pattern , guard }
1883+ defp extract_guard ( pattern ) , do: { pattern , nil }
1884+
1885+ defp apply_guard ( pattern , nil ) , do: pattern
1886+ defp apply_guard ( pattern , guard ) , do: { :when , [ ] , [ pattern , guard ] }
1887+
1888+ defp mark_vars_as_generated ( pattern ) do
1889+ Macro . prewalk ( pattern , fn
1890+ { name , meta , context } when is_atom ( name ) and is_atom ( context ) ->
1891+ { name , [ generated: true ] ++ meta , context }
1892+
1893+ other ->
1894+ other
1895+ end )
1896+ end
1897+
1898+ @ doc false
1899+ def __start_assert_will_receive__ ( % View { pid: pid } ) do
1900+ if not ( Code . ensure_loaded? ( :trace ) and function_exported? ( :trace , :session_create , 3 ) ) do
1901+ raise "assert_will_receive requires Erlang/OTP 27 or later"
1902+ end
1903+
1904+ ref = make_ref ( )
1905+ parent = self ( )
1906+ tracer = spawn ( fn -> assert_will_receive_loop ( parent , ref ) end )
1907+ session = :trace . session_create ( :phoenix_live_view_test , tracer , [ ] )
1908+
1909+ try do
1910+ :trace . process ( session , pid , true , [ :receive ] )
1911+ { ref , { tracer , session } }
1912+ rescue
1913+ exception ->
1914+ __stop_assert_will_receive__ ( ref , { tracer , session } )
1915+ reraise exception , __STACKTRACE__
1916+ catch
1917+ kind , reason ->
1918+ __stop_assert_will_receive__ ( ref , { tracer , session } )
1919+ :erlang . raise ( kind , reason , __STACKTRACE__ )
1920+ end
1921+ end
1922+
1923+ @ doc false
1924+ def __stop_assert_will_receive__ ( ref , { tracer , session } ) do
1925+ :trace . session_destroy ( session )
1926+
1927+ monitor_ref = Process . monitor ( tracer )
1928+ send ( tracer , { ref , :stop } )
1929+
1930+ receive do
1931+ { :DOWN , ^ monitor_ref , :process , ^ tracer , _reason } ->
1932+ :ok
1933+ after
1934+ 5_000 ->
1935+ Process . exit ( tracer , :kill )
1936+
1937+ receive do
1938+ { :DOWN , ^ monitor_ref , :process , ^ tracer , _reason } -> :ok
1939+ end
1940+ end
1941+
1942+ flush_assert_will_receive ( ref )
1943+ end
1944+
1945+ defp assert_will_receive_loop ( parent , ref ) do
1946+ receive do
1947+ { :trace , _pid , :receive , message } ->
1948+ send ( parent , { ref , message } )
1949+ assert_will_receive_loop ( parent , ref )
1950+
1951+ { ^ ref , :stop } ->
1952+ :ok
1953+ end
1954+ end
1955+
1956+ defp flush_assert_will_receive ( ref ) do
1957+ receive do
1958+ { ^ ref , _message } -> flush_assert_will_receive ( ref )
1959+ after
1960+ 0 -> :ok
1961+ end
1962+ end
1963+
18201964 @ doc """
18211965 Follows the redirect from a `render_*` action or an `{:error, redirect}`
18221966 tuple.
0 commit comments