@samuelbines/nunjucks 0.0.3 → 0.0.5

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.
@@ -1,137 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- // IMPORTANT: update this import path to where your nodes.ts exports live
5
- const nodes_1 = require("../src/nodes");
6
- (0, vitest_1.describe)('nodes: base Node init + fields normalization', () => {
7
- (0, vitest_1.it)('Node.init assigns lineno/colno and maps fields from args (undefined -> null)', () => {
8
- const n = new nodes_1.Pair(10, 20, 'k', undefined); // Pair.fields = ['key','value']
9
- (0, vitest_1.expect)(n.lineno).toBe(10);
10
- (0, vitest_1.expect)(n.colno).toBe(20);
11
- (0, vitest_1.expect)(n.key).toBe('k');
12
- (0, vitest_1.expect)(n.value).toBe(null); // normalized
13
- (0, vitest_1.expect)(n.typename).toBe('Pair');
14
- });
15
- (0, vitest_1.it)('Value node sets value field', () => {
16
- const v = new nodes_1.Literal(1, 2, 123);
17
- (0, vitest_1.expect)(v.typename).toBe('Literal');
18
- (0, vitest_1.expect)(v.value).toBe(123);
19
- });
20
- });
21
- (0, vitest_1.describe)('nodes: NodeList behavior', () => {
22
- (0, vitest_1.it)('NodeList.init stores children and addChild appends', () => {
23
- const a = new nodes_1.Literal(0, 0, 'a');
24
- const b = new nodes_1.Literal(0, 0, 'b');
25
- const list = new nodes_1.NodeList(5, 6, [a]);
26
- (0, vitest_1.expect)(list.typename).toBe('NodeList');
27
- (0, vitest_1.expect)(list.children).toEqual([a]);
28
- list.addChild(b);
29
- (0, vitest_1.expect)(list.children).toEqual([a, b]);
30
- });
31
- });
32
- (0, vitest_1.describe)('nodes: FromImport defaults names to NodeList', () => {
33
- (0, vitest_1.it)('FromImport.init defaults names when falsy', () => {
34
- const fi = new nodes_1.FromImport(1, 2, 'tmpl.njk', null, true);
35
- (0, vitest_1.expect)(fi.typename).toBe('FromImport');
36
- (0, vitest_1.expect)(fi.template).toBe('tmpl.njk');
37
- (0, vitest_1.expect)(fi.withContext).toBe(true);
38
- (0, vitest_1.expect)(fi.names).toBeInstanceOf(nodes_1.NodeList);
39
- });
40
- });
41
- (0, vitest_1.describe)('nodes: findAll traversal', () => {
42
- (0, vitest_1.it)('findAll traverses fields for non-NodeList nodes', () => {
43
- // If.fields = ['cond','body','else_']
44
- const cond = new nodes_1.Symbol(1, 1, 'x');
45
- const body = new nodes_1.Output(1, 2, [new nodes_1.Literal(1, 3, 'yes')]);
46
- const elseBody = new nodes_1.Output(1, 4, [new nodes_1.Literal(1, 5, 'no')]);
47
- const node = new nodes_1.If(0, 0, cond, body, elseBody);
48
- const lits = node.findAll(nodes_1.Literal);
49
- (0, vitest_1.expect)(lits.map((n) => n.value)).toEqual(['yes', 'no']);
50
- const syms = node.findAll(nodes_1.Symbol);
51
- (0, vitest_1.expect)(syms).toHaveLength(1);
52
- (0, vitest_1.expect)(syms[0].value).toBe('x');
53
- });
54
- (0, vitest_1.it)('findAll traverses children for NodeList nodes', () => {
55
- const out = new nodes_1.Output(0, 0, [
56
- new nodes_1.Literal(0, 0, 'a'),
57
- new nodes_1.Literal(0, 0, 'b'),
58
- ]);
59
- const lits = out.findAll(nodes_1.Literal);
60
- (0, vitest_1.expect)(lits.map((n) => n.value)).toEqual(['a', 'b']);
61
- });
62
- (0, vitest_1.it)('findAll returns empty when none found', () => {
63
- const n = new nodes_1.Pair(0, 0, 'k', 'v');
64
- (0, vitest_1.expect)(n.findAll(nodes_1.Symbol)).toEqual([]);
65
- });
66
- });
67
- (0, vitest_1.describe)('nodes: iterFields', () => {
68
- (0, vitest_1.it)('iterFields iterates (value, fieldName) for each field', () => {
69
- const p = new nodes_1.Pair(0, 0, 'k', 'v');
70
- const seen = [];
71
- p.iterFields((val, field) => {
72
- seen.push([field, val]);
73
- });
74
- (0, vitest_1.expect)(seen).toEqual([
75
- ['key', 'k'],
76
- ['value', 'v'],
77
- ]);
78
- });
79
- });
80
- (0, vitest_1.describe)('nodes: extend() typename + inheritance', () => {
81
- (0, vitest_1.it)('extended nodes report typename from __typename (not constructor.name)', () => {
82
- const x = new nodes_1.Output(0, 0, []);
83
- (0, vitest_1.expect)(x.typename).toBe('Output'); // __typename set on class definition
84
- });
85
- (0, vitest_1.it)('IfAsync extends If and preserves fields', () => {
86
- // IfAsync = If.extend('IfAsync')
87
- const cond = new nodes_1.Symbol(0, 0, 'c');
88
- const body = new nodes_1.Output(0, 0, []);
89
- const else_ = new nodes_1.Output(0, 0, []);
90
- const n = new nodes_1.NodeCreator('IfAsync')(0, 0, cond, body, else_);
91
- (0, vitest_1.expect)(n.typename).toBe('IfAsync');
92
- // still has If fields assigned
93
- (0, vitest_1.expect)(n.cond).toBe(cond);
94
- (0, vitest_1.expect)(n.body).toBe(body);
95
- (0, vitest_1.expect)(n.else_).toBe(else_);
96
- });
97
- });
98
- (0, vitest_1.describe)('nodes: CallExtension custom init uses parent + sets props', () => {
99
- (0, vitest_1.it)('CallExtension.init sets extName/prop/args/contentArgs/autoescape and keeps lineno/colno', () => {
100
- const ext = { __name: 'MyExt', autoescape: false };
101
- const args = new nodes_1.Dict(9, 9, []);
102
- const contentArgs = [new nodes_1.Output(1, 1, [new nodes_1.Literal(1, 1, 'x')])];
103
- // Note: CallExtension.init signature differs: (ext, prop, args, contentArgs?)
104
- const n = new nodes_1.CallExtension(3, 4, ext, 'doThing', args, contentArgs);
105
- (0, vitest_1.expect)(n.typename).toBe('CallExtension');
106
- (0, vitest_1.expect)(n.lineno).toBe(3);
107
- (0, vitest_1.expect)(n.colno).toBe(4);
108
- (0, vitest_1.expect)(n.extname).toBe('MyExt');
109
- (0, vitest_1.expect)(n.prop).toBe('doThing');
110
- (0, vitest_1.expect)(n.args).toBe(args);
111
- (0, vitest_1.expect)(n.contentArgs).toBe(contentArgs);
112
- (0, vitest_1.expect)(n.autoescape).toBe(false);
113
- });
114
- (0, vitest_1.it)('CallExtension defaults args to new NodeList when falsy', () => {
115
- const ext = { __name: 'X', autoescape: true };
116
- const n = new nodes_1.CallExtension(0, 0, ext, 'p', null, null);
117
- (0, vitest_1.expect)(n.args).toBeInstanceOf(nodes_1.NodeList);
118
- (0, vitest_1.expect)(n.contentArgs).toBeNull(); // because your init sets contentArgs param directly
119
- });
120
- (0, vitest_1.it)('CallExtensionAsync extends CallExtension', () => {
121
- const ext = { __name: 'X', autoescape: true };
122
- const n = new nodes_1.CallExtensionAsync(0, 0, ext, 'p', null, []);
123
- (0, vitest_1.expect)(n.typename).toBe('CallExtensionAsync');
124
- });
125
- });
126
- (0, vitest_1.describe)('NodeCreator', () => {
127
- (0, vitest_1.it)('returns constructor for valid key', () => {
128
- const Ctor = (0, nodes_1.NodeCreator)('Pair');
129
- const p = new Ctor(1, 1, 'k', 'v');
130
- (0, vitest_1.expect)(p).toBeInstanceOf(nodes_1.Pair);
131
- (0, vitest_1.expect)(p.typename).toBe('Pair');
132
- });
133
- (0, vitest_1.it)('throws for invalid key', () => {
134
- // NodeCreator throws a string in your implementation
135
- (0, vitest_1.expect)(() => (0, nodes_1.NodeCreator)('Nope')).toThrow(/No node type found/i);
136
- });
137
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,294 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- // test/parser.test.ts
37
- const vitest_1 = require("vitest");
38
- // IMPORTANT: update these imports to your project structure
39
- const parser_1 = require("../src/parser");
40
- const lexer = __importStar(require("../src/lexer"));
41
- const nodes_1 = require("../src/nodes");
42
- function parseRoot(src, opts = {}, extensions) {
43
- return (0, parser_1.parse)(src, extensions, opts);
44
- }
45
- (0, vitest_1.describe)('parser.ts', () => {
46
- (0, vitest_1.it)('parses plain text into Output(TemplateData)', () => {
47
- const ast = parseRoot('hello');
48
- (0, vitest_1.expect)(ast).toBeInstanceOf(nodes_1.Root);
49
- (0, vitest_1.expect)(ast.children).toHaveLength(1);
50
- const out = ast.children[0];
51
- (0, vitest_1.expect)(out).toBeInstanceOf(nodes_1.Output);
52
- (0, vitest_1.expect)(out.children).toHaveLength(1);
53
- (0, vitest_1.expect)(out.children[0]).toBeInstanceOf(nodes_1.TemplateData);
54
- (0, vitest_1.expect)(out.children[0].value).toBe('hello');
55
- });
56
- (0, vitest_1.it)('parses variable output: {{ 1 + 2 }}', () => {
57
- const ast = parseRoot('{{ 1 + 2 }}');
58
- const out = ast.children[0];
59
- (0, vitest_1.expect)(out).toBeInstanceOf(nodes_1.Output);
60
- (0, vitest_1.expect)(out.children[0]).toBeInstanceOf(nodes_1.Add);
61
- const add = out.children[0];
62
- (0, vitest_1.expect)(add.left).toBeInstanceOf(nodes_1.Literal);
63
- (0, vitest_1.expect)(add.right).toBeInstanceOf(nodes_1.Literal);
64
- (0, vitest_1.expect)(add.left.value).toBe(1);
65
- (0, vitest_1.expect)(add.right.value).toBe(2);
66
- });
67
- (0, vitest_1.it)('parses filters: {{ name | title }} into Filter node with args list', () => {
68
- const ast = parseRoot('{{ name | title }}');
69
- const out = ast.children[0];
70
- const filt = out.children[0];
71
- (0, vitest_1.expect)(filt).toBeInstanceOf(nodes_1.Filter);
72
- // Filter fields: ['name','args']
73
- (0, vitest_1.expect)(filt.name).toBeInstanceOf(nodes_1.Symbol);
74
- (0, vitest_1.expect)(filt.name.value).toBe('title');
75
- const args = filt.args;
76
- (0, vitest_1.expect)(args).toBeInstanceOf(nodes_1.NodeList);
77
- // first arg is the piped expression (Symbol("name"))
78
- (0, vitest_1.expect)(args.children[0]).toBeInstanceOf(nodes_1.Symbol);
79
- (0, vitest_1.expect)(args.children[0].value).toBe('name');
80
- });
81
- (0, vitest_1.it)('parses filter with dotted name: {{ x | a.b.c }}', () => {
82
- const ast = parseRoot('{{ x | a.b.c }}');
83
- const filt = ast.children[0].children[0];
84
- (0, vitest_1.expect)(filt).toBeInstanceOf(nodes_1.Filter);
85
- (0, vitest_1.expect)(filt.name.value).toBe('a.b.c');
86
- });
87
- (0, vitest_1.it)('parses filter statement block: {% filter title %}Hi{% endfilter %}', () => {
88
- const ast = parseRoot('{% filter title %}Hi{% endfilter %}');
89
- (0, vitest_1.expect)(ast.children).toHaveLength(1);
90
- const out = ast.children[0];
91
- (0, vitest_1.expect)(out).toBeInstanceOf(nodes_1.Output);
92
- const node = out.children[0];
93
- (0, vitest_1.expect)(node).toBeInstanceOf(nodes_1.Filter);
94
- const name = node.name;
95
- (0, vitest_1.expect)(name).toBeInstanceOf(nodes_1.Symbol);
96
- (0, vitest_1.expect)(name.value).toBe('title');
97
- const args = node.args;
98
- (0, vitest_1.expect)(args).toBeInstanceOf(nodes_1.NodeList);
99
- // First arg is a Capture node containing the filtered body
100
- const cap = args.children[0];
101
- (0, vitest_1.expect)(cap).toBeInstanceOf(nodes_1.Capture);
102
- const capBody = cap.body;
103
- (0, vitest_1.expect)(capBody).toBeInstanceOf(nodes_1.NodeList);
104
- (0, vitest_1.expect)(capBody.children[0]).toBeInstanceOf(nodes_1.Output);
105
- (0, vitest_1.expect)(capBody.children[0].children[0]).toBeInstanceOf(nodes_1.TemplateData);
106
- (0, vitest_1.expect)(capBody.children[0].children[0].value).toBe('Hi');
107
- });
108
- (0, vitest_1.it)('parses for-loop: {% for x in arr %}X{% endfor %}', () => {
109
- const ast = parseRoot('{% for x in arr %}X{% endfor %}');
110
- (0, vitest_1.expect)(ast.children).toHaveLength(1);
111
- const n = ast.children[0];
112
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.For);
113
- (0, vitest_1.expect)(n.name).toBeInstanceOf(nodes_1.Symbol);
114
- (0, vitest_1.expect)(n.name.value).toBe('x');
115
- (0, vitest_1.expect)(n.arr).toBeInstanceOf(nodes_1.Symbol);
116
- (0, vitest_1.expect)(n.arr.value).toBe('arr');
117
- const body = n.body;
118
- (0, vitest_1.expect)(body).toBeInstanceOf(nodes_1.NodeList);
119
- (0, vitest_1.expect)(body.children[0]).toBeInstanceOf(nodes_1.Output);
120
- (0, vitest_1.expect)(body.children[0].children[0]).toBeInstanceOf(nodes_1.TemplateData);
121
- (0, vitest_1.expect)(body.children[0].children[0].value).toBe('X');
122
- });
123
- (0, vitest_1.it)('parses for-loop with else: {% for x in arr %}A{% else %}B{% endfor %}', () => {
124
- const ast = parseRoot('{% for x in arr %}A{% else %}B{% endfor %}');
125
- const n = ast.children[0];
126
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.For);
127
- const body = n.body;
128
- const else_ = n.else_;
129
- (0, vitest_1.expect)(body.children[0].children[0].value).toBe('A');
130
- (0, vitest_1.expect)(else_.children[0].children[0].value).toBe('B');
131
- });
132
- (0, vitest_1.it)('parses if/elif/else nesting', () => {
133
- const ast = parseRoot('{% if a %}A{% elif b %}B{% else %}C{% endif %}');
134
- const n = ast.children[0];
135
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.If);
136
- (0, vitest_1.expect)(n.cond).toBeInstanceOf(nodes_1.Symbol);
137
- (0, vitest_1.expect)(n.cond.value).toBe('a');
138
- // elif becomes else_ which is another If node
139
- const elifNode = n.else_;
140
- (0, vitest_1.expect)(elifNode).toBeInstanceOf(nodes_1.If);
141
- (0, vitest_1.expect)(elifNode.cond.value).toBe('b');
142
- // elif's else_ is the else body NodeList
143
- const elseBody = elifNode.else_;
144
- (0, vitest_1.expect)(elseBody).toBeInstanceOf(nodes_1.NodeList);
145
- (0, vitest_1.expect)(elseBody.children[0].children[0].value).toBe('C');
146
- });
147
- (0, vitest_1.it)('parses include with ignore missing', () => {
148
- const ast = parseRoot('{% include "a.njk" ignore missing %}');
149
- const n = ast.children[0];
150
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.Include);
151
- (0, vitest_1.expect)(n.template).toBeInstanceOf(nodes_1.Literal);
152
- (0, vitest_1.expect)(n.template.value).toBe('a.njk');
153
- (0, vitest_1.expect)(n.ignoreMissing).toBe(true);
154
- });
155
- (0, vitest_1.it)('parses extends', () => {
156
- const ast = parseRoot('{% extends "base.njk" %}');
157
- const n = ast.children[0];
158
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.Extends);
159
- (0, vitest_1.expect)(n.template.value).toBe('base.njk');
160
- });
161
- (0, vitest_1.it)('parses set assignment: {% set x = 3 %}', () => {
162
- const ast = parseRoot('{% set x = 3 %}');
163
- const n = ast.children[0];
164
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.Set);
165
- (0, vitest_1.expect)(n.targets).toHaveLength(1);
166
- (0, vitest_1.expect)(n.targets[0]).toBeInstanceOf(nodes_1.Symbol);
167
- (0, vitest_1.expect)(n.targets[0].value).toBe('x');
168
- (0, vitest_1.expect)(n.value).toBeInstanceOf(nodes_1.Literal);
169
- (0, vitest_1.expect)(n.value.value).toBe(3);
170
- });
171
- (0, vitest_1.it)('parses set capture form: {% set x %}Hi{% endset %}', () => {
172
- const ast = parseRoot('{% set x %}Hi{% endset %}');
173
- const n = ast.children[0];
174
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.Set);
175
- (0, vitest_1.expect)(n.targets[0].value).toBe('x');
176
- (0, vitest_1.expect)(n.value).toBeNull();
177
- (0, vitest_1.expect)(n.body).toBeInstanceOf(nodes_1.Capture);
178
- const capBody = n.body.body;
179
- (0, vitest_1.expect)(capBody.children[0].children[0].value).toBe('Hi');
180
- });
181
- (0, vitest_1.it)('parses macro and its signature: {% macro m(a, b=2) %}X{% endmacro %}', () => {
182
- const ast = parseRoot('{% macro m(a, b=2) %}X{% endmacro %}');
183
- const n = ast.children[0];
184
- (0, vitest_1.expect)(n).toBeInstanceOf(nodes_1.Macro);
185
- // name is a Symbol
186
- (0, vitest_1.expect)(n.name).toBeInstanceOf(nodes_1.Symbol);
187
- (0, vitest_1.expect)(n.name.value).toBe('m');
188
- // args is NodeList, last element KeywordArgs for b=2
189
- const args = n.args;
190
- (0, vitest_1.expect)(args).toBeInstanceOf(nodes_1.NodeList);
191
- (0, vitest_1.expect)(args.children[0]).toBeInstanceOf(nodes_1.Symbol);
192
- (0, vitest_1.expect)(args.children[0].value).toBe('a');
193
- const last = args.children[args.children.length - 1];
194
- (0, vitest_1.expect)(last).toBeInstanceOf(nodes_1.KeywordArgs);
195
- (0, vitest_1.expect)(last.children[0]).toBeInstanceOf(nodes_1.Pair);
196
- (0, vitest_1.expect)(last.children[0].key.value).toBe('b');
197
- (0, vitest_1.expect)(last.children[0].value.value).toBe(2);
198
- // body contains TemplateData("X")
199
- (0, vitest_1.expect)(n.body.children[0].children[0].value).toBe('X');
200
- });
201
- (0, vitest_1.it)('parses call block into Output(FunCall) with caller kwarg', () => {
202
- const src = '{% call(x) foo(1) %}Body{% endcall %}';
203
- const ast = parseRoot(src);
204
- const out = ast.children[0];
205
- (0, vitest_1.expect)(out).toBeInstanceOf(nodes_1.Output);
206
- const fun = out.children[0];
207
- (0, vitest_1.expect)(fun).toBeInstanceOf(nodes_1.FunCall);
208
- const args = fun.args.children;
209
- // last arg becomes KeywordArgs with caller Pair injected
210
- const last = args[args.length - 1];
211
- (0, vitest_1.expect)(last).toBeInstanceOf(nodes_1.KeywordArgs);
212
- const pair = last.children.find((p) => p.key?.value === 'caller');
213
- (0, vitest_1.expect)(pair).toBeTruthy();
214
- (0, vitest_1.expect)(pair.value).toBeInstanceOf(nodes_1.Caller);
215
- // callerNode.body should contain "Body"
216
- const callerBody = pair.value.body;
217
- (0, vitest_1.expect)(callerBody.children[0].children[0].value).toBe('Body');
218
- });
219
- (0, vitest_1.it)('parses aggregates: (1,2), [1,2], {a:1}', () => {
220
- const ast1 = parseRoot('{{ (1, 2) }}');
221
- (0, vitest_1.expect)(ast1.children[0].children[0]).toBeInstanceOf(nodes_1.Group);
222
- (0, vitest_1.expect)(ast1.children[0].children[0].children).toHaveLength(2);
223
- const ast2 = parseRoot('{{ [1, 2] }}');
224
- (0, vitest_1.expect)(ast2.children[0].children[0]).toBeInstanceOf(nodes_1.ArrayNode);
225
- (0, vitest_1.expect)(ast2.children[0].children[0].children).toHaveLength(2);
226
- const ast3 = parseRoot('{{ {"a": 1} }}');
227
- (0, vitest_1.expect)(ast3.children[0].children[0]).toBeInstanceOf(nodes_1.Dict);
228
- const dictChildren = ast3.children[0].children[0].children;
229
- (0, vitest_1.expect)(dictChildren).toHaveLength(1);
230
- (0, vitest_1.expect)(dictChildren[0]).toBeInstanceOf(nodes_1.Pair);
231
- (0, vitest_1.expect)(dictChildren[0].key.value).toBe('a');
232
- (0, vitest_1.expect)(dictChildren[0].value.value).toBe(1);
233
- });
234
- (0, vitest_1.it)('parsePostfix: foo(1).bar[0]', () => {
235
- const ast = parseRoot('{{ foo(1).bar[0] }}');
236
- const expr = ast.children[0].children[0];
237
- // should end up as LookupVal(LookupVal(FunCall(Symbol(foo), [1]), Literal("bar")), Literal(0))
238
- (0, vitest_1.expect)(expr).toBeInstanceOf(nodes_1.LookupVal);
239
- const outer = expr;
240
- (0, vitest_1.expect)(outer.val).toBeInstanceOf(nodes_1.Literal);
241
- (0, vitest_1.expect)(outer.val.value).toBe(0);
242
- (0, vitest_1.expect)(outer.target).toBeInstanceOf(nodes_1.LookupVal);
243
- const inner = outer.target;
244
- (0, vitest_1.expect)(inner.val).toBeInstanceOf(nodes_1.Literal);
245
- (0, vitest_1.expect)(inner.val.value).toBe('bar');
246
- (0, vitest_1.expect)(inner.target).toBeInstanceOf(nodes_1.FunCall);
247
- (0, vitest_1.expect)(inner.target.name).toBeInstanceOf(nodes_1.Symbol);
248
- (0, vitest_1.expect)(inner.target.name.value).toBe('foo');
249
- });
250
- (0, vitest_1.it)('whitespace control: {%- ... -%} trims adjacent data', () => {
251
- const ast = parseRoot('A {%- if x -%}\n B \n{%- endif -%} C', {
252
- trimBlocks: true,
253
- lstripBlocks: true,
254
- });
255
- // Expect the outer text nodes to be trimmed around the control blocks.
256
- // This is intentionally "loose" because exact whitespace behavior depends on lexer opts.
257
- const templateData = ast
258
- .findAll(nodes_1.TemplateData)
259
- .map((n) => n.value);
260
- (0, vitest_1.expect)(templateData.join('')).toContain('A');
261
- (0, vitest_1.expect)(templateData.join('')).toContain('B');
262
- (0, vitest_1.expect)(templateData.join('')).toContain('C');
263
- // Ensure we did not keep leading newline after -%} when trimBlocks enabled
264
- // (again, loose check)
265
- (0, vitest_1.expect)(templateData.join('')).not.toContain('\n B \n');
266
- });
267
- (0, vitest_1.it)('extensions: unknown tag handled by extension.parse', () => {
268
- const ext = {
269
- tags: ['hello'],
270
- parse(p, ns, lx) {
271
- // consume tag name, then advance past block end
272
- p.nextToken(); // symbol "hello"
273
- p.advanceAfterBlockEnd('hello');
274
- return new ns.Output(0, 0, [new ns.TemplateData(0, 0, 'EXT')]);
275
- },
276
- };
277
- const ast = parseRoot('{% hello %}', {}, [ext]);
278
- (0, vitest_1.expect)(ast.children[0]).toBeInstanceOf(nodes_1.Output);
279
- (0, vitest_1.expect)(ast.children[0].children[0]).toBeInstanceOf(nodes_1.TemplateData);
280
- (0, vitest_1.expect)(ast.children[0].children[0].value).toBe('EXT');
281
- });
282
- (0, vitest_1.it)('throws on unknown block tag', () => {
283
- (0, vitest_1.expect)(() => parseRoot('{% doesnotexist %}')).toThrow(/unknown block tag/i);
284
- });
285
- (0, vitest_1.it)('advanceAfterVariableEnd throws if missing variable end', () => {
286
- // Use Parser directly so we can call method
287
- const toks = lexer.lex('{{ 1 ', {});
288
- const p = new parser_1.Parser(toks);
289
- // consume VARIABLE_START
290
- p.nextToken();
291
- p.parseExpression();
292
- (0, vitest_1.expect)(() => p.advanceAfterVariableEnd()).toThrow(/expected variable end/i);
293
- });
294
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,224 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // test/precompile.test.ts
4
- const vitest_1 = require("vitest");
5
- const precompile_1 = require("../src/precompile");
6
- function makeStat(kind) {
7
- return {
8
- isFile: () => kind === 'file',
9
- isDirectory: () => kind === 'dir',
10
- };
11
- }
12
- // In-memory fs tree for deterministic tests
13
- let TREE;
14
- function getNode(p) {
15
- const parts = p.replace(/\\/g, '/').split('/').filter(Boolean);
16
- let cur = TREE;
17
- for (const part of parts) {
18
- if (cur.type !== 'dir')
19
- throw new Error(`Not a directory: ${p}`);
20
- const next = cur.children[part];
21
- if (!next)
22
- throw new Error(`ENOENT: ${p}`);
23
- cur = next;
24
- }
25
- return cur;
26
- }
27
- function listDir(p) {
28
- const node = getNode(p);
29
- if (node.type !== 'dir')
30
- throw new Error(`ENOTDIR: ${p}`);
31
- return Object.keys(node.children);
32
- }
33
- function readFile(p) {
34
- const node = getNode(p);
35
- if (node.type !== 'file')
36
- throw new Error(`EISDIR: ${p}`);
37
- return node.content;
38
- }
39
- vitest_1.vi.mock('fs', () => {
40
- return {
41
- default: {
42
- statSync: (p) => {
43
- const n = getNode(p);
44
- return makeStat(n.type === 'file' ? 'file' : 'dir');
45
- },
46
- readdirSync: (p) => listDir(p),
47
- readFileSync: (p, _enc) => readFile(p),
48
- },
49
- statSync: (p) => {
50
- const n = getNode(p);
51
- return makeStat(n.type === 'file' ? 'file' : 'dir');
52
- },
53
- readdirSync: (p) => listDir(p),
54
- readFileSync: (p, _enc) => readFile(p),
55
- };
56
- });
57
- const compileMock = vitest_1.vi.fn();
58
- vitest_1.vi.mock('../src/compiler', () => {
59
- return { compile: (...args) => compileMock(...args) };
60
- });
61
- const prettifyMock = vitest_1.vi.fn((name, _a, err) => {
62
- // Keep it simple and easy to assert:
63
- const e = new Error(`PRETTY:${name}:${String(err?.message || err)}`);
64
- return e;
65
- });
66
- const templateErrorMock = vitest_1.vi.fn((e) => {
67
- // Make sure errors have `.message`
68
- return e instanceof Error ? e : new Error(String(e));
69
- });
70
- vitest_1.vi.mock('../src/lib', () => {
71
- return {
72
- _prettifyError: (...args) => prettifyMock(...args),
73
- TemplateError: (...args) => templateErrorMock(...args),
74
- };
75
- });
76
- class FakeEnv {
77
- asyncFilters = [];
78
- extensionsList = [];
79
- throwOnUndefined = false;
80
- constructor(_loaders) { }
81
- }
82
- vitest_1.vi.mock('../src/environment', () => {
83
- return { Environment: FakeEnv, Template: class Template {
84
- } };
85
- });
86
- const wrapperMock = vitest_1.vi.fn((templates, opts) => ({
87
- templates,
88
- opts,
89
- }));
90
- vitest_1.vi.mock('../src/globals', () => {
91
- return { precompileGlobal: (...args) => wrapperMock(...args) };
92
- });
93
- // Module under test (import AFTER mocks)
94
- // --------------------
95
- // Tests
96
- // --------------------
97
- (0, vitest_1.describe)('precompile.ts', () => {
98
- (0, vitest_1.beforeEach)(() => {
99
- vitest_1.vi.clearAllMocks();
100
- // default compile behavior
101
- compileMock.mockImplementation((_src, _async, _ext, name) => {
102
- return `CODE:${name}`;
103
- });
104
- // default fs tree
105
- TREE = {
106
- type: 'dir',
107
- children: {
108
- templates: {
109
- type: 'dir',
110
- children: {
111
- 'a.njk': { type: 'file', content: 'A' },
112
- 'b.txt': { type: 'file', content: 'B' },
113
- sub: {
114
- type: 'dir',
115
- children: {
116
- 'c.njk': { type: 'file', content: 'C' },
117
- 'ignore.njk': { type: 'file', content: 'NO' },
118
- },
119
- },
120
- skipdir: {
121
- type: 'dir',
122
- children: {
123
- 'd.njk': { type: 'file', content: 'D' },
124
- },
125
- },
126
- },
127
- },
128
- 'single.njk': { type: 'file', content: 'SINGLE' },
129
- },
130
- };
131
- });
132
- (0, vitest_1.describe)('default export precompile()', () => {
133
- (0, vitest_1.it)('precompiles a single file path', () => {
134
- const env = new FakeEnv([]);
135
- const out = (0, precompile_1.precompile)('single.njk', {
136
- isString: false,
137
- env,
138
- name: 'myname.njk',
139
- wrapper: wrapperMock,
140
- });
141
- // wrapper called with a single precompiled template
142
- (0, vitest_1.expect)(wrapperMock).toHaveBeenCalledTimes(1);
143
- (0, vitest_1.expect)(out.templates).toHaveLength(1);
144
- (0, vitest_1.expect)(out.templates[0]).toEqual({
145
- name: 'myname.njk',
146
- template: 'CODE:myname.njk',
147
- });
148
- });
149
- (0, vitest_1.it)('precompiles a directory: includes files matching include patterns and traverses subdirs not excluded', () => {
150
- const env = new FakeEnv([]);
151
- const out = (0, precompile_1.precompile)('templates', {
152
- isString: false,
153
- env,
154
- include: [/\.njk$/], // only *.njk
155
- exclude: [/skipdir\//, /sub\/ignore\.njk$/], // skip skipdir folder + one file in sub
156
- wrapper: wrapperMock,
157
- });
158
- // Expect only: a.njk and sub/c.njk (ignore skipdir/d.njk and sub/ignore.njk)
159
- const names = out.templates.map((t) => t.name).sort();
160
- (0, vitest_1.expect)(names).toEqual(['a.njk', 'sub/c.njk']);
161
- // compile called for each included template with its relative name
162
- const compiledNames = compileMock.mock.calls.map((c) => c[3]).sort();
163
- (0, vitest_1.expect)(compiledNames).toEqual(['a.njk', 'sub/c.njk']);
164
- });
165
- (0, vitest_1.it)('when force=false (default), a compile error in a directory aborts', () => {
166
- compileMock.mockImplementation((_src, _a, _e, name) => {
167
- if (name === 'sub/c.njk')
168
- throw new Error('bad');
169
- return `CODE:${name}`;
170
- });
171
- (0, vitest_1.expect)(() => (0, precompile_1.precompile)('templates', {
172
- isString: false,
173
- include: [/\.njk$/],
174
- exclude: [/skipdir\//],
175
- wrapper: wrapperMock,
176
- })).toThrowError(/PRETTY:sub\/c\.njk/);
177
- // wrapper should not be called because it aborted
178
- (0, vitest_1.expect)(wrapperMock).not.toHaveBeenCalled();
179
- });
180
- (0, vitest_1.it)('when force=true, a compile error in a directory is logged and compilation continues', () => {
181
- const errSpy = vitest_1.vi.spyOn(console, 'error').mockImplementation(() => { });
182
- compileMock.mockImplementation((_src, _a, _e, name) => {
183
- if (name === 'sub/c.njk')
184
- throw new Error('bad');
185
- return `CODE:${name}`;
186
- });
187
- const out = (0, precompile_1.precompile)('templates', {
188
- isString: false,
189
- include: [/\.njk$/],
190
- exclude: [/skipdir\//],
191
- force: true,
192
- wrapper: wrapperMock,
193
- });
194
- // a.njk compiled, sub/c.njk failed and is skipped
195
- const names = out.templates.map((t) => t.name).sort();
196
- (0, vitest_1.expect)(names).toEqual(['a.njk', 'sub/ignore.njk']); // NOTE: include matches *.njk, and we didn't exclude ignore.njk here
197
- (0, vitest_1.expect)(errSpy).toHaveBeenCalled(); // logged error
198
- errSpy.mockRestore();
199
- });
200
- });
201
- (0, vitest_1.describe)('precompileString', () => {
202
- (0, vitest_1.it)('throws if called with isString=true and no name (current bug path)', () => {
203
- // Your code intends to throw when opts.name missing, but currently checks `_precompile.name`
204
- // which is truthy, so it will fall through and then crash later.
205
- (0, vitest_1.expect)(() => (0, precompile_1.precompile)('Hello', {
206
- isString: true,
207
- // name intentionally missing
208
- wrapper: wrapperMock,
209
- })).toThrow();
210
- });
211
- (0, vitest_1.it)('compiles a string when name is provided', () => {
212
- const out = (0, precompile_1.precompile)('Hello', {
213
- isString: true,
214
- name: 'inline.njk',
215
- env: new FakeEnv([]),
216
- wrapper: wrapperMock,
217
- });
218
- (0, vitest_1.expect)(wrapperMock).toHaveBeenCalledTimes(1);
219
- (0, vitest_1.expect)(out.templates).toHaveLength(1);
220
- (0, vitest_1.expect)(out.templates[0].name).toBe('inline.njk');
221
- (0, vitest_1.expect)(out.templates[0].template).toBe('CODE:inline.njk');
222
- });
223
- });
224
- });
@@ -1 +0,0 @@
1
- export {};