Skip to content
Merged
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
2 changes: 2 additions & 0 deletions client/src/types/fsm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type LJStateMachine = {
// index of the state set among the class's @StateSet annotations, in declaration order
stateSet?: number;
initialTransitions: { to: string; toCondition?: string | null; constructorSignature?: string | null }[];
// methods that put an object in an unknown state (a field, a parameter or a getter's result) into a state
entryTransitions?: { to: string; label: string; toCondition?: string | null }[];
states: string[];
transitions: { from: string; to: string; label: string; fromCondition?: string | null; toCondition?: string | null }[];
// the state error on this state set, null if the call does not involve it
Expand Down
11 changes: 11 additions & 0 deletions client/src/webview/diagram-focus.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ describe('state error focus', () => {
expect(full).not.toMatch(/ljStateDimmed|lj-dimmed/);
});

it('draws entry transitions from the initial pseudo state, dimmed when focused', () => {
// a getter that puts a tree node in an unknown state into maybeHasChildren when it returns false
const sm = { ...treeNode(), entryTransitions: [{ to: 'maybeHasChildren', label: 'isLeaf', toCondition: '!return' }] };

expect(lines(createMermaidDiagram(sm, 'TB', false, false))).toContain('[*] --> maybeHasChildren : isLeaf');
expect(lines(createMermaidDiagram(sm, 'TB', true, false))).toContain(
'[*] --> maybeHasChildren : isLeaf<br/><span class="state-cond state-cond-post">!return</span>');
// the constructor and the entry transition come first
expect(dimmedTransitions(createMermaidDiagram(sm, 'TB', false, true))[0]).toMatch(/^%% lj-dimmed-transitions: edge0 edge1\b/);
});

it('only keeps the failed method and the ways out of the possible states bright', () => {
const diagram = lines(createMermaidDiagram(datagramSocket(), 'TB', false, true));

Expand Down
6 changes: 6 additions & 0 deletions client/src/webview/diagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ export function createMermaidDiagram(
const label = getInitialTransitionLabel(transition.constructorSignature, transition.toCondition, showConditions);
addTransition(` [*] --> ${transition.to}${label ? ` : ${label}` : ''}`, relatedStates.length > 0);
});

// entry transitions of objects in an unknown state, also from the initial pseudo state and dimmed in focus mode
sm.entryTransitions?.forEach(transition => {
const label = getTransitionLabel(transition.label, null, transition.toCondition, showConditions);
addTransition(` [*] --> ${transition.to} : ${label}`, relatedStates.length > 0);
});

// in focus mode, only the failed method and the ways out of the possible states stay bright
const isUsefulTransition = (from: string, to: string) => !!errorFocus?.possibleStates.includes(from) && from !== to;
Expand Down
1 change: 1 addition & 0 deletions client/src/webview/views/fsm/fsm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe('state machine view', () => {
['initial transition postcondition', machine({ initialTransitions: [{ to: 'Closed', toCondition: 'size == 0' }] })],
['method precondition', machine({ transitions: [{ from: 'Closed', to: 'Open', label: 'open', fromCondition: 'size > 0' }] })],
['method postcondition', machine({ transitions: [{ from: 'Closed', to: 'Open', label: 'open', toCondition: 'size == 1' }] })],
['entry transition postcondition', machine({ entryTransitions: [{ to: 'Open', label: 'isOpen', toCondition: 'return' }] })],
] as const)('enables condition expansion for an %s', (_, stateMachine) => {
renderStateMachineView(document.body, [stateMachine], [diagram], 'TB', false);

Expand Down
1 change: 1 addition & 0 deletions client/src/webview/views/fsm/fsm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function renderStateMachineViewHtml(sms: LJStateMachine[], diagrams: string[], o
const errorFocuses = getStateErrorFocuses(sms);
const hasConditionExpansions = sms.some(sm =>
sm.initialTransitions.some(transition => !!transition.toCondition)
|| sm.entryTransitions?.some(transition => !!transition.toCondition)
|| sm.transitions.some(transition => !!transition.fromCondition || !!transition.toCondition));
const conditionToggleLabel = !hasConditionExpansions
? 'No additional conditions to expand'
Expand Down
2 changes: 1 addition & 1 deletion server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
<dependency>
<groupId>io.github.liquid-java</groupId>
<artifactId>liquidjava-fsm</artifactId>
<version>0.2.1</version>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>tools.aqua</groupId>
Expand Down
5 changes: 4 additions & 1 deletion server/src/main/java/dtos/fsm/StateMachineDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import liquidjava.diagnostics.errors.StateErrorContext;
import liquidjava.fsm.StateMachine;
import liquidjava.fsm.StateMachineEntryTransition;
import liquidjava.fsm.StateMachineInitialTransition;
import liquidjava.fsm.StateMachineTransition;

/**
* DTO for serializing the state machine diagram of one state set, with the state error shown on it
*
* @param stateSet the index of the state set among the class's StateSet annotations
* @param entryTransitions the methods that put an object in an unknown state into a state of this state set
* @param errorContext the state error on this diagram, or null if the call does not involve this state set
*/
public record StateMachineDTO(
Expand All @@ -19,12 +21,13 @@ public record StateMachineDTO(
List<String> states,
List<StateMachineTransition> transitions,
List<StateMachineInitialTransition> initialTransitions,
List<StateMachineEntryTransition> entryTransitions,
StateErrorContextDTO errorContext) {

public static StateMachineDTO from(StateMachine sm, StateErrorContext context) {
if (sm == null) return null;
return new StateMachineDTO(sm.className(), sm.stateSet(), sm.states(), sm.transitions(),
sm.initialTransitions(), StateErrorContextDTO.from(context, sm.states(), sm.stateSet()));
sm.initialTransitions(), sm.entryTransitions(), StateErrorContextDTO.from(context, sm.states(), sm.stateSet()));
}

/**
Expand Down
10 changes: 10 additions & 0 deletions server/src/test/java/dtos/fsm/StateMachineDTOTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import liquidjava.diagnostics.errors.StateErrorContext;
import liquidjava.fsm.StateMachine;
import liquidjava.fsm.StateMachineEntryTransition;
import liquidjava.fsm.StateMachineInitialTransition;
import liquidjava.fsm.StateMachineTransition;

Expand Down Expand Up @@ -51,6 +52,15 @@ void showsTheErrorOnEveryStateSetWhenNoneHasTheCalledMethod() {
assertNotNull(dtos.get(1).errorContext());
}

@Test
void keepsTheEntryTransitions() {
StateMachine sm = new StateMachine("example.Edit", 0, List.of("start", "endDone"),
List.of(new StateMachineTransition("start", "endDone", "end")),
List.of(new StateMachineInitialTransition("start")),
List.of(new StateMachineEntryTransition("endDone", "canUndo", "return")));
assertEquals(sm.entryTransitions(), StateMachineDTO.from(sm, null).entryTransitions());
}

@Test
void hasNoErrorWithoutAContext() {
List<StateMachineDTO> dtos = StateMachineDTO.from(List.of(TILING, COMPRESSION), null);
Expand Down
Loading