Fix Credo Design in test support and member index test

Add aliases in fixtures, conn_case, data_case. Use aliases
in index_test.exs. Remove empty placeholder test files.
This commit is contained in:
Moritz 2026-03-03 19:04:07 +01:00
parent 7a8b069834
commit e537f4eb31
Signed by: moritz
GPG key ID: 1020A035E5DD0824
6 changed files with 132 additions and 118 deletions

View file

@ -3,8 +3,13 @@ defmodule MvWeb.MemberLive.IndexTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Helpers.SystemActor
alias Mv.Membership
alias Mv.Membership.CustomField
alias Mv.Membership.CustomFieldValue
alias Mv.MembershipFees.MembershipFeeCycle
alias Mv.MembershipFees.MembershipFeeType
alias MvWeb.MemberLive.Index, as: MemberIndex
# Helper to create a membership fee type (shared across all tests)
defp create_fee_type(attrs, actor) do
@ -298,10 +303,10 @@ defmodule MvWeb.MemberLive.IndexTest do
@tag :ui
test "member index does not render Edit or Delete actions", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, _member} =
Mv.Membership.create_member(
Membership.create_member(
%{first_name: "Test", last_name: "User", email: "test@example.com"},
actor: system_actor
)
@ -315,10 +320,10 @@ defmodule MvWeb.MemberLive.IndexTest do
@tag :ui
test "row click navigates to member show", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{first_name: "Row", last_name: "Click", email: "rowclick@example.com"},
actor: system_actor
)
@ -338,10 +343,10 @@ defmodule MvWeb.MemberLive.IndexTest do
@describetag :ui
test "clickable rows have hover and focus-within ring classes", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, _member} =
Mv.Membership.create_member(
Membership.create_member(
%{first_name: "Hover", last_name: "Test", email: "hover@example.com"},
actor: system_actor
)
@ -356,10 +361,10 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "selected outline only from checkbox selection, not from highlight param", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{first_name: "Highlight", last_name: "Only", email: "highlight@example.com"},
actor: system_actor
)
@ -374,11 +379,11 @@ defmodule MvWeb.MemberLive.IndexTest do
describe "copy_emails feature" do
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
# Create test members
{:ok, member1} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Max",
last_name: "Mustermann",
@ -388,7 +393,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member2} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Erika",
last_name: "Musterfrau",
@ -398,7 +403,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member3} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Hans",
last_name: "Müller-Lüdenscheidt",
@ -485,7 +490,7 @@ defmodule MvWeb.MemberLive.IndexTest do
render_click(view, "select_member", %{"id" => member1.id})
# Delete the member from the database
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
Ash.destroy!(member1, actor: system_actor)
# Trigger copy_emails event directly - selection still contains the deleted ID
@ -526,10 +531,10 @@ defmodule MvWeb.MemberLive.IndexTest do
conn = conn_with_oidc_user(conn)
# Create a member with known data
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, test_member} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Test",
last_name: "Format",
@ -598,10 +603,10 @@ defmodule MvWeb.MemberLive.IndexTest do
describe "export dropdown" do
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
{:ok, m1} =
Mv.Membership.create_member(
Membership.create_member(
%{first_name: "Export", last_name: "One", email: "export1@example.com"},
actor: system_actor
)
@ -755,12 +760,12 @@ defmodule MvWeb.MemberLive.IndexTest do
}
attrs = Map.merge(default_attrs, attrs)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
{:ok, member} = Membership.create_member(attrs, actor: actor)
member
end
test "filter shows only members with paid status in last cycle", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
today = Date.utc_today()
@ -807,7 +812,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "filter shows only members with unpaid status in last cycle", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
today = Date.utc_today()
@ -854,7 +859,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "filter shows only members with paid status in current cycle", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
today = Date.utc_today()
@ -901,7 +906,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "filter shows only members with unpaid status in current cycle", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
today = Date.utc_today()
@ -970,11 +975,9 @@ defmodule MvWeb.MemberLive.IndexTest do
end
describe "boolean custom field filters" do
alias Mv.Membership.CustomField
# Helper to create a boolean custom field (uses system actor for authorization)
defp create_boolean_custom_field(attrs \\ %{}) do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
default_attrs = %{
name: "test_boolean_#{System.unique_integer([:positive])}",
@ -990,7 +993,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Helper to create a non-boolean custom field (uses system actor for authorization)
defp create_string_custom_field(attrs \\ %{}) do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
default_attrs = %{
name: "test_string_#{System.unique_integer([:positive])}",
@ -1244,7 +1247,7 @@ defmodule MvWeb.MemberLive.IndexTest do
test "handle_params removes filter when custom field is deleted", %{conn: conn} do
conn = conn_with_oidc_user(conn)
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
# Set up filter via URL
@ -1359,10 +1362,10 @@ defmodule MvWeb.MemberLive.IndexTest do
}
|> Map.merge(member_attrs)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
{:ok, member} = Membership.create_member(attrs, actor: actor)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member.id,
custom_field_id: custom_field.id,
@ -1377,33 +1380,33 @@ defmodule MvWeb.MemberLive.IndexTest do
# Tests for get_boolean_custom_field_value/2
test "get_boolean_custom_field_value extracts true from Ash.Union format", %{conn: _conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
member = create_member_with_boolean_value(%{}, boolean_field, true, system_actor)
# Test the function (will fail until implemented)
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == true
end
test "get_boolean_custom_field_value extracts false from Ash.Union format", %{conn: _conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
member = create_member_with_boolean_value(%{}, boolean_field, false, system_actor)
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == false
end
test "get_boolean_custom_field_value extracts true from map format with _union_type and _union_value keys",
%{conn: _conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
@ -1414,7 +1417,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Create CustomFieldValue with map format (Ash expects _union_type and _union_value)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member.id,
custom_field_id: boolean_field.id,
@ -1425,7 +1428,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Reload member with custom field values
member = member |> Ash.load!(:custom_field_values, actor: system_actor)
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == true
end
@ -1433,11 +1436,11 @@ defmodule MvWeb.MemberLive.IndexTest do
test "get_boolean_custom_field_value returns nil when no CustomFieldValue exists", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
@ -1449,7 +1452,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member has no custom field value for this field
member = member |> Ash.load!(:custom_field_values, actor: system_actor)
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == nil
end
@ -1457,11 +1460,11 @@ defmodule MvWeb.MemberLive.IndexTest do
test "get_boolean_custom_field_value returns nil when CustomFieldValue has nil value", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
@ -1472,7 +1475,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Create CustomFieldValue with nil value (edge case)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member.id,
custom_field_id: boolean_field.id,
@ -1482,7 +1485,7 @@ defmodule MvWeb.MemberLive.IndexTest do
member = member |> Ash.load!(:custom_field_values, actor: system_actor)
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == nil
end
@ -1490,12 +1493,12 @@ defmodule MvWeb.MemberLive.IndexTest do
test "get_boolean_custom_field_value returns nil for non-boolean CustomFieldValue", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
string_field = create_string_custom_field()
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
@ -1506,7 +1509,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Create string custom field value (not boolean)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member.id,
custom_field_id: string_field.id,
@ -1517,7 +1520,7 @@ defmodule MvWeb.MemberLive.IndexTest do
member = member |> Ash.load!(:custom_field_values, actor: system_actor)
# Try to get boolean value from string field - should return nil
result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)
result = MemberIndex.get_boolean_custom_field_value(member, boolean_field)
assert result == nil
end
@ -1525,7 +1528,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Tests for apply_boolean_custom_field_filters/2
test "apply_boolean_custom_field_filters filters members with true value and excludes false/without values",
%{conn: _conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
member_with_true =
@ -1545,7 +1548,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member_without_value} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
@ -1559,10 +1562,10 @@ defmodule MvWeb.MemberLive.IndexTest do
members = [member_with_true, member_with_false, member_without_value]
filters = %{to_string(boolean_field.id) => true}
all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)
all_custom_fields = CustomField |> Ash.read!(actor: system_actor)
result =
MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
MemberIndex.apply_boolean_custom_field_filters(
members,
filters,
all_custom_fields
@ -1576,7 +1579,7 @@ defmodule MvWeb.MemberLive.IndexTest do
test "apply_boolean_custom_field_filters filters members with false value and excludes true/without values",
%{conn: _conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
member_with_true =
@ -1596,7 +1599,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member_without_value} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
@ -1610,10 +1613,10 @@ defmodule MvWeb.MemberLive.IndexTest do
members = [member_with_true, member_with_false, member_without_value]
filters = %{to_string(boolean_field.id) => false}
all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)
all_custom_fields = CustomField |> Ash.read!(actor: system_actor)
result =
MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
MemberIndex.apply_boolean_custom_field_filters(
members,
filters,
all_custom_fields
@ -1628,7 +1631,7 @@ defmodule MvWeb.MemberLive.IndexTest do
test "apply_boolean_custom_field_filters returns all members when filter map is empty", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
member1 =
@ -1649,10 +1652,10 @@ defmodule MvWeb.MemberLive.IndexTest do
members = [member1, member2]
filters = %{}
all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)
all_custom_fields = CustomField |> Ash.read!(actor: system_actor)
result =
MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
MemberIndex.apply_boolean_custom_field_filters(
members,
filters,
all_custom_fields
@ -1668,13 +1671,13 @@ defmodule MvWeb.MemberLive.IndexTest do
test "apply_boolean_custom_field_filters applies multiple filters with AND logic", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field1 = create_boolean_custom_field(%{name: "Field1"})
boolean_field2 = create_boolean_custom_field(%{name: "Field2"})
# Member with both fields = true
{:ok, member_both_true} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "BothTrue",
last_name: "Member",
@ -1684,7 +1687,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _cfv1} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_both_true.id,
custom_field_id: boolean_field1.id,
@ -1693,7 +1696,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|> Ash.create(actor: system_actor)
{:ok, _cfv2} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_both_true.id,
custom_field_id: boolean_field2.id,
@ -1705,7 +1708,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with field1 = true, field2 = false
{:ok, member_mixed} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "Mixed",
last_name: "Member",
@ -1715,7 +1718,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _cfv3} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_mixed.id,
custom_field_id: boolean_field1.id,
@ -1724,7 +1727,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|> Ash.create(actor: system_actor)
{:ok, _cfv4} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_mixed.id,
custom_field_id: boolean_field2.id,
@ -1741,10 +1744,10 @@ defmodule MvWeb.MemberLive.IndexTest do
to_string(boolean_field2.id) => true
}
all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)
all_custom_fields = CustomField |> Ash.read!(actor: system_actor)
result =
MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
MemberIndex.apply_boolean_custom_field_filters(
members,
filters,
all_custom_fields
@ -1758,7 +1761,7 @@ defmodule MvWeb.MemberLive.IndexTest do
test "apply_boolean_custom_field_filters ignores filter with non-existent custom field ID", %{
conn: _conn
} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
boolean_field = create_boolean_custom_field()
fake_id = Ecto.UUID.generate()
@ -1772,10 +1775,10 @@ defmodule MvWeb.MemberLive.IndexTest do
members = [member]
filters = %{fake_id => true}
all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)
all_custom_fields = CustomField |> Ash.read!(actor: system_actor)
result =
MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
MemberIndex.apply_boolean_custom_field_filters(
members,
filters,
all_custom_fields
@ -1788,7 +1791,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Integration tests for boolean custom field filters in load_members
test "boolean filter integration filters members by boolean custom field value via URL parameter",
%{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
boolean_field = create_boolean_custom_field()
@ -1809,7 +1812,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _member_without_value} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
@ -1836,7 +1839,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "boolean filter integration works together with cycle_status_filter", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
boolean_field = create_boolean_custom_field()
fee_type = create_fee_type(%{interval: :yearly}, system_actor)
@ -1845,7 +1848,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with true boolean value and paid status
{:ok, member_paid_true} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "PaidTrue",
last_name: "Member",
@ -1856,7 +1859,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_paid_true.id,
custom_field_id: boolean_field.id,
@ -1873,7 +1876,7 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with true boolean value but unpaid status
{:ok, member_unpaid_true} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "UnpaidTrue",
last_name: "Member",
@ -1884,7 +1887,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _cfv2} =
Mv.Membership.CustomFieldValue
CustomFieldValue
|> Ash.Changeset.for_create(:create, %{
member_id: member_unpaid_true.id,
custom_field_id: boolean_field.id,
@ -1909,7 +1912,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "boolean filter integration works together with search query", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
boolean_field = create_boolean_custom_field()
@ -1939,7 +1942,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "boolean filter works even when custom field is not visible in overview", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
# Create boolean field with show_in_overview: false
@ -1962,7 +1965,7 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _member_without_value} =
Mv.Membership.create_member(
Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
@ -2016,7 +2019,7 @@ defmodule MvWeb.MemberLive.IndexTest do
@tag :slow
test "boolean filter performance with 150 members", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
system_actor = SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn)
boolean_field = create_boolean_custom_field()