Skip to content

Commit 916b9e5

Browse files
committed
test(evaluate): add test cases for evaluation
1 parent dfe0f23 commit 916b9e5

File tree

6 files changed

+194
-13
lines changed

6 files changed

+194
-13
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import JSONPointerEvaluateError from './JSONPointerEvaluateError.js';
2+
3+
class JSONPointerIndexError extends JSONPointerEvaluateError {
4+
constructor(message, options) {
5+
super(message, options);
6+
}
7+
}
8+
9+
export default JSONPointerIndexError;

src/errors/JSONPointerKeyError.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import JSONPointerEvaluateError from './JSONPointerEvaluateError.js';
2+
3+
class JSONPointerKeyError extends JSONPointerEvaluateError {
4+
constructor(referenceToken, options) {
5+
super(`Invalid object key: '${referenceToken}' not found`, options);
6+
}
7+
}
8+
9+
export default JSONPointerKeyError;

src/errors/JSONPointerTypeError.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import JSONPointerEvaluateError from './JSONPointerEvaluateError.js';
2+
3+
class JSONPointerTypeError extends JSONPointerEvaluateError {
4+
constructor(referenceToken, options) {
5+
super(`Invalid reference token: '${referenceToken}' (not an object/array)`, options);
6+
}
7+
}
8+
9+
export default JSONPointerTypeError;

src/evaluate.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
import parse from './parse/index.js';
2-
import referenceTokenListEvaluator from './parse/evaluators/reference-token-list.js';
32
import testArrayDash from './test/array-dash.js';
43
import testArrayIndex from './test/array-index.js';
4+
import JSONPointerEvaluateError from './errors/JSONPointerEvaluateError.js';
5+
import JSONPointerTypeError from './errors/JSONPointerTypeError.js';
6+
import JSONPointerIndexError from './errors/JSONPointerIndexError.js';
7+
import JSONPointerKeyError from './errors/JSONPointerKeyError.js';
58

