Files
hcie-rust-v3.05/hcie-iced-app/crates/hcie-iced-gui/tests/feature_scorecard.rs
T

344 lines
14 KiB
Rust
Raw Normal View History

//! Stable Cycle 0 feature identifiers and source-level regression gates.
//!
//! **Purpose:** Prevents the highest-risk placeholder behaviors from returning while broader GUI
//! parity work proceeds. **Logic & Workflow:** Each test owns a stable feature ID and inspects the
//! production source for architectural invariants that complement module-level behavior tests.
//! **Side Effects / Dependencies:** Reads source files from the crate at test time; performs no
//! writes and does not launch the GUI.
use std::path::PathBuf;
const MENU_SEMANTIC_DISPATCH: &str = "shell.menu.semantic_dispatch";
const MENU_NO_CUT_COPY_ALIAS: &str = "clipboard.cut.no_copy_alias";
const SCREENSHOT_NATIVE_CAPTURE: &str = "audit.screenshot.native_capture";
const SCREENSHOT_PANEL_CROP: &str = "audit.screenshot.panel_crop";
const PANE_FULL_DROP_TARGETS: &str = "cycle1.pane.full_drop_targets";
const THEME_SYNC_ISOLATION: &str = "cycle1.theme.sync_isolation";
const SIDEBAR_MODE_PERSISTENCE: &str = "cycle1.sidebar.mode_persistence";
const VECTOR_SCREEN_CONSTANT_HANDLES: &str = "cycle2.vector.screen_constant_handles";
const VECTOR_CONSTRAINED_DRAG: &str = "cycle2.vector.constrained_drag";
const VECTOR_LAYER_BOUND_CANCEL: &str = "cycle2.vector.layer_bound_cancel";
const VECTOR_BOOLEAN_RESULT: &str = "cycle2.vector.boolean_result_selection";
const VECTOR_CLAMPED_CONTROLS: &str = "cycle2.vector.clamped_controls";
const SELECTION_IRREGULAR_EDGES: &str = "cycle3.selection.irregular_edges";
const CLIPBOARD_DISTINCT_CUT: &str = "cycle3.clipboard.distinct_cut";
const RASTER_TRANSFORM_PLACEMENT: &str = "cycle3.raster.transform_placement";
const CROP_LIFECYCLE: &str = "cycle3.crop.lifecycle";
const TEXT_MULTILINE: &str = "cycle3.text.multiline";
const TRUE_GRADIENT: &str = "cycle3.gradient.linear_radial";
const CANVAS_INPUT_PARITY: &str = "cycle3.canvas.input_parity";
const PEN_SPRAY_DISTINCT: &str = "cycle3.tools.pen_spray_distinct";
const LAYERS_HIERARCHY_CONTROLS: &str = "cycle4.layers.hierarchy_controls";
const LAYER_STYLES_CANCEL_RESTORE: &str = "cycle4.layer_styles.cancel_restore";
const IMMUTABLE_PIXEL_PREVIEW: &str = "cycle4.preview.immutable_baseline";
const FILTER_SCHEMA_COVERAGE: &str = "cycle4.filters.schema_coverage";
const DOCUMENT_PATH_IDENTITY: &str = "cycle4.documents.path_identity";
const CLOSE_SAVE_CONTINUATION: &str = "cycle4.documents.close_save_continuation";
const AI_REAL_STREAMING: &str = "cycle5.ai.real_streaming";
const AI_REASONING_TRANSCRIPT: &str = "cycle5.ai.reasoning_transcript";
const SCRIPT_VALIDATED_OUTPUT: &str = "cycle5.script.validated_output";
const VIEWER_DETERMINISTIC_NAV: &str = "cycle5.viewer.deterministic_navigation";
const VIEWER_SAFE_EDIT: &str = "cycle5.viewer.safe_edit_document";
/// Reads a crate-relative source file for structural audit assertions.
///
/// **Arguments:** `relative` is a path beneath the package manifest directory.
/// **Returns:** The complete UTF-8 source text.
/// **Side Effects / Dependencies:** Reads one local source file and panics on missing test input.
fn source(relative: &str) -> String {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(relative);
std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()))
}
#[test]
fn semantic_menu_dispatch_has_no_positional_message() {
let app = source("src/app.rs");
let menus = source("src/panels/menus.rs");
assert!(
app.contains("Message::MenuCommand(command)"),
"{MENU_SEMANTIC_DISPATCH}"
);
assert!(
!app.contains("MenuAction(usize, usize)"),
"{MENU_SEMANTIC_DISPATCH}"
);
assert!(
menus.contains("command: Option<MenuCommand>"),
"{MENU_SEMANTIC_DISPATCH}"
);
}
#[test]
fn cut_does_not_dispatch_copy() {
let app = source("src/app.rs");
let menus = source("src/panels/menus.rs");
assert!(
app.contains("MenuCommand::Cut => return Task::perform(async {}, |_| Message::CutImage)"),
"{MENU_NO_CUT_COPY_ALIAS}"
);
assert!(
menus.contains("MenuCommand::Cut"),
"{MENU_NO_CUT_COPY_ALIAS}"
);
assert!(
!menus.contains("MenuItem::with_shortcut(\"Cut\", \"Ctrl+X\")"),
"{MENU_NO_CUT_COPY_ALIAS}"
);
}
#[test]
fn cycle_three_canvas_selection_clipboard_crop_text_paths_are_connected() {
let app = source("src/app.rs");
let canvas = source("src/canvas/mod.rs");
let shader = source("src/canvas/shader_canvas.rs");
let state = source("src/selection/state.rs");
let clipboard = source("src/selection/clipboard.rs");
let crop = source("src/selection/crop.rs");
let raster = source("src/raster.rs");
assert!(
state.contains("pub fn selected_spans")
&& canvas.contains("selection_fill_spans")
&& canvas.contains("Fill exact mask runs"),
"{SELECTION_IRREGULAR_EDGES}"
);
assert!(
app.contains("Message::CutImage")
&& app.contains("Message::ClearPixels")
&& app.contains("Some(Message::CutImage)"),
"{CLIPBOARD_DISTINCT_CUT}"
);
assert!(
clipboard.contains("pub fn centered_transform")
&& app.contains("placed.pos.x += 10.0")
&& app.contains("tr.sanitize_geometry(self.modifiers.shift())"),
"{RASTER_TRANSFORM_PLACEMENT}"
);
assert!(
crop.contains("start_drag_clamped") && app.contains("Message::CropConfirm"),
"{CROP_LIFECYCLE}"
);
assert!(
canvas.contains("iced::widget::text_editor(&draft.editor)")
&& app.contains("Message::TextEditorAction(action)"),
"{TEXT_MULTILINE}"
);
assert!(
raster.contains("pub fn apply_gradient")
&& app.contains("self.settings.tool_settings.gradient_type == 1"),
"{TRUE_GRADIENT}"
);
assert!(
app.contains("paint_tool && (self.modifiers.control()")
&& app.contains("paint_tool && self.modifiers.shift()")
&& app.contains("Message::FitToArea")
&& shader.contains("state.space_left_pan = true"),
"{CANVAS_INPUT_PARITY}"
);
assert!(
app.contains(".draw_pen_segment(")
&& app.contains("tip.spray_particle_size")
&& app.contains("tip.spray_density"),
"{PEN_SPRAY_DISTINCT}"
);
}
#[test]
fn screenshot_cli_uses_native_capture_and_panel_crop() {
let app = source("src/app.rs");
let screenshot = source("src/screenshot.rs");
assert!(
app.contains("iced::window::screenshot(id)"),
"{SCREENSHOT_NATIVE_CAPTURE}"
);
assert!(
app.contains("crate::screenshot::crop_panel("),
"{SCREENSHOT_PANEL_CROP}"
);
assert!(
screenshot.contains(".layout()\n .pane_regions"),
"{SCREENSHOT_PANEL_CROP}"
);
}
#[test]
fn cycle_one_state_paths_are_complete_and_persisted() {
let app = source("src/app.rs");
let settings = source("src/settings.rs");
assert!(
app.contains("self.dock.pane_grid.drop(pane, target)"),
"{PANE_FULL_DROP_TARGETS}"
);
assert!(
!app.contains("Task::perform(async move { theme }, Message::ThemeChanged)"),
"{THEME_SYNC_ISOLATION}"
);
assert!(
app.contains("self.settings.theme_preset = theme"),
"{THEME_SYNC_ISOLATION}"
);
assert!(
settings.contains("pub theme_preset: crate::theme::ThemePreset"),
"{THEME_SYNC_ISOLATION}"
);
assert!(
app.contains("settings.panel_layout.sidebar_expanded"),
"{SIDEBAR_MODE_PERSISTENCE}"
);
assert!(
app.contains("self.settings.panel_layout.sidebar_expanded = self.sidebar_expanded"),
"{SIDEBAR_MODE_PERSISTENCE}"
);
}
/// Verifies the Cycle 2 vector completion paths remain connected to pure calculations and UI.
///
/// Purpose: Provides stable source-level scorecard evidence in addition to calculation unit tests.
/// Logic & Workflow: Checks semantic state/session types and reachable handlers rather than fragile
/// line positions. Side Effects / Dependencies: Reads local production sources only.
#[test]
fn cycle_two_vector_completion_paths_are_connected() {
let app = source("src/app.rs");
let canvas = source("src/canvas/mod.rs");
let edit = source("src/vector_edit.rs");
assert!(
edit.contains("HANDLE_HIT_HALF_PX / zoom") && edit.contains("ROTATE_HIT_RADIUS_PX / zoom"),
"{VECTOR_SCREEN_CONSTANT_HANDLES}"
);
assert!(
app.contains("crate::vector_edit::transform_shape(")
&& app.contains("self.modifiers.shift()")
&& app.contains("self.modifiers.alt()")
&& app.contains("engine.set_vector_shape_bounds(")
&& app.contains("engine.set_vector_shape_angle(")
&& edit.contains("MIN_VECTOR_SIZE"),
"{VECTOR_CONSTRAINED_DRAG}"
);
assert!(
app.contains("get_layer_shapes_direct(snapshot.layer_id)")
&& app.contains("self.vector_drag_session = None"),
"{VECTOR_LAYER_BOUND_CANCEL}"
);
assert!(
app.contains("let succeeded = valid")
&& app.contains("selected_vector_shape = Some(result_index)"),
"{VECTOR_BOOLEAN_RESULT}"
);
assert!(
canvas.contains("pane_w - controls_width") && canvas.contains("Message::VectorShapeDelete"),
"{VECTOR_CLAMPED_CONTROLS}"
);
}
/// Verifies Cycle 4 panel and document-lifecycle paths remain semantic and non-destructive.
///
/// Purpose: Records stable evidence for immutable previews, style cancellation, layer hierarchy,
/// filter schema coverage, and successful-save identity. Logic & Workflow: Inspects the GUI-only
/// implementations for their explicit state and engine-API routing. Side Effects / Dependencies:
/// Reads production source files only.
#[test]
fn cycle_four_panel_and_document_paths_are_connected() {
let app = source("src/app.rs");
let layers = source("src/panels/layers.rs");
let filters = source("src/panels/filters.rs");
let filter_params = source("src/panels/filter_params.rs");
let title_bar = source("src/panels/title_bar.rs");
assert!(
layers.contains("Message::LayerToggleCollapse(info.id)")
&& layers.contains("Message::LayerToggleVisibility(info.id)")
&& layers.contains("Message::LayerToggleLock(info.id)")
&& app.contains("set_layer_parent(active_id, Some(group_id))")
&& app.contains("move_layer(id, target_idx)"),
"{LAYERS_HIERARCHY_CONTROLS}"
);
assert!(
layers.contains("Fill opacity: unavailable in hcie-engine-api")
&& layers.contains("Disabled (engine API): duplicate / mask / rasterize")
&& layers.contains("button(text(\"Dup\")")
&& layers.contains("button(text(\"Mask\")")
&& layers.contains("button(text(\"Raster\")"),
"{LAYERS_HIERARCHY_CONTROLS}: unsupported operations must be visibly disabled"
);
assert!(
app.contains("layer_style_baseline: Option<LayerStyleBaseline>")
&& app.contains("remove_layer_style_by_index(baseline.layer_id, index)")
&& app.contains("doc.engine.update_layer_style(baseline.layer_id, style)"),
"{LAYER_STYLES_CANCEL_RESTORE}"
);
assert!(
app.contains("preview_baseline: Option<PreviewBaseline>")
&& app.contains("reset_preview_to_baseline()")
&& app.contains("set_layer_pixels(baseline.layer_id, baseline.pixels.clone())"),
"{IMMUTABLE_PIXEL_PREVIEW}"
);
for variant in [
"BoxBlurGamma",
"GaussianBlurGamma",
"MedianFilter",
"SelectiveColor",
"NoisePattern",
] {
assert!(
filters.contains(variant) && filter_params.contains(variant),
"{FILTER_SCHEMA_COVERAGE}: missing {variant}"
);
}
assert!(
app.contains("fn save_document_to(")
&& app.contains("\"hcie\" => engine.save_native(&path_str)")
&& app.contains("finish_successful_save")
&& title_bar.contains("if modified"),
"{DOCUMENT_PATH_IDENTITY}"
);
assert!(
app.contains("PendingFileOperation::SaveAs { close_after: true }")
&& app.contains("continue_close_after_save()")
&& app.contains("Some(PendingFileOperation::Open)"),
"{CLOSE_SAVE_CONTINUATION}"
);
}
/// Verifies Cycle 5 AI, script, and viewer workflows have semantic handlers and safety gates.
#[test]
fn cycle_five_practical_paths_are_connected() {
let app = source("src/app.rs");
let chat = source("src/ai_chat.rs");
let ai_script = source("src/ai_script.rs");
let viewer = source("src/viewer/mod.rs");
assert!(
chat.contains("iced::stream::channel")
&& app.contains("Task::run(")
&& app.contains("task.abortable()")
&& app.contains("Message::AiChatStreamChunk(chunk)"),
"{AI_REAL_STREAMING}"
);
assert!(
app.contains("Message::AiChatReasoningChunk")
&& app.contains("Message::AiChatCopyTranscript")
&& chat.contains("pub fn transcript_text"),
"{AI_REASONING_TRANSCRIPT}"
);
assert!(
ai_script.contains("pub fn validate_script_output")
&& ai_script.contains("ANTHROPIC_API_KEY")
&& ai_script.contains("OPENAI_API_KEY"),
"{SCRIPT_VALIDATED_OUTPUT}"
);
assert!(
viewer.contains("Message::ViewerPrev")
&& viewer.contains("Message::ViewerNext")
&& app.contains("Message::ViewerSetMode(mode)"),
"{VIEWER_DETERMINISTIC_NAV}"
);
assert!(
app.contains("Open into a new editor document")
&& app.contains("self.documents.remove(document_index)"),
"{VIEWER_SAFE_EDIT}"
);
}