mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
- Simplified the formatting of function calls in `encodePatchUpdate.spec.lua` for better readability. - Added multiple new snapshot files to enhance end-to-end testing for reference path changes, including scenarios for adding, removing, and handling nonexistent attributes. - Updated test cases in `apply.rs`, `compute.rs`, and `api.rs` to ensure consistent handling of reference properties and improve clarity in property updates. These changes enhance the maintainability of the test suite and improve the robustness of snapshot testing.
495 lines
15 KiB
Rust
495 lines
15 KiB
Rust
use std::borrow::Cow;
|
|
|
|
use insta::assert_yaml_snapshot;
|
|
|
|
use rbx_dom_weak::{types::Ref, ustr, UstrMap};
|
|
use rojo_insta_ext::RedactionMap;
|
|
|
|
use crate::snapshot::{compute_patch_set, InstanceSnapshot, RojoTree};
|
|
|
|
#[test]
|
|
fn set_name_and_class_name() {
|
|
let mut redactions = RedactionMap::default();
|
|
|
|
let tree = empty_tree();
|
|
redactions.intern(tree.get_root_id());
|
|
|
|
let snapshot = InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("Some Folder"),
|
|
class_name: ustr("Folder"),
|
|
properties: Default::default(),
|
|
children: Vec::new(),
|
|
};
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
|
let patch_value = redactions.redacted_yaml(patch_set);
|
|
|
|
assert_yaml_snapshot!(patch_value);
|
|
}
|
|
|
|
#[test]
|
|
fn set_property() {
|
|
let mut redactions = RedactionMap::default();
|
|
|
|
let tree = empty_tree();
|
|
redactions.intern(tree.get_root_id());
|
|
|
|
let snapshot = InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("ROOT"),
|
|
class_name: ustr("ROOT"),
|
|
properties: UstrMap::from_iter([(ustr("PropertyName"), "Hello, world!".into())]),
|
|
children: Vec::new(),
|
|
};
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
|
let patch_value = redactions.redacted_yaml(patch_set);
|
|
|
|
assert_yaml_snapshot!(patch_value);
|
|
}
|
|
|
|
#[test]
|
|
fn remove_property() {
|
|
let mut redactions = RedactionMap::default();
|
|
|
|
let mut tree = empty_tree();
|
|
redactions.intern(tree.get_root_id());
|
|
|
|
{
|
|
let root_id = tree.get_root_id();
|
|
let mut root_instance = tree.get_instance_mut(root_id).unwrap();
|
|
root_instance
|
|
.properties_mut()
|
|
.insert(ustr("Foo"), "This should be removed by the patch.".into());
|
|
}
|
|
|
|
let snapshot = InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("ROOT"),
|
|
class_name: ustr("ROOT"),
|
|
properties: Default::default(),
|
|
children: Vec::new(),
|
|
};
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
|
let patch_value = redactions.redacted_yaml(patch_set);
|
|
|
|
assert_yaml_snapshot!(patch_value);
|
|
}
|
|
|
|
#[test]
|
|
fn add_child() {
|
|
let mut redactions = RedactionMap::default();
|
|
|
|
let tree = empty_tree();
|
|
redactions.intern(tree.get_root_id());
|
|
|
|
let snapshot = InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("ROOT"),
|
|
class_name: ustr("ROOT"),
|
|
properties: Default::default(),
|
|
children: vec![InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("New"),
|
|
class_name: ustr("Folder"),
|
|
properties: Default::default(),
|
|
children: Vec::new(),
|
|
}],
|
|
};
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
|
let patch_value = redactions.redacted_yaml(patch_set);
|
|
|
|
assert_yaml_snapshot!(patch_value);
|
|
}
|
|
|
|
#[test]
|
|
fn remove_child() {
|
|
let mut redactions = RedactionMap::default();
|
|
|
|
let mut tree = empty_tree();
|
|
redactions.intern(tree.get_root_id());
|
|
|
|
{
|
|
let root_id = tree.get_root_id();
|
|
let new_id = tree.insert_instance(
|
|
root_id,
|
|
InstanceSnapshot::new().name("Should not appear in snapshot"),
|
|
);
|
|
|
|
redactions.intern(new_id);
|
|
}
|
|
|
|
let snapshot = InstanceSnapshot {
|
|
snapshot_id: Ref::none(),
|
|
metadata: Default::default(),
|
|
name: Cow::Borrowed("ROOT"),
|
|
class_name: ustr("ROOT"),
|
|
properties: Default::default(),
|
|
children: Vec::new(),
|
|
};
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
|
let patch_value = redactions.redacted_yaml(patch_set);
|
|
|
|
assert_yaml_snapshot!(patch_value);
|
|
}
|
|
|
|
fn empty_tree() -> RojoTree {
|
|
RojoTree::new(InstanceSnapshot::new().name("ROOT").class_name("ROOT"))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Rojo_Ref_* attribute resolution tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
use rbx_dom_weak::types::{Attributes, Variant};
|
|
|
|
/// Helper: build a tree with ROOT > Workspace > Part (to support path resolution).
|
|
fn tree_with_workspace_part() -> RojoTree {
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("ROOT")
|
|
.class_name("DataModel")
|
|
.children(vec![InstanceSnapshot::new()
|
|
.name("Workspace")
|
|
.class_name("Workspace")
|
|
.children(vec![InstanceSnapshot::new()
|
|
.name("Part")
|
|
.class_name("Part")])]);
|
|
RojoTree::new(snapshot)
|
|
}
|
|
|
|
/// Helper: build a tree with ROOT > Workspace > Model > Target + OtherPart.
|
|
fn tree_with_model_and_parts() -> RojoTree {
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("ROOT")
|
|
.class_name("DataModel")
|
|
.children(vec![InstanceSnapshot::new()
|
|
.name("Workspace")
|
|
.class_name("Workspace")
|
|
.children(vec![InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.children(vec![
|
|
InstanceSnapshot::new().name("Target").class_name("Part"),
|
|
InstanceSnapshot::new().name("OtherPart").class_name("Part"),
|
|
])])]);
|
|
RojoTree::new(snapshot)
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_resolves_primary_part() {
|
|
let tree = tree_with_model_and_parts();
|
|
|
|
// Find the Model instance in the tree
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
let model_id = tree
|
|
.get_instance(workspace_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
// Create a snapshot of Model with Rojo_Ref_PrimaryPart attribute
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert(
|
|
"Rojo_Ref_PrimaryPart".into(),
|
|
Variant::String("Workspace/Model/Target".into()),
|
|
);
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]))
|
|
.children(vec![
|
|
InstanceSnapshot::new().name("Target").class_name("Part"),
|
|
InstanceSnapshot::new().name("OtherPart").class_name("Part"),
|
|
]);
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, model_id);
|
|
|
|
// The patch should contain an update with PrimaryPart as a Variant::Ref
|
|
let has_primary_part_ref = patch_set.updated_instances.iter().any(|u| {
|
|
u.changed_properties
|
|
.get(&ustr("PrimaryPart"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(r)) if r.is_some()))
|
|
});
|
|
assert!(
|
|
has_primary_part_ref,
|
|
"Rojo_Ref_PrimaryPart should resolve to a Variant::Ref in the patch"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_resolves_value() {
|
|
let tree = tree_with_workspace_part();
|
|
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
// Add an ObjectValue child to Workspace in the tree
|
|
let mut tree = tree;
|
|
let objval_id = tree.insert_instance(
|
|
workspace_id,
|
|
InstanceSnapshot::new()
|
|
.name("MyRef")
|
|
.class_name("ObjectValue"),
|
|
);
|
|
|
|
// Create snapshot with Rojo_Ref_Value pointing to Workspace/Part
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert(
|
|
"Rojo_Ref_Value".into(),
|
|
Variant::String("Workspace/Part".into()),
|
|
);
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("MyRef")
|
|
.class_name("ObjectValue")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]));
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, objval_id);
|
|
|
|
let has_value_ref = patch_set.updated_instances.iter().any(|u| {
|
|
u.changed_properties
|
|
.get(&ustr("Value"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(r)) if r.is_some()))
|
|
});
|
|
assert!(
|
|
has_value_ref,
|
|
"Rojo_Ref_Value should resolve to a Variant::Ref in the patch"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_multiple_on_same_instance() {
|
|
let tree = tree_with_model_and_parts();
|
|
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
let model_id = tree
|
|
.get_instance(workspace_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
// Multiple Rojo_Ref_* attributes
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert(
|
|
"Rojo_Ref_PrimaryPart".into(),
|
|
Variant::String("Workspace/Model/Target".into()),
|
|
);
|
|
attrs.insert(
|
|
"Rojo_Ref_CustomRef".into(),
|
|
Variant::String("Workspace/Model/OtherPart".into()),
|
|
);
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]))
|
|
.children(vec![
|
|
InstanceSnapshot::new().name("Target").class_name("Part"),
|
|
InstanceSnapshot::new().name("OtherPart").class_name("Part"),
|
|
]);
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, model_id);
|
|
|
|
let update = patch_set
|
|
.updated_instances
|
|
.iter()
|
|
.find(|u| u.id == model_id);
|
|
assert!(update.is_some(), "Should have an update for the Model");
|
|
|
|
let update = update.unwrap();
|
|
let has_pp = update
|
|
.changed_properties
|
|
.get(&ustr("PrimaryPart"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(r)) if r.is_some()));
|
|
let has_custom = update
|
|
.changed_properties
|
|
.get(&ustr("CustomRef"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(r)) if r.is_some()));
|
|
|
|
assert!(has_pp, "PrimaryPart ref should resolve");
|
|
assert!(has_custom, "CustomRef ref should resolve");
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_nonexistent_path_does_not_produce_valid_ref() {
|
|
let tree = tree_with_workspace_part();
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
let mut tree = tree;
|
|
let model_id = tree.insert_instance(
|
|
workspace_id,
|
|
InstanceSnapshot::new().name("Model").class_name("Model"),
|
|
);
|
|
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert(
|
|
"Rojo_Ref_PrimaryPart".into(),
|
|
Variant::String("Workspace/DoesNotExist".into()),
|
|
);
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]));
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, model_id);
|
|
|
|
// A non-existent path should NOT produce a valid (non-nil) Ref.
|
|
// compute_ref_properties returns None for unresolvable paths, which
|
|
// means "property value not set" -- not "set to nil Ref".
|
|
let has_valid_ref = patch_set.updated_instances.iter().any(|u| {
|
|
u.changed_properties
|
|
.get(&ustr("PrimaryPart"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(r)) if r.is_some()))
|
|
});
|
|
assert!(
|
|
!has_valid_ref,
|
|
"Non-existent path should not produce a valid (non-nil) Ref"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_with_regular_attributes_mixed() {
|
|
let tree = tree_with_model_and_parts();
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
let model_id = tree
|
|
.get_instance(workspace_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
// Mix of Rojo_Ref_* and regular attributes
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert(
|
|
"Rojo_Ref_PrimaryPart".into(),
|
|
Variant::String("Workspace/Model/Target".into()),
|
|
);
|
|
attrs.insert("CustomAttribute".into(), Variant::String("hello".into()));
|
|
attrs.insert("NumberAttr".into(), Variant::Float64(42.0));
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]))
|
|
.children(vec![
|
|
InstanceSnapshot::new().name("Target").class_name("Part"),
|
|
InstanceSnapshot::new().name("OtherPart").class_name("Part"),
|
|
]);
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, model_id);
|
|
|
|
// Should have PrimaryPart as Ref AND the regular attributes should
|
|
// remain in the Attributes property (not be extracted as Ref properties).
|
|
let has_pp = patch_set.updated_instances.iter().any(|u| {
|
|
u.changed_properties
|
|
.get(&ustr("PrimaryPart"))
|
|
.map_or(false, |v| matches!(v, Some(Variant::Ref(_))))
|
|
});
|
|
assert!(has_pp, "PrimaryPart should be resolved from Rojo_Ref_*");
|
|
}
|
|
|
|
#[test]
|
|
fn ref_attr_empty_path_resolves_to_root() {
|
|
let tree = tree_with_workspace_part();
|
|
let root_id = tree.get_root_id();
|
|
let workspace_id = tree
|
|
.get_instance(root_id)
|
|
.unwrap()
|
|
.children()
|
|
.first()
|
|
.copied()
|
|
.unwrap();
|
|
|
|
let mut tree = tree;
|
|
let model_id = tree.insert_instance(
|
|
workspace_id,
|
|
InstanceSnapshot::new().name("Model").class_name("Model"),
|
|
);
|
|
|
|
let mut attrs = Attributes::new();
|
|
attrs.insert("Rojo_Ref_PrimaryPart".into(), Variant::String("".into()));
|
|
|
|
let snapshot = InstanceSnapshot::new()
|
|
.name("Model")
|
|
.class_name("Model")
|
|
.properties(UstrMap::from_iter([(
|
|
ustr("Attributes"),
|
|
Variant::Attributes(attrs),
|
|
)]));
|
|
|
|
let patch_set = compute_patch_set(Some(snapshot), &tree, model_id);
|
|
|
|
// Empty path should resolve to root (RojoTree.get_instance_by_path("") returns root)
|
|
let has_ref = patch_set.updated_instances.iter().any(|u| {
|
|
u.changed_properties.get(&ustr("PrimaryPart")).map_or(
|
|
false,
|
|
|v| matches!(v, Some(Variant::Ref(r)) if *r == root_id),
|
|
)
|
|
});
|
|
assert!(has_ref, "Empty path should resolve to root instance");
|
|
}
|