Skip to content

Commit a912bb6

Browse files
committed
removed find-nth formatter, optimized utils and converted all test to use fixtures
1 parent 7457082 commit a912bb6

6 files changed

Lines changed: 77 additions & 185 deletions

File tree

__tests__/plugins/formatters.core.test.ts

Lines changed: 0 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -370,159 +370,14 @@ loader.paths('f-json-pretty-%N.html').forEach((path) => {
370370
test(`json pretty - ${path}`, () => loader.execute(path));
371371
});
372372

373-
test('find-first', () => {
374-
// no args: return first element unconditionally
375-
let vars = variables(['a', 'b', 'c']);
376-
Core['find-first'].apply([], vars, CTX);
377-
expect(vars[0].get()).toEqual('a');
378-
379-
// empty array → missing
380-
vars = variables([]);
381-
Core['find-first'].apply([], vars, CTX);
382-
expect(vars[0].node.isMissing()).toBe(true);
383-
384-
// non-array → missing
385-
vars = variables('not-an-array');
386-
Core['find-first'].apply([], vars, CTX);
387-
expect(vars[0].node.isMissing()).toBe(true);
388-
389-
// no args: first object element
390-
vars = variables([{ x: 1 }, { x: 2 }]);
391-
Core['find-first'].apply([], vars, CTX);
392-
expect(vars[0].get()).toEqual({ x: 1 });
393-
394-
// 1 arg: find first element where path is truthy
395-
vars = variables([{ id: 'a', enabled: false }, { id: 'b', enabled: true }, { id: 'c', enabled: true }]);
396-
Core['find-first'].apply(['enabled'], vars, CTX);
397-
expect(vars[0].get()).toEqual({ id: 'b', enabled: true });
398-
399-
// 1 arg: none match → missing
400-
vars = variables([{ id: 'a', enabled: false }, { id: 'b' }]);
401-
Core['find-first'].apply(['enabled'], vars, CTX);
402-
expect(vars[0].node.isMissing()).toBe(true);
403-
});
404-
405373
loader.paths('f-find-first-%N.html').forEach((path) => {
406374
test(`find-first - ${path}`, () => loader.execute(path));
407375
});
408376

409-
test('find-last', () => {
410-
// no args: return last element unconditionally
411-
let vars = variables(['a', 'b', 'c']);
412-
Core['find-last'].apply([], vars, CTX);
413-
expect(vars[0].get()).toEqual('c');
414-
415-
// empty array → missing
416-
vars = variables([]);
417-
Core['find-last'].apply([], vars, CTX);
418-
expect(vars[0].node.isMissing()).toBe(true);
419-
420-
// non-array → missing
421-
vars = variables('not-an-array');
422-
Core['find-last'].apply([], vars, CTX);
423-
expect(vars[0].node.isMissing()).toBe(true);
424-
425-
// no args: last object element
426-
vars = variables([{ x: 1 }, { x: 2 }]);
427-
Core['find-last'].apply([], vars, CTX);
428-
expect(vars[0].get()).toEqual({ x: 2 });
429-
430-
// 1 arg: find last element where path is truthy
431-
vars = variables([{ id: 'a', enabled: true }, { id: 'b', enabled: true }, { id: 'c', enabled: false }]);
432-
Core['find-last'].apply(['enabled'], vars, CTX);
433-
expect(vars[0].get()).toEqual({ id: 'b', enabled: true });
434-
435-
// 1 arg: none match → missing
436-
vars = variables([{ id: 'a', enabled: false }, { id: 'b' }]);
437-
Core['find-last'].apply(['enabled'], vars, CTX);
438-
expect(vars[0].node.isMissing()).toBe(true);
439-
});
440-
441377
loader.paths('f-find-last-%N.html').forEach((path) => {
442378
test(`find-last - ${path}`, () => loader.execute(path));
443379
});
444380

