Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_const_object_css_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch"
},
"note": "Compose constant object rules in css tags without runtime styling, preserve lexical tag bindings and numeric-unit marks, and report authored build errors for mutated, escaped or unresolved object mixins and dynamic object leaves.",
"date": "2026-10-20T19:08:10.516Z"
}
26 changes: 26 additions & 0 deletions libs/extractor/src/ci_factory_alias_active_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use super::*;

#[rstest]
#[case::direct("className: OWN, css: { color: 'red' }")]
#[case::template("className: `external ${OWN}`, css: { color: 'red' }")]
#[serial]
fn active_native_css_prop_merges_class_aliases_with_emitted_css(
#[case] fragment: &str,
#[values(Binding::Alias, Binding::Namespace, Binding::ShadowAlias, Binding::ShadowNamespace)] binding: Binding,
#[values(false, true)] single_css: bool,
) {
// Given: pragma and alias jointly enable css-prop lowering (v2 evidence).
let _state = crate::test_state::TestStateGuard::new();
let props = fragment.replace("OWN", binding.call());
let source = format!("/** @jsxImportSource @emotion/react */\n{} import {{ jsx as o }} from 'react/jsx-runtime'; function render({}) {{ return o('div', {{ {props} }}); }}", binding.imports(), binding.parameter());
// When
let output = extract_fixture(&source, single_css, true);
// Then: red belongs to css-prop; hidden must never come from a lexical shadow.
assert_own_styles(&output, binding);
let invocation = format!("(() => {{ const result = render({}); const tokens = (result.props.className || '').split(/\\s+/); trace.push('css:' + ('css' in result.props), 'external:' + tokens.includes('external'), 'local:' + tokens.includes('local')); return result; }})()", binding.argument(true));
let mut trace = Vec::new();
if binding.shadow() { trace.push("own:hidden".to_string()); }
trace.extend(["css:false".to_string(), format!("external:{}", fragment.contains("external")), format!("local:{}", binding.shadow())]);
let expected = if binding.shadow() { vec!["red".to_string()] } else { vec!["hidden".to_string(), "red".to_string()] };
assert_eq!(observe(&output, &invocation, single_css), (trace, expected));
}
140 changes: 140 additions & 0 deletions libs/extractor/src/ci_factory_alias_native_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
include!("ci_factory_alias_support_tests.rs");

mod spreads {
include!("ci_factory_alias_spread_tests.rs");
}
mod active_css {
include!("ci_factory_alias_active_tests.rs");
}

#[derive(Clone, Copy, Debug)]
enum Shape {
Direct,
DirectMerged,
Conditional,
Template,
NestedTemplate,
TailwindConditional,
TailwindTest,
Logical,
Call,
Array,
Object,
TailwindTemplate,
StyleOrder,
}

impl Shape {
fn props(self) -> &'static str {
match self {
Self::Direct => "className: OWN",
Self::DirectMerged => "className: OWN, color: 'red'",
Self::Conditional => "className: h ? OWN : void 0",
Self::Template => "className: `external ${OWN}`",
Self::NestedTemplate => "className: `external ${h ? OWN : ''}`",
Self::TailwindConditional => "className: h ? 'flex' : OWN",
Self::TailwindTest => "className: OWN ? 'flex' : 'external'",
Self::Logical => "className: OWN || 'flex'",
Self::Call => "className: clsx('flex', OWN)",
Self::Array => "className: ['flex', OWN].join(' ')",
Self::Object => "className: clsx({ 'flex': OWN })",
Self::TailwindTemplate => "className: `flex ${OWN}`",
Self::StyleOrder => "className: OWN, color: 'red', styleOrder: h ? 1 : 0",
}
}
}

