Skip to content

Commit ef843d5

Browse files
authored
Unit test for json navigation (#203)
1 parent 283022e commit ef843d5

File tree

9 files changed

+282
-18
lines changed

9 files changed

+282
-18
lines changed

src/NppJsonViewer/JsonViewDlg.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
#include "ScintillaEditor.h"
1313
#include "JsonHandler.h"
1414
#include "JsonNode.h"
15+
#include "TreeHandler.h"
1516

1617

17-
class JsonViewDlg : public DockingDlgInterface
18+
class JsonViewDlg
19+
: public DockingDlgInterface
20+
, public TreeHandler
1821
{
1922
enum class eButton
2023
{
@@ -44,9 +47,9 @@ class JsonViewDlg : public DockingDlgInterface
4447
void HandleTabActivated();
4548
void UpdateTitle();
4649

47-
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text);
48-
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos);
49-
void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray);
50+
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) override;
51+
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) override;
52+
void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) override;
5053

5154
private:
5255
void DrawJsonTree();

src/NppJsonViewer/NPPJSONViewer.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
<ClCompile Include="ScintillaEditor.cpp" />
198198
<ClCompile Include="SettingsDlg.cpp" />
199199
<ClCompile Include="ShortcutCommand.cpp" />
200+
<ClCompile Include="TreeHandler.h" />
200201
<ClCompile Include="TreeViewCtrl.cpp" />
201202
</ItemGroup>
202203
<ItemGroup>

src/NppJsonViewer/NPPJSONViewer.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
<ClCompile Include="..\..\external\npp\StaticDialog.cpp">
5858
<Filter>ThirdParty\npp</Filter>
5959
</ClCompile>
60+
<ClCompile Include="TreeHandler.h">
61+
<Filter>Header Files</Filter>
62+
</ClCompile>
6063
</ItemGroup>
6164
<ItemGroup>
6265
<ClInclude Include="resource.h">

src/NppJsonViewer/RapidJsonHandler.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "RapidJsonHandler.h"
2-
#include "JsonViewDlg.h"
2+
#include "TreeHandler.h"
33

44
const char* const STR_NULL = "null";
55
const char* const STR_TRUE = "true";
@@ -69,7 +69,7 @@ bool RapidJsonHandler::String(const Ch* str, unsigned /*length*/, bool /*copy*/)
6969
// handle case, when there is only a value in input
7070
if (m_NodeStack.empty())
7171
{
72-
m_dlg->InsertToTree(m_treeRoot, str);
72+
m_pTreeHandler->InsertToTree(m_treeRoot, str);
7373
return true;
7474
}
7575

@@ -109,14 +109,14 @@ bool RapidJsonHandler::StartObject()
109109
HTREEITEM newNode = nullptr;
110110
if (parent->node.type != JsonNodeType::ARRAY)
111111
{
112-
newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
112+
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
113113
m_jsonLastKey.clear();
114114
}
115115
else
116116
{
117117
// It is an array
118118
std::string arr = "[" + std::to_string(parent->counter) + "]";
119-
newNode = m_dlg->InsertToTree(parent->subRoot, arr);
119+
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr);
120120
}
121121

122122
parent->counter++;
@@ -158,14 +158,14 @@ bool RapidJsonHandler::StartArray()
158158
HTREEITEM newNode;
159159
if (parent->node.type != JsonNodeType::ARRAY)
160160
{
161-
newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
161+
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
162162
m_jsonLastKey.clear();
163163
}
164164
else
165165
{
166166
// It is an array
167167
std::string arr = "[" + std::to_string(parent->counter) + "]";
168-
newNode = m_dlg->InsertToTree(parent->subRoot, arr);
168+
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr);
169169
}
170170

171171
parent->counter++;
@@ -210,9 +210,9 @@ void RapidJsonHandler::InsertToTree(TreeNode* node, const char* const str, bool
210210

211211
// Insert item to tree
212212
if (bQuote)
213-
m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : \"" + node->node.value + "\"", node->node.key.pos);
213+
m_pTreeHandler->InsertToTree(node->subRoot, node->node.key.strKey + " : \"" + node->node.value + "\"", node->node.key.pos);
214214
else
215-
m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : " + node->node.value, node->node.key.pos);
215+
m_pTreeHandler->InsertToTree(node->subRoot, node->node.key.strKey + " : " + node->node.value, node->node.key.pos);
216216
}
217217

