Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions test/ash_typescript/rpc/map_field_name_rpc_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshTypescript.Rpc.MapFieldNameRpcTest do
@moduledoc """
Tests for map field name handling in RPC run phase.

Verifies that:
1. RPC accepts camelCase field names (matching TypeScript types)
2. RPC accepts snake_case field names (matching Elixir definition)
3. The output is always camelCase (for TypeScript consistency)
"""
use ExUnit.Case, async: false
alias AshTypescript.Rpc
alias AshTypescript.Test.TestHelpers

setup do
conn = TestHelpers.build_rpc_conn()
{:ok, conn: conn}
end

describe "RPC should accept camelCase field names for map return types" do
test "get_metrics action accepts camelCase field names", %{conn: conn} do
result =
Rpc.run_action(:ash_typescript, conn, %{
"action" => "get_metrics",
"fields" => ["total", "lastWeek", "lastMonth", "lastYear"]
})

assert result["success"] == true,
"RPC rejected camelCase field names. Expected success but got: #{inspect(result)}"

data = result["data"]

assert Map.has_key?(data, "total"), "Expected 'total' in output"
assert Map.has_key?(data, "lastWeek"), "Expected 'lastWeek' in output"
assert Map.has_key?(data, "lastMonth"), "Expected 'lastMonth' in output"
assert Map.has_key?(data, "lastYear"), "Expected 'lastYear' in output"
end

test "get_metrics action also accepts snake_case field names for backwards compatibility", %{
conn: conn
} do
result =
Rpc.run_action(:ash_typescript, conn, %{
"action" => "get_metrics",
"fields" => ["total", "last_week", "last_month", "last_year"]
})

assert result["success"] == true,
"snake_case field names should work. Got: #{inspect(result)}"

data = result["data"]

assert Map.has_key?(data, "total"), "Expected 'total' in output"
assert Map.has_key?(data, "lastWeek"), "Expected 'lastWeek' in output"
assert Map.has_key?(data, "lastMonth"), "Expected 'lastMonth' in output"
assert Map.has_key?(data, "lastYear"), "Expected 'lastYear' in output"
end

test "get_nested_stats action accepts camelCase for top-level and nested field selection", %{

Check failure on line 62 in test/ash_typescript/rpc/map_field_name_rpc_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci / mix test

test RPC should accept camelCase field names for map return types get_nested_stats action accepts camelCase for top-level and nested field selection (AshTypescript.Rpc.MapFieldNameRpcTest)
conn: conn
} do
result =
Rpc.run_action(:ash_typescript, conn, %{
"action" => "get_nested_stats",
"fields" => [
%{
"userStats" => ["activeUsers", "newSignups", "churnRate"]
},
%{
"contentStats" => ["totalPosts", "postsThisWeek", "avgEngagementRate"]
}
]
})

assert result["success"] == true,
"RPC rejected camelCase nested field names. Expected success but got: #{inspect(result)}"

data = result["data"]

assert Map.has_key?(data, "userStats"), "Expected 'userStats' in output"
assert Map.has_key?(data, "contentStats"), "Expected 'contentStats' in output"

user_stats = data["userStats"]
assert Map.has_key?(user_stats, "activeUsers"), "Expected 'activeUsers' in userStats"
assert Map.has_key?(user_stats, "newSignups"), "Expected 'newSignups' in userStats"
assert Map.has_key?(user_stats, "churnRate"), "Expected 'churnRate' in userStats"

content_stats = data["contentStats"]
assert Map.has_key?(content_stats, "totalPosts"), "Expected 'totalPosts' in contentStats"

assert Map.has_key?(content_stats, "postsThisWeek"),
"Expected 'postsThisWeek' in contentStats"

assert Map.has_key?(content_stats, "avgEngagementRate"),
"Expected 'avgEngagementRate' in contentStats"
end
end

describe "list_users_map action with nested {:array, :map}" do
test "list_users_map action accepts camelCase for top-level and nested array fields", %{

Check failure on line 103 in test/ash_typescript/rpc/map_field_name_rpc_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci / mix test

test list_users_map action with nested {:array, :map} list_users_map action accepts camelCase for top-level and nested array fields (AshTypescript.Rpc.MapFieldNameRpcTest)
conn: conn
} do
result =
Rpc.run_action(:ash_typescript, conn, %{
"action" => "list_users_map",
"fields" => [
"totalCount",
%{
"results" => ["id", "email", "firstName", "lastName", "isAdmin"]
}
]
})

assert result["success"] == true,
"RPC rejected camelCase for nested array fields. Expected success but got: #{inspect(result)}"

data = result["data"]

assert Map.has_key?(data, "totalCount"), "Expected 'totalCount' in output"
assert Map.has_key?(data, "results"), "Expected 'results' in output"

[first_result | _] = data["results"]
assert Map.has_key?(first_result, "id"), "Expected 'id' in result item"
assert Map.has_key?(first_result, "email"), "Expected 'email' in result item"
assert Map.has_key?(first_result, "firstName"), "Expected 'firstName' in result item"
assert Map.has_key?(first_result, "lastName"), "Expected 'lastName' in result item"
assert Map.has_key?(first_result, "isAdmin"), "Expected 'isAdmin' in result item"
end
end

describe "output field formatting consistency" do
test "output fields are always camelCase regardless of input format", %{conn: conn} do
result =
Rpc.run_action(:ash_typescript, conn, %{
"action" => "get_metrics",
"fields" => ["total", "last_week", "last_month", "last_year"]
})

