Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions cypress/e2e/link_creation.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ context('Link Creation', () => {
},
{name: 'unicode dashes in verse ranges', texts: ['Gen 1:1–3', 'Gen 1:4—5'], containerSelector: '.unicode-dash-test'},
{name: 'mixed separators list', texts: ['Rev 1:1', '2-3', '4:5'], containerSelector: '.mixed-separators-test'},
{name: 'and separator list', texts: ['I Corinthians 5:11', '6:9-11', '6:18-20', '7:1-3', '7:8-9'], containerSelector: '.and-separator-test'},
{name: 'Jude list with and and comma separators', texts: ['Jude 6', '8', '10'], containerSelector: '.jude-list-test'},
];

for (const testCase of listReferenceCases) {
Expand Down Expand Up @@ -205,6 +207,64 @@ context('Link Creation', () => {
});


it('Should carry chapter context for and-separated list segments', () => {
assertLinkAttributes({
text: '7:8-9',
containerSelector: '.and-separator-test',
book: '1CO',
reference: '7:8-7:9',
hrefIncludes: ['/1CO.7?passageId=1CO.7.8-1CO.7.9'],
});
});

it('Should preserve and delimiters between transformed links', () => {
cy.get('.and-separator-test')
.invoke('text')
.then(text => {
expect(text.replace(/\s+/g, ' ').trim()).to.equal('I Corinthians 5:11, 6:9-11, 6:18-20, 7:1-3 and 7:8-9');
});

cy.get('.and-separator-test')
.find(CONTAINER_SELECTOR)
.should('have.length', 5);
});

it('Should parse Jude list segments as chapter 1 references', () => {
assertLinkAttributes({
text: 'Jude 6',
containerSelector: '.jude-list-test',
book: 'JUD',
reference: '1:6-1:6',
hrefIncludes: ['/JUD.1?passageId=JUD.1.6'],
});
assertLinkAttributes({
text: '8',
containerSelector: '.jude-list-test',
book: 'JUD',
reference: '1:8-1:8',
hrefIncludes: ['/JUD.1?passageId=JUD.1.8'],
});
assertLinkAttributes({
text: '10',
containerSelector: '.jude-list-test',
book: 'JUD',
reference: '1:10-1:10',
hrefIncludes: ['/JUD.1?passageId=JUD.1.10'],
});
});

it('Should preserve and delimiter in transformed Jude list text', () => {
cy.get('.jude-list-test')
.invoke('text')
.then(text => {
expect(text.replace(/\s+/g, ' ').trim()).to.equal('Jude 6 and 8, 10');
});

cy.get('.jude-list-test')
.find(CONTAINER_SELECTOR)
.should('have.length', 3);
});

it('Should preserve original delimiters between transformed links', () => {
cy.get('.mixed-separators-test')
.invoke('text')
Expand Down
2 changes: 2 additions & 0 deletions cypress/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<div>Psalm 119:105</div>
<div>Jude 6</div>
<div>Jude 1:7</div>
<div class="jude-list-test">Jude 6 and 8, 10</div>
<div class="duplicate-verse-test">Ex 19:20</div>
<div class="duplicate-verse-test">Ex 19:20</div>
<div class="keep-existing-link-test">
Expand All @@ -35,6 +36,7 @@
<div class="unicode-dash-test">Gen 1:1–3 and Gen 1:4—5</div>
<div class="case-insensitive-test">jOhN 3:16</div>
<div class="mixed-separators-test">Rev 1:1; 2-3, 4:5</div>
<div class="and-separator-test">I Corinthians 5:11, 6:9-11, 6:18-20, 7:1-3 and 7:8-9</div>
<div class="deutero-fallback-test">Sirach 2:1</div>
<div class="non-reference-test">Johnson 4:24 should not be transformed into a bible link</div>
<div id="hover-safe-zone">safe hover target</div>
Expand Down
32 changes: 18 additions & 14 deletions js/biblePreviewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const books_start_with_number = `(?:${SAMUEL_REG}|${KINGS_REG}|${CHRON_REG}|${MA
const firstPrefix = String.raw`(?:1(?:st)?|I|First)\s*`;
const secondPrefix = String.raw`(?:2(?:nd)?|II|Second)\s*`;
const thirdPrefix = String.raw`(?:3(?:rd)?|III|Third)\s*`;
const VERSE_LIST_CONNECTOR_REG = String.raw`(?:[,:;${DASHES_STR}]|\s+and\b)`;

const JUDE_BOOK_ID = 'Jud';

Expand Down Expand Up @@ -150,22 +151,26 @@ const bibleBooks = {
'R(?:e?v|evelation)': 'Rev'
};

let bibleRegex;

if (GENERATE_REGEX) {
/**
* Build the bible reference regex used to detect references in page text.
* @returns {RegExp} A case-insensitive, global bible reference matcher
*/
function buildBibleRegex() {
// The regex to match book names
bibleRegex = `(${Object.keys(bibleBooks).join('|')})`;
// Matches a required start chapter and verse, then matches an optional end chapter and verse, with lists also supported
bibleRegex += `\\.?\\s*(\\d{1,3}:\\s*\\d{1,3}(?:[,;:${DASHES_STR}]\\s*\\d{1,3}`;
let generatedRegex = `(${Object.keys(bibleBooks).join('|')})`;
// Matches a required start chapter and verse, then optional continuation delimiters and digits
generatedRegex += `\\.?\\s*(\\d{1,3}:\\s*\\d{1,3}(?:${VERSE_LIST_CONNECTOR_REG}\\s*\\d{1,3}`;
// But don't match a single verse if it is right before a book that has a number before it
bibleRegex += `(?!\\s*${books_start_with_number}))*)`;
generatedRegex += `(?!\\s*${books_start_with_number}))*)`;
// Add Jude separately because Jude only has 1 chapter, so people usually don't put a chapter with the verse
bibleRegex += `|${JUDE_REG}\\s*(\\d{1,2}(?:[,;]?\\s*\\d{1,2})*)`;
bibleRegex = new RegExp(bibleRegex, 'gi');
generatedRegex += `|${JUDE_REG}\\s*(\\d{1,2}(?:(?:[;,]|\\s+and\\b)?\\s*\\d{1,2})*)`;
return new RegExp(generatedRegex, 'gi');
}

const bibleRegex = buildBibleRegex();

if (GENERATE_REGEX) {
console.log(bibleRegex);
} else {
// eslint-disable-next-line max-len
bibleRegex = /(Ge?n(?:esis)?|Ex(?:od(?:us)?)?|Le(?:v(?:iticus)?)?|Nu?m(?:b(?:ers)?)?|D(?:t|eut(?:eronomy)?)|Jo(?:s(?:h(?:ua)?)?)?|J(?:dgs?|udg(?:es)?)|Ru?th|(?:1(?:st)?|I|First)\s*Sa?m(?:uel)?|(?:2(?:nd)?|II|Second)\s*Sa?m(?:uel)?|(?:1(?:st)?|I|First)\s*K(?:in)?gs|(?:2(?:nd)?|II|Second)\s*K(?:in)?gs|(?:1(?:st)?|I|First)\s*Chr(?:on(?:icles)?)?|(?:2(?:nd)?|II|Second)\s*Chr(?:on(?:icles)?)?|Ezra?|Ne(?:h(?:emiah)?)?|Tob(?:it|ias)?|J(?:d?th?|udith)|Est(?:h(?:er)?)?|(?:1(?:st)?|I|First)\s*Mac(?:c(?:abees)?)?|(?:2(?:nd)?|II|Second)\s*Mac(?:c(?:abees)?)?|Jo?b|Ps(?:a(?:lms?)?)?|Pro(?:v(?:erbs)?)?|Ecc(?:les?|lesiastes)?|So(?:S|ng(?:\s*of\s*(?:Sol(?:omon)?|Songs?))?)|Wis(?:dom)?(?:\s*of\s*Sol(?:omon)?)?|Sir(?:ach)?|Bar(?:uch)?|Is(?:a(?:iah)?)?|Jer(?:emiah)?|Lam(?:entations)?|Ez(?:e?k?|ekiel)|Da?n(?:iel)?|Hos(?:ea)?|Joel|Amos|Ob(?:ad(?:iah)?)?|Jon(?:ah)?|Mic(?:ah)?|Nah(?:um)?|Hab(?:akkuk)?|Zep(?:h(?:aniah)?)?|Hag(?:gai)?|Zec(?:h(?:ariah)?)?|Mal(?:achi)?|M(?:t|att(?:h(?:ew)?)?)|M(?:k|ark?)|L(?:k|uke?)|Jo?h?n|Acts?|Ro(?:m(?:ans)?)?|(?:1(?:st)?|I|First)\s*Co(?:r(?:inthians?)?)?|(?:2(?:nd)?|II|Second)\s*Co(?:r(?:inthians?)?)?|Gal(?:atians)?|Eph(?:es(?:ians)?)?|Phil(?:ippians)?|Col(?:ossians)?|(?:1(?:st)?|I|First)\s*Thes(?:s(?:alonians)?)?|(?:2(?:nd)?|II|Second)\s*Thes(?:s(?:alonians)?)?|(?:1(?:st)?|I|First)\s*T(?:imothy|im|i|m)|(?:2(?:nd)?|II|Second)\s*T(?:imothy|im|i|m)|Titus|Phil(?:em(?:on)?)?|Heb(?:rews?)?|Ja(?:me)?s|(?:1(?:st)?|I|First)\s*Pe?t(?:er)?|(?:2(?:nd)?|II|Second)\s*Pe?t(?:er)?|(?:1(?:st)?|I|First)\s*Jo?h?n|(?:2(?:nd)?|II|Second)\s*Jo?h?n|(?:3(?:rd)?|III|Third)\s*Jo?h?n|Jude?|R(?:e?v|evelation))\.?\s*(\d{1,3}:\s*\d{1,3}(?:[,;:–—-]\s*\d{1,3}(?!\s*(?:Sa?m(?:uel)?|K(?:in)?gs|Chr(?:on(?:icles)?)?|Mac(?:c(?:abees)?)?|Co(?:r(?:inthians?)?)?|Thes(?:s(?:alonians)?)?|T(?:imothy|im|i|m)|Pe?t(?:er)?|Jo?h?n)))*)|Jude?\s*(\d{1,2}(?:[,;]?\s*\d{1,2})*)/gi;
}

/**
Expand Down Expand Up @@ -244,8 +249,7 @@ function transformBibleReferences(element, trans, language) {
let startChap, startVerse, endChap, endVerse, previousChap = '';
let referenceList = [];
const {verses: verseList} = splitVerseListString(verseListString);
const splitText = orig.split(/[,;]/g);
const delimiters = orig.match(/[;,]\s*/g) ?? [];
const {verses: splitText, delimiters} = splitVerseListString(orig);
for (const [index, element] of verseList.entries()) {
[startChap, startVerse, endChap, endVerse, previousChap] = getVerseFromString(element, previousChap);
book = book.toUpperCase();
Expand Down
8 changes: 6 additions & 2 deletions js/verseParser.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const DASHES_STR = '–—-';
const DASHES_REG = new RegExp(`[${DASHES_STR}]`);
const VERSE_LIST_DELIMITER_REG = /(?:[;,]\s*|\s+and\s+)/gi;

/**
* Given a string, gets the verse components and previous chapter (if it exists)
Expand Down Expand Up @@ -41,8 +42,11 @@ function getVerseFromString(verseString, previousChap) {
* @returns {{verses: string[], delimiters: string[]}} Parsed verses and delimiters (in order)
*/
function splitVerseListString(verseListString) {
const delimiters = verseListString.match(/[;,]\s*/g) ?? [];
const verses = verseListString.split(/[;,]\s*/g).map(verse => verse.trim());
const delimiters = verseListString.match(VERSE_LIST_DELIMITER_REG) ?? [];
const verses = verseListString
.split(VERSE_LIST_DELIMITER_REG)
.map(verse => verse.trim())
.filter(Boolean);

return {verses, delimiters};
}
Expand Down
26 changes: 26 additions & 0 deletions js/verseParser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,29 @@ test('splits verse lists and preserves mixed delimiters', () => {
delimiters: [';', ', ']
});
});


test('splits verse lists and preserves and delimiters', () => {
assert.deepEqual(splitVerseListString('5:11, 6:9-11 and 6:18-20'), {
verses: ['5:11', '6:9-11', '6:18-20'],
delimiters: [', ', ' and ']
});
});

test('splits verse lists and preserves mixed punctuation and and delimiters', () => {
assert.deepEqual(splitVerseListString('7:1-3 AND 7:8-9; 7:10'), {
verses: ['7:1-3', '7:8-9', '7:10'],
delimiters: [' AND ', '; ']
});
});

test('splits Jude-style numeric lists and preserves and delimiter', () => {
assert.deepEqual(splitVerseListString('6 and 8, 10'), {
verses: ['6', '8', '10'],
delimiters: [' and ', ', ']
});
});

test('parses Jude chapter-one mapped range correctly', () => {
assert.deepEqual(getVerseFromString('1:6-8', ''), ['1', '6', '1', '8', '1']);
});
14 changes: 6 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"zip": "rm -rf *.zip && bash scripts/get_versions.sh && python scripts/create_zips.py",
"watch": "webpack --watch",
"cypress:gui": "cypress open",
"cypress:run": "cypress run --record false --browser chrome --headed",
"cypress:run": "cypress run --record false --browser C:\\Users\\codyg\\Documents\\CodingProjects\\BiblePreviewer\\chrome\\win64-146.0.7680.31\\chrome-win64\\chrome.exe --headed",
"test": "node --test",
"test:ci": "npm run test && npm run cypress:run",
"lint": "eslint . --max-warnings 0 --ignore-pattern dist && stylelint **/*.scss",
Expand Down