218218
void RapidJsonHandler::AppendNodeCount(unsigned elementCount, bool bArray)
@@ -223,7 +223,7 @@ void RapidJsonHandler::AppendNodeCount(unsigned elementCount, bool bArray)
223223
m_NodeStack.pop();
224224

225225
if (node->subRoot && node->subRoot != m_treeRoot)
226-
m_dlg->AppendNodeCount(node->subRoot, elementCount, bArray);
226+
m_pTreeHandler->AppendNodeCount(node->subRoot, elementCount, bArray);
227227

228228
delete node;
229229
}

src/NppJsonViewer/RapidJsonHandler.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include "JsonNode.h"
1111
#include "TrackingStream.h"
1212

13-
class JsonViewDlg;
13+
class TreeHandler;
1414

1515
struct TreeNode
1616
{
@@ -26,13 +26,13 @@ class RapidJsonHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
2626
std::stack<TreeNode*> m_NodeStack;
2727

2828
TrackingStreamSharedPtr m_pTS;
29-
JsonViewDlg* m_dlg = nullptr;
30-
HTREEITEM m_treeRoot = nullptr;
29+
TreeHandler* m_pTreeHandler = nullptr;
30+
HTREEITEM m_treeRoot = nullptr;
3131

3232
public:
33-
RapidJsonHandler(JsonViewDlg* dlg, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr)
33+
RapidJsonHandler(TreeHandler* handler, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr)
3434
: m_pTS(pTS ? pTS->GetShared() : nullptr)
35-
, m_dlg(dlg)
35+
, m_pTreeHandler(handler)
3636
, m_treeRoot(treeRoot)
3737
{
3838
}

src/NppJsonViewer/TreeHandler.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <Windows.h>
5+
#include <CommCtrl.h>
6+
7+
#include "JsonNode.h"
8+
9+
class TreeHandler
10+
{
11+
public:
12+
virtual ~TreeHandler() = default;
13+
14+
virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) = 0;
15+
virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) = 0;
16+
virtual void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) = 0;
17+
};

