test_that("design_review_html returns standalone review page", { spec <- .test_complete_agent_spec() html <- design_review_html(spec, title = "Fixture review") expect_true(is.character(html)) expect_length(html, 1L) expect_true(grepl("Fixture review", html, fixed = TRUE)) expect_true(grepl("agentr-review-data", html, fixed = TRUE)) expect_true(grepl("Workflow graph", html, fixed = TRUE)) expect_true(grepl("Structured feedback", html, fixed = TRUE)) expect_true(grepl("memory_schema", html, fixed = TRUE)) expect_true(grepl("splitter", html, fixed = TRUE)) expect_true(grepl("feedback-section-gap", html, fixed = TRUE)) expect_true(grepl("initSplit", html, fixed = TRUE)) expect_true(grepl("workflow-theme-select", html, fixed = TRUE)) expect_true(grepl("subsystemPalette", html, fixed = TRUE)) expect_true(grepl("graph-scroll", html, fixed = TRUE)) expect_true(grepl("drawReviewGraph", html, fixed = TRUE)) expect_true(grepl("prepareReviewEdgeLanes", html, fixed = TRUE)) expect_true(grepl("reviewEdgeAnchor", html, fixed = TRUE)) expect_true(grepl("nodeW:220", html, fixed = TRUE)) expect_true(grepl("rowGap:42", html, fixed = TRUE)) expect_false(grepl("id:'knowledge_spec'", html, fixed = TRUE)) expect_false(grepl("from:'knowledge_spec'", html, fixed = TRUE)) expect_true(grepl("Knowledge, memory, and resource schema", html, fixed = TRUE)) expect_true(grepl("renderResourcePanel", html, fixed = TRUE)) expect_true(grepl("relatedWorkflowResources", html, fixed = TRUE)) expect_true(grepl("workflowActionNodes", html, fixed = TRUE)) expect_true(grepl("id='resources'.*Detail inspector.*id='node_detail'.*Structured feedback", html)) expect_true(grepl("subworkflow_modal", html, fixed = TRUE)) expect_true(grepl("openSubworkflowModal", html, fixed = TRUE)) expect_true(grepl("modal-layout", html, fixed = TRUE)) expect_true(grepl("modal-splitter", html, fixed = TRUE)) expect_true(grepl("initModalSplit", html, fixed = TRUE)) expect_true(grepl("setModalSideWidth", html, fixed = TRUE)) expect_true(grepl("subworkflow_resources", html, fixed = TRUE)) expect_true(grepl("subworkflow_node_detail", html, fixed = TRUE)) expect_true(grepl("overflow:auto;overscroll-behavior-x:contain", html, fixed = TRUE)) expect_true(grepl("svg{display:block;max-width:none}", html, fixed = TRUE)) expect_true(grepl("const maxEdgeX=edges.length?", html, fixed = TRUE)) expect_true(grepl("const viewRight=Math.max", html, fixed = TRUE)) expect_true(grepl("Graph nodes show the action flow only", html, fixed = TRUE)) expect_true(grepl("Deterministic automation", html, fixed = TRUE)) expect_true(grepl("External stochastic LLM", html, fixed = TRUE)) expect_false(grepl("https://", html, fixed = TRUE)) expect_false(grepl("http://", html, fixed = TRUE)) }) test_that("design_review_html uses packaged renderer assets", { spec <- .test_complete_agent_spec() html <- design_review_html(spec, title = "Asset review") expect_true(file.exists(system.file("review", "design_review.js", package = "agentr", mustWork = FALSE)) || file.exists(file.path("inst", "review", "design_review.js"))) expect_true(grepl("function drawWorkflowGraph", html, fixed = TRUE)) expect_true(grepl("function nodeKind", html, fixed = TRUE)) }) test_that("design_review_html supports workflow data nodes", { workflow <- new_workflow_spec( nodes = rbind( workflow_node( id = "knowledge_rules", label = "Article style rules", node_kind = "knowledge", human_required = FALSE, source_path = "docs/knowledge_spec.yaml", retrieval_mode = "yaml_lookup", persistence = "static", linked_spec_ids = "knowledge_spec.yaml" ), workflow_node("build_prompt", "Build prompt", human_required = FALSE) ), edges = workflow_edge("knowledge_rules", "build_prompt", relation = "prompts_with"), task = "Data node review" ) html <- design_review_html(workflow, title = "Data node review", graph_layout = "process") expect_true(grepl('"node_kind":"knowledge"', html, fixed = TRUE)) expect_true(grepl("Knowledge data", html, fixed = TRUE)) expect_true(grepl("Source path", html, fixed = TRUE)) expect_true(grepl("prompts_with", html, fixed = TRUE)) expect_true(grepl("Workflow resource linked by", html, fixed = TRUE)) expect_true(grepl("resource -> action", html, fixed = TRUE)) expect_true(grepl("workflowActionEdges", html, fixed = TRUE)) }) test_that("design_review_html supports status nodes and separated edge ports", { workflow <- new_workflow_spec( nodes = rbind( workflow_node("manual_recovery", "Manual recovery prompt", human_required = TRUE), workflow_node("failure_status", "Failure status", node_kind = "status"), workflow_node("record_completion", "Record completion", human_required = FALSE), workflow_node("reload_next", "Reload next item", human_required = FALSE) ), edges = rbind( workflow_edge("manual_recovery", "record_completion", relation = "error_handling"), workflow_edge("record_completion", "reload_next", relation = "loop") ), task = "Status and edge ports" ) html <- design_review_html(workflow, title = "Status and edge ports", graph_layout = "process", edge_style = "orthogonal") expect_true(grepl('"node_kind":"status"', html, fixed = TRUE)) expect_true(grepl("Status marker", html, fixed = TRUE)) expect_true(grepl("function assignEdgePorts", html, fixed = TRUE)) expect_true(grepl("_sourcePortRatio", html, fixed = TRUE)) expect_true(grepl("stroke-dasharray='5 4'", html, fixed = TRUE)) }) test_that("design_review_html supports subsystem-based node color theme", { spec <- .test_complete_agent_spec() html <- design_review_html(spec, node_color_theme = "subsystems") expect_true(grepl('"node_color_theme":"subsystems"', html, fixed = TRUE)) expect_true(grepl("nodeColorTheme", html, fixed = TRUE)) expect_true(grepl("nodeSubsystem", html, fixed = TRUE)) expect_true(grepl("renderWorkflowLegend", html, fixed = TRUE)) expect_true(grepl("applyWorkflowTheme", html, fixed = TRUE)) expect_true(grepl("'RWM'", html, fixed = TRUE) || grepl("label:'RWM'", html, fixed = TRUE)) expect_true(grepl("node_subsystems", html, fixed = TRUE)) expect_true(grepl('"node_refresh"', html, fixed = TRUE)) expect_true(grepl('"node_interpret"', html, fixed = TRUE)) }) test_that("design_review_html inherits default categories from nested descendants", { nested <- new_workflow_spec( nodes = rbind( workflow_node("nested_human", "Human approval step", human_required = TRUE), workflow_node("nested_llm", "External LLM step", automation_status = "llm_assisted"), workflow_node("nested_auto", "Deterministic helper", automation_status = "agent_owned") ), edges = rbind( workflow_edge("nested_human", "nested_llm"), workflow_edge("nested_llm", "nested_auto") ), task = "Nested category review" ) workflow <- new_workflow_spec( nodes = workflow_node( "parent_node", "Parent node with nested workflow", human_required = FALSE, automation_status = "agent_owned", nested_workflow = nested ), edges = .empty_workflow_edges(), task = "Parent category review" ) html <- design_review_html(workflow, title = "Parent category review") expect_true(grepl("baseWorkflowCategory", html, fixed = TRUE)) expect_true(grepl("categoryPriority", html, fixed = TRUE)) expect_true(grepl("effectiveWorkflowCategory", html, fixed = TRUE)) expect_true(grepl("Category", html, fixed = TRUE)) expect_true(grepl("Graph nodes show the action flow only", html, fixed = TRUE)) }) test_that("design_review_html renders branch edge metadata visibly", { workflow <- new_workflow_spec( nodes = rbind( workflow_node("route", "Route by source count"), workflow_node("single", "Single-source prompt"), workflow_node("multi", "Multi-source prompt") ), edges = rbind( workflow_edge( "route", "single", relation = "exclusive_branch", condition = "source_count == 1", branch_group = "source_count_route", mutually_exclusive = TRUE ), workflow_edge( "route", "multi", relation = "exclusive_branch", condition = "source_count > 1", branch_group = "source_count_route", mutually_exclusive = TRUE ) ), task = "Branch review" ) html <- design_review_html(workflow, title = "Branch review") expect_true(grepl('"condition":"source_count == 1"', html, fixed = TRUE)) expect_true(grepl('"branch_group":"source_count_route"', html, fixed = TRUE)) expect_true(grepl('"mutually_exclusive":true', html, fixed = TRUE)) expect_true(grepl("function isBranchEdge", html, fixed = TRUE)) expect_true(grepl("function edgeLabel", html, fixed = TRUE)) expect_true(grepl("arrow-branch", html, fixed = TRUE)) expect_true(grepl("stroke-dasharray='${visual.dash}'", html, fixed = TRUE)) expect_true(grepl("Condition", html, fixed = TRUE)) expect_true(grepl("Branch group", html, fixed = TRUE)) expect_true(grepl("Mutually exclusive", html, fixed = TRUE)) }) test_that("design_review_html shows node detail schemas and nested workflow drilldown", { nested <- new_workflow_spec( nodes = rbind( workflow_node("nested_1", "Read input"), workflow_node("nested_2", "Return output") ), edges = workflow_edge("nested_1", "nested_2"), task = "Nested task" ) workflow <- new_workflow_spec( nodes = workflow_node( "node_detail", "High-level step with nested workflow", rule_spec = "Use the nested workflow for detailed review.", implementation_hint = "Return JSON with answer and confidence.", owner = "human", automation_status = "human_in_loop", knowledge_refs = "ki_nested", subworkflow_ref = "workflows/node_detail.json", input_schema = list(type = "object", required = "question"), output_schema = list(type = "object", properties = list(answer = "string")), nested_workflow = nested ), edges = .empty_workflow_edges(), task = "Node detail review" ) html <- design_review_html(workflow, title = "Node detail review") expect_true(grepl("Detail inspector", html, fixed = TRUE)) expect_true(grepl("renderNodeDetail", html, fixed = TRUE)) expect_true(grepl("renderNestedNodeDetail", html, fixed = TRUE)) expect_true(grepl("ensureSubworkflowModal", html, fixed = TRUE)) expect_true(grepl("openSubworkflowModal", html, fixed = TRUE)) expect_true(grepl("data-subworkflow-id", html, fixed = TRUE)) expect_true(grepl("sw_fb_target", html, fixed = TRUE)) expect_true(grepl("addSubworkflowFeedback", html, fixed = TRUE)) expect_true(grepl("renderSubworkflowResourcePanel", html, fixed = TRUE)) expect_true(grepl("onNodeClick=onNodeClick||selectWorkflowNode", html, fixed = TRUE)) expect_true(grepl("selectWorkflowNode", html, fixed = TRUE)) expect_true(grepl("data-node-id", html, fixed = TRUE)) expect_true(grepl("data-edge-index", html, fixed = TRUE)) expect_true(grepl("renderEdgeDetailObject", html, fixed = TRUE)) expect_true(grepl("Input schema", html, fixed = TRUE)) expect_true(grepl("Output schema", html, fixed = TRUE)) expect_true(grepl("Subworkflow", html, fixed = TRUE)) expect_true(grepl("- subworkflow", html, fixed = TRUE)) expect_true(grepl("function hasSubworkflow", html, fixed = TRUE)) expect_true(grepl("val(n.subworkflow_ref)", html, fixed = TRUE)) expect_true(grepl("e.stopPropagation()", html, fixed = TRUE)) expect_true(grepl("workflows/node_detail.json", html, fixed = TRUE)) expect_true(grepl("function knowledgeRefs", html, fixed = TRUE)) expect_true(grepl("function arrayValue", html, fixed = TRUE)) expect_true(grepl("knowledgeRefs(n.knowledge_refs).join(', ')", html, fixed = TRUE)) expect_true(grepl('"knowledge_refs":["ki_nested"]', html, fixed = TRUE)) expect_true(grepl('"input_schema":{"type":"object","required":["question"]}', html, fixed = TRUE)) expect_true(grepl('"output_schema":{"type":"object","properties":{"answer":"string"}}', html, fixed = TRUE)) expect_true(grepl('"nested_workflow":{"nodes"', html, fixed = TRUE)) expect_false(grepl("font-size='11'>${esc(n.id)}", html, fixed = TRUE)) expect_false(grepl("owner: ${esc(val(n.owner))} | automation: ${esc(val(n.automation_status))}", html, fixed = TRUE)) expect_false(grepl("human gate: ${esc(n.human_required)} | review: ${esc(val(n.review_status))}", html, fixed = TRUE)) expect_false(grepl("knowledge refs: ${esc(knowledgeRefs(n.knowledge_refs).join(', '))}", html, fixed = TRUE)) expect_false(grepl("