#[rstest]
#[case::direct(Shape::Direct)]
#[case::direct_merged(Shape::DirectMerged)]
#[case::conditional(Shape::Conditional)]
#[case::template(Shape::Template)]
#[case::nested_template(Shape::NestedTemplate)]
#[case::tailwind_conditional(Shape::TailwindConditional)]
#[case::tailwind_test(Shape::TailwindTest)]
#[case::logical(Shape::Logical)]
#[case::call(Shape::Call)]
#[case::array(Shape::Array)]
#[case::object(Shape::Object)]
#[case::tailwind_template(Shape::TailwindTemplate)]
#[case::style_order(Shape::StyleOrder)]
#[serial]
fn class_aliases_select_emitted_styles_when_preprocessing_retains_bindings(
#[case] shape: Shape,
#[values(
Binding::Alias,
Binding::Namespace,
Binding::ShadowAlias,
Binding::ShadowNamespace
)]
binding: Binding,
#[values(false, true)] single_css: bool,
) {
// Given
let _state = crate::test_state::TestStateGuard::new();
let props = shape.props().replace("OWN", binding.call());
let source = format!(
"{} import {{ jsx as o }} from 'react/jsx-runtime'; function render(h, {}, clsx) {{ return o(t, {{ {props} }}); }}",
binding.imports(),
binding.parameter()
);
// When
let output = extract_fixture(&source, single_css, false);
// Then: selected classes, not merely generated-but-unused rules, decide styles.
assert_own_styles(&output, binding);
for h in [false, true] {
let truthy = !binding.shadow() || h;
let called = match shape {
Shape::Conditional | Shape::NestedTemplate => h,
Shape::TailwindConditional => !h,
_ => true,
};
let selected = match shape {
Shape::TailwindTest | Shape::Object => false,
_ => called,
};
let flex = match shape {
Shape::TailwindConditional => h,
Shape::TailwindTest | Shape::Object => truthy,
Shape::Logical => !truthy,
Shape::Call | Shape::Array | Shape::TailwindTemplate => true,
_ => false,
};
let external = matches!(shape, Shape::Template | Shape::NestedTemplate)
|| (matches!(shape, Shape::TailwindTest) && !truthy);
let local = binding.shadow() && h && selected;
let invocation = format!(
"(() => {{ const result = render({h}, {}, {CLSX}); const tokens = (result.props.className || '').split(/\\s+/); trace.push('external:' + tokens.includes('external'), 'local:' + tokens.includes('local')); return result; }})()",
binding.argument(h)
);
let mut expected_trace = Vec::new();
if binding.shadow() && called {
expected_trace.push("own:hidden".to_string());
}
if matches!(shape, Shape::Call | Shape::Object) {
expected_trace.push("clsx".to_string());
}
expected_trace.extend([format!("external:{external}"), format!("local:{local}")]);
let mut expected = Vec::new();
if selected && !binding.shadow() {
expected.push("hidden".to_string());
}
if flex {
expected.push("flex".to_string());
}
if matches!(shape, Shape::DirectMerged | Shape::StyleOrder) {
expected.push("red".to_string());
}
expected.sort();
assert_eq!(
observe(&output, &invocation, single_css),
(expected_trace, expected),
"{shape:?}/{binding:?}/h={h}\n{}",
output.code
);
}
}

mod style_metadata {
include!("ci_factory_alias_style_tests.rs");
}
165 changes: 165 additions & 0 deletions libs/extractor/src/ci_factory_alias_spread_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use super::*;

#[rstest]
#[case::effectful_after_alias(("className: OWN, ...getProps()", false, true), Binding::Alias)]
#[case::effectful_after_namespace(("className: OWN, ...getProps()", false, true), Binding::Namespace)]
#[case::identifier_after_alias(("className: OWN, ...p", false, false), Binding::Alias)]
#[case::identifier_after_namespace(("className: OWN, ...p", false, false), Binding::Namespace)]
#[case::effectful_before_alias(("...getProps(), className: OWN", true, true), Binding::Alias)]
#[case::effectful_before_namespace(("...getProps(), className: OWN", true, true), Binding::Namespace)]
#[case::effectful_before_shadow_alias(("...getProps(), className: OWN", true, true), Binding::ShadowAlias)]
#[case::effectful_before_shadow_namespace(("...getProps(), className: OWN", true, true), Binding::ShadowNamespace)]
#[case::literal_alias(("...{ className: OWN }", true, false), Binding::Alias)]
#[case::literal_namespace(("...{ className: OWN }", true, false), Binding::Namespace)]
#[case::literal_shadow_alias(("...{ className: OWN }", true, false), Binding::ShadowAlias)]
#[case::literal_shadow_namespace(("...{ className: OWN }", true, false), Binding::ShadowNamespace)]
#[serial]
fn spread_aliases_preserve_class_presence_and_once_only_evaluation(
#[case] sample: (&str, bool, bool),
#[case] binding: Binding,
#[values(false, true)] single_css: bool,
) {
// Given
let _state = crate::test_state::TestStateGuard::new();
let (fragment, explicit_wins, effectful) = sample;
let props = fragment.replace("OWN", binding.call());
let source = format!(
"{} import {{ jsx as o }} from 'react/jsx-runtime'; function render(p, getProps, {}) {{ return o(t, {{ {props} }}); }}",
binding.imports(),
binding.parameter()
);
// When
let output = extract_fixture(&source, single_css, false);
// Then: an explicit undefined class differs from a missing class.
assert_own_styles(&output, binding);
for (spread, present, external) in [
("{}", false, false),
("{className:'external'}", true, true),
("{className:void 0}", true, false),
] {
let own_selected = explicit_wins || !present;
let helper_first = fragment.starts_with("className");
let invocation = format!(
"(() => {{ const result = render({spread}, () => {{ trace.push('spread'); return {spread}; }}, {}); const tokens = (result.props.className || '').split(/\\s+/); trace.push('external:' + tokens.includes('external'), 'local:' + tokens.includes('local')); return result; }})()",
binding.argument(true)
);
let mut expected_trace = Vec::new();
if binding.shadow() && helper_first {
expected_trace.push("own:hidden".to_string());
}
if effectful {
expected_trace.push("spread".to_string());
}
if binding.shadow() && !helper_first {
expected_trace.push("own:hidden".to_string());
}
expected_trace.extend([
format!("external:{}", !explicit_wins && external),
format!("local:{}", binding.shadow() && own_selected),
]);
let expected = if own_selected && !binding.shadow() {
vec!["hidden".to_string()]
} else {
vec![]
};
assert_eq!(
observe(&output, &invocation, single_css),
(expected_trace, expected),
"{fragment}/{binding:?}/{spread}\n{}",
output.code
);
}
}

