2021-07-16 06:09:15 +12:00
defmodule AshPhoenix.FormTest do
use ExUnit.Case
2022-07-08 14:22:30 +12:00
import ExUnit.CaptureLog
2021-07-16 06:09:15 +12:00
alias AshPhoenix.Form
2024-03-28 16:44:54 +13:00
alias AshPhoenix.Test . { Domain , Artist , Author , Comment , Post , PostWithDefault }
2021-07-16 06:09:15 +12:00
alias Phoenix.HTML.FormData
2024-01-22 06:59:55 +13:00
defp form_for ( form , _ ) do
Phoenix.HTML.FormData . to_form ( form , [ ] )
end
defp inputs_for ( form , key ) do
form [ key ] . value
end
2022-05-21 02:59:23 +12:00
describe " validate_opts " do
2022-05-22 18:08:43 +12:00
test " errors are not set on the parent and list child form " do
2022-05-21 02:59:23 +12:00
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-05-21 02:59:23 +12:00
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
)
|> Form . add_form ( [ :comments ] , validate_opts : [ errors : false ] )
|> form_for ( " action " )
assert form . errors == [ ]
assert Form . errors ( form . source , for_path : [ :comments , 0 ] ) == [ ]
end
2022-05-22 18:08:43 +12:00
2022-07-08 14:22:30 +12:00
test " unknown errors produce warnings " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-07-08 14:22:30 +12:00
params : %{ " text " = > " bar " } ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create_with_unknown_error
]
]
)
|> Form . add_form ( [ :comments ] , params : %{ " text " = > " foo " } , validate_opts : [ errors : true ] )
|> form_for ( " action " )
assert capture_log ( fn ->
Form . errors ( form . source , for_path : [ :comments , 0 ] ) == [ ]
end ) =~
" Unhandled error in form submission for AshPhoenix.Test.Comment.create_with_unknown_error "
end
2024-06-11 02:40:33 +12:00
test " empty atom field " do
2024-06-25 14:16:39 +12:00
Post
|> Form . for_create ( :create ,
domain : Domain ,
params : %{ }
)
|> Form . submit! (
params : %{ " inline_atom_field " = > " " , " custom_atom_field " = > " " , " text " = > " text " }
)
2024-06-11 02:40:33 +12:00
end
2022-08-17 14:16:47 +12:00
test " update_form marks touched by default " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-08-17 14:16:47 +12:00
params : %{ " text " = > " bar " } ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create_with_unknown_error
]
]
)
|> Form . add_form ( [ :comments ] , params : %{ " text " = > " foo " } )
|> Form . update_form ( [ :comments , 0 ] , & &1 )
assert MapSet . member? ( form . touched_forms , " comments " )
end
2022-05-22 18:08:43 +12:00
test " errors are not set on the parent and single child form " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :single ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( [ :post ] , validate_opts : [ errors : false ] )
|> form_for ( " action " )
assert form . errors == [ ]
assert Form . errors ( form . source , for_path : [ :post ] ) == [ ]
end
2022-05-21 02:59:23 +12:00
end
2022-12-21 16:18:55 +13:00
describe " clear_value/1 " do
test " it clears attributes " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ " text " = > " text " } )
assert Form . value ( form , :text ) == " text "
2023-01-06 06:39:42 +13:00
assert form . source . attributes == %{ text : " text " }
assert form . source . params == %{ " text " = > " text " }
assert form . params == %{ " text " = > " text " }
2022-12-21 16:18:55 +13:00
form = Form . clear_value ( form , :text )
2023-01-06 06:39:42 +13:00
2022-12-21 16:18:55 +13:00
assert Form . value ( form , :text ) == nil
2023-01-06 06:39:42 +13:00
assert form . source . attributes == %{ }
assert form . source . params == %{ }
assert form . params == %{ }
end
test " it clears arguments " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ " excerpt " = > " text " } )
assert Form . value ( form , :excerpt ) == " text "
assert form . source . arguments == %{ excerpt : " text " }
assert form . source . params == %{ " excerpt " = > " text " }
assert form . params == %{ " excerpt " = > " text " }
form = Form . clear_value ( form , :excerpt )
assert Form . value ( form , :excerpt ) == nil
assert form . source . arguments == %{ }
assert form . source . params == %{ }
assert form . params == %{ }
2022-12-21 16:18:55 +13:00
end
2023-01-06 07:51:03 +13:00
test " it clears multiple fields " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ " excerpt " = > " text " , " text " = > " text " } )
assert Form . value ( form , :excerpt ) == " text "
assert Form . value ( form , :text ) == " text "
assert form . source . attributes == %{ text : " text " }
assert form . source . arguments == %{ excerpt : " text " }
assert form . source . params == %{ " excerpt " = > " text " , " text " = > " text " }
assert form . params == %{ " excerpt " = > " text " , " text " = > " text " }
form = Form . clear_value ( form , [ :excerpt , :text ] )
assert Form . value ( form , :text ) == nil
assert Form . value ( form , :excerpt ) == nil
assert form . params == %{ }
assert form . source . arguments == %{ }
assert form . source . attributes == %{ }
assert form . source . params == %{ }
end
2022-12-21 16:18:55 +13:00
end
2023-09-28 11:56:55 +13:00
describe " validations and form values " do
test " validation errors don't clear fields " do
form =
AshPhoenix.Test.User
|> AshPhoenix.Form . for_create ( :register )
|> AshPhoenix.Form . validate ( %{ " password " = > " f " } )
|> AshPhoenix.Form . validate ( %{ " password " = > " fo " } )
|> AshPhoenix.Form . validate ( %{ " password " = > " fo " , " password_confirmation " = > " foo " } )
assert AshPhoenix.Form . value ( form , :password ) == " fo "
end
2023-11-28 13:48:02 +13:00
2024-03-12 05:35:44 +13:00
test " form values are retrieved casted for un-changing arguments " do
form =
AshPhoenix.Test.User
|> AshPhoenix.Form . for_create ( :register )
|> AshPhoenix.Form . validate ( %{ " password " = > " f " } )
|> AshPhoenix.Form . validate ( %{ " password " = > :f } )
assert AshPhoenix.Form . value ( form , :password ) == " f "
end
2023-11-28 13:48:02 +13:00
test " lists with invalid values return those invalid values when getting them " do
form =
Post
2024-03-28 16:44:54 +13:00
|> Form . for_create ( :create_author_required , domain : Domain , forms : [ auto? : true ] )
2023-11-28 13:48:02 +13:00
|> Form . validate ( %{ " list_of_ints " = > %{ " 0 " = > %{ " map " = > " of stuff " } } } )
2024-03-12 05:35:44 +13:00
assert AshPhoenix.Form . value ( form , :list_of_ints ) == [ %{ " map " = > " of stuff " } ]
2023-11-28 13:48:02 +13:00
end
2023-09-28 11:56:55 +13:00
end
2024-01-15 08:17:56 +13:00
describe " has_form? " do
test " checks for the existence of a list of forms " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2024-01-15 08:17:56 +13:00
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
)
|> Form . add_form ( [ :comments ] )
# assert Form.has_form?(form, [:comments])
assert Form . has_form? ( form , [ :comments , 0 ] )
assert Form . has_form? ( form , " form[comments][0] " )
refute Form . has_form? ( form , [ :comments , 1 ] )
refute Form . has_form? ( form , " form[comments][1] " )
end
test " checks for the existence of a single form " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :single ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( [ :post ] )
assert Form . has_form? ( form , [ :post ] )
assert Form . has_form? ( form , " form[post] " )
refute Form . has_form? ( form , [ :unknown ] )
refute Form . has_form? ( form , " form[unknown] " )
end
end
2021-07-16 06:09:15 +12:00
describe " form_for fields " do
test " it should show simple field values " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ " text " = > " text " } )
|> form_for ( " action " )
assert FormData . input_value ( form . source , form , :text ) == " text "
end
2021-07-19 04:28:47 +12:00
test " it sets the default id of a form " do
assert Form . for_create ( Post , :create ) . id == " form "
assert Form . for_create ( Post , :create , as : " post " ) . id == " post "
end
2021-07-16 06:09:15 +12:00
end
2022-05-10 07:45:57 +12:00
test " a read will validate attributes " do
form =
Post
|> Form . for_read ( :read )
|> Form . validate ( %{ " text " = > [ 1 , 2 , 3 ] } )
|> form_for ( " action " )
assert form . errors [ :text ] == { " is invalid " , [ ] }
end
2021-11-13 16:10:33 +13:00
test " validation errors are attached to fields " do
2024-03-28 16:44:54 +13:00
form = Form . for_create ( PostWithDefault , :create , domain : Domain )
2021-11-13 15:22:55 +13:00
form = AshPhoenix.Form . validate ( form , %{ " text " = > " " } , errors : form . submitted_once? )
{ :error , form } = Form . submit ( form , params : %{ " text " = > " " } )
2021-11-11 09:11:22 +13:00
assert %{ errors : [ text : { " is required " , [ ] } ] } = form_for ( form , " foo " )
assert form . valid? == false
end
2023-02-21 12:32:04 +13:00
test " blank form values unset - helps support dead view forms " do
form =
2023-07-19 16:35:29 +12:00
Form . for_create ( PostWithDefault , :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2023-07-19 16:35:29 +12:00
exclude_fields_if_empty : [ :text , :title ]
)
2023-02-21 12:32:04 +13:00
{ :ok , post } = Form . submit ( form , params : %{ " title " = > " " , " text " = > " bar " } )
assert post . text == " bar "
assert post . title == nil
end
test " blank nested form values unset - helps support dead view forms " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2023-02-21 12:32:04 +13:00
forms : [
post : [
resource : PostWithDefault ,
create_action : :create
]
] ,
exclude_fields_if_empty : [ post : [ :title , :description ] ]
)
|> Form . add_form ( :post )
{ :ok , comment } =
Form . submit ( form ,
params : %{ " text " = > " comment " , " post " = > %{ " title " = > " " , " text " = > " bar " } }
)
post = comment . post
assert post . text == " bar "
assert post . title == nil
end
2022-10-10 16:22:13 +13:00
test " phoenix forms are accepted as input in some cases " do
2024-03-28 16:44:54 +13:00
form = Form . for_create ( PostWithDefault , :create , domain : Domain )
2022-10-10 16:22:13 +13:00
form = AshPhoenix.Form . validate ( form , %{ " text " = > " " } , errors : form . submitted_once? )
form = form_for ( form , " foo " )
# This simply shouldn't raise
AshPhoenix.Form . params ( form )
end
2023-03-06 11:23:43 +13:00
test " a phoenix form is returned in cases where a phoenix form is passed in " do
2024-03-28 16:44:54 +13:00
form = Form . for_create ( PostWithDefault , :create , domain : Domain )
2022-10-10 16:22:13 +13:00
form = AshPhoenix.Form . validate ( form , %{ " text " = > " " } , errors : form . submitted_once? )
form = form_for ( form , " foo " )
2023-03-06 11:23:43 +13:00
assert % Phoenix.HTML.Form { } = AshPhoenix.Form . validate ( form , %{ } )
2022-10-10 16:22:13 +13:00
end
2022-09-21 17:24:52 +12:00
test " it supports forms with data and a `type: :append_and_remove` " do
2022-03-22 15:24:19 +13:00
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2022-03-22 15:24:19 +13:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2022-03-22 15:24:19 +13:00
form =
post
|> Form . for_update ( :update_with_replace ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-03-22 15:24:19 +13:00
forms : [
comments : [
read_resource : Comment ,
type : :list ,
read_action : :read ,
data : [ comment ]
]
]
)
2022-03-22 15:34:50 +13:00
assert [ comment_form ] = inputs_for ( form_for ( form , " blah " ) , :comments )
2022-03-22 15:24:19 +13:00
assert Phoenix.HTML.Form . input_value ( comment_form , :text ) == " comment "
2022-03-22 15:42:34 +13:00
form = Form . validate ( form , %{ " comments " = > [ %{ " id " = > comment . id } ] } )
2022-10-07 15:33:36 +13:00
comment_id = comment . id
assert %{ " comments " = > [ %{ " id " = > ^ comment_id } ] } = Form . params ( form )
2022-03-22 15:24:19 +13:00
end
2022-06-29 14:22:38 +12:00
test " ignoring a form filters it from the parameters " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2022-06-29 14:22:38 +12:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2022-06-29 14:22:38 +12:00
form =
post
|> Form . for_update ( :update_with_replace ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-06-29 14:22:38 +12:00
forms : [
comments : [
read_resource : Comment ,
type : :list ,
read_action : :read ,
data : [ comment ]
]
]
)
assert [ comment_form ] = inputs_for ( form_for ( form , " blah " ) , :comments )
assert Phoenix.HTML.Form . input_value ( comment_form , :text ) == " comment "
form = Form . validate ( form , %{ " comments " = > [ %{ " id " = > comment . id , " _ignore " = > " true " } ] } )
2022-10-07 15:33:36 +13:00
assert %{ " comments " = > [ ] } = Form . params ( form )
2022-06-29 14:22:38 +12:00
form = Form . validate ( form , %{ " comments " = > [ %{ " id " = > comment . id , " _ignore " = > " false " } ] } )
2022-10-07 15:33:36 +13:00
comment_id = comment . id
assert %{ " comments " = > [ %{ " id " = > ^ comment_id , " _ignore " = > " false " } ] } = Form . params ( form )
2022-06-29 14:28:44 +12:00
form = Form . validate ( form , %{ " comments " = > [ %{ " id " = > comment . id , " _ignore " = > " true " } ] } )
2022-10-07 15:33:36 +13:00
assert %{ " comments " = > [ ] } = Form . params ( form )
2022-06-29 14:22:38 +12:00
end
2021-11-07 05:15:50 +13:00
describe " the .changed? field is updated as data changes " do
2021-11-07 07:12:53 +13:00
test " it is false for a create form with no changes " do
2021-11-07 05:15:50 +13:00
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ } )
2021-11-07 07:12:53 +13:00
refute form . changed?
2021-11-07 05:15:50 +13:00
end
test " it is false by default for update forms " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-11-07 05:15:50 +13:00
form =
post
|> Form . for_update ( :update )
|> Form . validate ( %{ } )
refute form . changed?
end
test " it is true when a change is made " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-11-07 05:15:50 +13:00
form =
post
|> Form . for_update ( :update )
|> Form . validate ( %{ text : " post1 " } )
assert form . changed?
end
test " it goes back to false if the change is unmade " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-11-07 05:15:50 +13:00
form =
post
|> Form . for_update ( :update )
|> Form . validate ( %{ text : " post1 " } )
assert form . changed?
form =
form
|> Form . validate ( %{ text : " post " } )
refute form . changed?
end
2021-11-07 07:12:53 +13:00
test " adding a form causes changed? to be true on the root form, but not the nested form " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-11-07 07:12:53 +13:00
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
2021-11-08 22:47:12 +13:00
|> Form . add_form ( :post )
2021-11-07 07:12:53 +13:00
assert form . changed?
refute form . forms [ :post ] . changed?
end
test " removing a form that was there prior marks the form as changed " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-11-07 07:12:53 +13:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2021-11-07 07:12:53 +13:00
# Check the persisted post.comments count after create
2024-03-28 16:44:54 +13:00
post = Post |> Ash . get! ( post . id ) |> Ash . load! ( :comments )
2021-11-07 07:12:53 +13:00
assert Enum . count ( post . comments ) == 1
form =
post
|> Form . for_update ( :update ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-11-07 07:12:53 +13:00
forms : [
comments : [
resource : Comment ,
type : :list ,
data : [ comment ] ,
create_action : :create ,
update_action : :update
]
]
)
refute form . changed?
form = Form . remove_form ( form , [ :comments , 0 ] )
assert form . changed?
end
2022-10-07 15:33:36 +13:00
test " generated forms have default values even with no server round-trips " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2022-10-07 15:33:36 +13:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-10-07 15:33:36 +13:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2022-10-07 15:33:36 +13:00
# Check the persisted post.comments count after create
2024-03-28 16:44:54 +13:00
post = Post |> Ash . get! ( post . id ) |> Ash . load! ( :comments )
2022-10-07 15:33:36 +13:00
assert Enum . count ( post . comments ) == 1
form =
post
|> Form . for_update ( :update ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-10-07 15:33:36 +13:00
forms : [
comments : [
resource : Comment ,
type : :list ,
data : [ comment ] ,
create_action : :create ,
update_action : :update
]
]
)
form = AshPhoenix.Form . add_form ( form , :comments )
2024-03-28 16:44:54 +13:00
assert AshPhoenix.Form . params ( form , hidden? : true ) == %{
2022-10-07 15:33:36 +13:00
" _form_type " = > " update " ,
" _touched " = > " comments " ,
" comments " = > [
%{
" _form_type " = > " update " ,
" _touched " = > " _form_type,id " ,
" id " = > post . comments |> Enum . at ( 0 ) |> Map . get ( :id )
} ,
%{ " _form_type " = > " create " , " _touched " = > " _form_type " }
] ,
" id " = > post . id
}
end
2022-05-17 14:37:36 +12:00
test " removing a non-existant form should not change touched_forms " do
form =
Post
2024-03-28 16:44:54 +13:00
|> Form . for_create ( :create , domain : Domain , forms : [ auto? : true ] )
2022-05-17 14:37:36 +12:00
|> AshPhoenix.Form . remove_form ( [ :author ] )
assert MapSet . member? ( form . touched_forms , " author " ) == false
end
2021-11-07 07:12:53 +13:00
test " removing a form that was added does not mark the form as changed " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-11-07 07:12:53 +13:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2021-11-07 07:12:53 +13:00
# Check the persisted post.comments count after create
2024-03-28 16:44:54 +13:00
post = Post |> Ash . get! ( post . id ) |> Ash . load! ( :comments )
2021-11-07 07:12:53 +13:00
assert Enum . count ( post . comments ) == 1
form =
post
|> Form . for_update ( :update ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-11-07 07:12:53 +13:00
forms : [
comments : [
resource : Comment ,
type : :list ,
data : [ comment ] ,
create_action : :create ,
update_action : :update
]
]
)
refute form . changed?
form = Form . add_form ( form , [ :comments ] )
assert form . changed?
form = Form . remove_form ( form , [ :comments , 1 ] )
refute form . changed?
end
2021-11-07 05:15:50 +13:00
end
2021-07-16 06:09:15 +12:00
describe " errors " do
test " errors are not set on the form without validating " do
form =
Post
|> Form . for_create ( :create )
|> form_for ( " action " )
assert form . errors == [ ]
end
test " errors are set on the form according to changeset errors on validate " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ } )
|> form_for ( " action " )
assert form . errors == [ { :text , { " is required " , [ ] } } ]
end
test " nested errors are set on the appropriate form after submit " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-07-16 06:09:15 +12:00
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " text " = > " text " , " post " = > %{ } } )
2021-07-19 11:20:25 +12:00
|> Form . submit ( force? : true )
2021-07-16 06:09:15 +12:00
|> elem ( 1 )
|> form_for ( " action " )
assert form . errors == [ ]
2021-07-17 10:23:07 +12:00
assert hd ( inputs_for ( form , :post ) ) . errors == [ { :text , { " is required " , [ ] } } ]
2021-07-16 06:09:15 +12:00
end
2021-08-02 05:18:37 +12:00
test " relationship source data is retained, so that it can be properly removed " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-08-02 05:18:37 +12:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2021-08-02 05:18:37 +12:00
2024-03-28 16:44:54 +13:00
comment = Comment |> Ash . get! ( comment . id )
2021-08-02 05:18:37 +12:00
comment
2024-03-28 16:44:54 +13:00
|> Ash . load! ( :post )
2021-08-02 05:18:37 +12:00
|> Form . for_update ( :update ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-08-02 05:18:37 +12:00
forms : [
auto? : true
]
)
|> Form . remove_form ( [ :post ] )
|> Form . submit! ( params : %{ " text " = > " text " , " post " = > %{ " text " = > " new_post " } } )
2024-03-28 16:44:54 +13:00
assert [ %{ text : " new_post " } ] = Ash . read! ( Post )
2021-08-02 05:18:37 +12:00
end
2021-07-17 11:58:14 +12:00
test " nested errors are set on the appropriate form after submit, even if no submit actually happens " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-07-17 11:58:14 +12:00
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
2021-07-19 11:20:25 +12:00
|> Form . submit ( params : %{ " text " = > " text " , " post " = > %{ } } )
2021-07-17 11:58:14 +12:00
|> elem ( 1 )
|> form_for ( " action " )
assert form . errors == [ ]
assert hd ( inputs_for ( form , :post ) ) . errors == [ { :text , { " is required " , [ ] } } ]
end
2021-07-22 13:52:04 +12:00
test " nested errors can be fetched with `Form.errors/2` " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-07-22 13:52:04 +12:00
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " text " = > " text " , " post " = > %{ } } )
|> Form . submit ( force? : true )
|> elem ( 1 )
|> form_for ( " action " )
assert Form . errors ( form . source , for_path : [ :post ] ) == [ { :text , " is required " } ]
assert Form . errors ( form . source , for_path : [ :post ] , format : :raw ) == [
{ :text , { " is required " , [ ] } }
]
assert Form . errors ( form . source , for_path : [ :post ] , format : :plaintext ) == [
" text: is required "
]
assert Form . errors ( form . source , for_path : :all ) == %{ [ :post ] = > [ { :text , " is required " } ] }
end
test " errors can be fetched with `Form.errors/2` " do
form =
Comment
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-07-22 13:52:04 +12:00
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " post " = > %{ " text " = > " text " } } )
|> Form . submit ( force? : true )
|> elem ( 1 )
|> form_for ( " action " )
assert Form . errors ( form . source ) == [ { :text , " is required " } ]
assert Form . errors ( form . source , format : :raw ) == [
{ :text , { " is required " , [ ] } }
]
assert Form . errors ( form . source , format : :plaintext ) == [
" text: is required "
]
end
2022-05-18 03:06:31 +12:00
test " nested forms submit empty values when they have been touched, even if not included in future params " do
2021-07-16 06:09:15 +12:00
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " text " = > " text " } )
2022-10-07 15:33:36 +13:00
assert %{ " text " = > " text " , " post " = > nil } = Form . params ( form )
2021-07-16 06:09:15 +12:00
end
test " nested forms submit empty list values when not present in input params " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " text " = > " text " } )
2021-08-04 14:59:04 +12:00
assert Form . value ( form , :text ) == " text "
2022-10-07 15:33:36 +13:00
assert %{
" text " = > " text " ,
" post " = > [ ] ,
" _form_type " = > " create " ,
" _touched " = > " _form_type,_touched,post,text "
2024-03-28 16:44:54 +13:00
} = Form . params ( form , hidden? : true )
2021-07-16 06:09:15 +12:00
end
test " nested errors are set on the appropriate form after submit for many to many relationships " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-07-16 06:09:15 +12:00
forms : [
post : [
type : :list ,
for : :linked_posts ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ } )
|> Form . validate ( %{ " text " = > " text " , " post " = > [ %{ } ] } )
2021-07-19 11:20:25 +12:00
|> Form . submit ( force? : true )
2021-07-16 06:09:15 +12:00
|> elem ( 1 )
|> form_for ( " action " )
assert form . errors == [ ]
assert [ nested_form ] = inputs_for ( form , :post )
assert nested_form . errors == [ { :text , { " is required " , [ ] } } ]
end
2023-11-29 08:32:38 +13:00
test " errors with a path are propagated down to the appropirate nested form " do
author = % Author {
email : " me@example.com "
}
form =
author
2024-03-28 16:44:54 +13:00
|> Form . for_update ( :update_with_embedded_argument , domain : Domain , forms : [ auto? : true ] )
2023-11-29 08:32:38 +13:00
|> Form . add_form ( :embedded_argument , params : %{ } )
|> Form . validate ( %{ " embedded_argument " = > %{ " value " = > " you@example.com " } } )
|> form_for ( " action " )
2023-11-29 08:34:02 +13:00
2023-11-29 09:31:10 +13:00
assert AshPhoenix.Form . errors ( form , for_path : [ :embedded_argument ] ) == [
value : " must match email "
]
2023-11-29 08:34:02 +13:00
2023-11-29 09:31:10 +13:00
assert AshPhoenix.Form . errors ( form ) == [ ]
2023-11-30 12:41:34 +13:00
2023-11-30 13:45:34 +13:00
# Check that errors will appear on a nested form using the Phoenix Core Components inputs_for
2023-11-30 12:41:34 +13:00
# https://github.com/phoenixframework/phoenix_live_view/blob/main/lib/phoenix_component.ex#L2410
% Phoenix.HTML.FormField { field : field_name , form : parent_form } = form [ :embedded_argument ]
2023-11-30 12:48:57 +13:00
2023-11-30 12:41:34 +13:00
inputs_for_form =
parent_form . impl . to_form (
parent_form . source ,
parent_form ,
field_name ,
parent_form . options
)
|> List . first ( )
# This is the expected format for a phoenix core component
assert inputs_for_form . errors == [ { :value , { " must match email " , [ ] } } ]
2023-11-29 08:32:38 +13:00
end
2023-12-29 08:46:31 +13:00
test " deeply nested errors don't multiply " do
author = % Author {
email : " me@example.com "
}
form =
author
2024-03-28 16:44:54 +13:00
|> Form . for_update ( :update_with_embedded_argument , domain : Domain , forms : [ auto? : true ] )
2023-12-29 08:46:31 +13:00
|> Form . add_form ( :embedded_argument , params : %{ } )
|> Form . add_form ( [ :embedded_argument , :nested_embeds ] , params : %{ } )
|> Form . validate ( %{
" embedded_argument " = > %{
" value " = > " you@example.com " ,
nested_embeds : %{ " 0 " = > %{ " limit " = > " non-integer " , " four_chars " = > " not-4-chars " } }
}
} )
|> form_for ( " action " )
% Phoenix.HTML.FormField { field : field_name , form : parent_form } = form [ :embedded_argument ]
inputs_for_arg_form =
parent_form . impl . to_form (
parent_form . source ,
parent_form ,
field_name ,
parent_form . options
)
|> List . first ( )
% Phoenix.HTML.FormField { field : field_name , form : parent_form } =
inputs_for_arg_form [ :nested_embeds ]
inputs_for_nested_form =
parent_form . impl . to_form (
parent_form . source ,
parent_form ,
field_name ,
parent_form . options
)
|> List . first ( )
# Note: I'm not 100% which of the 3 errors messages are preferred. The opts are get_text bindings for error translation
# In the Phoenix core components the error translation is done `Gettext.dpgettext(MyApp.Gettext, domain, msgctxt, msgid, bindings)` with our tuple being `{msgid, bindings}`
assert Keyword . get_values ( inputs_for_nested_form . errors , :limit ) == [
2023-12-29 09:42:47 +13:00
{ " is invalid " , [ ] }
2023-12-29 08:46:31 +13:00
]
assert Keyword . get_values ( inputs_for_nested_form . errors , :four_chars ) == [
{ " must have length of exactly %{exact} " ,
[
2023-12-29 09:42:47 +13:00
exact : 4
2023-12-29 08:46:31 +13:00
] }
]
end
2021-07-16 06:09:15 +12:00
end
describe " data " do
test " it uses the provided data to create forms even without input " do
post1_id = Ash.UUID . generate ( )
post2_id = Ash.UUID . generate ( )
form =
Comment
|> Form . for_create (
:create ,
forms : [
post : [
type : :list ,
data : [ % Post { id : post1_id } , % Post { id : post2_id } ] ,
update_action : :update ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
]
]
)
2022-08-16 04:31:07 +12:00
|> form_for ( " foo " )
2021-07-16 06:09:15 +12:00
2022-08-16 04:31:07 +12:00
assert Enum . count ( inputs_for ( form , :post ) ) == 2
2021-07-16 06:09:15 +12:00
end
test " a function can be used to derive the data from the data of the parent form " do
post1_id = Ash.UUID . generate ( )
post2_id = Ash.UUID . generate ( )
comment_id = Ash.UUID . generate ( )
form =
Comment
|> Form . for_create (
:create ,
forms : [
post : [
type : :list ,
data : [
% Post { id : post1_id , comments : [ % Comment { id : comment_id } ] } ,
% Post { id : post2_id , comments : [ ] }
] ,
update_action : :update ,
forms : [
comments : [
data : & ( &1 . comments || [ ] ) ,
type : :list ,
resource : Comment ,
create_action : :create ,
update_action : :update
]
]
]
]
)
|> Form . validate ( %{
" text " = > " text " ,
" post " = > %{
" 0 " = > %{ " id " = > post1_id , " comments " = > %{ " 0 " = > %{ " id " = > comment_id } } } ,
" 1 " = > %{ " id " = > post2_id }
}
} )
2022-10-07 15:33:36 +13:00
assert %{
2021-07-16 06:09:15 +12:00
" post " = > [
2022-10-07 15:33:36 +13:00
%{ " comments " = > [ %{ " id " = > ^ comment_id } ] , " id " = > ^ post1_id } ,
%{ " id " = > ^ post2_id }
2021-07-16 06:09:15 +12:00
] ,
" text " = > " text "
2022-10-07 15:33:36 +13:00
} = Form . params ( form )
2021-07-16 06:09:15 +12:00
end
end
describe " submit " do
test " it runs the action with the params " do
assert { :ok , %{ text : " text " } } =
Post
2024-03-28 16:44:54 +13:00
|> Form . for_create ( :create , domain : Domain )
2021-07-16 06:09:15 +12:00
|> Form . validate ( %{ text : " text " } )
2021-07-19 11:20:25 +12:00
|> Form . submit ( )
2021-07-16 06:09:15 +12:00
end
2021-07-19 16:58:46 +12:00
2024-03-28 16:44:54 +13:00
test " it fallback to resource defined Domain if unset " do
2024-02-22 04:07:17 +13:00
assert { :ok , %{ name : " name " } } =
Artist
|> Form . for_action ( :create )
|> Form . validate ( %{ name : " name " } )
|> Form . submit ( )
assert { :ok , [ %{ name : " name " } ] } =
Artist
|> Form . for_action ( :read )
|> Form . validate ( %{ name : " name changed " } )
|> Form . submit ( )
artist =
Artist
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ name : " name " } )
|> Ash . create! ( )
2024-02-22 04:07:17 +13:00
assert { :ok , %{ name : " name changed " } } =
artist
|> Form . for_action ( :update )
|> Form . validate ( %{ name : " name changed " } )
|> Form . submit ( )
assert :ok =
artist
|> Form . for_action ( :destroy )
|> Form . validate ( %{ name : " name changed " } )
|> Form . submit ( )
end
2021-07-16 06:09:15 +12:00
end
2022-12-16 10:40:24 +13:00
describe " prepare_source " do
test " it runs on initial create " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-12-16 10:40:24 +13:00
prepare_source : & Ash.Changeset . put_context ( &1 , :foo , :bar )
)
assert form . source . context . foo == :bar
end
test " it is preserved on validate create " do
form =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-12-16 10:40:24 +13:00
prepare_source : & Ash.Changeset . put_context ( &1 , :foo , :bar )
)
|> Form . validate ( %{ text : " text " } )
assert form . source . context . foo == :bar
end
test " it is preserved through to submit " do
result =
Post
|> Form . for_create ( :create ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2022-12-16 10:40:24 +13:00
prepare_source : fn changeset ->
Ash.Changeset . before_action (
changeset ,
& Ash.Changeset . force_change_attribute ( &1 , :title , " special_title " )
)
end
)
|> Form . validate ( %{ text : " text " } )
|> Form . submit! ( )
assert result . title == " special_title "
end
end
2021-07-16 06:09:15 +12:00
describe " params " do
test " it includes nested forms, and honors their `for` configuration " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
] ,
other_post : [
type : :single ,
for : :for_posts ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( " form[post] " , params : %{ " text " = > " post_text " } )
|> Form . add_form ( " form[other_post] " , params : %{ " text " = > " post_text " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " post_text " } )
|> Form . validate ( %{
" text " = > " text " ,
" post " = > [ %{ " comments " = > [ %{ " text " = > " post_text " } ] , " text " = > " post_text " } ] ,
" other_post " = > %{ " text " = > " post_text " }
} )
2021-08-12 10:48:52 +12:00
assert %{
2021-07-16 06:09:15 +12:00
" text " = > " text " ,
" post " = > [ %{ " comments " = > [ %{ " text " = > " post_text " } ] , " text " = > " post_text " } ] ,
" for_posts " = > %{ " text " = > " post_text " }
2021-08-12 10:48:52 +12:00
} = Form . params ( form )
2021-07-16 06:09:15 +12:00
end
end
2022-10-28 16:43:20 +13:00
test " it properly retains form order " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
] ,
other_post : [
type : :single ,
for : :for_posts ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( " form[post] " , params : %{ " text " = > " post_text " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 0 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 1 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 2 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 3 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 4 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 5 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 6 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 7 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 8 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 9 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 10 " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ " text " = > " 11 " } )
assert %{
" post " = > [
%{
" comments " = > [
%{ " text " = > " 0 " } ,
%{ " text " = > " 1 " } ,
%{ " text " = > " 2 " } ,
%{ " text " = > " 3 " } ,
%{ " text " = > " 4 " } ,
%{ " text " = > " 5 " } ,
%{ " text " = > " 6 " } ,
%{ " text " = > " 7 " } ,
%{ " text " = > " 8 " } ,
%{ " text " = > " 9 " } ,
%{ " text " = > " 10 " } ,
%{ " text " = > " 11 " }
]
}
]
} = Form . params ( form )
end
2021-07-16 06:09:15 +12:00
describe " `inputs_for` with no configuration " do
2024-04-12 10:46:52 +12:00
test " it should raise an error " do
form =
Post
|> Form . for_create ( :create )
|> Form . validate ( %{ text : " text " } )
|> form_for ( " action " )
assert_raise AshPhoenix.Form.NoFormConfigured ,
~r/ There is a no attribute or relationship called `post` on the resource `AshPhoenix.Test.Post` / ,
fn ->
AshPhoenix.Form . add_form ( form , :post )
end
end
2021-07-16 06:09:15 +12:00
end
describe " inputs_for` relationships " do
2021-07-19 14:07:54 +12:00
test " it should name the fields correctly on `for_update` " do
post_id = Ash.UUID . generate ( )
comment_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ % Comment { id : comment_id } ]
}
}
form =
comment
|> Form . for_update ( :update ,
as : " comment " ,
forms : [
post : [
data : comment . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
data : & &1 . comments ,
type : :list ,
resource : Comment ,
update_action : :update ,
create_action : :create
]
]
]
]
)
comments_form =
form
|> form_for ( " action " )
|> inputs_for ( :post )
|> hd ( )
|> inputs_for ( :comments )
|> hd ( )
assert comments_form . name == " comment[post][comments][0] "
end
2021-07-16 06:09:15 +12:00
test " the `type: :single` option should create a form without integer paths " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text " } )
|> Form . validate ( %{ " text " = > " text " , " post " = > %{ " text " = > " post_text " } } )
|> form_for ( " action " )
assert % Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
2021-07-17 10:23:07 +12:00
related_form = hd ( inputs_for ( form , :post ) )
2021-07-16 06:09:15 +12:00
assert related_form . name == " form[post] "
assert FormData . input_value ( related_form . source , related_form , :text ) == " post_text "
end
test " it should show nothing in `inputs_for` by default " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . validate ( %{ " text " = > " text " } )
|> form_for ( " action " )
assert inputs_for ( form , :post ) == [ ]
end
test " when a value has been appended to the relationship, a form is created " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text " } )
|> form_for ( " action " )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
related_form
] = inputs_for ( form , :post )
assert FormData . input_value ( related_form . source , related_form , :text ) == " post_text "
end
test " a query path can be used when manipulating forms " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
]
]
)
|> Form . add_form ( " form[post] " , params : %{ text : " post_text " } )
|> Form . add_form ( " form[post][0][comments] " , params : %{ text : " post_text " } )
|> form_for ( " action " )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
post_form
] = inputs_for ( form , :post )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Comment } }
] = inputs_for ( post_form , :comments )
end
test " list values get an index in their name and id " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text0 " } )
|> Form . add_form ( :post , params : %{ text : " post_text1 " } )
|> Form . add_form ( :post , params : %{ text : " post_text2 " } )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_0 ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_1 ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_2
] = inputs_for ( form_for ( form , " action " ) , :post )
assert form_0 . name == " form[post][0] "
assert form_0 . id == " form_post_0 "
assert form_1 . name == " form[post][1] "
assert form_1 . id == " form_post_1 "
assert form_2 . name == " form[post][2] "
assert form_2 . id == " form_post_2 "
end
test " when a value has been removed from the relationship, the form is removed " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text0 " } )
|> Form . add_form ( :post , params : %{ text : " post_text1 " } )
|> Form . add_form ( :post , params : %{ text : " post_text2 " } )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } }
] = inputs_for ( form_for ( form , " action " ) , :post )
form =
form
|> Form . remove_form ( [ :post , 0 ] )
|> Form . remove_form ( [ :post , 1 ] )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
related_form
] = inputs_for ( form_for ( form , " action " ) , :post )
assert FormData . input_value ( related_form . source , related_form , :text ) == " post_text1 "
end
test " when all values have been removed from a relationship, the empty list remains " do
form =
Comment
|> Form . for_create ( :create ,
forms : [
post : [
type : :list ,
resource : Post ,
create_action : :create
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text0 " } )
|> Form . add_form ( :post , params : %{ text : " post_text1 " } )
|> Form . add_form ( :post , params : %{ text : " post_text2 " } )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } }
] = inputs_for ( form_for ( form , " action " ) , :post )
form =
form
|> Form . remove_form ( [ :post , 0 ] )
|> Form . remove_form ( [ :post , 0 ] )
|> Form . remove_form ( [ :post , 0 ] )
|> Form . validate ( %{ } )
assert [ ] = inputs_for ( form_for ( form , " action " ) , :post )
2022-10-07 15:33:36 +13:00
assert %{ " post " = > [ ] } = Form . params ( form )
2021-07-16 06:09:15 +12:00
end
test " when all values have been removed from an existing relationship, the empty list remains " do
post1_id = Ash.UUID . generate ( )
post2_id = Ash.UUID . generate ( )
comment = % Comment { text : " text " , post : [ % Post { id : post1_id } , % Post { id : post2_id } ] }
form =
comment
2022-05-29 13:20:54 +12:00
|> Form . for_update ( :update ,
2021-07-16 06:09:15 +12:00
forms : [
post : [
data : comment . post ,
type : :list ,
resource : Post ,
create_action : :create ,
update_action : :update
]
]
)
|> Form . add_form ( :post , params : %{ text : " post_text3 " } )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } }
] = inputs_for ( form_for ( form , " action " ) , :post )
form =
form
|> Form . remove_form ( [ :post , 0 ] )
|> Form . remove_form ( [ :post , 0 ] )
|> Form . remove_form ( [ :post , 0 ] )
|> Form . validate ( %{ } )
2022-10-07 15:33:36 +13:00
assert %{ " post " = > [ ] } = Form . params ( form )
2021-07-16 06:09:15 +12:00
end
2021-07-17 10:23:07 +12:00
2021-07-20 17:54:36 +12:00
test " when all values have been removed from an existing `:single` relationship, the empty list remains " do
post_id = Ash.UUID . generate ( )
comment_2 = % Comment { text : " text " }
comment = % Comment { text : " text " , post : % Post { id : post_id , comments : [ comment_2 ] } }
form =
comment
|> Form . for_update ( :update ,
forms : [
post : [
data : & &1 . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
data : & &1 . comments ,
create_action : :create ,
update_action : :update
]
]
]
]
)
assert [ % Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ] =
form
|> form_for ( " action " )
|> inputs_for ( :post )
form =
form
|> Form . remove_form ( [ :post , :comments , 0 ] )
2021-08-12 10:48:52 +12:00
# This is added by the hidden fields helper, so we add it here to simulate that.
|> Form . validate ( %{ " post " = > %{ " _touched " = > " comments " } } )
2021-07-20 17:54:36 +12:00
2021-08-12 10:48:52 +12:00
assert %{ " post " = > %{ " comments " = > [ ] } } = Form . params ( form )
2021-07-20 17:54:36 +12:00
end
2021-07-20 14:55:05 +12:00
test " remaining forms are reindexed after a form has been removed " do
post1_id = Ash.UUID . generate ( )
post2_id = Ash.UUID . generate ( )
post3_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : [ % Post { id : post1_id } , % Post { id : post2_id } , % Post { id : post3_id } ]
}
form =
comment
2022-05-29 13:20:54 +12:00
|> Form . for_update ( :update ,
2021-07-20 14:55:05 +12:00
forms : [
posts : [
data : comment . post ,
type : :list ,
resource : Post ,
create_action : :create ,
update_action : :update
]
]
)
|> Form . remove_form ( [ :posts , 1 ] )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_0 ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_1
] = inputs_for ( form_for ( form , " action " ) , :posts )
assert form_0 . name == " form[posts][0] "
assert form_0 . id == " form_posts_0 "
assert form_1 . name == " form[posts][1] "
assert form_1 . id == " form_posts_1 "
end
2024-05-30 02:54:13 +12:00
test " sparse forms can also be removed by index " do
post1_id = Ash.UUID . generate ( )
post2_id = Ash.UUID . generate ( )
post3_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : [ % Post { id : post1_id } , % Post { id : post2_id } , % Post { id : post3_id } ]
}
form =
comment
|> Form . for_update ( :update ,
forms : [
posts : [
data : comment . post ,
type : :list ,
resource : Post ,
create_action : :create ,
update_action : :update ,
sparse? : true
]
]
)
|> Form . remove_form ( [ :posts , 1 ] )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_0 ,
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } =
form_1
] = inputs_for ( form_for ( form , " action " ) , :posts )
assert form_0 . name == " form[posts][0] "
assert form_0 . id == " form_posts_0 "
assert form_1 . name == " form[posts][1] "
assert form_1 . id == " form_posts_1 "
form =
form
|> Form . remove_form ( [ :posts , 1 ] )
assert [
% Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } }
] = inputs_for ( form_for ( form , " action " ) , :posts )
end
2021-07-17 10:23:07 +12:00
test " when `:single`, `inputs_for` generates a list of one single item " do
post_id = Ash.UUID . generate ( )
comment = % Comment { text : " text " , post : % Post { id : post_id , text : " Some text " } }
form =
comment
|> Form . for_update ( :update ,
forms : [
post : [
data : comment . post ,
type : :single ,
resource : Post ,
update_action : :update
]
]
)
assert [ % Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Post } } ] =
inputs_for ( form_for ( form , " action " ) , :post )
end
2021-07-18 07:57:27 +12:00
2021-07-19 14:07:54 +12:00
test " it creates nested forms for single resources " do
2021-07-18 07:57:27 +12:00
post_id = Ash.UUID . generate ( )
2021-07-18 08:06:15 +12:00
comment_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ % Comment { id : comment_id } ]
}
}
2021-07-18 07:57:27 +12:00
form =
comment
|> Form . for_update ( :update ,
forms : [
post : [
data : comment . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
data : & &1 . comments ,
type : :list ,
resource : Comment ,
update_action : :update ,
create_action : :create
]
]
]
]
)
2021-07-18 08:06:15 +12:00
assert [ % Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Comment } } ] =
2021-07-18 07:57:27 +12:00
form
|> form_for ( " action " )
|> inputs_for ( :post )
|> hd ( )
|> inputs_for ( :comments )
end
2021-07-20 15:42:11 +12:00
test " it `add_form`s for nested single resources " do
post_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ ]
}
}
form =
comment
|> Form . for_update ( :update ,
forms : [
post : [
data : comment . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
]
]
)
|> Form . add_form ( [ :post , :comments ] )
assert [ % Phoenix.HTML.Form { source : % AshPhoenix.Form { resource : AshPhoenix.Test.Comment } } ] =
form
|> form_for ( " action " )
|> inputs_for ( :post )
|> hd ( )
|> inputs_for ( :comments )
end
2021-07-20 17:00:44 +12:00
test " it `remove_form`s for nested single resources " do
post_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ ]
}
}
form =
comment
|> Form . for_update ( :update ,
forms : [
post : [
data : comment . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
type : :list ,
resource : Comment ,
create_action : :create
]
]
]
]
)
|> Form . add_form ( [ :post , :comments ] )
|> Form . remove_form ( [ :post , :comments , 0 ] )
assert [ ] =
form
|> form_for ( " action " )
|> inputs_for ( :post )
|> hd ( )
|> inputs_for ( :comments )
end
2022-05-14 12:45:07 +12:00
test " when `remove_form`ing an existing `:single` relationship, a nil value is included in the params - if the form has been touched " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . new ( )
2022-05-14 12:45:07 +12:00
|> Ash.Changeset . set_argument ( :author , %{ email : " nigel@elixir-lang.org " } )
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2022-05-14 12:45:07 +12:00
form =
post
2024-03-28 16:44:54 +13:00
|> Form . for_update ( :update , domain : Domain , forms : [ auto? : true ] )
2022-05-18 03:06:31 +12:00
|> Form . remove_form ( [ :author ] )
2022-05-14 12:45:07 +12:00
params =
form
|> Form . params ( )
2022-05-18 03:06:31 +12:00
assert %{ " author " = > nil } = params
2022-05-14 12:45:07 +12:00
end
2022-05-18 02:39:46 +12:00
test " when add_forming a required argument, the added form should be valid without needing to manually validate it " do
form =
Post
2024-03-28 16:44:54 +13:00
|> Form . for_create ( :create_author_required , domain : Domain , forms : [ auto? : true ] )
2022-05-18 02:39:46 +12:00
|> Form . validate ( %{ " text " = > " foo " } )
|> Form . add_form ( [ :author ] , params : %{ " email " = > " james@foo.com " } )
assert form . valid? == true
end
2024-04-23 02:51:48 +12:00
test " add form with nested params generate form with correct name " do
post_id = Ash.UUID . generate ( )
comment_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ % Comment { id : comment_id } ]
}
}
form =
comment
|> Form . for_update ( :update , as : " comment " , forms : [ auto? : true ] )
|> Form . add_form ( [ :post ] )
|> Form . add_form ( [ :post , :comments ] , params : %{ " post " = > %{ } } )
post_form =
form
|> form_for ( " action " )
|> inputs_for ( :post )
|> hd ( )
|> inputs_for ( :comments )
|> hd ( )
|> inputs_for ( :post )
|> hd ( )
assert post_form . name == " comment[post][comments][0][post] "
end
2024-04-24 01:47:51 +12:00
test " update nested form name correctly when remove form in the middle " do
post_id = Ash.UUID . generate ( )
comment_id = Ash.UUID . generate ( )
comment = % Comment {
text : " text " ,
post : % Post {
id : post_id ,
text : " Some text " ,
comments : [ % Comment { id : comment_id } ]
}
}
form =
comment
|> Form . for_update ( :update , as : " comment " , forms : [ auto? : true ] )
|> Form . add_form ( [ :post , :comments ] , params : %{ " post " = > %{ } } )
|> Form . add_form ( [ :post , :comments ] , params : %{ " post " = > %{ } } )
|> Form . add_form ( [ :post , :comments ] , params : %{ " post " = > %{ } } )
|> Form . remove_form ( [ :post , :comments , 1 ] )
assert form |> AshPhoenix.Form . get_form ( [ :post , :comments , 0 ] )
assert form |> AshPhoenix.Form . get_form ( [ :post , :comments , 1 ] )
2024-04-24 02:14:38 +12:00
assert form |> AshPhoenix.Form . get_form ( [ :post , :comments , 2 ] )
assert form |> AshPhoenix.Form . get_form ( [ :post , :comments , 2 , :post ] ) |> Map . get ( :name ) ==
" comment[post][comments][2][post] "
refute form |> AshPhoenix.Form . get_form ( [ :post , :comments , 3 ] )
2024-04-24 01:47:51 +12:00
end
2021-07-16 06:09:15 +12:00
end
2021-08-05 16:45:09 +12:00
describe " issue # 259 " do
test " updating should not duplicate nested resources " do
post =
Post
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " post " } )
|> Ash . create! ( )
2021-08-05 16:45:09 +12:00
comment =
Comment
2024-03-28 16:44:54 +13:00
|> Ash.Changeset . for_create ( :create , %{ text : " comment " } )
2022-09-21 17:24:52 +12:00
|> Ash.Changeset . manage_relationship ( :post , post , type : :append_and_remove )
2024-03-28 16:44:54 +13:00
|> Ash . create! ( )
2021-08-05 16:45:09 +12:00
# Check the persisted post.comments count after create
2024-03-28 16:44:54 +13:00
post = Post |> Ash . get! ( post . id ) |> Ash . load! ( :comments )
2021-08-05 16:45:09 +12:00
assert Enum . count ( post . comments ) == 1
# Grab the persisted comment
2024-03-28 16:44:54 +13:00
comment = Comment |> Ash . get! ( comment . id ) |> Ash . load! ( post : [ :comments ] )
2021-08-05 16:45:09 +12:00
form =
comment
|> Form . for_update ( :update ,
as : " comment " ,
2024-03-28 16:44:54 +13:00
domain : Domain ,
2021-08-05 16:45:09 +12:00
forms : [
post : [
data : & &1 . post ,
type : :single ,
resource : Post ,
update_action : :update ,
create_action : :create ,
forms : [
comments : [
data : & &1 . comments ,
type : :list ,
resource : Comment ,
update_action : :update ,
create_action : :create
]
]
]
]
)
2021-08-05 18:07:36 +12:00
updated_comment =
2021-08-05 16:45:09 +12:00
form
2021-08-05 18:07:36 +12:00
|> AshPhoenix.Form . submit! (
2021-08-05 16:45:09 +12:00
params : %{
" post " = > %{
" id " = > post . id ,
" text " = > " text " ,
" comments " = > %{
" 0 " = > %{
" id " = > comment . id ,
" text " = > comment . text
}
}
}
}
)
assert Enum . count ( updated_comment . post . comments ) == 1
# now, check the persisted post
2024-03-28 16:44:54 +13:00
persisted_post = Post |> Ash . get! ( post . id ) |> Ash . load! ( :comments )
2021-08-05 16:45:09 +12:00
assert Enum . count ( persisted_post . comments ) == 1
end
end
2021-07-16 06:09:15 +12:00
end