1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
/*global Handlebars */
describe('parser', function() {
if (!Handlebars.print) {
return;
}
function ast_for(template) {
var ast = Handlebars.parse(template);
return Handlebars.print(ast);
}
it('parses simple mustaches', function() {
equals(ast_for('{{foo}}'), "{{ ID:foo [] }}\n");
equals(ast_for('{{foo?}}'), "{{ ID:foo? [] }}\n");
equals(ast_for('{{foo_}}'), "{{ ID:foo_ [] }}\n");
equals(ast_for('{{foo-}}'), "{{ ID:foo- [] }}\n");
equals(ast_for('{{foo:}}'), "{{ ID:foo: [] }}\n");
});
it('parses simple mustaches with data', function() {
equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
});
it('parses mustaches with paths', function() {
equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
});
it('parses mustaches with this/foo', function() {
equals(ast_for("{{this/foo}}"), "{{ ID:foo [] }}\n");
});
it('parses mustaches with - in a path', function() {
equals(ast_for("{{foo-bar}}"), "{{ ID:foo-bar [] }}\n");
});
it('parses mustaches with parameters', function() {
equals(ast_for("{{foo bar}}"), "{{ ID:foo [ID:bar] }}\n");
});
it('parses mustaches with string parameters', function() {
equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
});
it('parses mustaches with NUMBER parameters', function() {
equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{1}] }}\n");
});
it('parses mustaches with BOOLEAN parameters', function() {
equals(ast_for("{{foo true}}"), "{{ ID:foo [BOOLEAN{true}] }}\n");
equals(ast_for("{{foo false}}"), "{{ ID:foo [BOOLEAN{false}] }}\n");
});
it('parses mutaches with DATA parameters', function() {
equals(ast_for("{{foo @bar}}"), "{{ ID:foo [@ID:bar] }}\n");
});
it('parses mustaches with hash arguments', function() {
equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n");
equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=NUMBER{1}} }}\n");
equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n");
equals(ast_for("{{foo bar=baz bat=bam}}"), "{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n");
equals(ast_for("{{foo bar=baz bat=\"bam\"}}"), '{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n');
equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=NUMBER{1}} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
});
it('parses contents followed by a mustache', function() {
equals(ast_for("foo bar {{baz}}"), "CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
});
it('parses a partial', function() {
equals(ast_for("{{> foo }}"), "{{> PARTIAL:foo }}\n");
});
it('parses a partial with context', function() {
equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
});
it('parses a partial with hash', function() {
equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n");
});
it('parses a partial with context and hash', function() {
equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\n");
});
it('parses a partial with a complex name', function() {
equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
});
it('parses a comment', function() {
equals(ast_for("{{! this is a comment }}"), "{{! ' this is a comment ' }}\n");
});
it('parses a multi-line comment', function() {
equals(ast_for("{{!\nthis is a multi-line comment\n}}"), "{{! \'\nthis is a multi-line comment\n\' }}\n");
});
it('parses an inverse section', function() {
equals(ast_for("{{#foo}} bar {{^}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
});
it('parses an inverse (else-style) section', function() {
equals(ast_for("{{#foo}} bar {{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
});
it('parses empty blocks', function() {
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses empty blocks with empty inverse section', function() {
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses empty blocks with empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses non-empty blocks with empty inverse section', function() {
equals(ast_for("{{#foo}} bar {{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
});
it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}} bar {{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
});
it('parses empty blocks with non-empty inverse section', function() {
equals(ast_for("{{#foo}}{{^}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
});
it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}}{{else}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
});
it('parses a standalone inverse section', function() {
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
});
it("raises if there's a Parse error", function() {
shouldThrow(function() {
ast_for("foo{{^}}bar");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{foo}");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{foo &}}");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{#goodbyes}}{{/hellos}}");
}, Error, /goodbyes doesn't match hellos/);
shouldThrow(function() {
ast_for("{{{{goodbyes}}}} {{{{/hellos}}}}");
}, Error, /goodbyes doesn't match hellos/);
});
it('knows how to report the correct line number in errors', function() {
shouldThrow(function() {
ast_for("hello\nmy\n{{foo}");
}, Error, /Parse error on line 3/);
shouldThrow(function() {
ast_for("hello\n\nmy\n\n{{foo}");
}, Error, /Parse error on line 5/);
});
it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(function() {
ast_for("\n\nhello\n\nmy\n\n{{foo}");
}, Error, /Parse error on line 7/);
});
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
});
});
});
|