Skip to content

Commit

Permalink
Unit test for json navigation (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
SinghRajenM authored Oct 26, 2024
1 parent 283022e commit ef843d5
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 18 deletions.
11 changes: 7 additions & 4 deletions src/NppJsonViewer/JsonViewDlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
#include "ScintillaEditor.h"
#include "JsonHandler.h"
#include "JsonNode.h"
#include "TreeHandler.h"


class JsonViewDlg : public DockingDlgInterface
class JsonViewDlg
: public DockingDlgInterface
, public TreeHandler
{
enum class eButton
{
Expand Down Expand Up @@ -44,9 +47,9 @@ class JsonViewDlg : public DockingDlgInterface
void HandleTabActivated();
void UpdateTitle();

HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text);
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos);
void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray);
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) override;
HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) override;
void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) override;

private:
void DrawJsonTree();
Expand Down
1 change: 1 addition & 0 deletions src/NppJsonViewer/NPPJSONViewer.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<ClCompile Include="ScintillaEditor.cpp" />
<ClCompile Include="SettingsDlg.cpp" />
<ClCompile Include="ShortcutCommand.cpp" />
<ClCompile Include="TreeHandler.h" />
<ClCompile Include="TreeViewCtrl.cpp" />
</ItemGroup>
<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/NppJsonViewer/NPPJSONViewer.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
<ClCompile Include="..\..\external\npp\StaticDialog.cpp">
<Filter>ThirdParty\npp</Filter>
</ClCompile>
<ClCompile Include="TreeHandler.h">
<Filter>Header Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
Expand Down
18 changes: 9 additions & 9 deletions src/NppJsonViewer/RapidJsonHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "RapidJsonHandler.h"
#include "JsonViewDlg.h"
#include "TreeHandler.h"

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

Expand Down Expand Up @@ -109,14 +109,14 @@ bool RapidJsonHandler::StartObject()
HTREEITEM newNode = nullptr;
if (parent->node.type != JsonNodeType::ARRAY)
{
newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
m_jsonLastKey.clear();
}
else
{
// It is an array
std::string arr = "[" + std::to_string(parent->counter) + "]";
newNode = m_dlg->InsertToTree(parent->subRoot, arr);
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr);
}

parent->counter++;
Expand Down Expand Up @@ -158,14 +158,14 @@ bool RapidJsonHandler::StartArray()
HTREEITEM newNode;
if (parent->node.type != JsonNodeType::ARRAY)
{
newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos);
m_jsonLastKey.clear();
}
else
{
// It is an array
std::string arr = "[" + std::to_string(parent->counter) + "]";
newNode = m_dlg->InsertToTree(parent->subRoot, arr);
newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr);
}

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

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

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

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

delete node;
}
Expand Down
10 changes: 5 additions & 5 deletions src/NppJsonViewer/RapidJsonHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include "JsonNode.h"
#include "TrackingStream.h"

class JsonViewDlg;
class TreeHandler;

struct TreeNode
{
Expand All @@ -26,13 +26,13 @@ class RapidJsonHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
std::stack<TreeNode*> m_NodeStack;

TrackingStreamSharedPtr m_pTS;
JsonViewDlg* m_dlg = nullptr;
HTREEITEM m_treeRoot = nullptr;
TreeHandler* m_pTreeHandler = nullptr;
HTREEITEM m_treeRoot = nullptr;

public:
RapidJsonHandler(JsonViewDlg* dlg, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr)
RapidJsonHandler(TreeHandler* handler, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr)
: m_pTS(pTS ? pTS->GetShared() : nullptr)
, m_dlg(dlg)
, m_pTreeHandler(handler)
, m_treeRoot(treeRoot)
{
}
Expand Down
17 changes: 17 additions & 0 deletions src/NppJsonViewer/TreeHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <string>
#include <Windows.h>
#include <CommCtrl.h>

#include "JsonNode.h"

class TreeHandler
{
public:
virtual ~TreeHandler() = default;

virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) = 0;
virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) = 0;
virtual void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) = 0;
};
228 changes: 228 additions & 0 deletions tests/UnitTest/NavigationTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#include <gtest/gtest.h>

#include <unordered_map>
#include <optional>
#include <memory>

#include "TreeHandler.h"
#include "JsonHandler.h"
#include "TrackingStream.h"
#include "RapidJsonHandler.h"


