diff --git a/README.md b/README.md index ed3a40b..5d366cc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # Statifier - SCXML State Machines for Elixir -[![CI](https://github.com/riddler/statifier/workflows/CI/badge.svg?branch=main)](https://github.com/riddler/statifier/actions) -[![Coverage](https://codecov.io/gh/riddler/statifier/branch/main/graph/badge.svg)](https://codecov.io/gh/riddler/statifier) +[![CI](https://github.com/riddler/statifier/actions/workflows/ci.yml/badge.svg)](https://github.com/riddler/statifier/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/riddler/statifier/branch/main/graph/badge.svg)](https://codecov.io/gh/riddler/statifier) +[![Hex.pm Version](https://img.shields.io/hexpm/v/statifier.svg)](https://hex.pm/packages/statifier) +[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/statifier/) + +> ⚠️ **Active Development Notice** +> This project is still under active development and things may change even though it is passed version 1. APIs, features, and behaviors may evolve as we continue improving SCXML compliance and functionality. An Elixir implementation of SCXML (State Chart XML) state charts with a focus on W3C compliance. diff --git a/config/config.exs b/config/config.exs index 4603fb2..9bfdea8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,17 +7,7 @@ if config_env() == :dev do config :logger, :console, format: "[$level] $message $metadata\n", - metadata: [ - :current_state, - :event, - :action_type, - :state_id, - :phase, - :transition_type, - :source_state, - :condition, - :targets - ] + metadata: :all # Default Statifier logging configuration for dev config :statifier, diff --git a/lib/statifier/parser/scxml/state_stack.ex b/lib/statifier/parser/scxml/state_stack.ex index 8a4122b..9d69231 100644 --- a/lib/statifier/parser/scxml/state_stack.ex +++ b/lib/statifier/parser/scxml/state_stack.ex @@ -6,6 +6,8 @@ defmodule Statifier.Parser.SCXML.StateStack do and updating parent elements when child elements are completed. """ + alias Statifier.Actions.IfAction + @doc """ Handle the end of a state element by adding it to its parent. """ @@ -584,6 +586,17 @@ defmodule Statifier.Parser.SCXML.StateStack do {:ok, %{state | stack: [{"transition", updated_transition} | rest]}} end + # Handle nested if action within parent if container + def handle_if_end( + %{stack: [{_element_name, nested_if_container} | [{"if", parent_if_container} | rest]]} = + state + ) do + # Create IfAction from nested if container and add to parent if container + nested_if_action = create_if_action_from_container(nested_if_container) + updated_parent_container = add_action_to_current_block(parent_if_container, nested_if_action) + {:ok, %{state | stack: [{"if", updated_parent_container} | rest]}} + end + def handle_if_end(state) do # If element not in an onentry/onexit context, just pop it {:ok, pop_element(state)} @@ -842,7 +855,6 @@ defmodule Statifier.Parser.SCXML.StateStack do end) # Create IfAction with collected blocks - alias Statifier.Actions.IfAction IfAction.new(conditional_blocks, if_container[:location]) end diff --git a/test/passing_tests.json b/test/passing_tests.json index 3a96a73..e40280a 100644 --- a/test/passing_tests.json +++ b/test/passing_tests.json @@ -5,7 +5,7 @@ "test/statifier/**/*_test.exs", "test/mix/**/*_test.exs" ], - "last_updated": "2025-08-31", + "last_updated": "2025-09-02", "scion_tests": [ "test/scion_tests/actionSend/send1_test.exs", "test/scion_tests/actionSend/send2_test.exs", @@ -46,6 +46,7 @@ "test/scion_tests/history/history4b_test.exs", "test/scion_tests/history/history5_test.exs", "test/scion_tests/history/history6_test.exs", + "test/scion_tests/if_else/test0_test.exs", "test/scion_tests/misc/deep_initial_test.exs", "test/scion_tests/more_parallel/test0_test.exs", "test/scion_tests/more_parallel/test10_test.exs", diff --git a/test/statifier/parser/scxml/state_stack_if_test.exs b/test/statifier/parser/scxml/state_stack_if_test.exs index d737da3..13a5c4e 100644 --- a/test/statifier/parser/scxml/state_stack_if_test.exs +++ b/test/statifier/parser/scxml/state_stack_if_test.exs @@ -289,5 +289,93 @@ defmodule Statifier.Parser.SCXML.StateStackIfTest do assert length(current_block.actions) == 1 assert hd(current_block.actions) == assign_action end + + test "nested if within parent if container" do + # Test nested if action added to current conditional block within parent if container + nested_if_container = %{ + conditional_blocks: [ + %{ + type: :if, + cond: "x === 40", + actions: [ + %AssignAction{location: "x", expr: "x + 10"}, + %LogAction{label: "nested", expr: "x"} + ] + } + ], + current_block_index: 0, + location: %{line: 2, column: 5} + } + + parent_if_container = %{ + conditional_blocks: [ + %{ + type: :if, + cond: "x === 28", + actions: [ + %AssignAction{location: "x", expr: "x + 12"}, + %LogAction{label: "after_assign", expr: "x"} + ] + } + ], + current_block_index: 0, + location: %{line: 1, column: 1} + } + + state = %{ + stack: [ + {"if", nested_if_container}, + {"if", parent_if_container}, + {"onentry", :onentry_block}, + {"state", %Statifier.State{id: "test_state"}} + ] + } + + {:ok, result} = StateStack.handle_if_end(state) + + # Should have parent if container with nested if added as an action + [{"if", updated_parent_container} | _rest] = result.stack + current_block = Enum.at(updated_parent_container.conditional_blocks, 0) + + # Should have 3 actions: assign, log, nested_if_action + assert length(current_block.actions) == 3 + + # The last action should be the nested IfAction + nested_if_action = List.last(current_block.actions) + assert match?(%Statifier.Actions.IfAction{}, nested_if_action) + + # Verify the nested IfAction has correct structure + assert length(nested_if_action.conditional_blocks) == 1 + nested_block = hd(nested_if_action.conditional_blocks) + assert nested_block[:type] == :if + assert nested_block[:cond] == "x === 40" + assert length(nested_block[:actions]) == 2 + end + + test "nested if not in parent if context" do + # Test nested if element not within parent if container (fallback case) + nested_if_container = %{ + conditional_blocks: [ + %{type: :if, cond: "true", actions: [%LogAction{label: "test", expr: "value"}]} + ], + current_block_index: 0, + location: %{line: 1, column: 1} + } + + state = %{ + stack: [ + {"if", nested_if_container}, + {"onentry", :onentry_block}, + {"state", %Statifier.State{id: "test_state"}} + ] + } + + {:ok, result} = StateStack.handle_if_end(state) + + # Should create IfAction and add to onentry (normal if handling) + [{"onentry", actions} | _rest] = result.stack + assert length(actions) == 1 + assert match?(%Statifier.Actions.IfAction{}, hd(actions)) + end end end