6-
const evaluate = (
7-
value,
8-
jsonPointer,
9-
{ strictArrays = true, evaluator = referenceTokenListEvaluator } = {},
10-
) => {
11-
const { result, computed: referenceTokens } = parse(jsonPointer, { evaluator });
9+
const evaluate = (value, jsonPointer, { strictArrays = true, evaluator = null } = {}) => {
10+
const parseOptions = typeof evaluator === 'function' ? { evaluator } : undefined;
11+
const { result, computed: referenceTokens } = parse(jsonPointer, parseOptions);
1212

1313
if (!result.success) {
14-
throw new Error(`Invalid JSON Pointer: ${jsonPointer}`);
14+
throw new JSONPointerEvaluateError(`Invalid JSON Pointer: ${jsonPointer}`);
1515
}
1616

1717
return referenceTokens.reduce((current, referenceToken) => {
1818
if (typeof current !== 'object' || current === null) {
19-
throw new Error(`Invalid reference token: '${referenceToken}' (not an object/array)`);
19+
throw new JSONPointerTypeError(referenceToken);
2020
}
2121

2222
if (Array.isArray(current)) {
2323
if (testArrayDash(referenceToken)) {
2424
if (strictArrays) {
25-
throw new Error(
25+
throw new JSONPointerIndexError(
2626
'Invalid array index: "-" always refers to a nonexistent element during evaluation',
2727
);
2828
} else {
@@ -31,20 +31,20 @@ const evaluate = (
3131
}
3232

3333
if (!testArrayIndex(referenceToken)) {
34-
throw new Error(
34+
throw new JSONPointerIndexError(
3535
`Invalid array index: '${referenceToken}' (MUST be "0", or digits without a leading "0")`,
3636
);
3737
}
3838

3939
const index = Number(referenceToken);
4040
if (index >= current.length && strictArrays) {
41-
throw new Error(`Invalid array index: '${index}' out of bounds`);
41+
throw new JSONPointerIndexError(`Invalid array index: '${index}' out of bounds`);
4242
}
4343
return current[index];
4444
}
4545

4646
if (!Object.prototype.hasOwnProperty.call(current, referenceToken)) {
47-
throw new Error(`Invalid object key: '${referenceToken}' not found`);
47+
throw new JSONPointerKeyError(referenceToken);
4848
}
4949

5050
return current[referenceToken];

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ export { default as JSONPointerError } from './errors/JSONPointerError.js';
2323
export { default as JSONPointerParseError } from './errors/JSONPointerParseError.js';
2424
export { default as JSONPointerCompileError } from './errors/JSONPointerCompileError.js';
2525
export { default as JSONPointerEvaluateError } from './errors/JSONPointerEvaluateError.js';
26+
export { default as JSONPointerTypeError } from './errors/JSONPointerTypeError.js';
27+
export { default as JSONPointerKeyError } from './errors/JSONPointerKeyError.js';
28+
export { default as JSONPointerIndexError } from './errors/JSONPointerIndexError.js';

test/evaluate.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { assert } from 'chai';
2+
3+
import {
4+
evaluate,
5+
JSONPointerIndexError,
6+
JSONPointerTypeError,
7+
JSONPointerKeyError,
8+
JSONPointerEvaluateError,
9+
referenceTokenListEvaluator,
10+
} from '../src/index.js';
11+
import unescape from '../src/unescape.js';
12+
13+
describe('evaluate', function () {
14+
const data = {
15+
foo: ['bar', 'baz'],
16+
'': 0,
17+
'a/b': 1,
18+
'c%d': 2,
19+
'e^f': 3,
20+
'g|h': 4,
21+
'i\\j': 5,
22+
'k"l': 6,
23+
' ': 7,
24+
'm~n': 8,
25+
};
26+
27+
context('valid JSON Pointers', function () {
28+
specify('should return entire document for ""', function () {
29+
assert.deepEqual(evaluate(data, ''), data);
30+
});
31+
32+
specify('should return array ["bar", "baz"] for "/foo"', function () {
33+
assert.deepEqual(evaluate(data, '/foo'), ['bar', 'baz']);
34+
});
35+
36+
specify('should return "bar" for "/foo/0"', function () {
37+
assert.strictEqual(evaluate(data, '/foo/0'), 'bar');
38+
});
39+
40+
specify('should return 0 for "/"', function () {
41+
assert.strictEqual(evaluate(data, '/'), 0);
42+
});
43+
44+
specify('should return 1 for "/a~1b"', function () {
45+
assert.strictEqual(evaluate(data, '/a~1b'), 1);
46+
});
47+
48+
specify('should return 2 for "/c%d"', function () {
49+
assert.strictEqual(evaluate(data, '/c%d'), 2);
50+
});
51+
52+
specify('should return 3 for "/e^f"', function () {
53+
assert.strictEqual(evaluate(data, '/e^f'), 3);
54+
});
55+
56+
specify('should return 4 for "/g|h"', function () {
57+
assert.strictEqual(evaluate(data, '/g|h'), 4);
58+
});
59+
60+
specify('should return 5 for "/i\\j"', function () {
61+
assert.strictEqual(evaluate(data, '/i\\j'), 5);
62+
});
63+
64+
specify('should return 6 for "/k\"l"', function () {
65+
assert.strictEqual(evaluate(data, '/k"l'), 6);
66+
});
67+
68+
specify('should return 7 for "/ "', function () {
69+
assert.strictEqual(evaluate(data, '/ '), 7);
70+
});
71+
72+
specify('should return 8 for "/m~0n"', function () {
73+
assert.strictEqual(evaluate(data, '/m~0n'), 8);
74+
});
75+
});
76+
77+
context('custom evaluator option', function () {
78+
specify('should correctly use a default evaluator', function () {
79+
const result = evaluate(data, '/a~1b', { evaluator: referenceTokenListEvaluator });
80+
81+
assert.deepEqual(result, 1); // Evaluator should return unescaped reference token list
82+
});
83+
84+
specify('should use a custom evaluator', function () {
85+
const evaluator = (ast) => {
86+
const parts = [];
87+
88+
ast.translate(parts);
89+
90+
return parts.filter(([type]) => type === 'reference-token').map(([, value]) => value);
91+
};
92+
93+
assert.throws(() => evaluate(data, '/a~1b', { evaluator }), JSONPointerKeyError);
94+
});
95+
});
96+
97+
context('invalid JSON Pointers (should throw errors)', function () {
98+
specify('should throw JSONPointerEvaluateError for invalid JSON Pointer', function () {
99+
assert.throws(() => evaluate(data, 'invalid-pointer'), JSONPointerEvaluateError);
100+
});
101+
102+
specify(
103+
'should throw JSONPointerTypeError for accessing property on non-object/array',
104+
function () {
105+
assert.throws(() => evaluate(data, '/foo/0/bad'), JSONPointerTypeError);
106+
},
107+
);
108+
109+
specify('should throw JSONPointerKeyError for non-existing key', function () {
110+
assert.throws(() => evaluate(data, '/nonexistent'), JSONPointerKeyError);
111+
});
112+
113+
specify('should throw JSONPointerIndexError for non-numeric array index', function () {
114+
assert.throws(() => evaluate(data, '/foo/x'), JSONPointerIndexError);
115+
});
116+
117+
specify('should throw JSONPointerIndexError for out-of-bounds array index', function () {
118+
assert.throws(() => evaluate(data, '/foo/5'), JSONPointerIndexError);
119+
});
120+
121+
specify('should throw JSONPointerIndexError for leading zero in array index', function () {
122+
assert.throws(() => evaluate(data, '/foo/01'), JSONPointerIndexError);
123+
});
124+
125+
specify('should throw JSONPointerIndexError for "-" when strictArrays is true', function () {
126+
assert.throws(() => evaluate(data, '/foo/-', { strictArrays: true }), JSONPointerIndexError);
127+
});
128+
129+
specify('should return undefined for "-" when strictArrays is false', function () {
130+
assert.strictEqual(evaluate(data, '/foo/-', { strictArrays: false }), undefined);
131+
});
132+
133+
specify(
134+
'should throw JSONPointerKeyError for accessing object property that does not exist',
135+
function () {
136+
assert.throws(() => evaluate(data, '/missing/key'), JSONPointerKeyError);
137+
},
138+
);
139+
140+
specify('should throw JSONPointerTypeError when evaluating on primitive', function () {
141+
assert.throws(() => evaluate('not-an-object', '/foo'), JSONPointerTypeError);
142+
});
143+
144+
specify(
145+
'should throw JSONPointerTypeError when trying to access deep path on primitive',
146+
function () {
147+
assert.throws(() => evaluate({ foo: 42 }, '/foo/bar'), JSONPointerTypeError);
148+
},
149+
);
150+
});
151+
});

0 commit comments

Comments
 (0)