From 7c9cafc63f95cfd0e2b34413e4eaf7115a9706bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Apr 2020 09:31:44 +0530 Subject: [PATCH] Add tests for CFI round-tripping --- src/pyj/read_book/cfi.pyj | 8 +++---- src/pyj/read_book/test_cfi.pyj | 39 ++++++++++++++++++++++++++++++++-- src/pyj/test.pyj | 4 ++-- src/pyj/testing.pyj | 23 ++++++++++++++++++-- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index 9f98d092fc..ff4639108c 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -136,7 +136,9 @@ def encode(doc, node, offset, tail): # {{{ # Handle the offset, if any if node.nodeType is Node.ELEMENT_NODE: if jstype(offset) is 'number': - node = node.childNodes.item(offset) + q = node.childNodes.item(offset) + if q and q.nodeType is Node.ELEMENT_NODE: + node = q elif Node.TEXT_NODE <= node.nodeType <= Node.ENTITY_NODE: offset = offset or 0 while True: @@ -212,7 +214,7 @@ def decode(cfi, doc): # {{{ cfi = cfi.substr(r[0].length) break index |= 1 # Increment index by 1 if it is even - if child.nodeType is 1: + if child.nodeType is Node.ELEMENT_NODE: index += 1 if index is target: cfi = cfi.substr(r[0].length) @@ -276,8 +278,6 @@ def decode(cfi, doc): # {{{ # TODO: Handle text assertion # Find the text node that contains the offset - if node and node.parentNode: - node.parentNode.normalize() if offset is not None: while True: l = node.nodeValue.length diff --git a/src/pyj/read_book/test_cfi.pyj b/src/pyj/read_book/test_cfi.pyj index 0565c014af..2246dc488e 100644 --- a/src/pyj/read_book/test_cfi.pyj +++ b/src/pyj/read_book/test_cfi.pyj @@ -2,11 +2,46 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal from __python__ import bound_methods, hash_literals -from read_book.cfi import escape_for_cfi, unescape_from_cfi -from testing import test, assert_equal +from elementmaker import E + +from read_book.cfi import encode, decode, escape_for_cfi, unescape_from_cfi +from testing import assert_equal, test @test def cfi_escaping(): t = 'a^!,1' assert_equal(t, unescape_from_cfi(escape_for_cfi(t))) + + +@test +def cfi_roundtripping(): + idc = 0 + def nid(): + nonlocal idc + idc += 1 + return idc + '' + + document.body.appendChild(E.p('abc')) + p = document.body.firstChild + path_to_p = '/2/4/2' + assert_equal(encode(document, p), path_to_p) + assert_equal(decode(path_to_p), {'node': p}) + + assert_equal(encode(document, p.firstChild), f'{path_to_p}/1:0') + assert_equal(decode(f'{path_to_p}/1:0'), {'node': p.firstChild, 'offset': 0}) + + assert_equal(encode(document, p.firstChild, 1), f'{path_to_p}/1:1') + assert_equal(decode(f'{path_to_p}/1:1'), {'node': p.firstChild, 'offset': 1}) + + p.appendChild(document.createTextNode('def')) + assert_equal(encode(document, p.firstChild, 5), f'{path_to_p}/1:5') + assert_equal(p.childNodes.length, 2) + assert_equal(encode(document, p.lastChild, 1), f'{path_to_p}/1:4') + assert_equal(decode(f'{path_to_p}/1:5'), {'node': p.lastChild, 'offset': 2}) + assert_equal(decode(f'{path_to_p}/1:1'), {'node': p.firstChild, 'offset': 1}) + + p.appendChild(E.span('123', id=nid())) + p.appendChild(document.createTextNode('456')) + assert_equal(encode(document, p.lastChild, 1), f'{path_to_p}/1:7') + assert_equal(decode(f'{path_to_p}/1:7'), {'node': p.lastChild, 'offset': 1}) diff --git a/src/pyj/test.pyj b/src/pyj/test.pyj index 634861b55f..627f273b1b 100644 --- a/src/pyj/test.pyj +++ b/src/pyj/test.pyj @@ -25,8 +25,8 @@ def get_traceback(lines): lines = traceback.format_exception() last_line = lines[-1] final_lines = v'[]' - pat = /at assert_\w+ \(/ - for line in lines: + pat = /at assert_[0-9a-zA-Z_]+ \(/ + for line in lines[:-1]: if pat.test(line): break final_lines.push(line) diff --git a/src/pyj/testing.pyj b/src/pyj/testing.pyj index 4fe561fc7d..e283efafb6 100644 --- a/src/pyj/testing.pyj +++ b/src/pyj/testing.pyj @@ -14,10 +14,21 @@ def raise_fail(preamble, msg, call_site): raise AssertionError(preamble + msg) +def repr_of(a): + if not a: + return a + q = a.outerHTML + if q: + return q.split('>')[0] + '>' + return a + + def assert_equal(a, b, msg, call_site=None): def fail(): - p = f'{a} != {b}' + ra = repr_of(a) + rb = repr_of(b) + p = f'{ra} != {rb}' raise_fail(p, msg, call_site) atype = jstype(a) @@ -31,10 +42,18 @@ def assert_equal(a, b, msg, call_site=None): if not a.__eq__(b): fail() return + if a.isSameNode: + if not a.isSameNode(b): + fail() + return if b.__eq__: if not b.__eq__(a): fail() return + if b.isSameNode: + if not b.isSameNode(a): + fail() + return if a.length? or b.length?: if a.length is not b.length: fail() @@ -48,7 +67,7 @@ def assert_equal(a, b, msg, call_site=None): for key in Object.keys(b): assert_equal(a[key], b[key]) - if a is not b: + if atype is not 'object' and btype is not 'object': fail()