From f7dc464cb2aba912a65ca6ef8597540ee7dd5dcc Mon Sep 17 00:00:00 2001 From: Christopher Bays Date: Sat, 13 Jun 2026 12:18:30 -0400 Subject: [PATCH 1/2] feat(adapters): macro lens model-selector + first-order usage examples (Slice C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the experimental macro-lens section (cute-dbt#265, behind `Experiment::MacroLens`) with the founder D4 model-selector and the founder D6 first-order call-site examples. - `ImpactedModelView` / `CallSiteView` additive payload PODs on `ChangedMacroView`: each impacted (macro-calling) root-project model carries its inline raw SQL (server-rendered from the manifest `raw_code`, the existing Model-SQL source — no new I/O path) plus its first-order call sites of the changed macro, behind a generous cap (default 3 shown + the "more" copy; `MACRO_CALL_SITE_CAP`). - `impacted_model_views` / `macro_call_sites` / `line_invokes_macro`: pure render-lane string scans over already-loaded source (zero-egress, zero new I/O). The call-site match is bounded so `x_macro_name(` never false-matches; it is first-order only (full ref()-lineage downstream is the explorer, #345). - Template: the impacted-model ` change just flips which + // is visible. This proves (a) the call sites render, (b) at boot only + // the first model's panel is visible, and (c) selecting the second + // model swaps the visible panel — all in a real browser, file://, + // network-blocked (the same job that runs headless_zero_egress). + let url = render_macro_lens_to_file("cute_dbt_macro_lens_selector.html"); + + let browser = launch_browser(); + let tab = browser.new_tab().expect("new tab"); + tab.navigate_to(&url).expect("navigate"); + tab.wait_until_navigated().expect("await navigation"); + wait_for_document_ready(&tab); + + // The selector renders with both models as options. + assert_eq!( + visible_count(&tab, "[data-testid=\"macro-lens-model-select\"]"), + 1, + "the impacted-model selector renders", + ); + let option_count = eval( + &tab, + "document.querySelectorAll('[data-testid=\"macro-lens-model-select\"] option').length", + ); + assert_eq!(option_count.as_i64(), Some(2), "two model options"); + + // First-order call sites render (at least one per visible panel). + let callsites = eval( + &tab, + "document.querySelectorAll('[data-testid=\"macro-lens-callsite\"]').length", + ); + assert!( + callsites.as_i64().unwrap_or(0) >= 2, + "call sites render for the impacted models", + ); + + // At boot exactly one model panel is VISIBLE (the first); the others + // carry `hidden`. The selector is a pure renderer toggling visibility. + assert_eq!( + visible_count(&tab, "[data-testid=\"macro-lens-model-panel\"]"), + 1, + "exactly one model panel is visible at boot", + ); + // The visible panel is the first option's model (BTreeSet id order: + // fct_orders sorts before stg_orders). + let visible_model_at_boot = eval_string( + &tab, + "Array.from(document.querySelectorAll('[data-testid=\"macro-lens-model-panel\"]'))\ + .filter(el => el.checkVisibility()).map(el => el.getAttribute('data-model'))[0]", + ); + assert_eq!( + visible_model_at_boot, "model.shop.fct_orders", + "the first (id-order) model panel is shown at boot", + ); + + // Select the OTHER model — the visible panel swaps in place. + let _ = eval( + &tab, + "(function(){var s=document.querySelector('[data-testid=\"macro-lens-model-select\"]');\ + s.value='model.shop.stg_orders';s.dispatchEvent(new Event('change'));})()", + ); + assert_eq!( + visible_count(&tab, "[data-testid=\"macro-lens-model-panel\"]"), + 1, + "still exactly one panel visible after the switch", + ); + let visible_after = eval_string( + &tab, + "Array.from(document.querySelectorAll('[data-testid=\"macro-lens-model-panel\"]'))\ + .filter(el => el.checkVisibility()).map(el => el.getAttribute('data-model'))[0]", + ); + assert_eq!( + visible_after, "model.shop.stg_orders", + "selecting the second model shows ITS panel", + ); + let _ = tab.close(true); +} + #[test] #[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"] fn governance_meta_chip_tooltip_reveals_the_bubble_on_focus_in_a_real_browser() { diff --git a/tests/steps/macro_perspective.rs b/tests/steps/macro_perspective.rs index 28bb003e..ec2dfebc 100644 --- a/tests/steps/macro_perspective.rs +++ b/tests/steps/macro_perspective.rs @@ -47,12 +47,18 @@ fn root_macro_id() -> String { /// `original_file_path` and a passing compiled body (so Stage-2 preflight /// is a no-op — these models carry no unit tests). fn macro_model(bare: &str, ofp: &str, macro_id: &str) -> Node { + macro_model_with_raw(bare, ofp, macro_id, None) +} + +/// As [`macro_model`], but with an optional `raw_code` body — the +/// call-site + inline-SQL source the Slice C model-selector reveals. +fn macro_model_with_raw(bare: &str, ofp: &str, macro_id: &str, raw: Option<&str>) -> Node { Node::new( NodeId::new(format!("model.{PROJECT}.{bare}")), "model", Checksum::new("sha256", bare), Some("select 1".to_owned()), - None, + raw.map(str::to_owned), DependsOn::new(vec![macro_id.to_owned()], Vec::new()), Some(format!("models/{ofp}")), NodeConfig::default(), @@ -78,6 +84,40 @@ fn manifest_two_callers() -> Manifest { ) } +/// Build the Slice C current manifest: two root-project models both +/// calling the macro INLINE in their `raw_code` (the call-site source the +/// model-selector reveals). `fct_orders` calls it twice (the over-cap copy +/// only triggers past the cap, so two is shown in full — the cap is +/// exercised in the render-lane unit/headless layer, not the slow BDD +/// subprocess). The bodies name `add_dq_flags(` so the first-order +/// call-site scan resolves them. +fn manifest_two_callers_with_call_sites() -> Manifest { + let stg_raw = "select *\nfrom raw_orders\n {{ add_dq_flags() }}"; + let fct_raw = + "select *\nfrom stg_orders\n {{ add_dq_flags(amount) }}\n {{ add_dq_flags(qty) }}"; + let nodes = [ + macro_model_with_raw( + "stg_orders", + "staging/stg_orders.sql", + &root_macro_id(), + Some(stg_raw), + ), + macro_model_with_raw( + "fct_orders", + "marts/core/fct_orders.sql", + &root_macro_id(), + Some(fct_raw), + ), + ]; + Manifest::new( + ManifestMetadata::new("https://schemas.getdbt.com/dbt/manifest/v12.json") + .with_project_name(Some(PROJECT.to_owned())), + nodes.into_iter().map(|n| (n.id().clone(), n)).collect(), + HashMap::new(), + HashMap::new(), + ) +} + /// Build the vendor-arm manifest: a root-project model calls a /// VENDOR-package macro (`macro.dbt_utils.add_dq_flags`). Editing the /// vendor macro must NOT surface the lens (the root-project filter on both @@ -140,6 +180,12 @@ fn current_manifest_two_callers(world: &mut World) { world.fixture_choice = None; } +#[given("a current manifest with a root-project macro called inline by two models")] +fn current_manifest_two_callers_inline(world: &mut World) { + world.current_manifest = Some(manifest_two_callers_with_call_sites()); + world.fixture_choice = None; +} + #[given("a current manifest with a vendor-package macro called by a root-project model")] fn current_manifest_vendor_macro(world: &mut World) { world.current_manifest = Some(manifest_vendor_macro()); @@ -356,3 +402,62 @@ fn section_never_names_selector(world: &mut World, selector: String) { "the section must never name the {selector:?} selector (critique S2)", ); } + +// --------------------------------------------------------------------- +// Slice C — model-selector + first-order call sites +// --------------------------------------------------------------------- + +#[then("the macro-lens section carries an impacted-model selector")] +fn section_carries_model_selector(world: &mut World) { + assert!( + html(world).contains(r#"data-testid="macro-lens-model-select""#), + "the section must carry the impacted-model selector (founder D4)", + ); +} + +#[then("the impacted-model selector offers both models")] +fn selector_offers_both_models(world: &mut World) { + let html = html(world); + assert!( + html.contains(r#"