@samuelbines/nunjucks 0.0.3 → 0.0.4

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@samuelbines/nunjucks",
3
3
  "description": "A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "author": "Samuel Bines <samuelbines@gmail.com>",
6
6
  "browser": "./browser/nunjucks.js",
7
7
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- export {};
@@ -1,201 +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/compilers.test.ts
37
- const vitest_1 = require("vitest");
38
- // IMPORTANT: adjust import paths to match your repo layout
39
- const compilers = __importStar(require("../src/compiler"));
40
- const compile = (src, opts = {}, asyncFilters = [], extensions = [], name = 'test.njk') => compilers.compile(src, asyncFilters, extensions, name, opts);
41
- (0, vitest_1.describe)('compilers.ts', () => {
42
- (0, vitest_1.it)('emits a root render function wrapper', () => {
43
- const code = compile('hello');
44
- (0, vitest_1.expect)(code).toContain('function root(env, context, frame, runtime, cb)');
45
- (0, vitest_1.expect)(code).toContain('var output = ""');
46
- (0, vitest_1.expect)(code).toContain('cb(null, output)');
47
- });
48
- (0, vitest_1.it)('compiles TemplateData as direct string concat (no suppressValue)', () => {
49
- const code = compile('hello world');
50
- (0, vitest_1.expect)(code).toMatch(/output\s*\+=\s*".*hello world.*";/);
51
- (0, vitest_1.expect)(code).not.toContain('runtime.suppressValue("hello world"');
52
- });
53
- (0, vitest_1.it)('compiles {{ name }} using suppressValue + contextOrFrameLookup', () => {
54
- const code = compile('{{ name }}');
55
- (0, vitest_1.expect)(code).toContain('runtime.suppressValue(');
56
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "name")');
57
- (0, vitest_1.expect)(code).toContain(', env.opts.autoescape);');
58
- });
59
- (0, vitest_1.it)('when throwOnUndefined is true, wraps expressions with runtime.ensureDefined', () => {
60
- const code = compile('{{ name }}', { throwOnUndefined: true });
61
- (0, vitest_1.expect)(code).toContain('runtime.ensureDefined(');
62
- (0, vitest_1.expect)(code).toMatch(/runtime\.ensureDefined\([^)]*,\s*\d+,\s*\d+\)/);
63
- });
64
- (0, vitest_1.it)('compiles filters: {{ name | title }}', () => {
65
- const code = compile('{{ name | title }}');
66
- (0, vitest_1.expect)(code).toContain('env.getFilter("title").call(context,');
67
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "name")');
68
- });
69
- (0, vitest_1.it)('compiles lookup: {{ user.name }} to runtime.memberLookup(target, val)', () => {
70
- const code = compile('{{ user.name }}');
71
- (0, vitest_1.expect)(code).toContain('runtime.memberLookup((');
72
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "user")');
73
- (0, vitest_1.expect)(code).toContain('"name"');
74
- });
75
- (0, vitest_1.it)('compiles inline if: {{ a if b else c }}', () => {
76
- const code = compile('{{ a if b else c }}');
77
- (0, vitest_1.expect)(code).toContain('?');
78
- (0, vitest_1.expect)(code).toContain(':');
79
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "a")');
80
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "b")');
81
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "c")');
82
- });
83
- (0, vitest_1.it)('compiles comparisons: {{ 1 < 2 }}', () => {
84
- const code = compile('{{ 1 < 2 }}');
85
- (0, vitest_1.expect)(code).toMatch(/1\s*<\s*2/);
86
- });
87
- (0, vitest_1.it)('escapes literal strings in generated JS', () => {
88
- const code = compile('{{ "a\\n\\"b\\"" }}');
89
- (0, vitest_1.expect)(code).toContain('"a\\n\\"b\\""');
90
- });
91
- (0, vitest_1.it)('compiles set assignment: {% set x = 3 %}', () => {
92
- const code = compile('{% set x = 3 %}');
93
- (0, vitest_1.expect)(code).toContain('frame.set("x"');
94
- (0, vitest_1.expect)(code).toContain('context.setVariable("x"');
95
- });
96
- (0, vitest_1.it)('compiles for-loop: {% for x in arr %}{{ x }}{% endfor %}', () => {
97
- const code = compile('{% for x in arr %}{{ x }}{% endfor %}');
98
- (0, vitest_1.expect)(code).toContain('frame = frame.push();');
99
- (0, vitest_1.expect)(code).toContain('runtime.fromIterator(');
100
- (0, vitest_1.expect)(code).toContain('for(var');
101
- (0, vitest_1.expect)(code).toContain('frame.set("x"');
102
- (0, vitest_1.expect)(code).toContain('frame.set("loop.index"');
103
- (0, vitest_1.expect)(code).toContain('frame.set("loop.last"');
104
- });
105
- (0, vitest_1.it)('compiles blocks and returns blocks object with b_<name>', () => {
106
- const code = compile('{% block content %}Hi{% endblock %}');
107
- (0, vitest_1.expect)(code).toContain('function b_content(env, context, frame, runtime, cb)');
108
- (0, vitest_1.expect)(code).toContain('return {');
109
- (0, vitest_1.expect)(code).toContain('b_content: b_content');
110
- (0, vitest_1.expect)(code).toContain('root: root');
111
- });
112
- (0, vitest_1.it)('compiles extends: sets parentTemplate and adds parent blocks', () => {
113
- const code = compile('{% extends "base.njk" %}');
114
- (0, vitest_1.expect)(code).toContain('parentTemplate = ');
115
- (0, vitest_1.expect)(code).toContain('context.addBlock');
116
- });
117
- (0, vitest_1.it)('compiles include: uses env.getTemplate and env.waterfall tasks', () => {
118
- const code = compile('{% include "partial.njk" %}');
119
- (0, vitest_1.expect)(code).toContain('env.getTemplate(');
120
- (0, vitest_1.expect)(code).toContain('env.waterfall(tasks');
121
- });
122
- (0, vitest_1.it)('runs extension preprocessors before parse/compile', () => {
123
- const ext = {
124
- preprocess(src) {
125
- return src.replace('[[NAME]]', '{{ name }}');
126
- },
127
- };
128
- const code = compile('Hello [[NAME]]', {}, [], [ext]);
129
- (0, vitest_1.expect)(code).toContain('runtime.contextOrFrameLookup(context, frame, "name")');
130
- });
131
- (0, vitest_1.it)('throws when dict keys are not string literals or names', () => {
132
- (0, vitest_1.expect)(() => compile('{{ { 1: 2 } }}')).toThrow(/Dict keys must be strings or names/i);
133
- });
134
- // ---------------------------
135
- // SMOKE EXECUTION TESTS
136
- // ---------------------------
137
- (0, vitest_1.it)('smoke: generated JS is valid and returns an object with root()', () => {
138
- const code = compile('hello');
139
- const props = new Function(code)(); // <-- should not throw
140
- (0, vitest_1.expect)(props).toBeTruthy();
141
- (0, vitest_1.expect)(typeof props.root).toBe('function');
142
- });
143
- (0, vitest_1.it)('smoke: root() executes and renders expected output', async () => {
144
- const code = compile('Hello {{ name }}');
145
- const props = new Function(code)();
146
- const env = {
147
- opts: { autoescape: true },
148
- getFilter: () => {
149
- throw new Error('not used in this template');
150
- },
151
- getTest: () => {
152
- throw new Error('not used in this template');
153
- },
154
- getExtension: () => {
155
- throw new Error('not used in this template');
156
- },
157
- waterfall: () => {
158
- throw new Error('not used in this template');
159
- },
160
- getTemplate: () => {
161
- throw new Error('not used in this template');
162
- },
163
- };
164
- const context = {
165
- ctx: { name: 'Sam' },
166
- // only needed for some tags; safe to include
167
- getVariables() {
168
- return this.ctx;
169
- },
170
- setVariable(k, v) {
171
- this.ctx[k] = v;
172
- },
173
- addExport() { },
174
- getBlock() {
175
- throw new Error('not used');
176
- },
177
- };
178
- const frame = {}; // minimal frame for this template
179
- const runtime = {
180
- suppressValue(val) {
181
- // nunjucks would autoescape; for smoke test, just stringify
182
- return val == null ? '' : String(val);
183
- },
184
- contextOrFrameLookup(ctx, _frame, key) {
185
- return ctx?.ctx?.[key];
186
- },
187
- handleError(e) {
188
- return e;
189
- },
190
- };
191
- const out = await new Promise((resolve, reject) => {
192
- props.root(env, context, frame, runtime, (err, res) => {
193
- if (err)
194
- reject(err);
195
- else
196
- resolve(res);
197
- });
198
- });
199
- (0, vitest_1.expect)(out).toBe('Hello Sam');
200
- });
201
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,279 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // test/environment.test.ts
4
- const vitest_1 = require("vitest");
5
- // IMPORTANT: update this import to the file that exports Environment/Template/Context/asap/callbackAsap
6
- const environment_1 = require("../src/environment");
7
- // ---- Helpers: minimal loader stubs ----
8
- class SyncLoader {
9
- async = false;
10
- cache = {};
11
- on;
12
- sources = {};
13
- constructor(sources) {
14
- if (sources)
15
- this.sources = sources;
16
- }
17
- getSource(name) {
18
- const hit = this.sources[name];
19
- if (!hit)
20
- return null;
21
- return {
22
- src: hit.src,
23
- path: hit.path ?? name,
24
- noCache: hit.noCache ?? false,
25
- };
26
- }
27
- }
28
- class RelativeLoader extends SyncLoader {
29
- isRelative(filename) {
30
- return filename.startsWith('./') || filename.startsWith('../');
31
- }
32
- resolve(parent, filename) {
33
- // very small join; just enough for tests
34
- const base = parent.split('/').slice(0, -1).join('/');
35
- return `${base}/${filename.replace(/^\.\//, '')}`;
36
- }
37
- }
38
- // ---- Helpers: tiny code templates (no compiler involved) ----
39
- function codeTemplateReturning(text) {
40
- return {
41
- type: 'code',
42
- obj: {
43
- root(_env, _ctx, _frame, _runtime, cb) {
44
- cb(null, text);
45
- },
46
- },
47
- };
48
- }
49
- function codeTemplateCallingLookup(name) {
50
- return {
51
- type: 'code',
52
- obj: {
53
- root(_env, ctx, _frame, _runtime, cb) {
54
- cb(null, String(ctx.lookup(name)));
55
- },
56
- },
57
- };
58
- }
59
- (0, vitest_1.describe)('Environment basics', () => {
60
- (0, vitest_1.it)('addFilter/getFilter', () => {
61
- const env = new environment_1.Environment();
62
- const f = (v) => String(v).toUpperCase();
63
- env.addFilter('up', f);
64
- (0, vitest_1.expect)(env.getFilter('up')).toBe(f);
65
- (0, vitest_1.expect)(() => env.getFilter('missing')).toThrow(/filter not found/i);
66
- });
67
- (0, vitest_1.it)('addFilter with async marks asyncFilters', () => {
68
- const env = new environment_1.Environment();
69
- env.addFilter('a', () => 'x', [
70
- /*anything*/
71
- ]);
72
- (0, vitest_1.expect)(env.asyncFilters).toContain('a');
73
- });
74
- (0, vitest_1.it)('addExtension / hasExtension / getExtension / removeExtension', () => {
75
- const env = new environment_1.Environment();
76
- const ext = { foo: 1 };
77
- env.addExtension('myExt', ext);
78
- (0, vitest_1.expect)(env.hasExtension('myExt')).toBe(true);
79
- (0, vitest_1.expect)(env.getExtension('myExt')).toBe(ext);
80
- env.removeExtension('myExt');
81
- (0, vitest_1.expect)(env.hasExtension('myExt')).toBe(false);
82
- (0, vitest_1.expect)(env.getExtension('myExt')).toBeUndefined();
83
- });
84
- });
85
- (0, vitest_1.describe)('Environment.resolveTemplate', () => {
86
- (0, vitest_1.it)('uses loader.resolve for relative templates when parentName is provided', () => {
87
- const loader = new RelativeLoader();
88
- const env = new environment_1.Environment({ loaders: [loader] });
89
- const resolved = env.resolveTemplate(loader, 'pages/base.njk', './child.njk');
90
- (0, vitest_1.expect)(resolved).toBe('pages/child.njk');
91
- });
92
- (0, vitest_1.it)('returns filename as-is when not relative', () => {
93
- const loader = new RelativeLoader();
94
- const env = new environment_1.Environment({ loaders: [loader] });
95
- const resolved = env.resolveTemplate(loader, 'pages/base.njk', 'child.njk');
96
- (0, vitest_1.expect)(resolved).toBe('child.njk');
97
- });
98
- });
99
- (0, vitest_1.describe)('Environment.getTemplate (sync loaders)', () => {
100
- (0, vitest_1.it)('returns cached template when present', () => {
101
- const loader = new SyncLoader();
102
- const env = new environment_1.Environment({ loaders: [loader] });
103
- const t1 = new environment_1.Template(codeTemplateReturning('hello'), env, 't.njk', true);
104
- loader.cache['t.njk'] = t1;
105
- const got = env.getTemplate('t.njk', (e, r) => { }, {});
106
- (0, vitest_1.expect)(got).toBe(t1);
107
- });
108
- (0, vitest_1.it)('loads from loader and caches when noCache is false', () => {
109
- const loader = new SyncLoader({
110
- 'a.njk': {
111
- src: codeTemplateReturning('A'),
112
- path: 'a.njk',
113
- noCache: false,
114
- },
115
- });
116
- const env = new environment_1.Environment({ loaders: [loader] });
117
- const tmpl = env.getTemplate('a.njk', (e, r) => { }, {});
118
- (0, vitest_1.expect)(tmpl).toBeInstanceOf(environment_1.Template);
119
- // should now be cached at name
120
- (0, vitest_1.expect)(loader.cache['a.njk']).toBe(tmpl);
121
- // eagerCompile=true should have compiled
122
- (0, vitest_1.expect)(tmpl.compiled).toBe(true);
123
- // and it should render
124
- (0, vitest_1.expect)(tmpl.render({})).toBe('A');
125
- });
126
- (0, vitest_1.it)('does not cache when noCache is true', () => {
127
- const loader = new SyncLoader({
128
- 'a.njk': {
129
- src: codeTemplateReturning('A'),
130
- path: 'a.njk',
131
- noCache: true,
132
- },
133
- });
134
- const env = new environment_1.Environment({ loaders: [loader] });
135
- const tmpl = env.getTemplate('a.njk', (e, r) => { });
136
- (0, vitest_1.expect)(loader.cache['a.njk']).toBeUndefined();
137
- (0, vitest_1.expect)(tmpl.render({})).toBe('A');
138
- });
139
- (0, vitest_1.it)('throws when template missing and ignoreMissing not set', () => {
140
- const loader = new SyncLoader({});
141
- const env = new environment_1.Environment({ loaders: [loader] });
142
- (0, vitest_1.expect)(() => env.getTemplate('missing.njk', (e, r) => { })).toThrow(/template not found/i);
143
- });
144
- (0, vitest_1.it)('returns a noop template when ignoreMissing=true', () => {
145
- const loader = new SyncLoader({});
146
- const env = new environment_1.Environment({ loaders: [loader] });
147
- const tmpl = env.getTemplate('missing.njk', (e, r) => { }, {
148
- eagerCompile: false,
149
- parentName: null,
150
- ignoreMissing: true,
151
- });
152
- (0, vitest_1.expect)(tmpl).toBeInstanceOf(environment_1.Template);
153
- // should render empty string
154
- (0, vitest_1.expect)(tmpl.render({})).toBe('');
155
- });
156
- (0, vitest_1.it)('supports callback API and returns undefined', async () => {
157
- const loader = new SyncLoader({
158
- 'a.njk': { src: codeTemplateReturning('A'), path: 'a.njk' },
159
- });
160
- const env = new environment_1.Environment({ loaders: [loader] });
161
- const cb = vitest_1.vi.fn();
162
- const ret = env.getTemplate('a.njk', cb, {});
163
- (0, vitest_1.expect)(ret).toBeUndefined();
164
- // callback is invoked synchronously for sync loaders in this implementation
165
- (0, vitest_1.expect)(cb).toHaveBeenCalledTimes(1);
166
- (0, vitest_1.expect)(cb.mock.calls[0][0]).toBeNull();
167
- (0, vitest_1.expect)(cb.mock.calls[0][1]).toBeInstanceOf(environment_1.Template);
168
- });
169
- (0, vitest_1.it)('accepts Template instance directly', () => {
170
- const env = new environment_1.Environment();
171
- const t = new environment_1.Template(codeTemplateReturning('X'), env, 'x.njk', true);
172
- const got = env.getTemplate(t, (e, r) => { });
173
- (0, vitest_1.expect)(got).toBe(t);
174
- (0, vitest_1.expect)(got.render({})).toBe('X');
175
- });
176
- (0, vitest_1.it)('throws if name is not a string or Template', () => {
177
- const env = new environment_1.Environment();
178
- // @ts-expect-error
179
- (0, vitest_1.expect)(() => env.getTemplate(123, false)).toThrow(/template names must be a string/i);
180
- });
181
- });
182
- (0, vitest_1.describe)('Environment.render / renderString', () => {
183
- (0, vitest_1.it)('render returns sync string when callback not provided', () => {
184
- const loader = new SyncLoader({
185
- 'a.njk': { src: codeTemplateReturning('Hello'), path: 'a.njk' },
186
- });
187
- const env = new environment_1.Environment({ loaders: [loader] });
188
- const out = env.render('a.njk', { any: 1 });
189
- (0, vitest_1.expect)(out).toBe('Hello');
190
- });
191
- (0, vitest_1.it)('render calls callback asynchronously (callbackAsap) when no parentFrame', async () => {
192
- const loader = new SyncLoader({
193
- 'a.njk': { src: codeTemplateReturning('Hello'), path: 'a.njk' },
194
- });
195
- const env = new environment_1.Environment({ loaders: [loader] });
196
- const cb = vitest_1.vi.fn();
197
- env.render('a.njk', {}, cb);
198
- // callback should NOT have fired yet because Template.render forces async when no parentFrame
199
- (0, vitest_1.expect)(cb).not.toHaveBeenCalled();
200
- await Promise.resolve();
201
- (0, vitest_1.expect)(cb).toHaveBeenCalledTimes(1);
202
- (0, vitest_1.expect)(cb.mock.calls[0][0]).toBeNull();
203
- (0, vitest_1.expect)(cb.mock.calls[0][1]).toBe('Hello');
204
- });
205
- (0, vitest_1.it)('renderString renders from a code template object', () => {
206
- const env = new environment_1.Environment();
207
- const out = env.renderString(codeTemplateReturning('S'), {}, {});
208
- (0, vitest_1.expect)(out).toBe('S');
209
- });
210
- (0, vitest_1.it)('Context.lookup prefers globals when ctx lacks key', () => {
211
- const env = new environment_1.Environment();
212
- // env.addGlobal('g', 'GLOB');
213
- const tmpl = new environment_1.Template(codeTemplateCallingLookup('g'), env, 't.njk', true);
214
- (0, vitest_1.expect)(tmpl.render({})).toBe('GLOB');
215
- });
216
- (0, vitest_1.it)('Context.lookup prefers ctx value over globals when both exist', () => {
217
- const env = new environment_1.Environment();
218
- // env.addGlobal('g', 'GLOB');
219
- const tmpl = new environment_1.Template(codeTemplateCallingLookup('g'), env, 't.njk', true);
220
- (0, vitest_1.expect)(tmpl.render({ g: 'LOCAL' })).toBe('LOCAL');
221
- });
222
- });
223
- (0, vitest_1.describe)('Context blocks + super', () => {
224
- (0, vitest_1.it)('addBlock/getBlock', () => {
225
- const ctx = new environment_1.Context({}, {}, new environment_1.Environment());
226
- ctx.addBlock('b', () => 'x');
227
- (0, vitest_1.expect)(typeof ctx.getBlock('b')).toBe('function');
228
- (0, vitest_1.expect)(() => ctx.getBlock('missing')).toThrow(/unknown block/i);
229
- });
230
- (0, vitest_1.it)('getSuper calls the next block in the stack', () => {
231
- const env = new environment_1.Environment();
232
- const ctx = new environment_1.Context({}, {}, env);
233
- const b1 = vitest_1.vi.fn((_env, _ctx, _frame, _runtime, cb) => cb(null, 'one'));
234
- const b2 = vitest_1.vi.fn((_env, _ctx, _frame, _runtime, cb) => cb(null, 'two'));
235
- // first pushed is "top" (index 0), super should call next (index 1)
236
- ctx.addBlock('x', b1);
237
- ctx.addBlock('x', b2);
238
- const cb = vitest_1.vi.fn();
239
- // ask for super of b1 -> should run b2
240
- ctx.getSuper(env, 'x', b1, {}, {}, cb);
241
- (0, vitest_1.expect)(b2).toHaveBeenCalledTimes(1);
242
- (0, vitest_1.expect)(cb).toHaveBeenCalledWith(null, 'two');
243
- });
244
- (0, vitest_1.it)('getSuper throws when no super block exists', () => {
245
- const env = new environment_1.Environment();
246
- const ctx = new environment_1.Context({}, {}, env);
247
- const b1 = vitest_1.vi.fn();
248
- ctx.addBlock('x', b1);
249
- (0, vitest_1.expect)(() => ctx.getSuper(env, 'x', b1, {}, {}, vitest_1.vi.fn())).toThrow(/no super block available/i);
250
- });
251
- });
252
- (0, vitest_1.describe)('Template compilation from compiler (mocked)', () => {
253
- (0, vitest_1.beforeEach)(() => {
254
- vitest_1.vi.resetModules();
255
- vitest_1.vi.restoreAllMocks();
256
- });
257
- (0, vitest_1.it)('compiles string templates via compiler.compile (mocked) and renders', async () => {
258
- // We need to import a fresh copy after mocking
259
- vitest_1.vi.mock('../src/compiler', () => {
260
- return {
261
- compile: vitest_1.vi.fn(() => {
262
- // This source is executed with `new Function(source)` and must return props when invoked.
263
- // It should return an object like: { root(...) {}, b_name(...) {} }
264
- return `
265
- return function() {
266
- return {
267
- root: function(env, ctx, frame, runtime, cb) { cb(null, "FROM_COMPILER"); }
268
- };
269
- };
270
- `;
271
- }),
272
- };
273
- });
274
- const mod = await import('../src/environment');
275
- const env = new mod.Environment();
276
- const tmpl = new mod.Template('hello {{x}}', env, 't.njk', true);
277
- (0, vitest_1.expect)(tmpl.render({})).toBe('FROM_COMPILER');
278
- });
279
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,86 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const path_1 = __importDefault(require("path"));
7
- const vitest_1 = require("vitest");
8
- const express_app_1 = require("../src/express-app");
9
- (0, vitest_1.describe)('express-app.ts (express integration)', () => {
10
- (0, vitest_1.it)('registers the view class and env on the app via app.set', () => {
11
- const env = { render: vitest_1.vi.fn() };
12
- const app = { set: vitest_1.vi.fn() };
13
- const out = (0, express_app_1.express)(env, app);
14
- (0, vitest_1.expect)(out).toBe(env);
15
- // called with view ctor + env
16
- (0, vitest_1.expect)(app.set).toHaveBeenCalledWith('view', vitest_1.expect.any(Function));
17
- (0, vitest_1.expect)(app.set).toHaveBeenCalledWith('nunjucksEnv', env);
18
- });
19
- (0, vitest_1.it)('View constructor uses file extension when provided and does not change name', () => {
20
- const env = { render: vitest_1.vi.fn() };
21
- const app = { set: vitest_1.vi.fn() };
22
- (0, express_app_1.express)(env, app);
23
- const ViewCtor = app.set.mock.calls.find((c) => c[0] === 'view')[1];
24
- const view = new ViewCtor('index.njk', {
25
- name: 'index.njk',
26
- path: '',
27
- defaultEngine: 'express',
28
- ext: '',
29
- });
30
- // because you assign to closure var "name", but render uses this.name
31
- // so for correctness, the constructor should set instance fields (it currently doesn't).
32
- // This test documents current behavior: instance.name is undefined.
33
- (0, vitest_1.expect)(view.name).toBeUndefined();
34
- });
35
- (0, vitest_1.it)('render calls env.render with (this.name, opts, cb)', () => {
36
- const env = { render: vitest_1.vi.fn() };
37
- const app = { set: vitest_1.vi.fn() };
38
- (0, express_app_1.express)(env, app);
39
- const ViewCtor = app.set.mock.calls.find((c) => c[0] === 'view')[1];
40
- const view = new ViewCtor('index.njk', {
41
- name: 'index.njk',
42
- path: '',
43
- defaultEngine: 'express',
44
- ext: '',
45
- });
46
- const cb = vitest_1.vi.fn();
47
- const opts = { a: 1 };
48
- // render uses this.name (instance field)
49
- view.name = 'index.njk';
50
- view.render(opts, cb);
51
- (0, vitest_1.expect)(env.render).toHaveBeenCalledTimes(1);
52
- (0, vitest_1.expect)(env.render).toHaveBeenCalledWith('index.njk', opts, cb);
53
- });
54
- (0, vitest_1.it)('throws when there is no extension and no defaultEngine', () => {
55
- const env = { render: vitest_1.vi.fn() };
56
- const app = { set: vitest_1.vi.fn() };
57
- (0, express_app_1.express)(env, app);
58
- const ViewCtor = app.set.mock.calls.find((c) => c[0] === 'view')[1];
59
- // Your NunjucksView signature is (name, opts), but it ignores opts.defaultEngine
60
- // and uses the closure `defaultEngine = "express"` (always truthy).
61
- // So there is currently NO way to trigger this throw branch without code changes.
62
- //
63
- // This test expresses the intended behavior by constructing a small patched copy
64
- // of the constructor logic to demonstrate expected throw; but we cannot reach it
65
- // through the current public API. If you fix the impl to use opts.defaultEngine,
66
- // remove this workaround and test the real behavior.
67
- const makeThrowingCtor = () => {
68
- function NunjucksView(_name, _opts) {
69
- let name = _name;
70
- let ext = path_1.default.extname(name);
71
- const defaultEngine = ''; // falsy
72
- if (!ext && !defaultEngine) {
73
- throw new Error('No default engine was specified and no extension was provided.');
74
- }
75
- if (!ext) {
76
- name += ext = (defaultEngine[0] !== '.' ? '.' : '') + defaultEngine;
77
- }
78
- }
79
- return NunjucksView;
80
- };
81
- const Throwing = makeThrowingCtor();
82
- (0, vitest_1.expect)(() => new Throwing('index', {})).toThrow(/No default engine was specified/i);
83
- // and ViewCtor itself should not throw for "index" due to current bug
84
- (0, vitest_1.expect)(() => new ViewCtor('index', {})).not.toThrow();
85
- });
86
- });
@@ -1,13 +0,0 @@
1
- export {};
2
- /**
3
- * NOTE:
4
- * - I did NOT test escape/forceescape/safe because your `escape` filter currently calls itself recursively:
5
- * export const escape = (str) => r.markSafe(escape(str.toString()))
6
- * That will stack overflow. Once you fix it to call a real escaping function (e.g. lib.escape),
7
- * I can add tests for it too.
8
- *
9
- * - I did NOT test `batch` here because the loop in your snippet looks syntactically wrong:
10
- * for (i < arr.length; i++; )
11
- * If that’s a paste typo and your real code is valid, tell me your actual batch() implementation
12
- * and I’ll add tests for it as well.
13
- */