class TreeHandlerTest : public TreeHandler
{
std::unordered_map<std::string, Position> m_mapKeyPosition;

public:
virtual ~TreeHandlerTest() = default;


// Inherited via TreeHandler
HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text) override
{
m_mapKeyPosition[text] = {};
return HTREEITEM();
}

HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text, const Position& pos) override
{
m_mapKeyPosition[text] = pos;
return HTREEITEM();
}

void AppendNodeCount(HTREEITEM /*node*/, unsigned /*elementCount*/, bool /*bArray*/) override {}

auto GetPosition(const std::string& text) const -> std::optional<Position>
{
auto find = m_mapKeyPosition.find(text);

std::optional<Position> retVal = std::nullopt;
if (find != m_mapKeyPosition.cend())
retVal = find->second;

return retVal;
}
};

namespace JsonNavigation
{
using KeyPos = std::pair<std::string, Position>;
struct TestData
{
std::string input;
std::vector<KeyPos> output;
};

class NavigationTest : public ::testing::Test
{
protected:
TestData m_testData;
JsonHandler m_jsonHandler {{}};
TreeHandlerTest m_TreeHandler;
TrackingStreamSharedPtr m_pTSS = nullptr;
std::unique_ptr<RapidJsonHandler> m_pHandler;
};

TEST_F(NavigationTest, StringKey)
{
const std::string jsonText = R"({
"key1" : "value1",
"key2" : "value2",
"keyLong" : "Value very very long",
"k" : "V"
})";

m_testData.input = jsonText;
m_testData.output = {
{R"(key1 : "value1")", Position {.nLine = 1, .nColumn = 5, .nKeyLength = 4}},
{R"(key2 : "value2")", Position {.nLine = 2, .nColumn = 5, .nKeyLength = 4}},
{R"(keyLong : "Value very very long")", Position {.nLine = 3, .nColumn = 5, .nKeyLength = 7}},
{R"(k : "V")", Position {.nLine = 4, .nColumn = 5, .nKeyLength = 1}},
};

m_pTSS = std::make_shared<TrackingStream>(jsonText);
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
rapidjson::StringBuffer sb;

Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
ASSERT_TRUE(res.success);

for (const auto& each : m_testData.output)
{
const auto pos = m_TreeHandler.GetPosition(each.first);
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;

auto posValue = pos.value();
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
}
}

TEST_F(NavigationTest, StringKeyCompressed)
{
const std::string jsonText = R"({"key1":"value1","key2":"value2","keyLong":"Value very very long","k":"V"})";

m_testData.input = jsonText;
m_testData.output = {
{R"(key1 : "value1")", Position {.nLine = 0, .nColumn = 2, .nKeyLength = 4}},
{R"(key2 : "value2")", Position {.nLine = 0, .nColumn = 18, .nKeyLength = 4}},
{R"(keyLong : "Value very very long")", Position {.nLine = 0, .nColumn = 34, .nKeyLength = 7}},
{R"(k : "V")", Position {.nLine = 0, .nColumn = 67, .nKeyLength = 1}},
};

m_pTSS = std::make_shared<TrackingStream>(jsonText);
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
rapidjson::StringBuffer sb;

Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
ASSERT_TRUE(res.success);

for (const auto& each : m_testData.output)
{
const auto pos = m_TreeHandler.GetPosition(each.first);
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;

auto posValue = pos.value();
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
}
}

TEST_F(NavigationTest, MixedKeys)
{
const std::string jsonText = R"({
// this is comment
"Id": 100000000302052988.0,
"num": [12.148681171238422,42.835353759876654],
"timeInfo":1.234e5,
"FreeTextInput":"\u003Cscript\u003Ealert(\u0022links\u0022);\u003C/script\u003E",
"text1":["Hello", "World"],
"text2":[true, false, true],
"text3":[null, null, null, "power"],
"Test":[
{
"ok": [
"HelloWorld"
]
},
{
"Tata": [
"TataByeBye"
]
}
],
"Nan": NaN,
"Inf":[
-Infinity,
Infinity,
-Infinity,
Infinity
],
"Object":{"Test1":"Test1", "Test2":"Test2"}
})";

