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
13 changes: 13 additions & 0 deletions .github/instructions/dotnet.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,22 @@ To provide the broadest compatiblity, the projects target .NET Standard 2.0, all
- Avoid catching generic `Exception` types; catch specific exceptions
- Log exceptions with sufficient context for troubleshooting

### RowAction Semantics
Understanding RowAction values is critical for proper row state management in ScriptLink operations. The RowAction determines which rows are included in the response payload sent back to myAvatar:
- **RowActions.None** (`""` empty string or `null`): No action pending on the row. Both empty string and null are treated equivalently as None. Rows with RowAction.None are **excluded from the response payload** and not sent back to myAvatar, effectively preserving them as-is. When modifying fields in a row initially in None state, set it to RowActions.Edit to include it in the response payload.
- **RowActions.Add** (`"ADD"`): A new row is being added to the form. This row **is included in the response payload** to instruct myAvatar to add it. Preserve this action when modifying the row; do not override with Edit.
- **RowActions.Edit** (`"EDIT"`): An existing row is being modified. This row **is included in the response payload** to instruct myAvatar to update it. Set a row's RowAction to Edit when making field changes on a row that was previously in None state to ensure the modifications are sent to myAvatar.
- **RowActions.Delete** (`"DELETE"`): The row is marked for removal from the form. This row **is included in the response payload** to instruct myAvatar to delete it. Do not change this action even if fields are modified; the row deletion takes precedence.

When implementing helper methods that modify rows (e.g., `DisableAllFieldObjects`, `SetFieldValue`):
- Only set RowAction to Edit if it's currently None (use `string.IsNullOrEmpty()` to check for None), so the modified row is included in the response payload
- Preserve existing RowAction values (Add, Edit, Delete) to avoid overriding the intended operation
- This ensures that a row marked for deletion remains marked for deletion, a row being added retains that intent, and rows with no changes are not unnecessarily included in the response

### Documentation
- Include XML documentation for all public types and members
- Document non-obvious logic with inline comments
- Explain why certain decisions are made, especially regarding RowAction handling
- Include examples in XML documentation for complex methods
- Keep documentation up-to-date with code changes
- Document breaking changes and migration paths in release notes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@
FormId = "1"
};
var decorator = new FormObjectDecorator(formObject);
Assert.ThrowsException<ArgumentNullException>(() => decorator.DeleteRowObject((string)null));

Check warning on line 1093 in dotnet/RarelySimple.AvatarScriptLink.Net.Tests/Decorators/FormObjectDecoratorTests.cs

View workflow job for this annotation

GitHub Actions / Build / build

Converting null literal or possible null value to non-nullable type.

Check warning on line 1093 in dotnet/RarelySimple.AvatarScriptLink.Net.Tests/Decorators/FormObjectDecoratorTests.cs

View workflow job for this annotation

GitHub Actions / Build / build

Converting null literal or possible null value to non-nullable type.

Check warning on line 1093 in dotnet/RarelySimple.AvatarScriptLink.Net.Tests/Decorators/FormObjectDecoratorTests.cs

View workflow job for this annotation

GitHub Actions / Build / build

Converting null literal or possible null value to non-nullable type.
}

[TestMethod]
Expand Down Expand Up @@ -1190,4 +1190,246 @@
}

#endregion

#region DisableAllFieldObjects

[TestMethod]
public void DisableAllFieldObjects_NullExclusionList_ThrowsException()
{
var fieldObject = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var rowObject = new RowObject()
{
Fields = [fieldObject],
RowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = rowObject
};
var decorator = new FormObjectDecorator(formObject);
Assert.ThrowsException<ArgumentNullException>(() => decorator.DisableAllFieldObjects(null));
}

[TestMethod]
public void DisableAllFieldObjects_DisablesFieldsInCurrentRow()
{
var fieldObject = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var rowObject = new RowObject()
{
Fields = [fieldObject],
RowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = rowObject
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string>());
Assert.IsFalse(decorator.CurrentRow.Fields[0].Enabled);
}

[TestMethod]
public void DisableAllFieldObjects_DisablesFieldsInOtherRowsWhenMultipleIteration()
{
var fieldObject1 = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var fieldObject2 = new FieldObject()
{
Enabled = "1",
FieldNumber = "124",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var currentRow = new RowObject()
{
Fields = [fieldObject1],
RowId = "1"
};
var otherRow = new RowObject()
{
Fields = [fieldObject2],
RowId = "2",
ParentRowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = currentRow,
MultipleIteration = true,
OtherRows = [otherRow]
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string>());
Assert.IsFalse(decorator.CurrentRow.Fields[0].Enabled);
Assert.IsFalse(decorator.OtherRows[0].Fields[0].Enabled);
}

[TestMethod]
public void DisableAllFieldObjects_DisablesFieldsInOtherRowsEvenWhenNotMultipleIteration()
{
var fieldObject1 = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var fieldObject2 = new FieldObject()
{
Enabled = "1",
FieldNumber = "124",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var currentRow = new RowObject()
{
Fields = [fieldObject1],
RowId = "1"
};
var otherRow = new RowObject()
{
Fields = [fieldObject2],
RowId = "2",
ParentRowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = currentRow,
MultipleIteration = false,
OtherRows = [otherRow]
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string>());
Assert.IsFalse(decorator.CurrentRow.Fields[0].Enabled);
Assert.IsFalse(decorator.OtherRows[0].Fields[0].Enabled);
}

[TestMethod]
public void DisableAllFieldObjects_ExcludedFieldsRemainEnabled()
{
var fieldObject1 = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var fieldObject2 = new FieldObject()
{
Enabled = "1",
FieldNumber = "124",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var rowObject = new RowObject()
{
Fields = [fieldObject1, fieldObject2],
RowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = rowObject
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string> { "123" });
Assert.IsTrue(decorator.CurrentRow.Fields[0].Enabled);
Assert.IsFalse(decorator.CurrentRow.Fields[1].Enabled);
}

[TestMethod]
public void DisableAllFieldObjects_WithNullCurrentRow()
{
var formObject = new FormObject()
{
FormId = "1"
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string>());
Assert.IsNull(decorator.CurrentRow);
}

[TestMethod]
public void DisableAllFieldObjects_SetsRowActionToEdit()
{
var fieldObject = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var rowObject = new RowObject()
{
Fields = [fieldObject],
RowId = "1",
RowAction = ""
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = rowObject
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects(new List<string>());
Assert.AreEqual(RowActions.Edit, decorator.CurrentRow.RowAction);
}

[TestMethod]
public void DisableAllFieldObjects_WithoutExclusionList()
{
var fieldObject = new FieldObject()
{
Enabled = "1",
FieldNumber = "123",
FieldValue = "test",
Lock = "0",
Required = "0"
};
var rowObject = new RowObject()
{
Fields = [fieldObject],
RowId = "1"
};
var formObject = new FormObject()
{
FormId = "1",
CurrentRow = rowObject
};
var decorator = new FormObjectDecorator(formObject);
decorator.DisableAllFieldObjects();
Assert.IsFalse(decorator.CurrentRow.Fields[0].Enabled);
Assert.AreEqual(RowActions.Edit, decorator.CurrentRow.RowAction);
}

#endregion
}
Loading
Loading