mod common;
use common::{options_with_version, transform_fixture, transform_fixture_resolved};
#[test]
fn no_worklet_directive_passes_through() {
let code = r#"
function regular() {
return 42;
}
export default regular;
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
insta::assert_snapshot!(out);
}
#[test]
fn function_with_worklet_directive() {
let code = r#"
function add(a, b) {
'worklet';
return a + b;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
insta::assert_snapshot!(out);
}
#[test]
fn worklet_arrow_assigned_to_variable() {
let code = r#"
const square = (x) => {
'worklet';
return x * x;
};
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
insta::assert_snapshot!(out);
}
#[test]
fn use_animated_style_callback_is_workletized() {
let code = r#"
import { useAnimatedStyle } from 'react-native-reanimated';
function Component() {
const style = useAnimatedStyle(() => {
return { opacity: 1 };
});
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
insta::assert_snapshot!(out);
}
#[test]
fn worklet_captures_outer_variable() {
let code = r#"
const outer = 10;
function scale(x) {
'worklet';
return x * outer;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
insta::assert_snapshot!(out);
}
#[test]
fn strict_global_captures_unlisted_globals() {
let code = r#"
function fn() {
'worklet';
return Math.random();
}
"#;
let mut opts = options_with_version();
opts.strict_global = true;
let out = transform_fixture("Sample.ts", code, opts);
insta::assert_snapshot!(out);
}
fn assert_contains(out: &str, needle: &str) {
assert!(
out.contains(needle),
"expected output to contain {needle:?}, got:\n{out}"
);
}
fn assert_not_contains(out: &str, needle: &str) {
assert!(
!out.contains(needle),
"expected output to NOT contain {needle:?}, got:\n{out}"
);
}
#[test]
fn worklet_class_marker_emits_factory_wrapper() {
let code = r#"
class Sky {
__workletClass = true;
draw() { this.x = 1; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_not_contains(&out, "__workletClass");
assert_contains(&out, "Sky__classFactory");
assert_contains(&out, "Sky__classFactory()");
assert_contains(&out, "Sky.Sky__classFactory = Sky__classFactory");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_on_method_with_reserved_keyword_name() {
let code = r#"
class Ball {
__workletClass = true;
throw(vx, vy) { this.vx += vx; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_contains(&out, "Ball__classFactory");
assert_not_contains(&out, "var throw =");
assert_not_contains(&out, "function throw(");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_preserves_named_export() {
let code = r#"
export class Sky {
__workletClass = true;
draw() { this.x = 1; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_contains(&out, "Sky__classFactory");
assert_contains(&out, "export const Sky = Sky__classFactory()");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_preserves_default_export() {
let code = r#"
export default class Sky {
__workletClass = true;
draw() { this.x = 1; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_contains(&out, "Sky__classFactory");
assert_contains(&out, "const Sky = Sky__classFactory()");
assert_contains(&out, "export default Sky");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_skipped_when_disabled() {
let code = r#"
class Sky {
__workletClass = true;
draw() { this.x = 1; }
}
"#;
let mut opts = options_with_version();
opts.disable_worklet_classes = true;
let out = transform_fixture("Sample.ts", code, opts);
assert_not_contains(&out, "Sky__classFactory");
assert_contains(&out, "__workletClass");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_skipped_in_bundle_mode() {
let code = r#"
class Sky {
__workletClass = true;
draw() { this.x = 1; }
}
"#;
let mut opts = options_with_version();
opts.bundle_mode = true;
let out = transform_fixture("Sample.ts", code, opts);
assert_not_contains(&out, "Sky__classFactory");
assert_contains(&out, "__workletClass");
assert_not_contains(&out, "drawFactory");
assert_not_contains(&out, "__workletHash");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_marker_without_methods_still_wraps() {
let code = r#"
class Empty {
__workletClass = true;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_contains(&out, "Empty__classFactory");
assert_contains(&out, "const Empty = Empty__classFactory()");
assert_not_contains(&out, "__workletClass");
insta::assert_snapshot!(out);
}
#[test]
fn class_without_worklet_marker_passes_through() {
let code = r#"
class Plain {
draw() { this.x = 1; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
assert_not_contains(&out, "Plain__classFactory");
assert_not_contains(&out, "__workletHash");
assert_not_contains(&out, "__initData");
insta::assert_snapshot!(out);
}
#[test]
fn worklet_class_constructor_params_are_not_captured_as_closure() {
let code = r#"
const TOP = 10;
class Ball {
__workletClass = true;
constructor(id, x, y) {
this.id = id;
this.x = x;
this.y = y;
}
scale(factor) { this.x = factor * TOP; }
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let factory_iife_destructure = extract_factory_iife_destructure(&out, "Ball__classFactory")
.expect("Ball__classFactory IIFE destructuring should be emitted");
assert!(
factory_iife_destructure.contains("TOP"),
"factory closure should capture TOP, got: {factory_iife_destructure}"
);
for forbidden in ["id", "x", "y", "factor", "this"] {
assert!(
!ident_in_destructure(&factory_iife_destructure, forbidden),
"factory closure must NOT capture {forbidden:?}, got: {factory_iife_destructure}"
);
}
insta::assert_snapshot!(out);
}
fn extract_first_init_data_code(out: &str) -> Option<String> {
let init_marker = "init_data = {";
let init_pos = out.find(init_marker)?;
let tail = &out[init_pos..];
let code_pos = tail.find("code:")?;
let after_code = &tail[code_pos + "code:".len()..].trim_start();
let quote = after_code.chars().next()?;
if quote != '"' && quote != '\'' {
return None;
}
let body = &after_code[1..];
let mut chars = body.char_indices();
while let Some((i, c)) = chars.next() {
if c == '\\' {
chars.next();
continue;
}
if c == quote {
return Some(body[..i].to_string());
}
}
None
}
#[test]
fn worklet_body_shorthand_props_get_lowered() {
let code = r#"
function fn(x, y) {
'worklet';
return { x, y };
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
body.contains("x: x") && body.contains("y: y"),
"shorthand props should be expanded inside init_data.code, got: {body}"
);
}
#[test]
fn worklet_body_arrow_functions_get_lowered() {
let code = r#"
function fn(arr) {
'worklet';
const inc = (x) => x + 1;
return inc(arr);
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("=>"),
"arrow function should be lowered inside init_data.code, got: {body}"
);
assert!(
body.contains("function"),
"lowered arrow should become a function expression, got: {body}"
);
}
#[test]
fn worklet_body_template_literals_get_lowered() {
let code = r#"
function fn(name) {
'worklet';
return `hello ${name}`;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("`"),
"template literal should be lowered inside init_data.code, got: {body}"
);
assert!(
body.contains("\"hello \""),
"lowered template should preserve the literal segment, got: {body}"
);
}
#[test]
fn worklet_body_optional_chaining_gets_lowered() {
let code = r#"
function fn(obj) {
'worklet';
return obj?.foo;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("?."),
"optional chaining should be lowered inside init_data.code, got: {body}"
);
}
#[test]
fn worklet_body_nullish_coalescing_gets_lowered() {
let code = r#"
function fn(a, b) {
'worklet';
return a ?? b;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("??"),
"nullish coalescing should be lowered inside init_data.code, got: {body}"
);
}
#[test]
fn worklet_body_lowering_runs_hygiene_to_avoid_temp_collisions() {
let code = r#"
const FALLBACK = { level: 'info', strict: false };
function fn(options) {
'worklet';
return {
level: options?.level ?? FALLBACK.level,
strict: options?.strict ?? FALLBACK.strict,
};
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
let var_line = body
.lines()
.find(|l| l.trim_start().starts_with("var "))
.unwrap_or("");
let names: Vec<&str> = var_line
.trim_start()
.trim_start_matches("var ")
.trim_end_matches(|c: char| c == ';' || c.is_whitespace())
.split(',')
.map(str::trim)
.collect();
let mut sorted = names.clone();
sorted.sort();
sorted.dedup();
assert_eq!(
names.len(),
sorted.len(),
"hygiene should produce distinct temp idents in lowered worklet body, \
got `var {}` in: {body}",
names.join(", ")
);
}
#[test]
fn worklet_closure_destructure_keeps_enclosing_locals_arrow() {
let code = r#"
import { helper } from './helpers';
const createSerializable = (fn: any) => fn;
function outer(initializerFn: () => void) {
const local = helper();
return createSerializable(() => {
'worklet';
local();
initializerFn();
});
}
"#;
let out = transform_fixture_resolved("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("local:") && !body.contains("initializerFn:"),
"closure destructure must keep enclosing locals shorthand, got: {body}"
);
}
#[test]
fn worklet_closure_destructure_keeps_imported_binding_name_arrow() {
let code = r#"
import { helper, other } from './helpers';
const createSerializable = (fn: any) => fn;
const runtime = createSerializable(() => {
'worklet';
helper();
other();
});
"#;
let out = transform_fixture_resolved("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
!body.contains("helper:") && !body.contains("other:"),
"arrow worklet closure destructure must not rename imports, got: {body}"
);
}
#[test]
fn worklet_closure_destructure_keeps_imported_binding_name() {
let code = r#"
import { helper } from './helper';
function fn() {
'worklet';
helper();
helper();
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
body.contains("const { helper }") || body.contains("var { helper }"),
"closure destructure should keep the unrenamed `helper` binding, got: {body}"
);
assert!(
!body.contains("helper:"),
"closure destructure must not be renamed to `helper: helperN`, got: {body}"
);
}
#[test]
fn worklet_body_optional_chaining_nullish_keeps_precedence() {
let code = r#"
function fn(frame) {
'worklet';
return frame?.opacity ?? 0;
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
assert!(
body.contains("(_ref ="),
"optional-chain assignment must stay parenthesized, got: {body}"
);
}
#[test]
fn worklet_body_combined_modern_syntax_gets_lowered() {
let code = r#"
function fn(obj, fallback) {
'worklet';
const make = (x) => ({ x, msg: `value=${x}` });
return make(obj?.value ?? fallback);
}
"#;
let out = transform_fixture("Sample.ts", code, options_with_version());
let body = extract_first_init_data_code(&out).expect("init_data.code should be present");
for forbidden in ["=>", "`", "?.", "??"] {
assert!(
!body.contains(forbidden),
"modern syntax {forbidden:?} should be lowered inside init_data.code, got: {body}"
);
}
assert!(
body.contains("x: x"),
"shorthand props should be expanded, got: {body}"
);
}
#[test]
fn worklet_body_jsx_tag_is_not_captured() {
let code = r#"
import { Foo } from './foo';
const outer = 10;
function fn(): any {
'worklet';
const x = outer;
return <Foo>{x}</Foo>;
}
"#;
let out = transform_fixture("Sample.tsx", code, options_with_version());
let destructure = extract_factory_iife_destructure(&out, "fn")
.expect("fn factory IIFE destructuring should be emitted");
assert!(
ident_in_destructure(&destructure, "outer"),
"outer should be captured (positive control), got: {destructure}"
);
assert!(
!ident_in_destructure(&destructure, "Foo"),
"Foo (JSX tag) should not be captured, got: {destructure}"
);
}
#[test]
fn worklet_body_jsx_member_chain_is_not_captured() {
let code = r#"
import { Lib } from './lib';
function fn(): any {
'worklet';
return <Lib.View />;
}
"#;
let out = transform_fixture("Sample.tsx", code, options_with_version());
let destructure = extract_factory_iife_destructure(&out, "fn")
.expect("fn factory IIFE destructuring should be emitted");
assert!(
!ident_in_destructure(&destructure, "Lib"),
"Lib (JSX member chain root) should not be captured, got: {destructure}"
);
}
#[test]
fn worklet_body_jsx_expr_container_is_captured() {
let code = r#"
import { Foo } from './foo';
const message = 'hi';
function fn(): any {
'worklet';
return <Foo>{message}</Foo>;
}
"#;
let out = transform_fixture("Sample.tsx", code, options_with_version());
let destructure = extract_factory_iife_destructure(&out, "fn")
.expect("fn factory IIFE destructuring should be emitted");
assert!(
ident_in_destructure(&destructure, "message"),
"message (JSX expr container ref) should be captured, got: {destructure}"
);
assert!(
!ident_in_destructure(&destructure, "Foo"),
"Foo (JSX tag) should not be captured, got: {destructure}"
);
}
#[test]
fn worklet_class_new_substitution_keeps_binding_name() {
let code = r#"
class Other {
__workletClass = true;
value = 1;
}
class Owner {
__workletClass = true;
items = [];
constructor() {
this.items.push(new Other());
this.items.push(new Other());
}
}
"#;
let out = transform_fixture_resolved("Sample.ts", code, options_with_version());
let owner_code = out
.split("code:")
.find(|chunk| chunk.contains("new Other"))
.expect("a worklet body should contain `new Other`");
assert!(
owner_code.contains("const Other = Other__classFactory()")
|| owner_code.contains("var Other = Other__classFactory()"),
"class-new preamble must bind the unrenamed `Other`, got: {owner_code}"
);
assert!(
!owner_code.contains("Other1 = Other__classFactory"),
"class-new binding must not be hygiene-renamed, got: {owner_code}"
);
}
fn extract_factory_iife_destructure(out: &str, factory_name: &str) -> Option<String> {
let factory_start = format!("const {factory_name} =");
let start = out.find(&factory_start)?;
let tail = &out[start..];
let close_marker = "})(";
let close = tail.find(close_marker)?;
let after_open = close + close_marker.len();
let end_marker = ");";
let end_rel = tail[after_open..].find(end_marker)?;
Some(tail[after_open..after_open + end_rel].to_string())
}
fn ident_in_destructure(destructure: &str, name: &str) -> bool {
for line in destructure.lines() {
let trimmed = line.trim().trim_end_matches([',', ' ']);
let key = trimmed.split(':').next().unwrap_or("").trim();
if key == name {
return true;
}
}
false
}