修复UI设计状态浮点回读失败
统一 UI State 写入与严格回读的浮点序列化表示 补充拖拽 Transform 与灵体素材保存回归测试 记录 UI State 浮点 JSON 校验排障经验
This commit is contained in:
@@ -284,8 +284,9 @@ fn read_ui_design_document_path(path: &Path) -> Result<PersistedUiDesignState, S
|
||||
.map_err(|error| format!("解析 UI 设计 State 失败:{}: {error}", path.display()))?;
|
||||
let document: PersistedUiDesignState = serde_json::from_value(value.clone())
|
||||
.map_err(|error| format!("解析 UI 设计 State 契约失败:{}: {error}", path.display()))?;
|
||||
let canonical = serde_json::to_value(&document)
|
||||
.map_err(|error| format!("序列化 UI 设计 State 契约失败:{error}"))?;
|
||||
let canonical: serde_json::Value =
|
||||
serde_json::from_slice(&serialize_ui_design_document(&document)?)
|
||||
.map_err(|error| format!("序列化 UI 设计 State 契约失败:{error}"))?;
|
||||
if value != canonical {
|
||||
return Err("UI 设计 State 包含不受当前 schema 支持的字段或值".to_string());
|
||||
}
|
||||
@@ -701,6 +702,99 @@ mod tests {
|
||||
.expect("deserialize valid UI design state")
|
||||
}
|
||||
|
||||
fn state_with_sprite_and_dragged_transform() -> State {
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"ui_trees": [{
|
||||
"src_ui_design": "page",
|
||||
"root": {
|
||||
"id": "root",
|
||||
"layout": {
|
||||
"transform": {
|
||||
"anchor_min": [0.0, 0.0],
|
||||
"anchor_max": [1.0, 1.0],
|
||||
"offset_min": [0.0, 0.0],
|
||||
"offset_max": [0.0, 0.0]
|
||||
},
|
||||
"custom_minimum_size": [0.0, 0.0],
|
||||
"size_flags_horizontal": 1,
|
||||
"size_flags_vertical": 1,
|
||||
"size_flags_stretch_ratio": 1.0,
|
||||
"container": "None"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "页面根节点",
|
||||
"description": "",
|
||||
"layout_status": "Passed",
|
||||
"components_status": "Passed",
|
||||
"allow_llm_edit_layout": true,
|
||||
"allow_llm_edit_component": true,
|
||||
"source": "System"
|
||||
},
|
||||
"components": [],
|
||||
"children_display_mode": "Stack",
|
||||
"children": [{
|
||||
"id": "dragged-node",
|
||||
"layout": {
|
||||
"transform": {
|
||||
"anchor_min": [0.5, 0.5],
|
||||
"anchor_max": [0.5, 0.5],
|
||||
"offset_min": [-248.53181076672104, -104.40456769983687],
|
||||
"offset_max": [348.53181076672104, 204.40456769983687]
|
||||
},
|
||||
"custom_minimum_size": [0.0, 0.0],
|
||||
"size_flags_horizontal": 1,
|
||||
"size_flags_vertical": 1,
|
||||
"size_flags_stretch_ratio": 1.0,
|
||||
"container": "None"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "拖拽节点",
|
||||
"description": "",
|
||||
"layout_status": "Passed",
|
||||
"components_status": "Passed",
|
||||
"allow_llm_edit_layout": true,
|
||||
"allow_llm_edit_component": true,
|
||||
"source": "Human"
|
||||
},
|
||||
"components": [{
|
||||
"Image": {
|
||||
"target_graphic": "spirit",
|
||||
"image_type": { "Simple": { "preserve_aspect": false } }
|
||||
}
|
||||
}],
|
||||
"children_display_mode": "Stack",
|
||||
"children": []
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"ui_design_images": {
|
||||
"page": {
|
||||
"metadata": {
|
||||
"name": "主界面",
|
||||
"description": "",
|
||||
"role": "Page",
|
||||
"slave_to": null
|
||||
},
|
||||
"path": "assets/page.png",
|
||||
"pixel_size": [1280.0, 720.0],
|
||||
"pixels_per_unit": 1.0
|
||||
}
|
||||
},
|
||||
"sprite_assets": {
|
||||
"spirit": {
|
||||
"asset_id": "spirit",
|
||||
"metadata": { "name": "灵体", "asset_type": "角色" },
|
||||
"path": "assets/spirit.png",
|
||||
"pixel_size": [128.0, 256.0],
|
||||
"pixels_per_unit": 1.0,
|
||||
"border": { "left": 0, "right": 0, "top": 0, "bottom": 0 }
|
||||
}
|
||||
},
|
||||
"font_assets": {}
|
||||
}))
|
||||
.expect("deserialize dragged UI design state")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saves_loads_and_compares_per_resource_revisions() {
|
||||
let (directory, asset_id) = fixture();
|
||||
@@ -745,6 +839,45 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_sprite_asset_when_saving_a_dragged_node_transform() {
|
||||
let (directory, asset_id) = fixture();
|
||||
let state = state_with_sprite_and_dragged_transform();
|
||||
|
||||
let expected_document = PersistedUiDesignState {
|
||||
schema_version: UI_DESIGN_STATE_SCHEMA_VERSION.to_string(),
|
||||
project_id: PROJECT_ID.to_string(),
|
||||
asset_id: asset_id.clone(),
|
||||
revision: 1,
|
||||
state: state.clone(),
|
||||
};
|
||||
let serialized = serialize_ui_design_document(&expected_document)
|
||||
.expect("serialize dragged state fixture");
|
||||
let decoded: PersistedUiDesignState =
|
||||
serde_json::from_slice(&serialized).expect("deserialize dragged state fixture");
|
||||
assert_eq!(decoded, expected_document);
|
||||
|
||||
let saved = save_ui_design_state_at(input(directory.path(), &asset_id, 0, state.clone()))
|
||||
.expect("save dragged state with sprite asset");
|
||||
assert!(matches!(
|
||||
saved,
|
||||
SaveUiDesignStateResult::Saved {
|
||||
revision: 1,
|
||||
state: ref installed,
|
||||
..
|
||||
} if installed == &state
|
||||
));
|
||||
|
||||
let loaded = load_ui_design_state_at(LoadUiDesignStateInput {
|
||||
project_path: directory.path().to_string_lossy().into_owned(),
|
||||
expected_project_id: PROJECT_ID.to_string(),
|
||||
asset_id,
|
||||
})
|
||||
.expect("reload dragged state with sprite asset");
|
||||
assert_eq!(loaded.revision, 1);
|
||||
assert_eq!(loaded.state, state);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovers_last_valid_state_when_primary_is_corrupt() {
|
||||
let (directory, asset_id) = fixture();
|
||||
|
||||
Reference in New Issue
Block a user