(in-package :ps-test)
;;; Hand-written unit tests
(eval-when (:compile-toplevel :load-toplevel :execute)
(def-suite ps-tests))
(in-suite ps-tests)
(test-ps-js plus-is-not-commutative
(setf x (+ "before" x "after"))
"x = 'before' + x + 'after';")
(test-ps-js plus-works-if-first
(setf x (+ x "middle" "after"))
"x += 'middle' + 'after';")
(test-ps-js setf-side-effects
(progn
(let* ((x 10))
(defun side-effect()
(setf x 4)
(return 3))
(setf x (+ 2 (side-effect) x 5))))
"var x = 10;
function sideEffect() {
x = 4;
return 3;
};
x = 2 + sideEffect() + x + 5;")
;; Parenscript used to optimize incorrectly:
;; var x = 10;
;; function sideEffect() {
;; x = 4;
;; return 3;
;; };
;; x += 2 + sideEffect() + 5;
;;
;; Which is 20, not 14
(test-ps-js method-call-op-form
((@ (+ "" x) to-string))
"('' + x).toString()")
(test-ps-js method-call-op-form-args
((@ (+ "" x) to-string) 1 2 :baz 3)
"('' + x).toString(1, 2, 'baz', 3)")
(test-ps-js method-call-number
((@ 10 to-string))
"( 10 ).toString()")
(test-ps-js method-call-string
((@ "hi" to-string))
"'hi'.toString()")
(test-ps-js method-call-lit-object
((@ (create :to-string (lambda () (return "it works"))) to-string))
"( { toString : function () { return 'it works'; } } ).toString()")
(test-ps-js method-call-conditional
((if a x y) 1)
"(a ? x : y)(1)")
(test-ps-js method-call-variable
((@ x to-string))
"x.toString()")
(test-ps-js method-call-array
((@ (list 10 20) to-string))
"[ 10, 20 ].toString()")
(test-ps-js method-call-fn-call
((@ (foo) to-string))
"foo().toString()")
(test-ps-js method-call-lambda-fn
((@ (lambda () (alert 10)) to-string))
"( function () { alert(10); } ).toString()")
(test-ps-js method-call-lambda-call
((@ ((lambda (x) (return x)) 10) to-string))
"(function (x) { return x; })(10).toString()")
(test no-whitespace-before-dot
(let* ((str (ps1* '((@ ((lambda (x) (return x)) 10) to-string))))
(dot-pos (position #\. str :test #'char=))
(char-before (elt str (1- dot-pos)))
(a-parenthesis #\)))
(is (char= char-before a-parenthesis))))
(test-ps-js simple-slot-value
(let* ((foo (create :a 1)))
(alert (slot-value foo 'a)))
"var foo = { a : 1 };
alert(foo.a);")
(test-ps-js buggy-slot-value
(let* ((foo (create :a 1))
(slot-name "a"))
(alert (slot-value foo slot-name)))
" var foo = { a : 1 };
var slotName = 'a';
alert(foo[slotName]);
"); Last line was alert(foo.slotName) before bug-fix.
(test-ps-js buggy-slot-value-two
(slot-value foo (get-slot-name))
"foo[getSlotName()]")
(test-ps-js old-case-is-now-switch
;; Switch was "case" before, but that was very non-lispish.
;; For example, this code makes three messages and not one
;; which may have been expected. This is because a switch
;; statment must have a break statement for it to return
;; after the alert. Otherwise it continues on the next
;; clause.
(switch (aref blorg i)
(1 (alert "one"))
(2 (alert "two"))
(default (alert "default clause")))
"switch (blorg[i]) {
case 1: alert('one');
case 2: alert('two');
default: alert('default clause');
}")
(test-ps-js lisp-like-case
(case (aref blorg i)
(1 (alert "one"))
(2 (alert "two"))
(default (alert "default clause")))
"switch (blorg[i]) {
case 1:
alert('one');
break;
case 2:
alert('two');
break;
default: alert('default clause');
}")
(test-ps-js even-lispier-case
(case (aref blorg i)
((1 2) (alert "Below three"))
(3 (alert "Three"))
(t (alert "Something else")))
"switch (blorg[i]) {
case 1:
case 2:
alert('Below three');
break;
case 3:
alert('Three');
break;
default: alert('Something else');
}")
(test-ps-js otherwise-case
(case (aref blorg i)
(1 (alert "one"))
(otherwise (alert "default clause")))
"switch (blorg[i]) {
case 1:
alert('one');
break;
default: alert('default clause');
}")
(test escape-sequences-in-string
(let ((escapes `((#\\ . #\\)
(#\b . #\Backspace)
(#\f . ,(code-char 12))
("u000B" . ,(code-char #x000b));;Vertical tab, too uncommon to bother with
(#\n . #\Newline)
(#\r . #\Return)
(#\' . #\');;Double quote need not be quoted because parenscript strings are single quoted
(#\t . #\Tab)
("u001F" . ,(code-char #x001f));; character below 32
("u0080" . ,(code-char 128)) ;;Character over 127. Actually valid, parenscript escapes them to be sure.
("uABCD" . ,(code-char #xabcd)))));; Really above ascii.
(loop for (js-escape . lisp-char) in escapes
for generated = (ps1* `(let* ((x ,(format nil "hello~ahi" lisp-char)))))
for wanted = (format nil "var x = 'hello\\~ahi';" js-escape)
do (is (string= (normalize-js-code generated) wanted)))))
(test-ps-js complicated-symbol-name1
grid-rows[foo].bar
"gridRows[foo].bar")
(test-ps-js complicated-symbol-name2
*grid-rows*[foo].bar
"GRIDROWS[foo].bar")
(test-ps-js slot-value-setf
(setf (slot-value x 'y) (+ (+ a 3) 4))
"x.y = (a + 3) + 4;")
(test-ps-js slot-value-conditional1
(slot-value (if zoo foo bar) 'x)
"(zoo ? foo : bar).x")
(test-ps-js slot-value-conditional2
(slot-value (if (not zoo) foo bar) 'x)
"(!zoo ? foo : bar).x")
(test script-star-eval1
(is (string= "x = 1; y = 2;" (normalize-js-code (ps* '(setf x 1) '(setf y 2))))))
(test script-star-eval2
(is (string= "x = 1;" (normalize-js-code (ps* '(setf x 1))))))
(test-ps-js unquoted-nil
nil
"null")
(test-ps-js list-with-single-nil
(array nil)
"[null]")
(test-ps-js quoted-nil-is-array
'nil
"[]")
(test-ps-js defsetf1
(progn (defsetf baz (x y) (newval) `(set-baz ,x ,y ,newval))
(setf (baz 1 2) 3))
"var _js2 = 1; var _js3 = 2; var _js1 = 3; setBaz(_js2, _js3, _js1);")
(test-ps-js defsetf-short
(progn (defsetf baz set-baz "docstring")
(setf (baz 1 2 3) "foo"))
"setBaz(1, 2, 3, 'foo');")
(test-ps-js defun-setf1
(progn (defun (setf some-thing) (new-val i1 i2)
(setf (aref *some-thing* i1 i2) new-val))
(setf (some-thing 1 2) "foo"))
"function __setf_someThing(newVal, i1, i2) {
SOMETHING[i1][i2] = newVal;
};
var _js2 = 1;
var _js3 = 2;
var _js1 = 'foo';
__setf_someThing(_js1, _js2, _js3);")
(test-ps-js defun-optional1
(defun test-opt (&optional x) (return (if x "yes" "no")))
"function testOpt(x) {
if (x === undefined) {
x = null;
};
return x ? 'yes' : 'no';
}")
(test-ps-js defun-optional2
(defun foo (x &optional y) (+ x y))
"function foo(x, y) {
if (y === undefined) {
y = null;
};
x + y;
}")
(test-ps-js defun-optional3
(defun blah (&optional (x 0))
(return x))
"function blah(x) {
if (x === undefined) {
x = 0;
};
return x;
}")
(test-ps-js return-nothing
(return)
"return null")
(test-ps-js set-timeout
(do-set-timeout (10) (alert "foo"))
"setTimeout(function () { alert('foo'); }, 10)")
(test-ps-js operator-precedence
(* 3 (+ 4 5) 6)
"3 * (4 + 5) * 6")
(test-ps-js operators-1
(in prop obj)
"prop in obj")
(test-ps-js incf1
(incf foo bar)
"foo += bar")
(test-ps-js decf1
(decf foo bar)
"foo -= bar")
(test-ps-js incf2
(incf x 5)
"x += 5")
(test-ps-js decf2
(decf y 10)
"y -= 10")
(test-ps-js setf-conditional
(setf foo (if x 1 2))
"foo = x ? 1 : 2;")
(test-ps-js obj-literal-numbers
(create 1 "foo")
"{ 1 : 'foo' }")
(test-ps-js obj-literal-strings
(create "foo" 2)
"{ 'foo' : 2 }")
(test-ps-js slot-value-string
(slot-value foo "bar")
"foo['bar']")
(test-ps-js slot-value-string1
(slot-value "bar" 'length)
"'bar'.length")
(test-ps-js slot-value-progn
(slot-value (progn (some-fun "abc") "123") "length")
"(someFun('abc'), '123')['length']")
(test-ps-js method-call-block
((@ (progn (some-fun "abc") "123") to-string))
"(someFun('abc'), '123').toString()")
(test-ps-js create-blank
(create)
"{ }")
(test-ps-js blank-object-literal
{}
"{ }")
(test-ps-js array-literal1
[]
"[]")
(test-ps-js array-literal2
([])
"[]")
(test-ps-js array-literal3
([] 1 2 3)
"[1, 2, 3]")
(test-ps-js array-literal4
([] 1 (2 3))
"[1, [2, 3]]")
(test-ps-js array-literal5
([] (1 2) ("a" "b"))
"[[1, 2], ['a', 'b']]")
(test-ps-js defun-rest1
(defun foo (&rest bar) (alert bar[1]))
"function foo() {
var bar = [];
for (var i1 = 0; i1 < arguments.length - 0; i1 += 1) {
bar[i1] = arguments[i1 + 0];
};
alert(bar[1]);
}")
(test-ps-js defun-rest2
(defun foo (baz &rest bar) (return (+ baz (aref bar 1))))
"function foo(baz) {
var bar = [];
for (var i1 = 0; i1 < arguments.length - 1; i1 += 1) {
bar[i1] = arguments[i1 + 1];
};
return baz + bar[1];
}")
(test-ps-js defun-keyword1
(defun zoo (foo bar &key baz) (return (+ foo bar baz)))
"function zoo(foo, bar) {
var baz;
var _js3 = arguments.length;
for (var n1 = 2; n1 < _js3; n1 += 2) {
switch (arguments[n1]) {
case 'baz':
{
baz = arguments[n1 + 1];
};
};
};
if (baz === undefined) {
baz = null;
};
return foo + bar + baz;
}")
(test-ps-js defun-keyword2
(defun zoo (&key baz) (return (* baz baz)))
"function zoo() {
var baz;
var _js3 = arguments.length;
for (var n1 = 0; n1 < _js3; n1 += 2) {
switch (arguments[n1]) {
case 'baz':
{
baz = arguments[n1 + 1];
};
};
};
if (baz === undefined) {
baz = null;
};
return baz * baz;
}")
(test-ps-js defun-keyword3
(defun zoo (&key baz (bar 4)) (return (* baz bar)))
"function zoo() {
var baz;
var bar;
var _js3 = arguments.length;
for (var n1 = 0; n1 < _js3; n1 += 2) {
switch (arguments[n1]) {
case 'baz':
{
baz = arguments[n1 + 1];
};
break;
case 'bar':
{
bar = arguments[n1 + 1];
};
};
};
if (baz === undefined) {
baz = null;
};
if (bar === undefined) {
bar = 4;
};
return baz * bar;
}")
(test-ps-js keyword-funcall1
(func :baz 1)
"func('baz', 1)")
(test-ps-js keyword-funcall2
(func :baz 1 :bar foo)
"func('baz', 1, 'bar', foo)")
(test-ps-js keyword-funcall3
(fun a b :baz c)
"fun(a, b, 'baz', c)")
(test-ps-js cond1
(cond ((= x 1) 1))
"if (x == 1) {
1;
}")
(test-ps-js cond2
(cond ((= x 1) 2)
((= y (* x 4)) (foo "blah") (* x y)))
"if (x == 1) {
2;
} else if (y == x * 4) {
foo('blah');
x * y;
}")
(test-ps-js if-exp-without-else-returns-null
(return (if x 1))
"return x ? 1 : null")
(test-ps-js progn-expression-single-statement
(return (progn (* x y)))
"return x * y")
(test-ps-js cond-expression1
(defun foo () (return (cond ((< 1 2) (bar "foo") (* 4 5)))))
"function foo() {
return 1 < 2 ? (bar('foo'), 4 * 5) : null;
}")
(test-ps-js cond-expression2
(defun foo () (return (cond ((< 2 1) "foo") ((= 7 7) "bar"))))
"function foo() {
return 2 < 1 ? 'foo' : (7 == 7 ? 'bar' : null);
}")
(test-ps-js cond-expression-final-t-clause
(defun foo () (return (cond ((< 1 2) (bar "foo") (* 4 5)) ((= a b) (+ c d)) ((< 1 2 3 4 5) x) (t "foo"))))
"function foo() {
return 1 < 2 ? (bar('foo'), 4 * 5) : (a == b ? c + d : (1 < 2 < 3 < 4 < 5 ? x : 'foo'));
}")
(test-ps-js cond-expression-middle-t-clause ;; should this signal a warning?
(defun foo () (return (cond ((< 2 1) 5) (t "foo") ((< 1 2) "bar"))))
"function foo() {
return 2 < 1 ? 5 : 'foo';
}")
(test-ps-js funcall-if-expression
(document.write
(if (= *linkornot* 1)
(ps-html ((:a :href "#"
:onclick (ps-inline (transport)))
img))
img))
"document.write(LINKORNOT == 1 ? '' + img + '' : img)")
(test-ps-js negate-number-literal ;; ok, this was broken and fixed before, but no one bothered to add the test!
(- 1)
"-1")
(test macro-environment1
(is (string= (normalize-js-code (let* ((macroname (gensym)))
(ps* `(defmacro ,macroname (x) `(+ ,x 123))
`(defun test1 ()
(macrolet ((,macroname (x) `(aref data ,x)))
(when (,macroname x)
(setf (,macroname x) 123)))))))
(normalize-js-code
"function test1() {
if (data[x]) {
data[x] = 123;
};
};
"))))
(test macro-environment2
(is (string= (normalize-js-code (let ((outer-lexical-variable 1))
(defpsmacro macro-environment2-macro (x)
`(+ ,outer-lexical-variable ,x))
(ps* '(macro-environment2-macro 2))))
(normalize-js-code "1 + 2;"))))
(test-ps-js ampersand-whole-1
(macrolet ((foo (&whole foo bar baz)
(declare (ignore bar baz))
(format nil "~a" foo)))
(foo 1 2))
"'(FOO 1 2)';")
(test-ps-js keyword-consistent
:x
"'x'")
(test-ps-js simple-symbol-macrolet
(symbol-macrolet ((x 1)) x)
"1;")
(test-ps-js compound-symbol-macrolet
(symbol-macrolet ((x 123)
(y (* 2 x)))
y)
"2 * 123;")
(test-ps-js define-symbol-macro
(progn (define-symbol-macro tst-sym-macro 2)
tst-sym-macro)
"2;")
(test-ps-js expression-progn
(defun f () (return (progn (foo) (if x 1 2))))
"function f() {
return (foo(), x ? 1 : 2);
}")
(test-ps-js let-decl-in-expression
(defun f (x) (return (if x 1 (let* ((foo x)) foo))))
"function f(x) {
var foo;
return x ? 1 : (foo = x, foo);
}")
(test-ps-js special-var1
(progn (defvar *foo*) (let* ((*foo* 2)) (* *foo* 2)))
"var FOO;
var tempstackvar1;
try {
tempstackvar1 = FOO;
FOO = 2;
FOO * 2;
} finally {
FOO = tempstackvar1;
delete tempstackvar1;
};")
(test-ps-js special-var2
(progn (defvar *foo*) (let* ((*baz* 3) (*foo* 2)) (* *foo* 2 *baz*)))
"var FOO;
var BAZ = 3;
var tempstackvar1;
try {
tempstackvar1 = FOO;
FOO = 2;
FOO * 2 * BAZ;
} finally {
FOO = tempstackvar1;
delete tempstackvar1;
};
")
(test-ps-js literal1
(setf x undefined)
"x = undefined;")
(test-ps-js literal2
(aref this x)
"this[x]")
(test-ps-js setf-dec1
(setf x (- 1 x 2))
"x = 1 - x - 2;")
(test-ps-js setf-dec2
(setf x (- x 1 2))
"x = x - 1 - 2;")
(test-ps-js special-char-equals
blah=
"blahequals")
(test-ps-js setf-operator-priority
(return (or (slot-value cache id)
(setf (slot-value cache id) (document.get-element-by-id id))))
"return cache[id] || (cache[id] = document.getElementById(id))")
(test-ps-js aref-operator-priority
(aref (if (and x (> (length x) 0))
(aref x 0)
y)
z)
"(x && x.length > 0 ? x[0] : y)[z]")
(test-ps-js aref-operator-priority1
(aref (or (slot-value x 'y)
(slot-value a 'b))
z)
"(x.y || a.b)[z]")
(test-ps-js aref-operator-priority2
(aref (if a b c) 0)
"(a ? b : c)[0]")
(test-ps-js negative-operator-priority
(- (if x y z))
"-(x ? y : z)")
(test-ps-js op-p1
(new (or a b))
"new (a || b)")
(test-ps-js op-p2
(delete (if a (or b c) d))
"delete (a ? b || c : d)")
(test-ps-js op-p3
(not (if (or x (not y)) z))
"!(x || !y ? z : null)")
(test-ps-js op-p4
(- (- (* 1 2) 3))
"-(1 * 2 - 3)")
(test-ps-js op-p5
(instanceof (or a b) (if x y z))
"((a || b) instanceof (x ? y : z))")
(test-ps-js op-p7
(or x (if (= x 0) "zero" "empty"))
"x || (x == 0 ? 'zero' : 'empty')")
(test-ps-js named-op-expression
(throw (if a b c))
"throw a ? b : c")
(test-ps-js named-op-expression1
(typeof (or x y))
"typeof (x || y)")
(test-ps-js aref-array-expression
(aref (or a b c) 0)
"(a || b || c)[0]")
(test-ps-js slot-value-operator
(slot-value (or a b c) 'd)
"(a || b || c).d")
(test-ps-js slot-value-parens
(slot-value (slot-value foo 'bar) 'baz)
"foo.bar.baz")
(test-ps-js funcall-funcall
((foo))
"foo()()")
(test-ps-js expression-funcall
((or (@ window eval) eval) foo nil)
"(window.eval || eval)(foo, null)")
(test-ps-js expression-funcall1
(((or (@ window eval) eval) foo nil))
"(window.eval || eval)(foo, null)()")
(test-ps-js expression-funcall2
(((or (@ window eval) eval)) foo nil)
"(window.eval || eval)()(foo, null)")
(test-ps-js slot-value-object-literal
(slot-value (create :a 1) 'a)
"({ a : 1 }).a")
(test-ps-js slot-value-lambda
(slot-value (lambda ()) 'prototype)
"(function () { }).prototype")
(test-ps-js who-html1
(who-ps-html (:span :class "ticker-symbol"
:ticker-symbol symbol
(:a :href "http://foo.com"
symbol)
(:span :class "ticker-symbol-popup")))
"'' + symbol + ''")
(test-ps-js flet1
((lambda () (flet ((foo (x) (return (1+ x)))) (return (foo 1)))))
"(function () {
var foo = function (x) {
return x + 1;
};
return foo(1);
})()")
(test-ps-js labels1
((lambda () (labels ((foo (x)
(return (if (=== 0 x)
0
(+ x (foo (1- x)))))))
(return (foo 3)))))
"(function () {
var foo = function foo(x) {
return 0 === x ? 0 : x + foo(x - 1);
};
return foo(3);
})()")
(test-ps-js for-loop-var-init-exp
((lambda (x)
(return (do* ((y (if x 0 1) (1+ y))
(z 0 (1+ z)))
((= y 3) z))))
true)
"(function (x) {
return (function () {
for (var y = x ? 0 : 1, z = 0; y != 3; y += 1, z += 1) {
};
return z;
})();
})(true)")
(test-ps-js math-pi
pi
"Math.PI")
(test-ps-js literal-array
'(1 2 3)
"[1, 2, 3]")
(test-ps-js literal-array-1
'(1 foo 3)
"[1, 'foo', 3]")
(test ps-lisp-expands-in-lexical-environment
(is (string= "5;" (let ((x 5)) (ps (lisp x))))))
(test ps*-lisp-expands-in-null-lexical-environment
(signals error (let ((x 5)) (declare (ignore x)) (ps* '(lisp x)))))
(test ps*-lisp-expands-in-dynamic-environment
(is (string= "1 + 2;" (let ((*print-level* 2)) (ps* '(+ 1 (lisp *print-level*)))))))
(test ps-lisp-dynamic-environment
(is (string= "1 + 2;" (let ((*print-level* 2)) (ps (+ 1 (lisp *print-level*)))))))
(test-ps-js ps-js-target-version-keyword-test1
(defun foo (x y &key bar baz))
"function foo(x, y) {
var x1 = Array.prototype.indexOf.call(arguments, 'bar', 2);
var bar = -1 == x1 ? null : arguments[x1 + 1];
var x2 = Array.prototype.indexOf.call(arguments, 'baz', 2);
var baz = -1 == x2 ? null : arguments[x2 + 1];
}"
:js-target-version 1.6)