assert result["success"] == true

data = result["data"]

refute Map.has_key?(data, "last_week"),
"Output should use camelCase 'lastWeek', not snake_case 'last_week'"

refute Map.has_key?(data, "last_month"),
"Output should use camelCase 'lastMonth', not snake_case 'last_month'"

refute Map.has_key?(data, "last_year"),
"Output should use camelCase 'lastYear', not snake_case 'last_year'"
end
end
end
107 changes: 107 additions & 0 deletions test/ash_typescript/rpc/nested_map_field_formatting_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshTypescript.Rpc.NestedMapFieldFormattingTest do
@moduledoc """
Tests for nested map field formatting in TypeScript generation.

Verifies that:
1. Nested map fields in {:array, :map} constraints are properly camelCased
2. Deeply nested map fields are properly camelCased
"""
use ExUnit.Case, async: true

setup_all do
{:ok, generated_content} =
AshTypescript.Rpc.Codegen.generate_typescript_types(:ash_typescript)

{:ok, generated: generated_content}
end

describe "nested map fields in {:array, :map} should be camelCased" do
test "list_users_map action generates camelCase field names for nested array fields", %{

Check failure on line 23 in test/ash_typescript/rpc/nested_map_field_formatting_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci / mix test

test nested map fields in {:array, :map} should be camelCased list_users_map action generates camelCase field names for nested array fields (AshTypescript.Rpc.NestedMapFieldFormattingTest)
generated: generated
} do
# Check that the output type uses camelCase for nested fields
assert generated =~ "firstName",
"Expected 'firstName' in generated TypeScript but got snake_case"

assert generated =~ "lastName",
"Expected 'lastName' in generated TypeScript but got snake_case"

assert generated =~ "isAdmin",
"Expected 'isAdmin' in generated TypeScript but got snake_case"

assert generated =~ "confirmedAt",
"Expected 'confirmedAt' in generated TypeScript but got snake_case"

assert generated =~ "insertedAt",
"Expected 'insertedAt' in generated TypeScript but got snake_case"

# The top-level field should also be camelCase
assert generated =~ "totalCount",
"Expected 'totalCount' in generated TypeScript but got snake_case"
end

test "nested map fields should NOT contain snake_case versions", %{generated: generated} do
# Look for the NestedMapResource types specifically to avoid false positives
nested_map_types =
generated
|> String.split("\n")
|> Enum.filter(&String.contains?(&1, "NestedMapResource"))
|> Enum.join("\n")

# Check that the generated types don't contain snake_case field names
refute nested_map_types =~ ~r/first_name.*:/,
"Found 'first_name' in NestedMapResource types - should be 'firstName'"

refute nested_map_types =~ ~r/last_name.*:/,
"Found 'last_name' in NestedMapResource types - should be 'lastName'"

refute nested_map_types =~ ~r/is_admin.*:/,
"Found 'is_admin' in NestedMapResource types - should be 'isAdmin'"

refute nested_map_types =~ ~r/confirmed_at.*:/,
"Found 'confirmed_at' in NestedMapResource types - should be 'confirmedAt'"

refute nested_map_types =~ ~r/inserted_at.*:/,
"Found 'inserted_at' in NestedMapResource types - should be 'insertedAt'"

refute nested_map_types =~ ~r/total_count.*:/,
"Found 'total_count' in NestedMapResource types - should be 'totalCount'"
end
end

describe "deeply nested map fields should be camelCased" do
test "get_nested_stats action generates camelCase for deeply nested map fields", %{
generated: generated
} do
# Check top-level nested fields are camelCased
assert generated =~ "userStats",
"Expected 'userStats' in generated TypeScript but got snake_case"

assert generated =~ "contentStats",
"Expected 'contentStats' in generated TypeScript but got snake_case"

# Check deeply nested fields are camelCased
assert generated =~ "activeUsers",
"Expected 'activeUsers' in generated TypeScript but got snake_case"

assert generated =~ "newSignups",
"Expected 'newSignups' in generated TypeScript but got snake_case"

assert generated =~ "churnRate",
"Expected 'churnRate' in generated TypeScript but got snake_case"

assert generated =~ "totalPosts",
"Expected 'totalPosts' in generated TypeScript but got snake_case"

assert generated =~ "postsThisWeek",
"Expected 'postsThisWeek' in generated TypeScript but got snake_case"

assert generated =~ "avgEngagementRate",
"Expected 'avgEngagementRate' in generated TypeScript but got snake_case"
end
end
end
8 changes: 8 additions & 0 deletions test/support/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@
rpc_action :destroy_tenant_setting, :destroy
end

# Test resource for nested map field formatting bugs
resource AshTypescript.Test.NestedMapResource do
rpc_action :list_users_map, :list_users_map
rpc_action :get_metrics, :get_metrics
rpc_action :get_nested_stats, :get_nested_stats
end

Check warning on line 220 in test/support/domain.ex

View workflow job for this annotation

GitHub Actions / ash-ci / mix credo --strict

There should be no trailing white-space at the end of a line.
# Input parsing stress test resource - covers all edge cases for input formatting
resource AshTypescript.Test.InputParsing.Resource do
rpc_action :list_input_parsing, :read
Expand Down Expand Up @@ -243,6 +250,7 @@
resource AshTypescript.Test.Article
resource AshTypescript.Test.Subscription
resource AshTypescript.Test.TenantSetting
resource AshTypescript.Test.NestedMapResource
resource AshTypescript.Test.InputParsing.Resource
end
end
Loading
Loading