m_testData.input = jsonText;
m_testData.output = {
{R"(Id : 100000000302052988.0)", Position {.nLine = 2, .nColumn = 3, .nKeyLength = 2}},
{R"(num)", Position {.nLine = 3, .nColumn = 3, .nKeyLength = 3}},
{R"([0] : 12.148681171238422)", Position {.nLine = 3, .nColumn = 10, .nKeyLength = 18}},
{R"([1] : 42.835353759876654)", Position {.nLine = 3, .nColumn = 29, .nKeyLength = 18}},
{R"(timeInfo : 1.234e5)", Position {.nLine = 4, .nColumn = 3, .nKeyLength = 8}},
{R"(FreeTextInput : "<script>alert("links");</script>")", Position {.nLine = 5, .nColumn = 3, .nKeyLength = 13}},

{R"(text1)", Position {.nLine = 6, .nColumn = 3, .nKeyLength = 5}},
{R"([0] : "Hello")", Position {.nLine = 6, .nColumn = 12, .nKeyLength = 5}},
{R"([1] : "World")", Position {.nLine = 6, .nColumn = 21, .nKeyLength = 5}},

{R"(text2)", Position {.nLine = 7, .nColumn = 3, .nKeyLength = 5}},
{R"([0] : true)", Position {.nLine = 7, .nColumn = 11, .nKeyLength = 4}},
{R"([1] : false)", Position {.nLine = 7, .nColumn = 17, .nKeyLength = 5}},
{R"([2] : true)", Position {.nLine = 7, .nColumn = 24, .nKeyLength = 4}},

{R"(text3)", Position {.nLine = 8, .nColumn = 3, .nKeyLength = 5}},
{R"([0] : null)", Position {.nLine = 8, .nColumn = 11, .nKeyLength = 4}},
{R"([1] : null)", Position {.nLine = 8, .nColumn = 17, .nKeyLength = 4}},
{R"([2] : null)", Position {.nLine = 8, .nColumn = 23, .nKeyLength = 4}},
{R"([3] : "power")", Position {.nLine = 8, .nColumn = 30, .nKeyLength = 5}},

{R"(Test)", Position {.nLine = 9, .nColumn = 3, .nKeyLength = 4}},
{R"(ok)", Position {.nLine = 11, .nColumn = 7, .nKeyLength = 2}},
{R"([0] : "HelloWorld")", Position {.nLine = 12, .nColumn = 9, .nKeyLength = 10}},
{R"(Tata)", Position {.nLine = 16, .nColumn = 7, .nKeyLength = 4}},
{R"([0] : "TataByeBye")", Position {.nLine = 17, .nColumn = 9, .nKeyLength = 10}},

{R"(Nan : NaN)", Position {.nLine = 21, .nColumn = 3, .nKeyLength = 3}},

{R"(Inf)", Position {.nLine = 22, .nColumn = 3, .nKeyLength = 3}},
{R"([0] : -Infinity)", Position {.nLine = 23, .nColumn = 4, .nKeyLength = 9}},
{R"([1] : Infinity)", Position {.nLine = 24, .nColumn = 4, .nKeyLength = 8}},
{R"([2] : -Infinity)", Position {.nLine = 25, .nColumn = 4, .nKeyLength = 9}},
{R"([3] : Infinity)", Position {.nLine = 26, .nColumn = 4, .nKeyLength = 8}},

{R"(Object)", Position {.nLine = 28, .nColumn = 3, .nKeyLength = 6}},
{R"(Test1 : "Test1")", Position {.nLine = 28, .nColumn = 13, .nKeyLength = 5}},
{R"(Test2 : "Test2")", Position {.nLine = 28, .nColumn = 30, .nKeyLength = 5}},
};

m_pTSS = std::make_shared<TrackingStream>(jsonText);
m_pHandler = std::make_unique<RapidJsonHandler>(&m_TreeHandler, nullptr, m_pTSS);
rapidjson::StringBuffer sb;

Result res = m_jsonHandler.ParseJson<flgBaseReader>(jsonText, sb, *m_pHandler, m_pTSS);
ASSERT_TRUE(res.success);

for (const auto& each : m_testData.output)
{
const auto pos = m_TreeHandler.GetPosition(each.first);
ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl;

auto posValue = pos.value();
ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl;
ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl;
}
}
} // namespace JsonNavigation
Loading

0 comments on commit ef843d5

Please sign in to comment.