|
describe("oboe component (sXHR stubbed)", function(){ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('handles empty object detected with bang', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!') |
|
.whenGivenInput('{}') |
|
.thenTheInstance( |
|
matched({}).atRootOfJson(), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('handles empty object detected with bang when explicitly selected', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('$!') |
|
.whenGivenInput('{}') |
|
.thenTheInstance( |
|
matched({}).atRootOfJson(), |
|
foundOneMatch |
|
); |
|
|
|
}) |
|
|
|
it('gives the oboe instance as context', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!') |
|
.whenGivenInput('{}') |
|
.thenTheInstance( wasGivenTheOboeAsContext() ); |
|
}) |
|
|
|
|
|
it('find only emits when has whole object', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!') |
|
.whenGivenInput('{') |
|
.thenTheInstance( |
|
foundNoMatches |
|
) |
|
.whenGivenInput('}') |
|
.thenTheInstance( |
|
matched({}).atRootOfJson(), |
|
foundOneMatch |
|
); |
|
|
|
}) |
|
|
|
it('emits path to listener when root object starts', function() { |
|
|
|
|
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!') |
|
.whenGivenInput('{"foo":') |
|
.thenTheInstance( |
|
foundNMatches(1), |
|
matched({}).atRootOfJson() |
|
); |
|
}) |
|
|
|
it('emits path to listener when root array starts', function() { |
|
|
|
|
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!') |
|
.whenGivenInput('[1') |
|
|
|
.thenTheInstance( |
|
foundNMatches(1), |
|
matched([]).atRootOfJson() |
|
); |
|
}) |
|
|
|
|
|
it('emits empty object node detected with single star', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('*') |
|
.whenGivenInput('{}') |
|
.thenTheInstance( |
|
matched({}).atRootOfJson(), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('doesnt detect spurious path off empty object', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.foo.*') |
|
.whenGivenInput( {foo:{}} ) |
|
.thenTheInstance( |
|
foundNoMatches |
|
); |
|
}) |
|
|
|
it('handles empty object detected with double dot', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('*') |
|
.whenGivenInput('{}') |
|
.thenTheInstance( |
|
matched({}).atRootOfJson(), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('notifies of strings when listened to', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.string') |
|
.whenGivenInput('{"string":"s"}') |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with hyphen in the name', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.a-string') |
|
.whenGivenInput({"a-string":"s"}) |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with underscore in the name', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.a_string') |
|
.whenGivenInput({"a_string":"s"}) |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with quoted hyphen in the name', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!["a-string"]') |
|
.whenGivenInput({"a-string":"s"}) |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with quoted underscore in the name', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!["a_string"]') |
|
.whenGivenInput({"a_string":"s"}) |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with quoted unusual ascii chars in the name', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!["£@$%^"]') |
|
.whenGivenInput({"£@$%^":"s"}) |
|
.thenTheInstance( |
|
matched("s"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with non-ascii keys', function() { |
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!["我讨厌IE浏览器!"]') |
|
.whenGivenInput({"我讨厌IE浏览器!":"indeed!"}) |
|
.thenTheInstance( |
|
matched("indeed!"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('can detect nodes with non-ascii keys and values', function() { |
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!["☂"]') |
|
.whenGivenInput({"☂":"☁"}) |
|
.thenTheInstance( |
|
matched("☁"), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('notifies of path before given the json value for a property', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.string') |
|
.whenGivenInput('{"string":') |
|
.thenTheInstance( |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('notifies of second property name with incomplete json', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.pencils') |
|
.whenGivenInput('{"pens":4, "pencils":') |
|
.thenTheInstance( |
|
|
|
|
|
matched(undefined).atPath(['pencils']), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('is able to notify of null', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.pencils') |
|
.whenGivenInput('{"pens":4, "pencils":null}') |
|
.thenTheInstance( |
|
|
|
|
|
matched(null).atPath(['pencils']), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('is able to notify of boolean true', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.pencils') |
|
.whenGivenInput('{"pens":false, "pencils":true}') |
|
.thenTheInstance( |
|
|
|
|
|
matched(true).atPath(['pencils']), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('is able to notify of boolean false', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.pens') |
|
.whenGivenInput('{"pens":false, "pencils":true}') |
|
.thenTheInstance( |
|
|
|
|
|
matched(false).atPath(['pens']), |
|
foundOneMatch |
|
); |
|
}) |
|
|
|
it('notifies of multiple children of root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput('{"a":"A","b":"B","c":"C"}') |
|
.thenTheInstance( |
|
matched('A').atPath(['a']) |
|
, matched('B').atPath(['b']) |
|
, matched('C').atPath(['c']) |
|
, foundNMatches(3) |
|
); |
|
}) |
|
|
|
it('notifies of multiple children of root when selecting the root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('$!.*') |
|
.whenGivenInput({"a":"A", "b":"B", "c":"C"}) |
|
.thenTheInstance( |
|
|
|
|
|
matched({"a":"A"}) |
|
, matched({"a":"A", "b":"B"}) |
|
, matched({"a":"A", "b":"B", "c":"C"}) |
|
, foundNMatches(3) |
|
); |
|
}) |
|
|
|
it('does not notify spuriously of descendant of roots when key is actually in another object', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.a') |
|
.whenGivenInput([{a:'a'}]) |
|
.thenTheInstance(foundNoMatches); |
|
}) |
|
|
|
it('does not notify spuriously of found child of root when ndoe is not child of root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.a') |
|
.whenGivenInput([{a:'a'}]) |
|
.thenTheInstance(foundNoMatches); |
|
}) |
|
|
|
it('notifies of multiple properties of an object without waiting for entire object', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput('{"a":') |
|
.thenTheInstance( |
|
foundNoMatches |
|
) |
|
.whenGivenInput('"A",') |
|
.thenTheInstance( |
|
matched('A').atPath(['a']) |
|
, foundOneMatch |
|
) |
|
.whenGivenInput('"b":"B"}') |
|
.thenTheInstance( |
|
matched('B').atPath(['b']) |
|
, foundNMatches(2) |
|
); |
|
}) |
|
|
|
it('can get root json as json object is built up', function() { |
|
|
|
givenAnOboeInstance() |
|
.whenGivenInput('{"a":') |
|
.thenTheInstance( |
|
hasRootJson({a:undefined}) |
|
) |
|
.whenGivenInput('"A",') |
|
.thenTheInstance( |
|
hasRootJson({a:'A'}) |
|
) |
|
.whenGivenInput('"b":') |
|
.thenTheInstance( |
|
hasRootJson({a:'A', b:undefined}) |
|
) |
|
.whenGivenInput('"B"}') |
|
.thenTheInstance( |
|
hasRootJson({a:'A', b:'B'}) |
|
) |
|
.whenInputFinishes() |
|
.thenTheInstance( |
|
gaveFinalCallbackWithRootJson({a:'A', b:'B'}) |
|
); |
|
}) |
|
|
|
it('can notify progressively as root json array is built up', function() { |
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.whenGivenInput('[') |
|
.thenTheInstance( |
|
|
|
|
|
hasRootJson(undefined) |
|
) |
|
.whenGivenInput('1') |
|
.thenTheInstance( |
|
|
|
|
|
hasRootJson([]) |
|
) |
|
.whenGivenInput('1,') |
|
.thenTheInstance( |
|
hasRootJson([11]) |
|
) |
|
.whenGivenInput('2') |
|
.thenTheInstance( |
|
hasRootJson([11]) |
|
) |
|
.whenGivenInput('2]') |
|
.thenTheInstance( |
|
hasRootJson([11,22]) |
|
) |
|
.whenInputFinishes() |
|
.thenTheInstance( |
|
gaveFinalCallbackWithRootJson([11,22]) |
|
); |
|
}) |
|
|
|
it('notifies of named child of root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.b') |
|
.whenGivenInput('{"a":"A","b":"B","c":"C"}') |
|
.thenTheInstance( |
|
matched('B').atPath(['b']) |
|
, foundOneMatch |
|
); |
|
}) |
|
it('notifies of array elements', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.testArray.*') |
|
.whenGivenInput('{"testArray":["a","b","c"]}') |
|
.thenTheInstance( |
|
matched('a').atPath(['testArray',0]) |
|
, matched('b').atPath(['testArray',1]) |
|
, matched('c').atPath(['testArray',2]) |
|
, foundNMatches(3) |
|
); |
|
}) |
|
it('notifies of path match when array starts', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.testArray') |
|
.whenGivenInput('{"testArray":["a"') |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
, matched(undefined) |
|
|
|
|
|
|
|
); |
|
}) |
|
it('notifies of path match when second array starts', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('!.array2') |
|
.whenGivenInput('{"array1":["a","b"], "array2":["a"') |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
, matched(undefined) |
|
|
|
|
|
|
|
); |
|
}) |
|
it('notifies of paths inside arrays', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![*]') |
|
.whenGivenInput( [{}, 'b', 2, []] ) |
|
.thenTheInstance( |
|
foundNMatches(4) |
|
); |
|
}) |
|
|
|
describe('correctly give index inside arrays', function(){ |
|
it('when finding objects in array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2]') |
|
.whenGivenInput( [{}, {}, 'this_one'] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
it('when finding arrays inside array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2]') |
|
.whenGivenInput( [[], [], 'this_one'] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
it('when finding arrays inside arrays etc', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2][2]') |
|
.whenGivenInput( [ |
|
[], |
|
[], |
|
[ |
|
[], |
|
[], |
|
['this_array'] |
|
] |
|
] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
it('when finding strings inside array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2]') |
|
.whenGivenInput( ['', '', 'this_one'] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
it('when finding numbers inside array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2]') |
|
.whenGivenInput( [1, 1, 'this_one'] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
it('when finding nulls inside array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![2]') |
|
.whenGivenInput( [null, null, 'this_one'] ) |
|
.thenTheInstance( |
|
foundNMatches(1) |
|
); |
|
}) |
|
}) |
|
|
|
it('notifies of paths inside objects', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('![*]') |
|
.whenGivenInput( {a:{}, b:'b', c:2, d:[]} ) |
|
.thenTheInstance( |
|
foundNMatches(4) |
|
); |
|
}) |
|
|
|
describe('selecting by index', function(){ |
|
it('notifies of array elements', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.testArray[2]') |
|
.whenGivenInput('{"testArray":["a","b","this_one"]}') |
|
.thenTheInstance( |
|
matched('this_one').atPath(['testArray',2]) |
|
, foundOneMatch |
|
); |
|
}) |
|
|
|
it('notifies nested array elements', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.testArray[2][2]') |
|
.whenGivenInput( {"testArray": |
|
["a","b", |
|
["x","y","this_one"] |
|
] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched('this_one') |
|
.atPath(['testArray',2,2]) |
|
.withParent( ["x","y","this_one"] ) |
|
.withGrandparent( ["a","b", ["x","y","this_one"]] ) |
|
, foundOneMatch |
|
); |
|
}) |
|
it('can notify nested array elements by passing the root array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.$testArray[2][2]') |
|
.whenGivenInput( {"testArray": |
|
["a","b", |
|
["x","y","this_one"] |
|
] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched( ["a","b", |
|
["x","y","this_one"] |
|
]) |
|
, foundOneMatch |
|
); |
|
}) |
|
}); |
|
|
|
describe('deeply nested objects', function(){ |
|
it('notifies with star pattern', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('*') |
|
.whenGivenInput({"a":{"b":{"c":{"d":"e"}}}}) |
|
.thenTheInstance( |
|
matched('e') |
|
.atPath(['a', 'b', 'c', 'd']) |
|
.withParent({d:'e'}) |
|
, matched({d:"e"}) |
|
.atPath(['a', 'b', 'c']) |
|
, matched({c:{d:"e"}}) |
|
.atPath(['a', 'b']) |
|
, matched({b:{c:{d:"e"}}}) |
|
.atPath(['a']) |
|
, matched({a:{b:{c:{d:"e"}}}}) |
|
.atRootOfJson() |
|
, foundNMatches(5) |
|
); |
|
}) |
|
it('notifies of with double dot pattern', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('..') |
|
.whenGivenInput({"a":{"b":{"c":{"d":"e"}}}}) |
|
.thenTheInstance( |
|
matched('e') |
|
.atPath(['a', 'b', 'c', 'd']) |
|
.withParent({d:'e'}) |
|
, matched({d:"e"}) |
|
.atPath(['a', 'b', 'c']) |
|
, matched({c:{d:"e"}}) |
|
.atPath(['a', 'b']) |
|
, matched({b:{c:{d:"e"}}}) |
|
.atPath(['a']) |
|
, matched({a:{b:{c:{d:"e"}}}}) |
|
.atRootOfJson() |
|
, foundNMatches(5) |
|
); |
|
}) |
|
it('notifies of objects with double dot star pattern', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('..*') |
|
.whenGivenInput({"a":{"b":{"c":{"d":"e"}}}}) |
|
.thenTheInstance( |
|
matched('e') |
|
.atPath(['a', 'b', 'c', 'd']) |
|
.withParent({d:'e'}) |
|
, matched({d:"e"}) |
|
.atPath(['a', 'b', 'c']) |
|
, matched({c:{d:"e"}}) |
|
.atPath(['a', 'b']) |
|
, matched({b:{c:{d:"e"}}}) |
|
.atPath(['a']) |
|
, matched({a:{b:{c:{d:"e"}}}}) |
|
.atRootOfJson() |
|
, foundNMatches(5) |
|
); |
|
}) |
|
}); |
|
|
|
it('can express all but root as a pattern', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('*..*') |
|
.whenGivenInput({"a":{"b":{"c":{"d":"e"}}}}) |
|
.thenTheInstance( |
|
matched('e') |
|
.atPath(['a', 'b', 'c', 'd']) |
|
.withParent({d:'e'}) |
|
, matched({d:"e"}) |
|
.atPath(['a', 'b', 'c']) |
|
, matched({c:{d:"e"}}) |
|
.atPath(['a', 'b']) |
|
, matched({b:{c:{d:"e"}}}) |
|
.atPath(['a']) |
|
|
|
, foundNMatches(4) |
|
); |
|
}) |
|
it('can detect similar ancestors', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('foo..foo') |
|
|
|
.whenGivenInput({"foo":{"foo":{"foo":{"foo":"foo"}}}}) |
|
.thenTheInstance( |
|
matched("foo") |
|
, matched({"foo":"foo"}) |
|
, matched({"foo":{"foo":"foo"}}) |
|
, matched({"foo":{"foo":{"foo":"foo"}}}) |
|
, foundNMatches(4) |
|
); |
|
}) |
|
|
|
it('can detect inside the second object element of an array', function() { |
|
|
|
|
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..find') |
|
.whenGivenInput( |
|
{ |
|
array:[ |
|
{a:'A'} |
|
, {find:'should_find_this'} |
|
] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched('should_find_this') |
|
.atPath(['array',1,'find']) |
|
); |
|
}) |
|
it('ignores keys if only start matches', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..a') |
|
.whenGivenInput({ |
|
ab:'should_not_find_this' |
|
, a0:'nor this' |
|
, a:'but_should_find_this' |
|
} |
|
) |
|
.thenTheInstance( |
|
matched('but_should_find_this') |
|
, foundOneMatch |
|
); |
|
}) |
|
it('ignores keys if only end of pattern matches', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..a') |
|
.whenGivenInput({ |
|
aa:'should_not_find_this' |
|
, ba:'nor this' |
|
, a:'but_should_find_this' |
|
} |
|
) |
|
.thenTheInstance( |
|
matched('but_should_find_this') |
|
, foundOneMatch |
|
); |
|
}) |
|
it('ignores partial path matches in array indices', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..[1]') |
|
.whenGivenInput({ |
|
array : [0,1,2,3,4,5,6,7,8,9,10,11,12] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched(1) |
|
.withParent([0,1]) |
|
, foundOneMatch |
|
); |
|
}) |
|
it('can give an array back when just partially done', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('$![5]') |
|
.whenGivenInput([0,1,2,3,4,5,6,7,8,9,10,11,12]) |
|
.thenTheInstance( |
|
matched([0,1,2,3,4,5]) |
|
, foundOneMatch |
|
); |
|
}) |
|
|
|
describe('json arrays give correct parent and grandparent', function(){ |
|
it('gives parent and grandparent for every item of an array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : ['a','b','c'] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched('a') |
|
.withParent(['a']) |
|
.withGrandparent({array:['a']}) |
|
, matched('b') |
|
.withParent(['a', 'b']) |
|
.withGrandparent({array:['a','b']}) |
|
, matched('c') |
|
.withParent(['a', 'b', 'c']) |
|
.withGrandparent({array:['a','b','c']}) |
|
); |
|
}) |
|
it('is correct for array of objects', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},{'b':2},{'c':3}] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched({'a':1}) |
|
.withParent([{'a':1}]) |
|
, matched({'b':2}) |
|
.withParent([{'a':1},{'b':2}]) |
|
, matched({'c':3}) |
|
.withParent([{'a':1},{'b':2},{'c':3}]) |
|
); |
|
}) |
|
it('is correct for object in a mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
matched({'a':1}) |
|
.withParent([{'a':1}]) |
|
); |
|
}) |
|
it('has correct parent for string in mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched('b') |
|
.withParent([{'a':1},'b']) |
|
|
|
); |
|
}) |
|
it('has correct parent for second object in mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched({'c':3}) |
|
.withParent([{'a':1},'b',{'c':3}]) |
|
|
|
); |
|
}) |
|
|
|
it('has correct parent for empty object in mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched({}) |
|
.withParent([{'a':1},'b',{'c':3}, {}]) |
|
|
|
); |
|
|
|
}) |
|
it('has correct parent for singleton string array in mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched(['d']) |
|
.withParent([{'a':1},'b',{'c':3}, {}, ['d']]) |
|
|
|
); |
|
}) |
|
it('gives correct parent for singleton string array in singleton array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [['d']] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched(['d']) |
|
.withParent([['d']]) |
|
|
|
); |
|
}) |
|
it('gives correct parent for last string in a mixed array', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.array.*') |
|
.whenGivenInput({ |
|
array : [{'a':1},'b',{'c':3}, {}, ['d'], 'e'] |
|
} |
|
) |
|
.thenTheInstance( |
|
|
|
matched('e') |
|
.withParent([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
|
|
); |
|
}) |
|
|
|
it('gives correct parent for opening object in a mixed array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched({'a':1}) |
|
.withParent([{'a':1}]) |
|
|
|
); |
|
}) |
|
|
|
it('gives correct parent for string in a mixed array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched('b') |
|
.withParent([{'a':1},'b']) |
|
|
|
); |
|
}) |
|
|
|
it('gives correct parent for second object in a mixed array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched({'c':3}) |
|
.withParent([{'a':1},'b',{'c':3}]) |
|
|
|
); |
|
}) |
|
|
|
it('gives correct parent for empty object in a mixed array at root of json', function() { |
|
|
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched({}) |
|
.withParent([{'a':1},'b',{'c':3}, {}]) |
|
|
|
); |
|
|
|
}) |
|
|
|
it('gives correct parent for singleton string array in a mixed array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched(['d']) |
|
.withParent([{'a':1},'b',{'c':3}, {}, ['d']]) |
|
|
|
); |
|
}) |
|
it('gives correct parent for singleton string array in a singleton array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([['d']]) |
|
.thenTheInstance( |
|
|
|
matched(['d']) |
|
.withParent([['d']]) |
|
|
|
); |
|
}) |
|
|
|
it('gives correct parent for final string in a mixed array at root of json', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!.*') |
|
.whenGivenInput([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
.thenTheInstance( |
|
|
|
matched('e') |
|
.withParent([{'a':1},'b',{'c':3}, {}, ['d'], 'e']) |
|
); |
|
}) |
|
}); |
|
|
|
it('can detect at multiple depths using double dot', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..find') |
|
.whenGivenInput({ |
|
|
|
array:[ |
|
{find:'first_find'} |
|
, {padding:{find:'second_find'}, find:'third_find'} |
|
] |
|
, find: { |
|
find:'fourth_find' |
|
} |
|
|
|
}) |
|
.thenTheInstance( |
|
matched('first_find').atPath(['array',0,'find']) |
|
, matched('second_find').atPath(['array',1,'padding','find']) |
|
, matched('third_find').atPath(['array',1,'find']) |
|
, matched('fourth_find').atPath(['find','find']) |
|
, matched({find:'fourth_find'}).atPath(['find']) |
|
|
|
, foundNMatches(5) |
|
); |
|
}) |
|
it('passes ancestors of found object correctly', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..find') |
|
.whenGivenInput({ |
|
|
|
array:[ |
|
{find:'first_find'} |
|
, {padding:{find:'second_find'}, find:'third_find'} |
|
] |
|
, find: { |
|
find:'fourth_find' |
|
} |
|
|
|
}) |
|
.thenTheInstance( |
|
matched('first_find') |
|
.withParent( {find:'first_find'} ) |
|
.withGrandparent( [{find:'first_find'}] ) |
|
|
|
, matched('second_find') |
|
.withParent({find:'second_find'}) |
|
.withGrandparent({padding:{find:'second_find'}}) |
|
|
|
, matched('third_find') |
|
.withParent({padding:{find:'second_find'}, find:'third_find'}) |
|
.withGrandparent([ |
|
{find:'first_find'} |
|
, {padding:{find:'second_find'}, find:'third_find'} |
|
]) |
|
); |
|
}) |
|
|
|
it('can detect at multiple depths using implied ancestor of root relationship', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('find') |
|
.whenGivenInput({ |
|
|
|
array:[ |
|
{find:'first_find'} |
|
, {padding:{find:'second_find'}, find:'third_find'} |
|
] |
|
, find: { |
|
find:'fourth_find' |
|
} |
|
|
|
}) |
|
.thenTheInstance( |
|
matched('first_find').atPath(['array',0,'find']) |
|
, matched('second_find').atPath(['array',1,'padding','find']) |
|
, matched('third_find').atPath(['array',1,'find']) |
|
, matched('fourth_find').atPath(['find','find']) |
|
, matched({find:'fourth_find'}).atPath(['find']) |
|
|
|
, foundNMatches(5) |
|
); |
|
}) |
|
|
|
it('matches nested adjacent selector', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..[0].colour') |
|
.whenGivenInput({ |
|
|
|
foods: [ |
|
{ name:'aubergine', |
|
colour:'purple' |
|
}, |
|
{name:'apple', colour:'red'}, |
|
{name:'nuts', colour:'brown'} |
|
], |
|
non_foods: [ |
|
{ name:'brick', |
|
colour:'red' |
|
}, |
|
{name:'poison', colour:'pink'}, |
|
{name:'broken_glass', colour:'green'} |
|
] |
|
}) |
|
.thenTheInstance |
|
( matched('purple') |
|
, matched('red') |
|
, foundNMatches(2) |
|
); |
|
}) |
|
it('matches nested selector separated by a single star selector', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('!..foods.*.name') |
|
.whenGivenInput({ |
|
|
|
foods: [ |
|
{name:'aubergine', colour:'purple'}, |
|
{name:'apple', colour:'red'}, |
|
{name:'nuts', colour:'brown'} |
|
], |
|
non_foods: [ |
|
{name:'brick', colour:'red'}, |
|
{name:'poison', colour:'pink'}, |
|
{name:'broken_glass', colour:'green'} |
|
] |
|
}) |
|
.thenTheInstance |
|
( matched('aubergine') |
|
, matched('apple') |
|
, matched('nuts') |
|
, foundNMatches(3) |
|
); |
|
}) |
|
it('gets all simple objects from an array', function() { |
|
|
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('foods.*') |
|
.whenGivenInput({ |
|
foods: [ |
|
{name:'aubergine'}, |
|
{name:'apple'}, |
|
{name:'nuts'} |
|
] |
|
}) |
|
.thenTheInstance |
|
( foundNMatches(3) |
|
, matched({name:'aubergine'}) |
|
, matched({name:'apple'}) |
|
, matched({name:'nuts'}) |
|
); |
|
}) |
|
|
|
it('gets same object repeatedly using css4 syntax', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForNodes('$foods.*') |
|
.whenGivenInput({ |
|
foods: [ |
|
{name:'aubergine'}, |
|
{name:'apple'}, |
|
{name:'nuts'} |
|
] |
|
}) |
|
|
|
|
|
.thenTheInstance |
|
( foundNMatches(3) |
|
, matched([{name:'aubergine'}]) |
|
, matched([{name:'aubergine'},{name:'apple'}]) |
|
, matched([{name:'aubergine'},{name:'apple'},{name:'nuts'}]) |
|
); |
|
}) |
|
|
|
it('matches nested selector separated by double dot', function() { |
|
|
|
givenAnOboeInstance() |
|
|
|
.andWeAreListeningForNodes('!..foods..fr') |
|
.whenGivenInput({ |
|
|
|
foods: [ |
|
{name:{en:'aubergine', fr:'aubergine'}, colour:'purple'}, |
|
{name:{en:'apple', fr:'pomme'}, colour:'red'}, |
|
{name:{en:'nuts', fr:'noix'}, colour:'brown'} |
|
], |
|
non_foods: [ |
|
{name:{en:'brick'}, colour:'red'}, |
|
{name:{en:'poison'}, colour:'pink'}, |
|
{name:{en:'broken_glass'}, colour:'green'} |
|
] |
|
}) |
|
.thenTheInstance |
|
( matched('aubergine') |
|
, matched('pomme') |
|
, matched('noix') |
|
, foundNMatches(3) |
|
); |
|
}) |
|
|
|
describe('duck types', function(){ |
|
|
|
|
|
it('can detect', function() { |
|
|
|
givenAnOboeInstance() |
|
|
|
.andWeAreListeningForNodes('{en fr}') |
|
.whenGivenInput({ |
|
|
|
foods: [ |
|
{name:{en:'aubergine', fr:'aubergine' }, colour:'purple'}, |
|
{name:{en:'apple', fr:'pomme' }, colour:'red' }, |
|
{name:{en:'nuts', fr:'noix' }, colour:'brown' } |
|
], |
|
non_foods: [ |
|
{name:{en:'brick' }, colour:'red' }, |
|
{name:{en:'poison' }, colour:'pink' }, |
|
{name:{en:'broken_glass'}, colour:'green' } |
|
] |
|
}) |
|
.thenTheInstance |
|
( matched({en:'aubergine', fr:'aubergine' }) |
|
, matched({en:'apple', fr:'pomme' }) |
|
, matched({en:'nuts', fr:'noix' }) |
|
, foundNMatches(3) |
|
); |
|
}) |
|
it('can detect by matches with additional keys', function() { |
|
|
|
givenAnOboeInstance() |
|
|
|
|
|
.andWeAreListeningForNodes('{en de}') |
|
.whenGivenInput({ |
|
|
|
foods: [ |
|
{name:{en:'aubergine', fr:'aubergine', de: 'aubergine' }, colour:'purple'}, |
|
{name:{en:'apple', fr:'pomme', de: 'apfel' }, colour:'red' }, |
|
{name:{en:'nuts', de: 'eier' }, colour:'brown' } |
|
], |
|
non_foods: [ |
|
{name:{en:'brick' }, colour:'red' }, |
|
{name:{en:'poison' }, colour:'pink' }, |
|
{name:{en:'broken_glass'}, colour:'green'} |
|
] |
|
}) |
|
.thenTheInstance |
|
( matched({en:'aubergine', fr:'aubergine', de:'aubergine' }) |
|
, matched({en:'apple', fr:'pomme', de: 'apfel' }) |
|
, matched({en:'nuts', de: 'eier' }) |
|
, foundNMatches(3) |
|
); |
|
}) |
|
|
|
}) |
|
|
|
|
|
describe('error cases', function() { |
|
it('notifies of error given unquoted string keys', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('{invalid:"json"}') |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
it('errors on malformed json', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('{{') |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
it('detects error when stream halts early between children of root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('[[1,2,3],[4,5') |
|
.whenInputFinishes() |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
it('detects error when stream halts early between children of root', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('[[1,2,3],') |
|
.whenInputFinishes() |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
it('detects error when stream halts early inside mid-tree node', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('[[1,2,3') |
|
.whenInputFinishes() |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
it('calls error listener if an error is thrown in the callback', function() { |
|
|
|
givenAnOboeInstance() |
|
.andWeHaveAFaultyCallbackListeningFor('!') |
|
.andWeAreExpectingSomeErrors() |
|
.whenGivenInput('{}') |
|
.thenTheInstance |
|
( calledCallbackOnce |
|
, wasPassedAnErrorObject |
|
); |
|
}) |
|
}); |
|
|
|
describe('aborting a request', function(){ |
|
|
|
it('does not throw an error', function(){ |
|
|
|
expect( function(){ |
|
|
|
givenAnOboeInstance() |
|
.andWeAreListeningForPaths('*') |
|
.whenGivenInput('[1') |
|
.andWeAbortTheRequest(); |
|
|
|
}).not.toThrow(); |
|
}); |
|
|
|
it('can abort once some data has been found in response', function() { |
|
|
|
|
|
|
|
var asserter = givenAnOboeInstance(); |
|
asserter.andWeAreListeningForNodes('![5]', function(){ |
|
asserter.andWeAbortTheRequest(); |
|
}) |
|
.whenGivenInput([0,1,2,3,4,5,6,7,8,9]) |
|
.thenTheInstance( |
|
|
|
|
|
hasRootJson([0,1,2,3,4,5]) |
|
); |
|
}) |
|
}); |
|
|
|
|
|
beforeEach(function() { |
|
sinon.stub(window, 'streamingHttp') |
|
.returns(sinon.stub()); |
|
}) |
|
|
|
afterEach(function() { |
|
window.streamingHttp.restore(); |
|
}) |
|
|
|
}); |
|
|
|
|