From a0324d2bdaa1873a72dd19676a9c11cd93d54fed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:37:47 +0000 Subject: [PATCH] test(swc-plugin): verify class-name preservation at runtime (#4015) Signed-off-by: Nathan Rajlich --- .changeset/swc-class-name-runtime-tests.md | 4 + .../transform/tests/class_names.rs | 107 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 .changeset/swc-class-name-runtime-tests.md create mode 100644 packages/swc-plugin-workflow/transform/tests/class_names.rs diff --git a/.changeset/swc-class-name-runtime-tests.md b/.changeset/swc-class-name-runtime-tests.md new file mode 100644 index 0000000000..969c4362f0 --- /dev/null +++ b/.changeset/swc-class-name-runtime-tests.md @@ -0,0 +1,4 @@ +--- +--- + +Add runtime regression tests for class names and registration identity in both compiler transform modes, with no package release. diff --git a/packages/swc-plugin-workflow/transform/tests/class_names.rs b/packages/swc-plugin-workflow/transform/tests/class_names.rs new file mode 100644 index 0000000000..de0ec62c89 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/class_names.rs @@ -0,0 +1,107 @@ +use std::process::Command; +use swc_core::ecma::{transforms::testing::Tester, visit::visit_mut_pass}; +use swc_workflow::{StepTransform, TransformMode}; + +fn assert_class_names(mode: TransformMode) { + for members in [ + r#"async run() { 'use step'; return 42; }"#, + r#" + static [Symbol.for('workflow-serialize')](instance) { return {}; } + static [Symbol.for('workflow-deserialize')](data) { return new this(); } + "#, + ] { + let source = r#" + import assert from 'node:assert/strict'; + + globalThis[Symbol.for('WORKFLOW_USE_STEP')] = () => function proxy() {}; + + var C = class { static initialName = this.name; __MEMBERS__ }; + let LetClass = class { __MEMBERS__ }; + const ConstClass = class { __MEMBERS__ }; + var Alpha = class { __MEMBERS__ }, Beta = class { __MEMBERS__ }; + var Parenthesized = (class { __MEMBERS__ }); + let Assigned; + Assigned = class { __MEMBERS__ }; + var Explicit = class InternalName { __MEMBERS__ }; + const objects = { + Key: class { __MEMBERS__ }, + 'key-with-dashes': class { __MEMBERS__ }, + }; + const array = [class { __MEMBERS__ }]; + const conditional = true ? class { __MEMBERS__ } : null; + function capture(value) { return value; } + const argument = capture(class { __MEMBERS__ }); + + const classes = [ + [C, 'C'], + [LetClass, 'LetClass'], + [ConstClass, 'ConstClass'], + [Alpha, 'Alpha'], + [Beta, 'Beta'], + [Parenthesized, 'Parenthesized'], + [Assigned, 'Assigned'], + [Explicit, 'InternalName'], + [objects.Key, 'Key'], + [objects['key-with-dashes'], 'key-with-dashes'], + [array[0], ''], + [conditional, ''], + [argument, ''], + ]; + assert.equal(C.initialName, 'C'); + for (const [cls, name] of classes) { + assert.equal(cls.name, name); + assert.deepEqual(Object.getOwnPropertyDescriptor(cls, 'name'), { + value: name, + writable: false, + enumerable: false, + configurable: true, + }); + } + "# + .replace("__MEMBERS__", members); + + let code = Tester::run(|tester| { + let program = tester.apply_transform( + visit_mut_pass(StepTransform::new(mode.clone(), "input.js".into(), None)), + "input.js", + Default::default(), + Some(true), + &source, + )?; + Ok(tester.print(&program, &tester.comments.clone())) + }); + + // Execute both the original and freshly transformed source, rather + // than relying on snapshots of the generated class identifiers. + let registered = format!( + r#"{code} + const registry = globalThis[Symbol.for('workflow-class-registry')]; + for (const [cls] of classes) {{ + assert.equal(typeof cls.classId, 'string'); + assert.equal(registry.get(cls.classId), cls); + }} + "# + ); + for (label, code) in [("original", &source), ("transformed", ®istered)] { + let output = Command::new("node") + .args(["--input-type=module", "--eval", code]) + .output() + .expect("Node.js is required for class-name runtime tests"); + assert!( + output.status.success(), + "{label} {mode:?}: {}\n{code}", + String::from_utf8_lossy(&output.stderr) + ); + } + } +} + +#[test] +fn inferred_class_names_step_mode() { + assert_class_names(TransformMode::Step); +} + +#[test] +fn inferred_class_names_workflow_mode() { + assert_class_names(TransformMode::Workflow); +}