#[rstest]
#[case::native("props: { className: OWN }")]
#[case::custom("as: Target, props: { className: OWN }")]
#[serial]
fn passthrough_class_aliases_retain_imports_without_compiling_local_helpers(
#[case] fragment: &str,
#[values(
Binding::Alias,
Binding::Namespace,
Binding::ShadowAlias,
Binding::ShadowNamespace
)]
binding: Binding,
#[values(false, true)] single_css: bool,
) {
// Given
let _state = crate::test_state::TestStateGuard::new();
let props = fragment.replace("OWN", binding.call());
let source = format!(
"{} import {{ jsx as o }} from 'react/jsx-runtime'; function render(Target, {}) {{ return o(t, {{ {props} }}); }}",
binding.imports(),
binding.parameter()
);
// When
let output = extract_fixture(&source, single_css, false);
// Then
assert_own_styles(&output, binding);
let invocation = format!(
"(() => {{ const result = render('article', {}); trace.push('target:' + result.target, 'local:' + (result.props.className || '').split(/\\s+/).includes('local')); return result; }})()",
binding.argument(true)
);
let mut trace = Vec::new();
if binding.shadow() {
trace.push("own:hidden".to_string());
}
trace.push(format!(
"target:{}",
if fragment.starts_with("as:") {
"article"
} else {
"div"
}
));
trace.push(format!("local:{}", binding.shadow()));
let expected = if binding.shadow() {
vec![]
} else {
vec!["hidden".to_string()]
};
assert_eq!(observe(&output, &invocation, single_css), (trace, expected));
}

#[rstest]
#[serial]
fn spread_argument_aliases_are_moved_before_factory_body_rewriting(
#[values(false, true)] single_css: bool,
#[values(
Binding::Alias,
Binding::Namespace,
Binding::ShadowAlias,
Binding::ShadowNamespace
)]
binding: Binding,
) {
// Given
let _state = crate::test_state::TestStateGuard::new();
let source = format!(
"{} import {{ jsx as o }} from 'react/jsx-runtime'; function render(getProps, {}) {{ return o(t, {{ ...getProps({}) }}); }}",
binding.imports(),
binding.parameter(),
binding.call()
);
// When
let output = extract_fixture(&source, single_css, false);
// Then: getProps consumes the actual compiled class, not a copied expectation.
assert_own_styles(&output, binding);
let invocation = format!(
"render(className => {{ trace.push('spread'); return {{className}}; }}, {})",
binding.argument(true)
);
let trace = if binding.shadow() {
vec!["own:hidden".to_string(), "spread".to_string()]
} else {
vec!["spread".to_string()]
};
let expected = if binding.shadow() {
vec![]
} else {
vec!["hidden".to_string()]
};
assert_eq!(observe(&output, &invocation, single_css), (trace, expected));
}
Loading
Loading