www_app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +84 -0
- data/TODO.md +13 -0
- data/VERSION +1 -0
- data/bin/www_app +46 -0
- data/doc/Design.md +123 -0
- data/doc/Why_this_arch.rb +104 -0
- data/lib/public/jquery-2.1.1.js +4 -0
- data/lib/public/jquery.serialize-object.min.js +8 -0
- data/lib/public/underscore-1.7.0.js +6 -0
- data/lib/public/underscore-min.map +1 -0
- data/lib/public/underscore.string-2.3.0.js +1 -0
- data/lib/public/www_app.js +824 -0
- data/lib/www_app/Clean.rb +169 -0
- data/lib/www_app/dsl.rb +86 -0
- data/lib/www_app/source.rb +53 -0
- data/lib/www_app.rb +1024 -0
- data/specs/as_ruby/0000-new.rb +23 -0
- data/specs/as_ruby/0010-attrs.rb +29 -0
- data/specs/as_ruby/0011-class.rb +39 -0
- data/specs/as_ruby/0011-href.rb +37 -0
- data/specs/as_ruby/0011-id.rb +39 -0
- data/specs/as_ruby/0020-tag.rb +21 -0
- data/specs/as_ruby/0020-tag_content.rb +43 -0
- data/specs/as_ruby/0021-body.rb +16 -0
- data/specs/as_ruby/0021-form.rb +22 -0
- data/specs/as_ruby/0021-link.rb +26 -0
- data/specs/as_ruby/0021-script.rb +44 -0
- data/specs/as_ruby/0030-mustache.rb +113 -0
- data/specs/as_ruby/0040-css.rb +174 -0
- data/specs/as_ruby/0050-on.rb +64 -0
- data/specs/client-side/index.html +90 -0
- data/specs/client-side/index.js +777 -0
- data/specs/lib/config.ru +96 -0
- data/specs/lib/helpers.rb +230 -0
- data/specs/lib/qunit/qunit-1.15.0.css +237 -0
- data/specs/lib/qunit/qunit-1.15.0.js +2495 -0
- data/specs/lib/sample.rb +23 -0
- data/specs/sampe.2.rb +14 -0
- data/specs/sample.3.rb +17 -0
- data/specs/sample.rb +44 -0
- data/www_app.gemspec +38 -0
- metadata +271 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
describe "IS_DEV" do
|
4
|
+
|
5
|
+
before {
|
6
|
+
@orig = ENV['IS_DEV']
|
7
|
+
}
|
8
|
+
|
9
|
+
after {
|
10
|
+
ENV['IS_DEV'] = @orig
|
11
|
+
}
|
12
|
+
|
13
|
+
it "raises RuntimeError if passed a block and non-IS_DEV" do
|
14
|
+
should.raise(RuntimeError) {
|
15
|
+
ENV['IS_DEV'] = nil
|
16
|
+
WWW_App.new {
|
17
|
+
div {}
|
18
|
+
}
|
19
|
+
}.message.should.match /non-DEV/i
|
20
|
+
end
|
21
|
+
|
22
|
+
end # === describe IS_DEV ===
|
23
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
describe "all attrs" do
|
3
|
+
|
4
|
+
it "raises a RuntimeError if tag has an unknown attribute" do
|
5
|
+
should.raise(RuntimeError) {
|
6
|
+
actual {
|
7
|
+
a.href('/href') {
|
8
|
+
tag![:attrs][:hi] = 'hiya'
|
9
|
+
"here"
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}.message.should.match /Unknown attr: :hi/
|
13
|
+
end
|
14
|
+
|
15
|
+
it "escapes attributes" do
|
16
|
+
target %^<a rel="<hello">hello</a>^
|
17
|
+
actual {
|
18
|
+
a.rel('<hello') { 'hello' }
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end # === describe all attrs
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
describe :^ do
|
5
|
+
|
6
|
+
it "raises Invalid if class has invalid chars" do
|
7
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
8
|
+
actual {
|
9
|
+
div.^('hi)o') { 'hello' }
|
10
|
+
}
|
11
|
+
}.message.should.match /hi\)o/
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# ==========================================================================
|
16
|
+
# =========== end sanitization specs =====================================
|
17
|
+
# ==========================================================================
|
18
|
+
|
19
|
+
|
20
|
+
it "adds 'class' attribute: a.^(:warning, :red) { }" do
|
21
|
+
target '<a class="warning red" href="/here">Here</a>'
|
22
|
+
|
23
|
+
actual do
|
24
|
+
a.^(:warning, :red).href('/here') { "Here" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "merges classes: a.^(:super).^(:low)" do
|
29
|
+
target '<a class="super low" href="/now">Now</a>'
|
30
|
+
|
31
|
+
actual do
|
32
|
+
a.^(:super).^(:low).href("/now") { "Now" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end # === describe :^
|
37
|
+
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
describe :href do
|
4
|
+
|
5
|
+
it "produces 'a' elements with 'href'" do
|
6
|
+
target '<a href="/here">Here</a>'
|
7
|
+
|
8
|
+
actual do
|
9
|
+
a.href('/here') { "Here" }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "escapes chars in 'href' attributes as a url" do
|
14
|
+
target %^<a href="/home/?a&b">home</a>^
|
15
|
+
|
16
|
+
actual do
|
17
|
+
a.href('/home/?a&b') { "home" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises Invalid_HREF for :href: javacript:" do
|
22
|
+
should.raise(Escape_Escape_Escape::Invalid_HREF) {
|
23
|
+
actual do
|
24
|
+
a.href('javascript://alert()') { 'hello' }
|
25
|
+
end
|
26
|
+
}.message.should.match /javascript/
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raises Escape_Escape_Escape::Invalid_Relative_HREF if not relative using :link" do
|
30
|
+
should.raise(Escape_Escape_Escape::Invalid_Relative_HREF) {
|
31
|
+
actual {
|
32
|
+
link.href('http://www.google.com/s.css')./
|
33
|
+
}
|
34
|
+
}.message.should.match /google/
|
35
|
+
end
|
36
|
+
|
37
|
+
end # === describe :href
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
describe :* do
|
5
|
+
|
6
|
+
it "raises Invalid if id has invalid chars" do
|
7
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
8
|
+
actual do
|
9
|
+
div.*('a<&a') { 'hello' }
|
10
|
+
end
|
11
|
+
}.message.should.match /a<&a/
|
12
|
+
end
|
13
|
+
|
14
|
+
it "raises HTML_ID_Duplicate if id is used more than once" do
|
15
|
+
should.raise(WWW_App::HTML_ID_Duplicate) {
|
16
|
+
actual do
|
17
|
+
div.*(:my_id) { '1' }
|
18
|
+
div.*(:my_id) { '2' }
|
19
|
+
end
|
20
|
+
}.message.should.match /my_id/
|
21
|
+
end
|
22
|
+
|
23
|
+
# ==========================================================================
|
24
|
+
# =========== end sanitization specs =====================================
|
25
|
+
# ==========================================================================
|
26
|
+
|
27
|
+
|
28
|
+
it "adds 'id' attribute: a.*(:warning)(...) { }" do
|
29
|
+
target '<a id="warning" href="/there">There</a>'
|
30
|
+
|
31
|
+
actual do
|
32
|
+
a.*(:warning).href('/there') { "There" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end # === describe WWW_App.new ===
|
37
|
+
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
describe :tag do
|
3
|
+
|
4
|
+
it "raises Invalid if tag starts w/ a number" do
|
5
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
6
|
+
actual do
|
7
|
+
div.*('0a') { 'hello' }
|
8
|
+
end
|
9
|
+
}.message.should.match /0a/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "raises Invalid if tag is unknown: e.g. :footers" do
|
13
|
+
should.raise(StandardError) {
|
14
|
+
actual do
|
15
|
+
tag(:footers) { 'bye' }
|
16
|
+
end
|
17
|
+
}.message.should.match /footers/
|
18
|
+
end
|
19
|
+
|
20
|
+
end # === describe :tag ===
|
21
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
describe "HTML contents" do
|
3
|
+
|
4
|
+
it "uses last String value as content" do
|
5
|
+
target %^<p>Hello</p>^
|
6
|
+
|
7
|
+
actual do
|
8
|
+
p { "Hello" }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "closes tag with :/" do
|
13
|
+
target %^<p></p>^
|
14
|
+
actual {
|
15
|
+
p./
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "does not give end-tags to void tags (self-closing tags)" do
|
20
|
+
target %^<br />\n<img />^
|
21
|
+
actual {
|
22
|
+
br./
|
23
|
+
img./
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "escapes inner text" do
|
28
|
+
target %^<p>& here lies jack</p>^
|
29
|
+
|
30
|
+
actual do
|
31
|
+
p { "& here lies jack" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "strips out W3C unallowed Unicode chars" do
|
36
|
+
target %^<div>hello hello</div>^
|
37
|
+
actual do
|
38
|
+
div { "hello \u0340\u0341\u17a3\u17d3\u2028\u2029 hello" }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end # === describe HTML contents
|
43
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
describe :forms do
|
3
|
+
|
4
|
+
it "requires a CSRF token" do
|
5
|
+
should.raise(Mustache::ContextMiss) {
|
6
|
+
actual do
|
7
|
+
form.action('/home') {
|
8
|
+
}
|
9
|
+
end
|
10
|
+
}.message.should.match /auth_token/
|
11
|
+
end
|
12
|
+
|
13
|
+
it "raises Invalid_Relative_HREF if :action is not a relative url" do
|
14
|
+
should.raise(Escape_Escape_Escape::Invalid_Relative_HREF) {
|
15
|
+
actual :auth_token => 'mytoken' do
|
16
|
+
form.action('http://www.google.com/') {}
|
17
|
+
end
|
18
|
+
}.message.should.match /google.com/
|
19
|
+
end
|
20
|
+
|
21
|
+
end # === describe :forms ===
|
22
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
describe :link do
|
3
|
+
|
4
|
+
it "raises Invalid_Relative_HREF" do
|
5
|
+
should.raise(Escape_Escape_Escape::Invalid_Relative_HREF) {
|
6
|
+
actual do
|
7
|
+
link.href('http://www.google.com/')./
|
8
|
+
end
|
9
|
+
}.message.should.match /google/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "escapes slashes in :href" do
|
13
|
+
target %^<link href="/css/css/styles.css" />^
|
14
|
+
actual {
|
15
|
+
link.href('/css/css/styles.css')./
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "allows a relative :href" do
|
20
|
+
target %^<link href="/css.css" />^
|
21
|
+
actual {
|
22
|
+
link.href('/css.css')./
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
end # === describe :link
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
describe :script do
|
3
|
+
|
4
|
+
it "raises Invalid_Relative_HREF if :src is not relative" do
|
5
|
+
should.raise(Escape_Escape_Escape::Invalid_Relative_HREF) {
|
6
|
+
actual do
|
7
|
+
script.src('http://www.example.org/file.js')./
|
8
|
+
end
|
9
|
+
}.message.should.match /example\.org/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "allows a relative :src" do
|
13
|
+
target %^<script src="/file.js"></script>^
|
14
|
+
actual {
|
15
|
+
script.src('/file.js')./
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "escapes slashes in attr :src" do
|
20
|
+
target %^<script src="/dir/file.js"></script>^
|
21
|
+
actual {
|
22
|
+
script.src('/dir/file.js')./
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "does not allow vars in :script :src attribute" do
|
27
|
+
target :body, <<-EOF
|
28
|
+
<script src="help"></script>
|
29
|
+
EOF
|
30
|
+
|
31
|
+
actual do
|
32
|
+
script.src(:help)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "ignores text for :script block" do
|
37
|
+
target %^<script src="/hello.js"></script>^
|
38
|
+
actual {
|
39
|
+
script.src('/hello.js') { 'hello' }
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end # === describe :JS ===
|
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
describe :mustache do
|
3
|
+
|
4
|
+
it "replaces values with hash" do
|
5
|
+
target <<-EOF
|
6
|
+
<div>hello</div>
|
7
|
+
EOF
|
8
|
+
|
9
|
+
actual greeting: 'hello' do
|
10
|
+
div { :greeting }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "raises ContextMiss for unauthorized methods" do
|
15
|
+
should.raise(Mustache::ContextMiss) {
|
16
|
+
target "nothing"
|
17
|
+
actual name: 'Bob' do
|
18
|
+
div { :object_id }
|
19
|
+
end
|
20
|
+
}.message.should.match /Can't find \.html\(:object_id\)/i
|
21
|
+
|
22
|
+
should.raise(Mustache::ContextMiss) {
|
23
|
+
target "nothing"
|
24
|
+
actual name: 'Bob' do
|
25
|
+
div { :to_s }
|
26
|
+
end
|
27
|
+
}.message.should.match /Can't find \.html\(:to_s\)/i
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises ContextMiss when an unknown value is requested" do
|
31
|
+
should.raise(Mustache::ContextMiss) {
|
32
|
+
actual name: 'Bob' do
|
33
|
+
div { :object_ids }
|
34
|
+
end
|
35
|
+
}.message.should.match /object_ids/
|
36
|
+
end
|
37
|
+
|
38
|
+
it "renders if inverted section and key is not defined" do
|
39
|
+
target <<-EOF
|
40
|
+
<div>hello</div>
|
41
|
+
EOF
|
42
|
+
|
43
|
+
actual {
|
44
|
+
render_unless(:here) { div { 'hello' } }
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it "escapes html" do
|
49
|
+
target <<-EOF
|
50
|
+
<div>& / hello</div>
|
51
|
+
EOF
|
52
|
+
|
53
|
+
actual :hello=>'& / hello' do
|
54
|
+
div { :hello }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "escapes html in nested values" do
|
59
|
+
target <<-EOF
|
60
|
+
<div id="my_box"><span>& hello 1</span>
|
61
|
+
<span>&& hello 2</span></div>
|
62
|
+
EOF
|
63
|
+
|
64
|
+
actual(:hello=>{:msg=>'& hello 1', :goodbye=>nil}) {
|
65
|
+
div.*(:my_box) {
|
66
|
+
render_if(:hello) {
|
67
|
+
span { :msg }
|
68
|
+
render_unless(:goodbye) { span { '&& hello 2' } }
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "escapes :href in nested values" do
|
75
|
+
target %^<div><div><a href="/hello">hello</a></div></div>^
|
76
|
+
actual(o: {url: '/hello', msg: 'hello'}) {
|
77
|
+
div {
|
78
|
+
render_if(:o) {
|
79
|
+
div { a.href(:url) { :msg } }
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not allow vars to be used in form :action" do
|
86
|
+
target %^<form action="url"><input type="hidden" name="auth_token" value="hello" /></form>^
|
87
|
+
|
88
|
+
actual :auth_token => 'hello' do
|
89
|
+
form.action(:url) {}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "does not allow vars to be used in :link :href" do
|
94
|
+
target %^<link href="hello" />^
|
95
|
+
actual {
|
96
|
+
link.href(:hello)./
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
it "raises ContextMiss if encounters unescaped value" do
|
101
|
+
should.raise(Mustache::ContextMiss) {
|
102
|
+
actual(blue: 'hello<') {
|
103
|
+
div { '-- {{{ blue }}}' }
|
104
|
+
}
|
105
|
+
}.message.should.match /blue/
|
106
|
+
end
|
107
|
+
|
108
|
+
end # === describe Sanitize mustache ===
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
@@ -0,0 +1,174 @@
|
|
1
|
+
|
2
|
+
describe :css do
|
3
|
+
|
4
|
+
it "raises Invalid if class name has invalid chars: class=\"hi;o\"" do
|
5
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
6
|
+
actual {
|
7
|
+
div.^('hi;o') {
|
8
|
+
border '1px solid #fff'
|
9
|
+
'hi o'
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}.message.should.match /hi;o/
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises Invalid if css value has invalid chars: something *" do
|
16
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
17
|
+
actual do
|
18
|
+
div {
|
19
|
+
background 'something *'
|
20
|
+
}
|
21
|
+
end
|
22
|
+
}.message.should.match /something \*/
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'raises Invalid if css selector has invalid chars: s*s' do
|
26
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
27
|
+
actual do
|
28
|
+
div.^("s*s") { border '1px'}
|
29
|
+
end
|
30
|
+
}.message.should.match /s\*s/
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises Invalid if contains 'expression:'" do
|
34
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
35
|
+
actual do
|
36
|
+
div {
|
37
|
+
background 'solid expression:'
|
38
|
+
}
|
39
|
+
end
|
40
|
+
}.message.should.match /expression:/
|
41
|
+
end
|
42
|
+
|
43
|
+
it "raises Invalid if contains 'expression&'" do
|
44
|
+
should.raise(Escape_Escape_Escape::Invalid) {
|
45
|
+
actual do
|
46
|
+
div {
|
47
|
+
background 'solid expression&'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
}.message.should.match /expression&/
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sanitizes urls" do
|
54
|
+
target :style, <<-EOF
|
55
|
+
div.box {
|
56
|
+
background-image: url(http://www.example.com/back.png);
|
57
|
+
}
|
58
|
+
EOF
|
59
|
+
|
60
|
+
actual do
|
61
|
+
div.^(:box) {
|
62
|
+
background_image 'http://www.example.com/back.png'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# ==========================================================================
|
69
|
+
# =========== end sanitization specs =====================================
|
70
|
+
# ==========================================================================
|
71
|
+
|
72
|
+
|
73
|
+
it 'allows css selectors with valid chars: #my_box div.box' do
|
74
|
+
target :style, <<-EOF
|
75
|
+
#my_box div.box {
|
76
|
+
border: 1px;
|
77
|
+
}
|
78
|
+
EOF
|
79
|
+
|
80
|
+
actual {
|
81
|
+
div.*(:my_box) {
|
82
|
+
div.^(:box) { border '1px' }
|
83
|
+
}
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
it "adds a 'style' tag to 'head'" do
|
88
|
+
target :outer, :style, %^
|
89
|
+
<style type="text/css">
|
90
|
+
#the_box {
|
91
|
+
border-width: 10px;
|
92
|
+
}
|
93
|
+
</style>
|
94
|
+
^
|
95
|
+
|
96
|
+
actual do
|
97
|
+
div.*(:the_box) {
|
98
|
+
border_width '10px'
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "uses id of element to add style" do
|
104
|
+
target :style, %^
|
105
|
+
#my_box {
|
106
|
+
border-width: 1px;
|
107
|
+
}
|
108
|
+
^
|
109
|
+
|
110
|
+
actual do
|
111
|
+
div.*(:my_box) {
|
112
|
+
border_width '1px'
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "uses tag hierarchy if no id found" do
|
118
|
+
target :style, %^
|
119
|
+
div div span {
|
120
|
+
width: 20px;
|
121
|
+
}
|
122
|
+
^
|
123
|
+
|
124
|
+
actual do
|
125
|
+
div { div { span { width '20px' } } }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "does not include parents when element has id" do
|
130
|
+
target :style, <<-EOF
|
131
|
+
#my_box div.box {
|
132
|
+
border: 15px;
|
133
|
+
}
|
134
|
+
EOF
|
135
|
+
|
136
|
+
actual do
|
137
|
+
div.^(:top) {
|
138
|
+
div.*(:my_box) {
|
139
|
+
div.^(:box) { border '15px' }
|
140
|
+
}
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
it "does not accept vars for css values" do
|
147
|
+
target :style, %^
|
148
|
+
div {
|
149
|
+
border: something;
|
150
|
+
}
|
151
|
+
^
|
152
|
+
actual {
|
153
|
+
div {
|
154
|
+
border :something
|
155
|
+
}
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
it "does not accept vars for css -image values" do
|
160
|
+
target :style, %^
|
161
|
+
div {
|
162
|
+
background-image: url(something);
|
163
|
+
}
|
164
|
+
^
|
165
|
+
actual {
|
166
|
+
div {
|
167
|
+
background_image :something
|
168
|
+
}
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
end # === sanitize css selectors
|
173
|
+
|
174
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
describe "HTML :on" do
|
3
|
+
|
4
|
+
it "escapes text as :html" do
|
5
|
+
target :script, <<-EOF
|
6
|
+
WWW_App.compile(
|
7
|
+
["create_event",["div","click","add_class",["red<red"]]]
|
8
|
+
);
|
9
|
+
EOF
|
10
|
+
|
11
|
+
actual do
|
12
|
+
div {
|
13
|
+
on(:click) { add_class "red<red" }
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "renders js" do
|
19
|
+
target :script, <<-EOF
|
20
|
+
WWW_App.compile(
|
21
|
+
#{
|
22
|
+
Escape_Escape_Escape.json_encode( ["create_event", [ "#my_box", "click", "add_class", ["hello"] ] ] )
|
23
|
+
}
|
24
|
+
);
|
25
|
+
EOF
|
26
|
+
|
27
|
+
actual {
|
28
|
+
div.*(:my_box) {
|
29
|
+
on(:click) { add_class :hello }
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
it "adds class to id of element: #id.class" do
|
35
|
+
target :style, %^
|
36
|
+
#me.highlight {
|
37
|
+
border-color: #fff;
|
38
|
+
}
|
39
|
+
^
|
40
|
+
|
41
|
+
actual do
|
42
|
+
div.*(:me) {
|
43
|
+
on(:highlight) { border_color '#fff' }
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "adds a psuedo class if passed a String" do
|
49
|
+
target :style, <<-EOF
|
50
|
+
a:hover {
|
51
|
+
border: 12px;
|
52
|
+
}
|
53
|
+
EOF
|
54
|
+
|
55
|
+
actual {
|
56
|
+
a.href('/home') {
|
57
|
+
on(':hover') { border '12px' }
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
end # === describe
|
63
|
+
|
64
|
+
|