445-
test('find-nth', () => {
446-
// no filter: return element at index 0
447-
let vars = variables(['a', 'b', 'c']);
448-
Core['find-nth'].apply(['0'], vars, CTX);
449-
expect(vars[0].get()).toEqual('a');
450-
451-
// no filter: return element at index 1
452-
vars = variables(['a', 'b', 'c']);
453-
Core['find-nth'].apply(['1'], vars, CTX);
454-
expect(vars[0].get()).toEqual('b');
455-
456-
// no filter: negative index counts from end
457-
vars = variables(['a', 'b', 'c']);
458-
Core['find-nth'].apply(['-1'], vars, CTX);
459-
expect(vars[0].get()).toEqual('c');
460-
461-
vars = variables(['a', 'b', 'c']);
462-
Core['find-nth'].apply(['-2'], vars, CTX);
463-
expect(vars[0].get()).toEqual('b');
464-
465-
// non-numeric index defaults to 0
466-
vars = variables(['a', 'b', 'c']);
467-
Core['find-nth'].apply(['foo'], vars, CTX);
468-
expect(vars[0].get()).toEqual('a');
469-
470-
// index out of bounds → missing
471-
vars = variables(['a', 'b', 'c']);
472-
Core['find-nth'].apply(['5'], vars, CTX);
473-
expect(vars[0].node.isMissing()).toBe(true);
474-
475-
// negative index out of bounds → missing
476-
vars = variables(['a', 'b', 'c']);
477-
Core['find-nth'].apply(['-5'], vars, CTX);
478-
expect(vars[0].node.isMissing()).toBe(true);
479-
480-
// empty array → missing
481-
vars = variables([]);
482-
Core['find-nth'].apply(['0'], vars, CTX);
483-
expect(vars[0].node.isMissing()).toBe(true);
484-
485-
// non-array → missing
486-
vars = variables('not-an-array');
487-
Core['find-nth'].apply(['0'], vars, CTX);
488-
expect(vars[0].node.isMissing()).toBe(true);
489-
490-
// no filter: nth object element
491-
vars = variables([{ x: 1 }, { x: 2 }, { x: 3 }]);
492-
Core['find-nth'].apply(['2'], vars, CTX);
493-
expect(vars[0].get()).toEqual({ x: 3 });
494-
495-
// with path: find nth element where path is truthy
496-
vars = variables([
497-
{ id: 'a', enabled: false },
498-
{ id: 'b', enabled: true },
499-
{ id: 'c', enabled: true },
500-
{ id: 'd', enabled: true },
501-
]);
502-
Core['find-nth'].apply(['1', 'enabled'], vars, CTX);
503-
expect(vars[0].get()).toEqual({ id: 'c', enabled: true });
504-
505-
// with path: index 0 of matching elements
506-
vars = variables([{ id: 'a', enabled: false }, { id: 'b', enabled: true }, { id: 'c', enabled: true }]);
507-
Core['find-nth'].apply(['0', 'enabled'], vars, CTX);
508-
expect(vars[0].get()).toEqual({ id: 'b', enabled: true });
509-
510-
// with path: negative index among matching elements
511-
vars = variables([{ id: 'a', enabled: true }, { id: 'b', enabled: false }, { id: 'c', enabled: true }]);
512-
Core['find-nth'].apply(['-1', 'enabled'], vars, CTX);
513-
expect(vars[0].get()).toEqual({ id: 'c', enabled: true });
514-
515-
// with path: none match → missing
516-
vars = variables([{ id: 'a', enabled: false }, { id: 'b' }]);
517-
Core['find-nth'].apply(['0', 'enabled'], vars, CTX);
518-
expect(vars[0].node.isMissing()).toBe(true);
519-
520-
// with path: index out of bounds among matches → missing
521-
vars = variables([{ id: 'a', enabled: true }, { id: 'b', enabled: false }]);
522-
Core['find-nth'].apply(['1', 'enabled'], vars, CTX);
523-
expect(vars[0].node.isMissing()).toBe(true);
524-
});
525-
526381
test('key-by', () => {
527382
let vars = variables([{ id: 1 }]);
528383
Core['key-by'].apply([], vars, CTX);

__tests__/plugins/resources/f-find-first-1.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
}
99

1010
:TEMPLATE
11+
{.var @first items|find-first}{@first.id}
1112
{.var @first items|find-first enabled}{@first.id}
13+
{.var @first items|find-first disabled}{@first.id} {# Note: "disabled" is not a valid path, so it should return nothing #}
1214

1315
:OUTPUT
16+
a
1417
b
18+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:JSON
2+
{
3+
"strings": ["a", "b", "c"],
4+
"empty": [],
5+
"notArray": "not-an-array",
6+
"objects": [{ "x": 1 }, { "x": 2 }],
7+
"noMatch": [{ "id": "a", "enabled": false }, { "id": "b" }]
8+
}
9+
10+
:TEMPLATE
11+
{.var @r strings|find-first}{@r}
12+
{.var @r empty|find-first}-{@r}
13+
{.var @r notArray|find-first}-{@r}
14+
{.var @r objects|find-first}{@r.x}
15+
{.var @r noMatch|find-first enabled}-{@r}
16+
17+
:OUTPUT
18+
a
19+
-
20+
-
21+
1
22+
-
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:JSON
2+
{
3+
"strings": ["a", "b", "c"],
4+
"empty": [],
5+
"notArray": "not-an-array",
6+
"objects": [{ "x": 1 }, { "x": 2 }],
7+
"noMatch": [{ "id": "a", "enabled": false }, { "id": "b" }]
8+
}
9+
10+
:TEMPLATE
11+
{.var @r strings|find-last}{@r}
12+
{.var @r empty|find-last}-{@r}
13+
{.var @r notArray|find-last}-{@r}
14+
{.var @r objects|find-last}{@r.x}
15+
{.var @r noMatch|find-last enabled}-{@r}
16+
17+
:OUTPUT
18+
c
19+
-
20+
-
21+
2
22+
-

src/plugins/formatters.core.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export class FindFirstFormatter extends Formatter {
248248
apply(args: string[], vars: Variable[], ctx: Context): void {
249249
const first = vars[0];
250250
const { lookup, path } = getLookupAndPath(ctx, args);
251-
first.set(findNthValidEntry(first.get(), path, lookup, 0));
251+
first.set(findNthValidEntry(first.get(), path, lookup, 1));
252252
}
253253
}
254254

@@ -260,15 +260,6 @@ export class FindLastFormatter extends Formatter {
260260
}
261261
}
262262

263-
export class FindNthFormatter extends Formatter {
264-
apply(args: string[], vars: Variable[], ctx: Context): void {
265-
const first = vars[0];
266-
const { lookup, path } = getLookupAndPath(ctx, args.slice(1));
267-
const n = parseInt(args[0], 10) || 0;
268-
first.set(findNthValidEntry(first.get(), path, lookup, n));
269-
}
270-
}
271-
272263
const NEWLINE = /\n/g;
273264

274265
export class LineBreaksFormatter extends Formatter {
@@ -449,7 +440,6 @@ export const CORE_FORMATTERS: FormatterTable = {
449440
'encode-uri-component': new EncodeUriComponentFormatter(),
450441
'find-first': new FindFirstFormatter(),
451442
'find-last': new FindLastFormatter(),
452-
'find-nth': new FindNthFormatter(),
453443
format: new FormatFormatter(),
454444
get: new GetFormatter(),
455445
html: new HtmlFormatter(),

src/plugins/util.find.ts

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Context } from "../context";
2-
import { MISSING_NODE, Node, isTruthy, toNode } from "../node";
3-
import { splitVariable } from "../util";
1+
import { Context } from '../context';
2+
import { MISSING_NODE, Node, isTruthy, toNode } from '../node';
3+
import { splitVariable } from '../util';
44

55
export const getLookupAndPath = (ctx: Context, args: string[]) => {
66
const argsCount = args.length;
@@ -11,36 +11,35 @@ export const getLookupAndPath = (ctx: Context, args: string[]) => {
1111
return { lookup, path };
1212
};
1313

14-
export const findNthValidEntry = (
15-
items: any[],
16-
path: (string | number)[] | null,
17-
lookup: Record<string, any> | null,
18-
n: number,
19-
): Node => {
14+
export const findNthValidEntry = (items: Node, path: (string | number)[] | null, lookup: Node | null, nth: number): Node => {
2015
if (!Array.isArray(items) || items.length === 0) {
2116
return MISSING_NODE;
2217
}
23-
let validEntries = [];
24-
const hasPath = path !== null;
25-
if (hasPath) {
26-
for (const element of items) {
27-
const node = toNode(element);
28-
const lookupNode = lookup ? toNode(lookup).get(node.asString()) : node;
29-
const candidate = lookupNode.path(path);
30-
if (isTruthy(candidate)) {
31-
validEntries.push(element);
18+
const forward = nth > 0;
19+
const start = forward ? 0 : items.length - 1;
20+
const end = forward ? items.length : -1;
21+
const step = forward ? 1 : -1;
22+
23+
let count = 0;
24+
const hasLookup = lookup != null;
25+
const hasPath = path != null;
26+
27+
for (let i = start; forward ? i < end : i > end; i += step) {
28+
const node = toNode(items[i]);
29+
if (!hasPath) {
30+
count += step;
31+
if (count == nth) {
32+
return node;
33+
}
34+
} else {
35+
const candidate = hasLookup ? lookup.path([node.asString()]) : node;
36+
if (isTruthy(candidate.path(path))) {
37+
count += step;
38+
if (count == nth) {
39+
return node;
40+
}
3241
}
3342
}
34-
} else {
35-
validEntries = items;
36-
}
37-
const size = validEntries.length;
38-
if (size == 0) {
39-
return MISSING_NODE;
40-
}
41-
const index = n < 0 ? size + n : n;
42-
if (index < 0 || index >= size) {
43-
return MISSING_NODE;
4443
}
45-
return toNode(validEntries[index]);
44+
return MISSING_NODE;
4645
};

0 commit comments

Comments
 (0)