Skip to content

Commit e1d3900

Browse files
Fix: Properly escape single quotes in Unicorn.call() arguments
1 parent 2368bad commit e1d3900

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

src/django_unicorn/static/unicorn/js/unicorn.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ export function call(componentNameOrKey, methodName, ...args) {
171171
args.forEach((arg) => {
172172
if (typeof arg !== "undefined") {
173173
if (typeof arg === "string") {
174-
argString = `${argString}'${arg}', `;
174+
// Use JSON.stringify to properly escape quotes and special characters
175+
// Then replace double quotes with single quotes for Python compatibility
176+
const escapedArg = JSON.stringify(arg).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'");
177+
argString = `${argString}'${escapedArg}', `;
175178
} else {
176179
argString = `${argString}${arg}, `;
177180
}

tests/call_method_parser/test_parse_call_method_name.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,44 @@ def test_special_method_with_parens():
8282
actual = parse_call_method_name("$reset()")
8383

8484
assert actual == expected
85+
86+
87+
def test_string_with_single_quotes():
88+
expected = ("set_name", ("It's a test",), {})
89+
actual = parse_call_method_name("set_name('It\\'s a test')")
90+
91+
assert actual == expected
92+
93+
94+
def test_string_with_double_quotes():
95+
expected = ("set_name", ('He said "hello"',), {})
96+
actual = parse_call_method_name('set_name(\'He said "hello"\')')
97+
98+
assert actual == expected
99+
100+
101+
def test_string_with_backslashes():
102+
expected = ("set_name", ("C:\\Users\\test",), {})
103+
actual = parse_call_method_name("set_name('C:\\\\Users\\\\test')")
104+
105+
assert actual == expected
106+
107+
108+
def test_complex_chemical_name_issue_607():
109+
"""Test for issue #607: string values with single quotes break function calls"""
110+
chemical_name = "Chloro(2-dicyclohexylphosphino-3,6-dimethoxy-2',4',6'-tri-i-propyl-1,1'-biphenyl)(2'-amino-1,1'-biphenyl-2-yl)palladium(II)"
111+
expected = ("set_new_catalyst", (chemical_name,), {})
112+
actual = parse_call_method_name(
113+
"set_new_catalyst('Chloro(2-dicyclohexylphosphino-3,6-dimethoxy-2\\',"
114+
"4\\',6\\'-tri-i-propyl-1,1\\'-biphenyl)(2\\'-amino-1,1\\'-biphenyl-2-yl)palladium(II)')"
115+
)
116+
117+
assert actual == expected
118+
119+
120+
def test_string_with_mixed_quotes_and_parentheses():
121+
expected = ("set_name", ("test(with 'quotes' and \"double\")",), {})
122+
actual = parse_call_method_name("set_name('test(with \\'quotes\\' and \"double\")')")
123+
124+
assert actual == expected
125+

tests/js/unicorn/call.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,61 @@ test("call a method with string and int argument", (t) => {
3535

3636
call("text-inputs", "testMethod", "test1", 2);
3737
});
38+
39+
test("call a method with string containing single quotes", (t) => {
40+
const component = getComponent();
41+
components[component.id] = component;
42+
43+
component.callMethod = (methodName) => {
44+
t.true(methodName === "testMethod('It\\'s a test')");
45+
};
46+
47+
call("text-inputs", "testMethod", "It's a test");
48+
});
49+
50+
test("call a method with string containing double quotes", (t) => {
51+
const component = getComponent();
52+
components[component.id] = component;
53+
54+
component.callMethod = (methodName) => {
55+
t.true(methodName === 'testMethod(\'He said "hello"\')');
56+
};
57+
58+
call("text-inputs", "testMethod", 'He said "hello"');
59+
});
60+
61+
test("call a method with string containing backslashes", (t) => {
62+
const component = getComponent();
63+
components[component.id] = component;
64+
65+
component.callMethod = (methodName) => {
66+
t.true(methodName === "testMethod('C:\\\\Users\\\\test')");
67+
};
68+
69+
call("text-inputs", "testMethod", "C:\\Users\\test");
70+
});
71+
72+
test("call a method with complex chemical name (issue #607)", (t) => {
73+
const component = getComponent();
74+
components[component.id] = component;
75+
76+
const chemicalName = "Chloro(2-dicyclohexylphosphino-3,6-dimethoxy-2',4',6'-tri-i-propyl-1,1'-biphenyl)(2'-amino-1,1'-biphenyl-2-yl)palladium(II)";
77+
78+
component.callMethod = (methodName) => {
79+
t.true(methodName === `testMethod('Chloro(2-dicyclohexylphosphino-3,6-dimethoxy-2\\',4\\',6\\'-tri-i-propyl-1,1\\'-biphenyl)(2\\'-amino-1,1\\'-biphenyl-2-yl)palladium(II)')`);
80+
};
81+
82+
call("text-inputs", "testMethod", chemicalName);
83+
});
84+
85+
test("call a method with string containing both quotes and parentheses", (t) => {
86+
const component = getComponent();
87+
components[component.id] = component;
88+
89+
component.callMethod = (methodName) => {
90+
t.true(methodName === "testMethod('test(with \\'quotes\\' and \"double\")')");
91+
};
92+
93+
call("text-inputs", "testMethod", "test(with 'quotes' and \"double\")");
94+
});
95+

0 commit comments

Comments
 (0)