Implemented content sets.

master
Pacman Ghost 3 years ago
parent e9951acc62
commit a00ae08942
  1. 136
      asl_rulebook2/webapp/content.py
  2. 16
      asl_rulebook2/webapp/search.py
  3. 4
      asl_rulebook2/webapp/startup.py
  4. 23
      asl_rulebook2/webapp/static/ContentPane.js
  5. 21
      asl_rulebook2/webapp/static/MainApp.js
  6. 16
      asl_rulebook2/webapp/static/SearchPane.js
  7. 24
      asl_rulebook2/webapp/static/SearchResult.js
  8. 7
      asl_rulebook2/webapp/static/utils.js
  9. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1 (linked).docx
  10. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1 (linked).pdf
  11. 12
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1 (linked).targets
  12. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1.docx
  13. 23
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1.index
  14. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1.pdf
  15. 11
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 1.targets
  16. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 2.docx
  17. 13
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 2.index
  18. BIN
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 2.pdf
  19. 12
      asl_rulebook2/webapp/tests/fixtures/content-sets/content set 2.targets
  20. 54
      asl_rulebook2/webapp/tests/test_search.py

@ -8,18 +8,28 @@ import glob
from flask import jsonify, send_file, url_for, abort
from asl_rulebook2.webapp import app
from asl_rulebook2.webapp.utils import change_extn, slugify
from asl_rulebook2.webapp.utils import slugify
content_docs = None
content_sets = None
content_doc_index = None
# ---------------------------------------------------------------------
def load_content_docs( startup_msgs, logger ):
"""Load the content documents from the data directory."""
def load_content_sets( startup_msgs, logger ):
"""Load the content from the data directory."""
# NOTE: A "content set" is an index file, together with one or more "content docs".
# A "content doc" is a PDF file, with an associated targets and/or footnote file.
# This architecture allows us to have:
# - a single index file that references content spread over multiple PDF's (e.g. the MMP eASLRB,
# together with additional modules in separate PDF's (e.g. RB or KGP), until such time these
# get included in the main eASLRB).
# - rules for completely separate modules (e.g. third-party modules) that are not included
# in the MMP eASLRB index, and have their own index.
# initialize
global content_docs
content_docs = {}
global content_sets
content_sets = {}
dname = app.config.get( "DATA_DIR" )
if not dname:
return
@ -27,7 +37,15 @@ def load_content_docs( startup_msgs, logger ):
startup_msgs.error( "Invalid data directory.", dname )
return
def load_file( fname, content_doc, key, on_error, binary=False ):
def load_content_doc( fname_stem, title ):
# load the content doc files
content_doc = { "title": title }
load_file( fname_stem+".targets", content_doc, "targets", startup_msgs.warning )
load_file( fname_stem+".footnotes", content_doc, "footnotes", startup_msgs.warning )
load_file( fname_stem+".pdf", content_doc, "content", startup_msgs.warning, binary=True )
return content_doc
def load_file( fname, save_loc, key, on_error, binary=False ):
fname = os.path.join( dname, fname )
if not os.path.isfile( fname ):
return False
@ -45,30 +63,68 @@ def load_content_docs( startup_msgs, logger ):
on_error( "Couldn't load \"{}\".".format( os.path.basename(fname) ), str(ex) )
return False
# save the file data
content_doc[ key ] = data
save_loc[ key ] = data
return True
# load each content doc
logger.info( "Loading content docs: %s", dname )
def find_assoc_cdocs( fname_stem ):
# find other content docs associated with the content set (names have the form "Foo (...)")
matches = set()
for fname in os.listdir( dname ):
if not fname.startswith( fname_stem ):
continue
fname = os.path.splitext( fname )[0]
fname = fname[len(fname_stem):].strip()
if fname.startswith( "(" ) and fname.endswith( ")" ):
matches.add( fname[1:-1] )
return matches
# load each content set
logger.info( "Loading content sets: %s", dname )
fspec = os.path.join( dname, "*.index" )
for fname in glob.glob( fspec ):
# load the main index file
fname2 = os.path.basename( fname )
logger.info( "- %s", fname2 )
# load the index file
title = os.path.splitext( fname2 )[0]
content_doc = {
"_fname": fname,
"doc_id": slugify( title ),
cset_id = slugify( title )
content_set = {
"cset_id": cset_id,
"title": title,
"content_docs": {},
"index_fname": fname,
}
if not load_file( fname2, content_doc, "index", startup_msgs.error ):
if not load_file( fname2, content_set, "index", startup_msgs.error ):
continue # nb: we can't do anything without an index file
# load any associated files
load_file( change_extn(fname2,".targets"), content_doc, "targets", startup_msgs.warning )
load_file( change_extn(fname2,".footnotes"), content_doc, "footnotes", startup_msgs.warning )
load_file( change_extn(fname2,".pdf"), content_doc, "content", startup_msgs.warning, binary=True )
# save the new content doc
content_docs[ content_doc["doc_id"] ] = content_doc
# load the main content doc
fname_stem = os.path.splitext( fname2 )[0]
content_doc = load_content_doc( fname_stem, fname_stem )
cdoc_id = cset_id # nb: because this the main content document
content_doc[ "cdoc_id" ] = cdoc_id
content_set[ "content_docs" ][ cdoc_id ] = content_doc
# load any associated content docs
for fname_stem2 in find_assoc_cdocs( fname_stem ):
# nb: we assume there's only one space between the two filename stems :-/
content_doc = load_content_doc(
"{} ({})".format( fname_stem, fname_stem2 ),
fname_stem2
)
cdoc_id2 = "{}!{}".format( cdoc_id, slugify(fname_stem2) )
content_doc[ "cdoc_id" ] = cdoc_id2
content_set[ "content_docs" ][ cdoc_id2 ] = content_doc
# save the new content set
content_sets[ content_set["cset_id"] ] = content_set
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _dump_content_sets():
"""Dump the available content sets."""
for cset_id, cset in content_sets.items():
print( "=== {} ({}) ===".format( cset["title"], cset_id ) )
for cdoc_id, cdoc in cset["content_docs"].items():
print( "Content doc: {} ({})".format( cdoc["title"], cdoc_id ) )
for key in [ "targets", "footnotes", "content" ]:
if key in cdoc:
print( "- {}: {}".format( key, len(cdoc[key]) ))
# ---------------------------------------------------------------------
@ -76,25 +132,29 @@ def load_content_docs( startup_msgs, logger ):
def get_content_docs():
"""Return the available content docs."""
resp = {}
for cdoc in content_docs.values():
cdoc2 = {
"doc_id": cdoc["doc_id"],
"title": cdoc["title"],
}
if "content" in cdoc:
cdoc2["url"] = url_for( "get_content", doc_id=cdoc["doc_id"] )
if "targets" in cdoc:
cdoc2["targets"] = cdoc["targets"]
resp[ cdoc["doc_id"] ] = cdoc2
for cset in content_sets.values():
for cdoc in cset["content_docs"].values():
cdoc2 = {
"cdoc_id": cdoc["cdoc_id"],
"parent_cset_id": cset["cset_id"],
"title": cdoc["title"],
}
if "content" in cdoc:
cdoc2["url"] = url_for( "get_content", cdoc_id=cdoc["cdoc_id"] )
if "targets" in cdoc:
cdoc2["targets"] = cdoc["targets"]
resp[ cdoc["cdoc_id"] ] = cdoc2
return jsonify( resp )
# ---------------------------------------------------------------------
@app.route( "/content/<doc_id>" )
def get_content( doc_id ):
@app.route( "/content/<cdoc_id>" )
def get_content( cdoc_id ):
"""Return the content for the specified document."""
cdoc = content_docs.get( doc_id )
if not cdoc or "content" not in cdoc:
abort( 404 )
buf = io.BytesIO( cdoc["content"] )
return send_file( buf, mimetype="application/pdf" )
for cset in content_sets.values():
for cdoc in cset["content_docs"].values():
if cdoc["cdoc_id"] == cdoc_id and "content" in cdoc:
buf = io.BytesIO( cdoc["content"] )
return send_file( buf, mimetype="application/pdf" )
abort( 404 )
return None # stupid pylint :-/

@ -85,7 +85,7 @@ def _do_search( args ):
def highlight( n ):
# NOTE: highlight() is an FTS extension function, and takes column numbers :-/
return "highlight(searchable,{},'{}','{}')".format( n, _BEGIN_HIGHLIGHT, _END_HIGHLIGHT )
sql = "SELECT rowid,doc_id,sr_type,rank,{},{},{},{} FROM searchable".format(
sql = "SELECT rowid,cset_id,sr_type,rank,{},{},{},{} FROM searchable".format(
highlight(2), highlight(3), highlight(4), highlight(5)
)
sql += " WHERE searchable MATCH ?"
@ -106,7 +106,7 @@ def _do_search( args ):
continue
index_entry = _fts_index_entries[ row[0] ]
result = {
"doc_id": row[1],
"cset_id": row[1],
"sr_type": row[2],
"_key": "{}:{}:{}".format( row[1], row[2], row[0] ),
"_score": - row[3],
@ -347,17 +347,17 @@ def init_search( startup_msgs, logger ):
# the overall content, and also lets us do AND/OR queries across all searchable content.
conn.execute(
"CREATE VIRTUAL TABLE searchable USING fts5"
" ( doc_id, sr_type, title, subtitle, content, rulerefs, tokenize='porter unicode61' )"
" ( cset_id, sr_type, title, subtitle, content, rulerefs, tokenize='porter unicode61' )"
)
# load the searchable content
logger.info( "Loading the search index..." )
conn.execute( "DELETE FROM searchable" )
curs = conn.cursor()
for cdoc in webapp_content.content_docs.values():
logger.info( "- Loading index file: %s", cdoc["_fname"] )
for cset in webapp_content.content_sets.values():
logger.info( "- Loading index file: %s", cset["index_fname"] )
nrows = 0
for index_entry in cdoc["index"]:
for index_entry in cset["index"]:
rulerefs = _RULEREF_SEPARATOR.join( r.get("caption","") for r in index_entry.get("rulerefs",[]) )
# NOTE: We should really strip content before adding it to the search index, otherwise any HTML tags
# will need to be included in search terms. However, this means that the content returned by a query
@ -365,8 +365,8 @@ def init_search( startup_msgs, logger ):
# but that means we would lose the highlighting of search terms that SQLite gives us. We opt to insert
# the original content, since none of it should contain HTML, anyway.
curs.execute(
"INSERT INTO searchable (doc_id,sr_type,title,subtitle,content,rulerefs) VALUES (?,?,?,?,?,?)", (
cdoc["doc_id"], "index",
"INSERT INTO searchable (cset_id,sr_type,title,subtitle,content,rulerefs) VALUES (?,?,?,?,?,?)", (
cset["cset_id"], "index",
index_entry.get("title"), index_entry.get("subtitle"), index_entry.get("content"), rulerefs
) )
_fts_index_entries[ curs.lastrowid ] = index_entry

@ -6,7 +6,7 @@ from collections import defaultdict
from flask import jsonify
from asl_rulebook2.webapp import app
from asl_rulebook2.webapp.content import load_content_docs
from asl_rulebook2.webapp.content import load_content_sets
from asl_rulebook2.webapp.search import init_search
_logger = logging.getLogger( "startup" )
@ -26,7 +26,7 @@ def init_webapp():
_startup_msgs = StartupMsgs()
# initialize the webapp
load_content_docs( _startup_msgs, _logger )
load_content_sets( _startup_msgs, _logger )
init_search( _startup_msgs, _logger )
# ---------------------------------------------------------------------

@ -8,14 +8,14 @@ gMainApp.component( "content-pane", {
template: `
<tabbed-pages ref="tabbedPages">
<tabbed-page v-for="doc in contentDocs" :tabId=doc.doc_id :caption=doc.title :key=doc.doc_id >
<content-doc :doc=doc />
<tabbed-page v-for="cdoc in contentDocs" :tabId=cdoc.cdoc_id :caption=cdoc.title :key=cdoc.cdoc_id >
<content-doc :cdoc=cdoc />
</tabbed-page>
</tabbed-pages>`,
mounted() {
gEventBus.on( "show-target", (docId, target) => { //eslint-disable-line no-unused-vars
this.$refs.tabbedPages.activateTab( docId ) ; // nb: tabId == docId
gEventBus.on( "show-target", (cdocId, target) => { //eslint-disable-line no-unused-vars
this.$refs.tabbedPages.activateTab( cdocId ) ; // nb: tabId == cdocId
} ) ;
},
@ -25,7 +25,7 @@ gMainApp.component( "content-pane", {
gMainApp.component( "content-doc", {
props: [ "doc" ],
props: [ "cdoc" ],
data() { return {
target: null,
noContent: gUrlParams.get( "no-content" ),
@ -33,14 +33,17 @@ gMainApp.component( "content-doc", {
template: `
<div class="content-doc" :data-target=target >
<div v-if=noContent class="disabled"> Content disabled. <div v-if=target>target = {{target}}</div> </div>
<iframe v-else-if=doc.url :src=makeDocUrl />
<div v-if=noContent class="disabled">
<div style='margin-bottom:0.25em;'>&mdash;&mdash;&mdash; Content disabled &mdash;&mdash;&mdash; </div>
{{cdoc.title}} <div v-if=target> target = {{target}} </div>
</div>
<iframe v-else-if=cdoc.url :src=makeDocUrl />
<div v-else class="disabled"> No content. </div>
</div>`,
created() {
gEventBus.on( "show-target", (docId, target) => {
if ( docId != this.doc.doc_id )
gEventBus.on( "show-target", (cdocId, target) => {
if ( cdocId != this.cdoc.cdoc_id )
return ;
// FUDGE! We give the tab time to show itself before we scroll to the target.
setTimeout( () => {
@ -52,7 +55,7 @@ gMainApp.component( "content-doc", {
computed: {
makeDocUrl() {
let url = this.doc.url ;
let url = this.cdoc.url ;
if ( this.target )
url += "#nameddest=" + this.target ;
return url ;

@ -12,9 +12,8 @@ $(document).ready( () => {
gMainApp.mount( "#main-app" ) ;
} ) ;
// FUDGE! Can't seem to get access to the content docs via gMainApp, so we make them available
// to the rest of the program via this global variable :-/
export let gContentDocs = null ;
// FUDGE! Can't seem to get access to gMainApp member variables, so we make them available
// to the rest of the program via global variables :-/
export let gTargetIndex = null ;
// --------------------------------------------------------------------
@ -58,13 +57,13 @@ gMainApp.component( "main-app", {
// get the content docs
$.getJSON( gGetContentDocsUrl, (resp) => { //eslint-disable-line no-undef
if ( gUrlParams.get( "add-empty-doc" ) )
resp["empty"] = { "doc_id": "empty", "title": "Empty document" } ; // nb: for testing porpoises
resp["empty"] = { "cdoc_id": "empty", "title": "Empty document" } ; // nb: for testing porpoises
self.contentDocs = resp ;
self.installContentDocs( resp ) ;
let docIds = Object.keys( resp ) ;
if ( docIds.length > 0 ) {
let cdocIds = Object.keys( resp ) ;
if ( cdocIds.length > 0 ) {
Vue.nextTick( () => {
gEventBus.emit( "show-target", docIds[0], null ) ; // FIXME! which one do we choose?
gEventBus.emit( "show-target", cdocIds[0], null ) ; // FIXME! which one do we choose?
} ) ;
}
resolve() ;
@ -77,8 +76,6 @@ gMainApp.component( "main-app", {
},
installContentDocs( contentDocs ) {
// install the content docs
gContentDocs = contentDocs ;
// build an index of all the targets
gTargetIndex = {} ;
Object.values( contentDocs ).forEach( (cdoc) => {
@ -88,7 +85,11 @@ gMainApp.component( "main-app", {
let key = target.toLowerCase() ;
if ( ! gTargetIndex[ key ] )
gTargetIndex[ key ] = [] ;
gTargetIndex[ key ].push( [ cdoc.doc_id, target ] ) ;
gTargetIndex[ key ].push( {
cset_id: cdoc.parent_cset_id,
cdoc_id: cdoc.cdoc_id,
target: target
} ) ;
}
} ) ;
},

@ -1,5 +1,5 @@
import { gMainApp, gEventBus } from "./MainApp.js" ;
import { findTarget, fixupSearchHilites } from "./utils.js" ;
import { findTargets, fixupSearchHilites } from "./utils.js" ;
// --------------------------------------------------------------------
@ -71,11 +71,11 @@ gMainApp.component( "search-results", {
}
// check if the query string is just a target
let docIds = findTarget( queryString ) ;
if ( docIds ) {
// yup - just show it directly
let targets = findTargets( queryString, null ) ;
if ( targets && targets.length > 0 ) {
// yup - just show it directly (first one, if multiple)
this.searchResults = null ;
gEventBus.emit( "show-target", docIds[0][0], docIds[0][1] ) ;
gEventBus.emit( "show-target", targets[0].cdoc_id, targets[0].target ) ;
onSearchDone() ;
return ;
}
@ -109,9 +109,9 @@ gMainApp.component( "search-results", {
const ruleids = resp[i].ruleids ;
if ( ! ruleids )
continue ;
const targets = findTarget( ruleids[0] ) ;
if ( targets ) {
gEventBus.emit( "show-target", targets[0][0], targets[0][1] ) ;
const targets = findTargets( ruleids[0], resp[i].cset_id ) ;
if ( targets && targets.length > 0 ) {
gEventBus.emit( "show-target", targets[0].cdoc_id, targets[0].target ) ;
break ;
}
}

@ -1,5 +1,5 @@
import { gMainApp, gEventBus, gContentDocs, gUrlParams } from "./MainApp.js" ;
import { fixupSearchHilites, hasHilite } from "./utils.js" ;
import { gMainApp, gEventBus, gUrlParams } from "./MainApp.js" ;
import { findTargets, fixupSearchHilites, hasHilite } from "./utils.js" ;
// --------------------------------------------------------------------
@ -23,12 +23,12 @@ gMainApp.component( "index-sr", {
<div v-if=sr.content class="content" v-html=sr.content />
<div v-if=makeSeeAlso v-html=makeSeeAlso class="see-also" />
<div v-if=sr.ruleids class="ruleids" >
<ruleid v-for="rid in sr.ruleids" :docId=sr.doc_id :ruleId=rid :key=rid />
<ruleid v-for="rid in sr.ruleids" :csetId=sr.cset_id :ruleId=rid :key=rid />
</div>
<ul v-if=sr.rulerefs class="rulerefs" >
<li v-for="rref in sr.rulerefs" v-show=showRuleref(rref) :key=rref >
<span v-if=rref.caption class="caption" v-html=fixupHilites(rref.caption) />
<ruleid v-for="rid in rref.ruleids" :docId=sr.doc_id :ruleId=rid :key=rid />
<ruleid v-for="rid in rref.ruleids" :csetId=sr.cset_id :ruleId=rid :key=rid />
</li>
</ul>
</div>
@ -103,9 +103,9 @@ gMainApp.component( "index-sr", {
gMainApp.component( "ruleid", {
props: [ "docId", "ruleId" ],
props: [ "csetId", "ruleId" ],
data() { return {
target: null,
cdocId: null, target: null,
} ; },
template: `<span class="ruleid" v-bind:class="{unknown:!target}">[<a v-if=target @click=onClick>{{ruleId}}</a><span v-else>{{ruleId}}</span>]</span>`,
@ -119,16 +119,20 @@ gMainApp.component( "ruleid", {
ruleId = ruleId.substring( 0, pos ) ;
}
// check if the rule is one we know about
if ( gContentDocs[this.docId] && gContentDocs[this.docId].targets ) {
if ( gContentDocs[this.docId].targets[ ruleId ] )
this.target = ruleId ;
let targets = findTargets( ruleId, this.csetId ) ;
if ( targets && targets.length > 0 ) {
// NOTE: We assume that targets are unique within a content set. This might not be true if MMP
// ever adds Chapter Z stuff to the main index, but we'll cross that bridge if and when we come to it.
// TBH, that stuff would probably be better off as a separate content set, anyway.
this.cdocId = targets[0].cdoc_id ;
this.target = ruleId ;
}
},
methods: {
onClick() {
// show the target
gEventBus.emit( "show-target", this.docId, this.target ) ;
gEventBus.emit( "show-target", this.cdocId, this.target ) ;
},
},

@ -2,10 +2,13 @@ import { gTargetIndex, gUrlParams } from "./MainApp.js" ;
// --------------------------------------------------------------------
export function findTarget( target )
export function findTargets( target, csetId )
{
// check if the target is known to us
return gTargetIndex[ target.toLowerCase() ] ;
let targets = gTargetIndex[ target.toLowerCase() ] ;
if ( targets && csetId )
targets = targets.filter( (m) => m.cset_id == csetId ) ;
return targets ;
}
// --------------------------------------------------------------------

@ -0,0 +1,12 @@
{
"1b": { "caption": "Item 1b", "page_no": 1, "pos": [72,718] },
"2b": { "caption": "Item 2b", "page_no": 1, "pos": [72,503] },
"3b": { "caption": "Item 3b", "page_no": 1, "pos": [72,292] },
"4b": { "caption": "Item 4b", "page_no": 2, "pos": [72,718] },
"5b": { "caption": "Item 5b", "page_no": 2, "pos": [72,503] },
"6b": { "caption": "Item 6b", "page_no": 2, "pos": [72,292] }
}

@ -0,0 +1,23 @@
[
{ "title": "Content Set 1",
"rulerefs": [
{ "caption": "Main document", "ruleids": [
"1a", "2a", "3a", "4a", "5a", "6a"
]
},
{ "caption": "Linked document", "ruleids": [
"1b", "2b", "3b", "4b", "5b", "6b"
]
}
]
},
{ "title": "Unknown ruleid",
"content": "This ruleid belongs to Content Set 2, so shouldn't work in <i>this</i> content set.",
"rulerefs": [ {
"caption": "Incorrect ruleref", "ruleids": [ "cs2a" ]
} ]
}
]

@ -0,0 +1,11 @@
{
"1a": { "caption": "Item 1a", "page_no": 1, "pos": [72,718] },
"2a": { "caption": "Item 2a", "page_no": 1, "pos": [72,503] },
"3a": { "caption": "Item 3a", "page_no": 1, "pos": [72,292] },
"4a": { "caption": "Item 4a", "page_no": 2, "pos": [72,718] },
"5a": { "caption": "Item 5a", "page_no": 2, "pos": [72,503] },
"6a": { "caption": "Item 6a", "page_no": 2, "pos": [72,292] }
}

@ -0,0 +1,13 @@
[
{ "title": "Content Set 2",
"rulerefs": [
{ "caption": "The only document", "ruleids": [
"cs2a", "cs2b", "cs2c", "cs2d", "cs2e", "cs2f"
]
}
]
}
]

@ -0,0 +1,12 @@
{
"cs2a": { "caption": "Item 1", "page_no": 1, "pos": [72,718] },
"cs2b": { "caption": "Item 2", "page_no": 1, "pos": [72,503] },
"cs2c": { "caption": "Item 3", "page_no": 1, "pos": [72,292] },
"cs2d": { "caption": "Item 4", "page_no": 2, "pos": [72,718] },
"cs2e": { "caption": "Item 5", "page_no": 2, "pos": [72,503] },
"cs2f": { "caption": "Item 6", "page_no": 2, "pos": [72,292] }
}

@ -167,6 +167,60 @@ def test_target_search( webapp, webdriver ):
# ---------------------------------------------------------------------
def test_content_sets( webapp, webdriver ):
"""Test how content sets are managed."""
# initialize
webapp.control_tests.set_data_dir( "content-sets" )
init_webapp( webapp, webdriver, add_empty_doc=1 )
# bring up all the targets
results = _do_search( "content" )
results = {
sr["title"]: [
[ rref["caption"], rref["ruleids"] ]
for rref in sr["rulerefs"]
] for sr in results
}
assert results == {
"((Content)) Set 1": [
[ "Main document", [ "1a", "2a", "3a", "4a", "5a", "6a" ] ],
[ "Linked document", [ "1b", "2b", "3b", "4b", "5b", "6b" ] ],
],
"((Content)) Set 2": [
[ "The only document", [ "cs2a", "cs2b", "cs2c", "cs2d", "cs2e", "cs2f" ] ]
],
"Unknown ruleid": [
[ "Incorrect ruleref", [ "cs2a" ] ]
],
}
# check that the ruleid links are enabled/disabled correctly
ruleid_elems = {}
for sr_elem in find_children( "#search-results .sr" ):
title = find_child( ".title", sr_elem ).text
for ruleid_elem in find_children( ".ruleid", sr_elem ):
link = find_child( "a", ruleid_elem )
if title == "Unknown ruleid":
assert link is None
else:
assert link
assert link.text not in ruleid_elems
ruleid_elems[ link.text ] = ruleid_elem
def do_test( ruleid, expected ):
ruleid_elems[ ruleid ].click()
wait_for( 2, lambda: _get_curr_target() == (expected, ruleid) )
# test clicking on ruleid targets
do_test( "4b", "content-set-1!linked" )
do_test( "1a", "content-set-1" )
do_test( "cs2d", "content-set-2" )
select_tabbed_page( "#content", "empty" )
do_test( "1b", "content-set-1!linked" )
# ---------------------------------------------------------------------
def test_make_fts_query_string():
"""Test generating the FTS query string."""

Loading…
Cancel
Save