#include #include #include #include #include #include "ChapterXPathResolver.h" namespace { std::shared_ptr epubWith(std::string xhtml) { std::vector spine; spine.push_back(std::move(xhtml)); return std::make_shared(std::move(spine)); } constexpr char kNestedFixture[] = R"(

Alpha bravo

Second nested tail

)"; constexpr char kNonVisibleInlineFixture[] = R"(

hiddenVisible text

)"; constexpr char kCommentBoundaryFixture[] = R"(

beforeafter

)"; constexpr char kProcessingInstructionBoundaryFixture[] = R"(

beforeafter

)"; constexpr char kCdataBoundaryFixture[] = R"(

beforeafter

)"; constexpr char kHiddenCdataFixture[] = R"(

beforeafter

)"; } // namespace TEST(KOReaderXPathResolver, ResolvesExactOffsetWithFullAncestry) { const auto epub = epubWith(kNestedFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 6), "/body/DocFragment[1]/body/div[1]/section[1]/p[1]/text()[1].6"); } TEST(KOReaderXPathResolver, PreservesNestedInlineTextNode) { const auto epub = epubWith(kNestedFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 26), "/body/DocFragment[1]/body/div[1]/section[1]/p[2]/text()[2].2"); } TEST(KOReaderXPathResolver, EmitsDetailedAnchorForOffsetZero) { const auto epub = epubWith(kNestedFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 0), "/body/DocFragment[1]/body/div[1]/section[1]/p[1]/text()[1].0"); } TEST(KOReaderXPathResolver, IgnoresNestedNonVisibleInlineText) { const auto epub = epubWith(kNonVisibleInlineFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 0), "/body/DocFragment[1]/body/p[1]/text()[1].0"); } TEST(KOReaderXPathResolver, ResolvesProgressAfterNestedNonVisibleInlineText) { const auto epub = epubWith(kNonVisibleInlineFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForProgress(epub, 0, 1.0f), "/body/DocFragment[1]/body/p[1]/text()[1].12"); } TEST(KOReaderXPathResolver, CountsUtf8CodepointsInsteadOfBytes) { const auto epub = epubWith( "

A\xC3\xA9\xE4\xB8\xAD" "B

"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 3), "/body/DocFragment[1]/body/p[1]/text()[1].3"); } TEST(KOReaderXPathResolver, SplitsTextNodesAroundComments) { const auto epub = epubWith(kCommentBoundaryFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 6), "/body/DocFragment[1]/body/p[1]/text()[2].0"); EXPECT_TRUE(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 11).empty()); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 7), "/body/DocFragment[1]/body/p[1]/text()[2].1"); } TEST(KOReaderXPathResolver, SplitsTextNodesAroundProcessingInstructions) { const auto epub = epubWith(kProcessingInstructionBoundaryFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 7), "/body/DocFragment[1]/body/p[1]/text()[2].1"); } TEST(KOReaderXPathResolver, SplitsTextNodesAroundCdata) { const auto epub = epubWith(kCdataBoundaryFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 7), "/body/DocFragment[1]/body/p[1]/text()[2].1"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 13), "/body/DocFragment[1]/body/p[1]/text()[3].1"); } TEST(KOReaderXPathResolver, DoesNotCreateNodesBeforeFirstComment) { const auto epub = epubWith(R"(

text

)"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 0), "/body/DocFragment[1]/body/p[1]/text()[1].0"); } TEST(KOReaderXPathResolver, DoesNotCreateNodesBeforeFirstProcessingInstruction) { const auto epub = epubWith(R"(

text

)"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 0), "/body/DocFragment[1]/body/p[1]/text()[1].0"); } TEST(KOReaderXPathResolver, DoesNotCreateNodesBeforeFirstCdata) { const auto epub = epubWith(R"(

)"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(epub, 0, 0), "/body/DocFragment[1]/body/p[1]/text()[1].0"); } TEST(KOReaderXPathResolver, CountsVisibleCdataAndIgnoresHiddenCdata) { const auto visible = epubWith(kCdataBoundaryFixture); const auto hidden = epubWith(kHiddenCdataFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(visible, 0, 7), "/body/DocFragment[1]/body/p[1]/text()[2].1"); EXPECT_EQ(ChapterXPathResolver::findXPathForVisibleTextOffset(hidden, 0, 7), "/body/DocFragment[1]/body/p[1]/text()[2].1"); } TEST(KOReaderXPathResolver, ReturnsEmptyForUnusableContent) { EXPECT_TRUE(ChapterXPathResolver::findXPathForVisibleTextOffset(epubWith(""), 0, 0).empty()); EXPECT_TRUE(ChapterXPathResolver::findXPathForVisibleTextOffset(epubWith("

broken"), 0, 100).empty()); EXPECT_TRUE(ChapterXPathResolver::findXPathForVisibleTextOffset( epubWith("

not a paragraph or list item
"), 0, 0) .empty()); } TEST(KOReaderXPathResolver, KeepsParagraphOnlyResolutionUnchanged) { const auto epub = epubWith(kNestedFixture); EXPECT_EQ(ChapterXPathResolver::findXPathForParagraph(epub, 0, 2), "/body/DocFragment[1]/body/div[1]/section[1]/p[2]"); }