@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 +1 -1
- package/dist/tests/compiler.test.d.ts +0 -1
- package/dist/tests/compiler.test.js +0 -201
- package/dist/tests/enviornment.test.d.ts +0 -1
- package/dist/tests/enviornment.test.js +0 -279
- package/dist/tests/express.test.d.ts +0 -1
- package/dist/tests/express.test.js +0 -86
- package/dist/tests/filters.test.d.ts +0 -13
- package/dist/tests/filters.test.js +0 -286
- package/dist/tests/globals.test.d.ts +0 -1
- package/dist/tests/globals.test.js +0 -579
- package/dist/tests/interpreter.test.d.ts +0 -1
- package/dist/tests/interpreter.test.js +0 -208
- package/dist/tests/lexer.test.d.ts +0 -1
- package/dist/tests/lexer.test.js +0 -249
- package/dist/tests/lib.test.d.ts +0 -1
- package/dist/tests/lib.test.js +0 -236
- package/dist/tests/loader.test.d.ts +0 -1
- package/dist/tests/loader.test.js +0 -301
- package/dist/tests/nodes.test.d.ts +0 -1
- package/dist/tests/nodes.test.js +0 -137
- package/dist/tests/parser.test.d.ts +0 -1
- package/dist/tests/parser.test.js +0 -294
- package/dist/tests/precompile.test.d.ts +0 -1
- package/dist/tests/precompile.test.js +0 -224
- package/dist/tests/runtime.test.d.ts +0 -1
- package/dist/tests/runtime.test.js +0 -237
- package/dist/tests/transformer.test.d.ts +0 -1
- package/dist/tests/transformer.test.js +0 -125
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// test/filters.test.ts
|
|
4
|
-
const vitest_1 = require("vitest");
|
|
5
|
-
/**
|
|
6
|
-
* These tests mock ./runtime and ./lib so we can test filter logic in isolation
|
|
7
|
-
* without depending on your runtime safety/markSafe implementations.
|
|
8
|
-
*
|
|
9
|
-
* IMPORTANT: Update the import paths below if your filters file lives elsewhere.
|
|
10
|
-
*/
|
|
11
|
-
vitest_1.vi.mock('../src/runtime', () => {
|
|
12
|
-
return {
|
|
13
|
-
default: {
|
|
14
|
-
// keep it simple: "safe" handling just wraps + copySafeness returns output
|
|
15
|
-
markSafe: (v) => ({ __safe: true, val: String(v) }),
|
|
16
|
-
copySafeness: (_inp, out) => out,
|
|
17
|
-
makeMacro: (_args, _kwargs, fn) => fn,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
vitest_1.vi.mock('../src/lib', () => {
|
|
22
|
-
const TemplateError = (msg) => new Error(msg);
|
|
23
|
-
return {
|
|
24
|
-
repeat: (s, n) => s.repeat(Math.max(0, Math.floor(n))),
|
|
25
|
-
isObject: (o) => o !== null && typeof o === 'object' && !Array.isArray(o),
|
|
26
|
-
TemplateError,
|
|
27
|
-
isString: (v) => typeof v === 'string' || v instanceof String,
|
|
28
|
-
_entries: (o) => Object.entries(o ?? {}),
|
|
29
|
-
getAttrGetter: (attr) => (obj) => attr ? obj?.[attr] : obj,
|
|
30
|
-
toArray: (v) => (Array.isArray(v) ? v : v == null ? [] : [v]),
|
|
31
|
-
groupBy: (arr, attr, _throwOnUndefined) => {
|
|
32
|
-
const out = {};
|
|
33
|
-
for (const item of arr ?? []) {
|
|
34
|
-
const k = String(item?.[attr]);
|
|
35
|
-
(out[k] ||= []).push(item);
|
|
36
|
-
}
|
|
37
|
-
// jinja-style groupby returns array of {grouper, list} often,
|
|
38
|
-
// but your filter just forwards groupBy; we return a deterministic shape for tests:
|
|
39
|
-
return Object.entries(out).map(([grouper, list]) => ({ grouper, list }));
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
async function loadFilters() {
|
|
44
|
-
return await import('../src/filters');
|
|
45
|
-
}
|
|
46
|
-
(0, vitest_1.describe)('filters.ts', () => {
|
|
47
|
-
(0, vitest_1.beforeEach)(() => {
|
|
48
|
-
vitest_1.vi.restoreAllMocks();
|
|
49
|
-
});
|
|
50
|
-
(0, vitest_1.it)('center pads evenly and uses copySafeness', async () => {
|
|
51
|
-
const f = await loadFilters();
|
|
52
|
-
(0, vitest_1.expect)(f.center('hi', 6)).toBe(' hi ');
|
|
53
|
-
(0, vitest_1.expect)(f.center('already long', 3)).toBe('already long');
|
|
54
|
-
});
|
|
55
|
-
(0, vitest_1.it)('default_ (and alias d) chooses val/def based on bool flag', async () => {
|
|
56
|
-
const f = await loadFilters();
|
|
57
|
-
(0, vitest_1.expect)(f.default_(null, 'x')).toBe('x');
|
|
58
|
-
(0, vitest_1.expect)(f.default_('a', 'x')).toBe('a');
|
|
59
|
-
// bool=true path in your code: return def || val
|
|
60
|
-
(0, vitest_1.expect)(f.default_(null, 'x', true)).toBe('x');
|
|
61
|
-
(0, vitest_1.expect)(f.default_('a', '', true)).toBe('a');
|
|
62
|
-
(0, vitest_1.expect)(f.d).toBe(f.default_);
|
|
63
|
-
});
|
|
64
|
-
(0, vitest_1.it)('dictsort sorts by key/value and respects caseSensitive', async () => {
|
|
65
|
-
const f = await loadFilters();
|
|
66
|
-
const obj = Object.create({ z: 0 });
|
|
67
|
-
obj.B = 2;
|
|
68
|
-
obj.a = 1;
|
|
69
|
-
// by value default
|
|
70
|
-
(0, vitest_1.expect)(f.dictsort(obj, true)).toEqual([
|
|
71
|
-
['z', 0],
|
|
72
|
-
['a', 1],
|
|
73
|
-
['B', 2],
|
|
74
|
-
]);
|
|
75
|
-
// by key, case-insensitive should treat 'B' and 'a' accordingly
|
|
76
|
-
(0, vitest_1.expect)(f.dictsort({ B: 2, a: 1 }, false, 'key')).toEqual([
|
|
77
|
-
['a', 1],
|
|
78
|
-
['B', 2],
|
|
79
|
-
]);
|
|
80
|
-
(0, vitest_1.expect)(() => f.dictsort('nope', true)).toThrow(/val must be an object/i);
|
|
81
|
-
(0, vitest_1.expect)(() => f.dictsort({ a: 1 }, true, 'nope')).toThrow(/only sort by/i);
|
|
82
|
-
});
|
|
83
|
-
(0, vitest_1.it)('indent indents lines, optionally skipping the first', async () => {
|
|
84
|
-
const f = await loadFilters();
|
|
85
|
-
(0, vitest_1.expect)(f.indent('a\nb', 2, false)).toBe('a\n b');
|
|
86
|
-
(0, vitest_1.expect)(f.indent('a\nb', 2, true)).toBe(' a\n b');
|
|
87
|
-
(0, vitest_1.expect)(f.indent('', 4, true)).toBe('');
|
|
88
|
-
});
|
|
89
|
-
(0, vitest_1.it)('join joins array, supports attr picking', async () => {
|
|
90
|
-
const f = await loadFilters();
|
|
91
|
-
(0, vitest_1.expect)(f.join([1, 2, 3], '-')).toBe('1-2-3');
|
|
92
|
-
(0, vitest_1.expect)(f.join([{ x: 'a' }, { x: 'b' }], ',', 'x')).toBe('a,b');
|
|
93
|
-
});
|
|
94
|
-
(0, vitest_1.it)('length counts array/string/map/set; object branch currently returns 0 due to missing return', async () => {
|
|
95
|
-
const f = await loadFilters();
|
|
96
|
-
(0, vitest_1.expect)(f.length([1, 2, 3])).toBe(3);
|
|
97
|
-
(0, vitest_1.expect)(f.length('abc')).toBe(3);
|
|
98
|
-
(0, vitest_1.expect)(f.length(new Map([['a', 1]]))).toBe(1);
|
|
99
|
-
(0, vitest_1.expect)(f.length(new Set([1, 2]))).toBe(2);
|
|
100
|
-
// NOTE: your implementation has:
|
|
101
|
-
// if (isObject(str)) Object.keys(str).length; // missing return
|
|
102
|
-
// so currently it falls through and returns 0. This test locks that in (and highlights the bug).
|
|
103
|
-
(0, vitest_1.expect)(f.length({ a: 1, b: 2 })).toBe(0);
|
|
104
|
-
});
|
|
105
|
-
(0, vitest_1.it)('list: string -> chars, object -> {key,value} entries, array -> itself; throws otherwise', async () => {
|
|
106
|
-
const f = await loadFilters();
|
|
107
|
-
(0, vitest_1.expect)(f.list('ab')).toEqual(['a', 'b']);
|
|
108
|
-
(0, vitest_1.expect)(f.list({ a: 1, b: 2 })).toEqual([
|
|
109
|
-
{ key: 'a', value: 1 },
|
|
110
|
-
{ key: 'b', value: 2 },
|
|
111
|
-
]);
|
|
112
|
-
(0, vitest_1.expect)(f.list([1, 2])).toEqual([1, 2]);
|
|
113
|
-
(0, vitest_1.expect)(() => f.list(123)).toThrow(/type not iterable/i);
|
|
114
|
-
});
|
|
115
|
-
(0, vitest_1.it)('random uses Math.random (stubbed)', async () => {
|
|
116
|
-
const f = await loadFilters();
|
|
117
|
-
const spy = vitest_1.vi.spyOn(Math, 'random').mockReturnValue(0.6); // floor(0.6*5)=3
|
|
118
|
-
(0, vitest_1.expect)(f.random([0, 1, 2, 3, 4])).toBe(3);
|
|
119
|
-
spy.mockRestore();
|
|
120
|
-
});
|
|
121
|
-
(0, vitest_1.it)('reject/select use env.getTest + toArray', async () => {
|
|
122
|
-
const f = await loadFilters();
|
|
123
|
-
const ctx = {
|
|
124
|
-
env: {
|
|
125
|
-
getTest: (name) => {
|
|
126
|
-
if (name === 'truthy')
|
|
127
|
-
return (x) => !!x;
|
|
128
|
-
if (name === 'gt')
|
|
129
|
-
return (x, n) => x > n;
|
|
130
|
-
throw new Error('unknown test');
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
// select expects test === true
|
|
135
|
-
(0, vitest_1.expect)(f.select.call(ctx, [0, 1, 2], 'truthy')).toEqual([1, 2]);
|
|
136
|
-
// reject expects test === false
|
|
137
|
-
(0, vitest_1.expect)(f.reject.call(ctx, [0, 1, 2], 'truthy')).toEqual([0]);
|
|
138
|
-
(0, vitest_1.expect)(f.select.call(ctx, [1, 2, 3], 'gt', 2)).toEqual([3]);
|
|
139
|
-
(0, vitest_1.expect)(f.reject.call(ctx, [1, 2, 3], 'gt', 2)).toEqual([1, 2]);
|
|
140
|
-
// toArray: non-array becomes [value]
|
|
141
|
-
(0, vitest_1.expect)(f.select.call(ctx, 5, 'truthy')).toEqual([5]);
|
|
142
|
-
});
|
|
143
|
-
(0, vitest_1.it)('rejectattr/selectattr filter by truthiness of attr', async () => {
|
|
144
|
-
const f = await loadFilters();
|
|
145
|
-
const arr = [{ ok: true }, { ok: false }, {}];
|
|
146
|
-
(0, vitest_1.expect)(f.selectattr(arr, 'ok')).toEqual([{ ok: true }]);
|
|
147
|
-
(0, vitest_1.expect)(f.rejectattr(arr, 'ok')).toEqual([{ ok: false }, {}]);
|
|
148
|
-
});
|
|
149
|
-
(0, vitest_1.it)('replace supports regex replacement and string replacement with maxCount and empty old', async () => {
|
|
150
|
-
const f = await loadFilters();
|
|
151
|
-
(0, vitest_1.expect)(f.replace('a-b-c', /-/g, '_', 0)).toBe('a_b_c');
|
|
152
|
-
(0, vitest_1.expect)(f.replace('aaaa', 'a', 'b', 2)).toBe('bbaa');
|
|
153
|
-
(0, vitest_1.expect)(f.replace('aaaa', 'a', 'b', 0)).toBe('aaaa');
|
|
154
|
-
(0, vitest_1.expect)(f.replace('aaaa', 'x', 'b', 10)).toBe('aaaa');
|
|
155
|
-
// old === '' inserts between chars and both ends
|
|
156
|
-
(0, vitest_1.expect)(f.replace('ab', '', '-', 99)).toBe('-a-b-');
|
|
157
|
-
});
|
|
158
|
-
(0, vitest_1.it)('reverse reverses arrays in-place and strings as a new string', async () => {
|
|
159
|
-
const f = await loadFilters();
|
|
160
|
-
const arr = [1, 2, 3];
|
|
161
|
-
(0, vitest_1.expect)(f.reverse(arr)).toEqual([3, 2, 1]);
|
|
162
|
-
(0, vitest_1.expect)(arr).toEqual([3, 2, 1]); // in-place
|
|
163
|
-
(0, vitest_1.expect)(f.reverse('abc')).toBe('cba');
|
|
164
|
-
});
|
|
165
|
-
(0, vitest_1.it)('round supports round/floor/ceil with precision', async () => {
|
|
166
|
-
const f = await loadFilters();
|
|
167
|
-
(0, vitest_1.expect)(f.round(1.234, 2)).toBe(1.23);
|
|
168
|
-
(0, vitest_1.expect)(f.round(1.235, 2)).toBe(1.24);
|
|
169
|
-
(0, vitest_1.expect)(f.round(1.231, 2, 'ceil')).toBe(1.24);
|
|
170
|
-
(0, vitest_1.expect)(f.round(1.239, 2, 'floor')).toBe(1.23);
|
|
171
|
-
});
|
|
172
|
-
(0, vitest_1.it)('slice splits into N slices and can fill', async () => {
|
|
173
|
-
const f = await loadFilters();
|
|
174
|
-
(0, vitest_1.expect)(f.slice([1, 2, 3, 4], 2)).toEqual([
|
|
175
|
-
[1, 2],
|
|
176
|
-
[3, 4],
|
|
177
|
-
]);
|
|
178
|
-
// fillWith=true pushes true into later slices when i >= extra
|
|
179
|
-
(0, vitest_1.expect)(f.slice([1, 2, 3], 2, true)).toEqual([
|
|
180
|
-
[1, 2],
|
|
181
|
-
[3, true],
|
|
182
|
-
]);
|
|
183
|
-
});
|
|
184
|
-
(0, vitest_1.it)('sum sums, optionally by attr, and supports start', async () => {
|
|
185
|
-
const f = await loadFilters();
|
|
186
|
-
(0, vitest_1.expect)(f.sum([1, 2, 3], '', 0)).toBe(6);
|
|
187
|
-
(0, vitest_1.expect)(f.sum([{ n: 1 }, { n: 2 }], 'n', 10)).toBe(13);
|
|
188
|
-
});
|
|
189
|
-
(0, vitest_1.it)('sort (macro) sorts with reverse/case_sensitive/attribute and throwOnUndefined', async () => {
|
|
190
|
-
const f = await loadFilters();
|
|
191
|
-
const ctx1 = { env: { opts: { throwOnUndefined: false } } };
|
|
192
|
-
(0, vitest_1.expect)(f.sort.call(ctx1, ['b', 'A', 'c'], false, false, undefined)).toEqual(['A', 'b', 'c']); // case-insensitive -> 'a','b','c'
|
|
193
|
-
const ctx2 = { env: { opts: { throwOnUndefined: true } } };
|
|
194
|
-
(0, vitest_1.expect)(() => f.sort.call(ctx2, [{ x: 1 }, { y: 2 }], false, false, 'x')).toThrow(/resolved to undefined/i);
|
|
195
|
-
(0, vitest_1.expect)(f.sort.call(ctx1, [{ x: 2 }, { x: 1 }], true, false, 'x')).toEqual([
|
|
196
|
-
{ x: 2 },
|
|
197
|
-
{ x: 1 },
|
|
198
|
-
]); // reversed
|
|
199
|
-
});
|
|
200
|
-
(0, vitest_1.it)('striptags removes tags and optionally preserves linebreaks', async () => {
|
|
201
|
-
const f = await loadFilters();
|
|
202
|
-
const input = 'a <b>bold</b>\r\n\r\n c';
|
|
203
|
-
(0, vitest_1.expect)(f.striptags(input, true)).toBe('a bold\n\nc');
|
|
204
|
-
(0, vitest_1.expect)(f.striptags(input, false)).toBe('a bold c');
|
|
205
|
-
});
|
|
206
|
-
(0, vitest_1.it)('title/capitalize/lower/upper/isUpper', async () => {
|
|
207
|
-
const f = await loadFilters();
|
|
208
|
-
(0, vitest_1.expect)(f.title('hello WORLD')).toBe('Hello World');
|
|
209
|
-
(0, vitest_1.expect)(f.capitalize('hELLO')).toBe('Hello');
|
|
210
|
-
(0, vitest_1.expect)(f.lower('HeLLo')).toBe('hello');
|
|
211
|
-
(0, vitest_1.expect)(f.upper('hi')).toBe('HI');
|
|
212
|
-
(0, vitest_1.expect)(f.isUpper('HI')).toBe(true);
|
|
213
|
-
(0, vitest_1.expect)(f.isUpper('Hi')).toBe(false);
|
|
214
|
-
});
|
|
215
|
-
(0, vitest_1.it)('truncate respects killwords and default end', async () => {
|
|
216
|
-
const f = await loadFilters();
|
|
217
|
-
(0, vitest_1.expect)(f.truncate('short', 10, false)).toBe('short');
|
|
218
|
-
(0, vitest_1.expect)(f.truncate('hello world there', 8, false)).toBe('hello...');
|
|
219
|
-
(0, vitest_1.expect)(f.truncate('hello world there', 8, true)).toBe('hello wo...');
|
|
220
|
-
(0, vitest_1.expect)(f.truncate('hello world there', 8, false, '>>>')).toBe('hello>>>');
|
|
221
|
-
});
|
|
222
|
-
(0, vitest_1.it)('urlencode encodes strings and objects/arrays of pairs', async () => {
|
|
223
|
-
const f = await loadFilters();
|
|
224
|
-
(0, vitest_1.expect)(f.urlencode('a b')).toBe('a%20b');
|
|
225
|
-
(0, vitest_1.expect)(f.urlencode({ a: 'x y', b: 1 })).toBe('a=x%20y&b=1');
|
|
226
|
-
(0, vitest_1.expect)(f.urlencode([
|
|
227
|
-
['a', 'x y'],
|
|
228
|
-
['b', 1],
|
|
229
|
-
])).toBe('a=x%20y&b=1');
|
|
230
|
-
});
|
|
231
|
-
(0, vitest_1.it)('urlize turns urls/emails into links and supports nofollow + length', async () => {
|
|
232
|
-
const f = await loadFilters();
|
|
233
|
-
(0, vitest_1.expect)(f.urlize('go https://example.com now', Infinity, true)).toBe('go <a href="https://example.com" rel="nofollow">https://example.com</a> now');
|
|
234
|
-
// www.
|
|
235
|
-
(0, vitest_1.expect)(f.urlize('www.example.com', 7, false)).toBe('<a href="http://www.example.com">www.exa</a>');
|
|
236
|
-
// email
|
|
237
|
-
(0, vitest_1.expect)(f.urlize('me@test.com', Infinity, false)).toBe('<a href="mailto:me@test.com">me@test.com</a>');
|
|
238
|
-
// tld without scheme
|
|
239
|
-
(0, vitest_1.expect)(f.urlize('example.org', Infinity, true)).toBe('<a href="http://example.org" rel="nofollow">example.org</a>');
|
|
240
|
-
});
|
|
241
|
-
(0, vitest_1.it)('wordcount counts words or returns null', async () => {
|
|
242
|
-
const f = await loadFilters();
|
|
243
|
-
(0, vitest_1.expect)(f.wordcount('one two three')).toBe(3);
|
|
244
|
-
(0, vitest_1.expect)(f.wordcount(' ')).toBe(null);
|
|
245
|
-
});
|
|
246
|
-
(0, vitest_1.it)('float/int/isInt/isFloat', async () => {
|
|
247
|
-
const f = await loadFilters();
|
|
248
|
-
(0, vitest_1.expect)(f.float('1.5', 9)).toBe(1.5);
|
|
249
|
-
(0, vitest_1.expect)(f.float('nope', 9)).toBe(9);
|
|
250
|
-
(0, vitest_1.expect)(f.isInt(2)).toBe(true);
|
|
251
|
-
(0, vitest_1.expect)(f.isInt(2.2)).toBe(false);
|
|
252
|
-
(0, vitest_1.expect)(f.isFloat(2.2)).toBe(true);
|
|
253
|
-
(0, vitest_1.expect)(f.isFloat(2)).toBe(false);
|
|
254
|
-
// int macro (makeMacro mocked to return fn)
|
|
255
|
-
(0, vitest_1.expect)(f.int('ff', 0, 16)).toBe(255);
|
|
256
|
-
(0, vitest_1.expect)(f.int('nope', 7, 10)).toBe(7);
|
|
257
|
-
});
|
|
258
|
-
(0, vitest_1.it)('trim removes leading/trailing whitespace', async () => {
|
|
259
|
-
const f = await loadFilters();
|
|
260
|
-
(0, vitest_1.expect)(f.trim(' a \n')).toBe('a');
|
|
261
|
-
});
|
|
262
|
-
(0, vitest_1.it)('first/last', async () => {
|
|
263
|
-
const f = await loadFilters();
|
|
264
|
-
(0, vitest_1.expect)(f.first([1, 2, 3])).toBe(1);
|
|
265
|
-
(0, vitest_1.expect)(f.last([1, 2, 3])).toBe(3);
|
|
266
|
-
});
|
|
267
|
-
(0, vitest_1.it)('nl2br replaces newlines with <br />', async () => {
|
|
268
|
-
const f = await loadFilters();
|
|
269
|
-
(0, vitest_1.expect)(f.nl2br('a\nb')).toBe('a<br />\n<b');
|
|
270
|
-
// NOTE: your implementation is: replace(/\r\n|\n/g, '<br />\n')
|
|
271
|
-
// so "a\nb" -> "a<br />\nb"
|
|
272
|
-
(0, vitest_1.expect)(f.nl2br('a\nb')).toBe('a<br />\nb');
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
/**
|
|
276
|
-
* NOTE:
|
|
277
|
-
* - I did NOT test escape/forceescape/safe because your `escape` filter currently calls itself recursively:
|
|
278
|
-
* export const escape = (str) => r.markSafe(escape(str.toString()))
|
|
279
|
-
* That will stack overflow. Once you fix it to call a real escaping function (e.g. lib.escape),
|
|
280
|
-
* I can add tests for it too.
|
|
281
|
-
*
|
|
282
|
-
* - I did NOT test `batch` here because the loop in your snippet looks syntactically wrong:
|
|
283
|
-
* for (i < arr.length; i++; )
|
|
284
|
-
* If that’s a paste typo and your real code is valid, tell me your actual batch() implementation
|
|
285
|
-
* and I’ll add tests for it as well.
|
|
286
|
-
*/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|