tests/UnitTest/NavigationTest.cpp

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <unordered_map>
4+
#include <optional>
5+
#include <memory>
6+
7+
#include "TreeHandler.h"
8+
#include "JsonHandler.h"
9+
#include "TrackingStream.h"
10+
#include "RapidJsonHandler.h"
11+
12+
13+
class TreeHandlerTest : public TreeHandler
14+
{
15+
std::unordered_map<std::string, Position> m_mapKeyPosition;
16+
17+
public:
18+
virtual ~TreeHandlerTest() = default;
19+
20+
21+
// Inherited via TreeHandler
22+
HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text) override
23+
{
24+
m_mapKeyPosition[text] = {};
25+
return HTREEITEM();
26+
}
27+
28+
HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text, const Position& pos) override
29+
{
30+
m_mapKeyPosition[text] = pos;
31+
return HTREEITEM();
32+
}
33+
34+
void AppendNodeCount(HTREEITEM /*node*/, unsigned /*elementCount*/, bool /*bArray*/) override {}
35+
36+
auto GetPosition(const std::string& text) const -> std::optional<Position>
37+
{
38+
auto find = m_mapKeyPosition.find(text);
39+
40+
std::optional<Position> retVal = std::nullopt;
41+
if (find != m_mapKeyPosition.cend())
42+
retVal = find->second;
43+
44+
return retVal;
45+
}
46+
};
47+
48+
namespace JsonNavigation
49+
{
50+
using KeyPos = std::pair<std::string, Position>;
51+
struct TestData
52+
{
53+
std::string input;
54+
std::vector<KeyPos> output;
55+
};
56+
57+
class NavigationTest : public ::testing::Test
58+
{
59+
protected:
60+
TestData m_testData;
61+
JsonHandler m_jsonHandler {{}};
62+
TreeHandlerTest m_TreeHandler;
63+
TrackingStreamSharedPtr m_pTSS = nullptr;
64+
std::unique_ptr<RapidJsonHandler> m_pHandler;
65+
};
66+
67+
TEST_F(NavigationTest, StringKey)
68+
{
69+
const std::string jsonText = R"({
70+
"key1" : "value1",
71+
"key2" : "value2",
72+
"keyLong" : "Value very very long",
73+
"k" : "V"
74+
})";
75+
76+
m_testData.input = jsonText;
77+
m_testData.output = {
78+
{R"(key1 : "value1")", Position {.nLine = 1, .nColumn = 5, .nKeyLength = 4}},
79+
{R"(key2 : "value2")", Position {.nLine = 2, .nColumn = 5, .nKeyLength = 4}},
80+
{R"(keyLong : "Value very very long")", Position {.nLine = 3, .nColumn = 5, .nKeyLength = 7}},
81+
{R"(k : "V")", Position {.nLine = 4, .nColumn = 5, .nKeyLength = 1}},
82+
};
83+
84+
m_pTSS = std::make_shared<TrackingStream>(jsonText);
85+
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
86+
rapidjson::StringBuffer sb;
87+
88+
Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
89+
ASSERT_TRUE(res.success);
90+
91+
for (const auto& each : m_testData.output)
92+
{
93+
const auto pos = m_TreeHandler.GetPosition(each.first);
94+
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;
95+
96+
auto posValue = pos.value();
97+
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
98+
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
99+
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
100+
}
101+
}
102+
103+
TEST_F(NavigationTest, StringKeyCompressed)
104+
{
105+
const std::string jsonText = R"({"key1":"value1","key2":"value2","keyLong":"Value very very long","k":"V"})";
106+
107+
m_testData.input = jsonText;
108+
m_testData.output = {
109+
{R"(key1 : "value1")", Position {.nLine = 0, .nColumn = 2, .nKeyLength = 4}},
110+
{R"(key2 : "value2")", Position {.nLine = 0, .nColumn = 18, .nKeyLength = 4}},
111+
{R"(keyLong : "Value very very long")", Position {.nLine = 0, .nColumn = 34, .nKeyLength = 7}},
112+
{R"(k : "V")", Position {.nLine = 0, .nColumn = 67, .nKeyLength = 1}},
113+
};
114+
115+
m_pTSS = std::make_shared<TrackingStream>(jsonText);
116+
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
117+
rapidjson::StringBuffer sb;
118+
119+
Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
120+
ASSERT_TRUE(res.success);
121+
122+
for (const auto& each : m_testData.output)
123+
{
124+
const auto pos = m_TreeHandler.GetPosition(each.first);
125+
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;
126+
127+
auto posValue = pos.value();
128+
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
129+
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
130+
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
131+
}
132+
}
133+
134+
TEST_F(NavigationTest, MixedKeys)
135+
{
136+
const std::string jsonText = R"({
137+
// this is comment
138+
"Id": 100000000302052988.0,
139+
"num": [12.148681171238422,42.835353759876654],
140+
"timeInfo":1.234e5,
141+
"FreeTextInput":"\u003Cscript\u003Ealert(\u0022links\u0022);\u003C/script\u003E",
142+
"text1":["Hello", "World"],
143+
"text2":[true, false, true],
144+
"text3":[null, null, null, "power"],
145+
"Test":[
146+
{
147+
"ok": [
148+
"HelloWorld"
149+
]
150+
},
151+
{
152+
"Tata": [
153+
"TataByeBye"
154+
]
155+
}
156+
],
157+
"Nan": NaN,
158+
"Inf":[
159+
-Infinity,
160+
Infinity,
161+
-Infinity,
162+
Infinity
163+
],
164+
"Object":{"Test1":"Test1", "Test2":"Test2"}
165+
})";
166+
167+
m_testData.input = jsonText;
168+
m_testData.output = {
169+
{R"(Id : 100000000302052988.0)", Position {.nLine = 2, .nColumn = 3, .nKeyLength = 2}},
170+
{R"(num)", Position {.nLine = 3, .nColumn = 3, .nKeyLength = 3}},
171+
{R"([0] : 12.148681171238422)", Position {.nLine = 3, .nColumn = 10, .nKeyLength = 18}},
172+
{R"([1] : 42.835353759876654)", Position {.nLine = 3, .nColumn = 29, .nKeyLength = 18}},
173+
{R"(timeInfo : 1.234e5)", Position {.nLine = 4, .nColumn = 3, .nKeyLength = 8}},
174+
{R"(FreeTextInput : "<script>alert("links");</script>")", Position {.nLine = 5, .nColumn = 3, .nKeyLength = 13}},
175+
176+
{R"(text1)", Position {.nLine = 6, .nColumn = 3, .nKeyLength = 5}},
177+
{R"([0] : "Hello")", Position {.nLine = 6, .nColumn = 12, .nKeyLength = 5}},
178+
{R"([1] : "World")", Position {.nLine = 6, .nColumn = 21, .nKeyLength = 5}},
179+
180+
{R"(text2)", Position {.nLine = 7, .nColumn = 3, .nKeyLength = 5}},
181+
{R"([0] : true)", Position {.nLine = 7, .nColumn = 11, .nKeyLength = 4}},
182+
{R"([1] : false)", Position {.nLine = 7, .nColumn = 17, .nKeyLength = 5}},
183+
{R"([2] : true)", Position {.nLine = 7, .nColumn = 24, .nKeyLength = 4}},
184+
185+
{R"(text3)", Position {.nLine = 8, .nColumn = 3, .nKeyLength = 5}},
186+
{R"([0] : null)", Position {.nLine = 8, .nColumn = 11, .nKeyLength = 4}},
187+
{R"([1] : null)", Position {.nLine = 8, .nColumn = 17, .nKeyLength = 4}},
188+
{R"([2] : null)", Position {.nLine = 8, .nColumn = 23, .nKeyLength = 4}},
189+
{R"([3] : "power")", Position {.nLine = 8, .nColumn = 30, .nKeyLength = 5}},
190+
191+
{R"(Test)", Position {.nLine = 9, .nColumn = 3, .nKeyLength = 4}},
192+
{R"(ok)", Position {.nLine = 11, .nColumn = 7, .nKeyLength = 2}},
193+
{R"([0] : "HelloWorld")", Position {.nLine = 12, .nColumn = 9, .nKeyLength = 10}},
194+
{R"(Tata)", Position {.nLine = 16, .nColumn = 7, .nKeyLength = 4}},
195+
{R"([0] : "TataByeBye")", Position {.nLine = 17, .nColumn = 9, .nKeyLength = 10}},
196+
197+
{R"(Nan : NaN)", Position {.nLine = 21, .nColumn = 3, .nKeyLength = 3}},
198+
199+
{R"(Inf)", Position {.nLine = 22, .nColumn = 3, .nKeyLength = 3}},
200+
{R"([0] : -Infinity)", Position {.nLine = 23, .nColumn = 4, .nKeyLength = 9}},
201+
{R"([1] : Infinity)", Position {.nLine = 24, .nColumn = 4, .nKeyLength = 8}},
202+
{R"([2] : -Infinity)", Position {.nLine = 25, .nColumn = 4, .nKeyLength = 9}},
203+
{R"([3] : Infinity)", Position {.nLine = 26, .nColumn = 4, .nKeyLength = 8}},
204+
205+
{R"(Object)", Position {.nLine = 28, .nColumn = 3, .nKeyLength = 6}},
206+
{R"(Test1 : "Test1")", Position {.nLine = 28, .nColumn = 13, .nKeyLength = 5}},
207+
{R"(Test2 : "Test2")", Position {.nLine = 28, .nColumn = 30, .nKeyLength = 5}},
208+
};
209+
210+
m_pTSS = std::make_shared<TrackingStream>(jsonText);
211+
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
212+
rapidjson::StringBuffer sb;
213+
214+
Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
215+
ASSERT_TRUE(res.success);
216+
217+
for (const auto& each : m_testData.output)
218+
{
219+
const auto pos = m_TreeHandler.GetPosition(each.first);
220+
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;
221+
222+
auto posValue = pos.value();
223+
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
224+
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
225+
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
226+
}
227+
}
228+
} // namespace JsonNavigation

0 commit comments

Comments
 (0)