UNPKG

hyperpubee

Version:

Self-publishing over the decentralised internet

524 lines (453 loc) 14.9 kB
const { expect } = require('chai') const { strict: nodeAssert } = require('assert') const { POEM, VERSE, LINE, TITLE, CHAPTER, CHAPTER_TITLE, EMBEDDING_HASH, EMBEDDING_LOCATION, EMBEDDING_PATCH, CONTENT, PROSE, PARAGRAPH, METADATA, COLLECTION, LINK_HASH, LINK_LOCATION, STRUCTURE } = require('../lib/core/constants') const { parseWork } = require('../lib/creation/parser') const utils = require('../lib/core/utils') const { resolvePubeeToJson } = require('../lib/helpers') const { getSimplePoemTxt, getChapteredPoemTxt, getChapteredProseTxt, getInvalidChapteredProseTxt } = require('./shared-data') const { hyperInterfaceFactory } = require('./fixtures') describe('Test parser', function () { let hyperInterface, bee const contentLocation = utils.createHexLexedReference(CONTENT, 1) this.beforeEach(async function () { hyperInterface = await hyperInterfaceFactory() bee = await hyperInterface.createHyperbee('testbee') }) this.afterEach(async function () { await hyperInterface.close() }) it('Can parse a simple poem of one verse and no title', async function () { const text = '[poetry]I am a poem\nThis is my second line\nI end at the third' const expected = [ { [POEM]: [ { [VERSE]: [ { [LINE]: ['I am a poem'] }, { [LINE]: ['This is my second line'] }, { [LINE]: ['I end at the third'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('Can parse a poem with verses and a title', async function () { const text = await getSimplePoemTxt() const expected = [ { [TITLE]: ['Test Poem'] }, { [POEM]: [ { [VERSE]: [ { [LINE]: ['I am a test poem'] }, { [LINE]: ['This is my first verse'] } ] }, { [VERSE]: [ { [LINE]: ['Here is my second verse'] }, { [LINE]: ['The end'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('Can parse a poem with chapters', async function () { const text = await getChapteredPoemTxt() const expected = [ { [TITLE]: ['I have 2 chapters'] }, { [POEM]: [ { [CHAPTER]: [ { [VERSE]: [ { [LINE]: ['This is my first chapter'] }, { [LINE]: ['It has no title'] } ] }, { [VERSE]: [ { [LINE]: ['Second verse'] }, { [LINE]: ['First chapter'] } ] } ] }, { [CHAPTER]: [ { [CHAPTER_TITLE]: ['Chapter 2'] }, { [VERSE]: [ { [LINE]: ['This is the second chapter'] }, { [LINE]: ['It ends here'] } ] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('throws on a text which does not start with a type indication', async function () { const text = 'I should start\nWith a type' await nodeAssert.rejects(parseWork({ text, bee }), { message: "A text should start with either '[poetry]', '[prose]' or '[collection]' to indicate its type" }) }) it('Can parse simple prose', async function () { const text = '[prose]I am a prose paragraph.\n\nMe too, and I am last.' const expected = [ { [PROSE]: [ { [PARAGRAPH]: ['I am a prose paragraph.'] }, { [PARAGRAPH]: ['Me too, and I am last.'] } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) it('Ignores white space at start', async function () { const text = '\n\n [prose]I am a prose paragraph.' const expected = [ { [PROSE]: [ { [PARAGRAPH]: ['I am a prose paragraph.'] } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) it('Correctly handles author field', async function () { const text = '[prose]I am a prose paragraph.' const parsed = await parseWork({ text, bee, author: 'someone' }) const actual = (await parsed.get(METADATA)).author expect(actual).to.equal('someone') }) it('Does not include an author if none specified', async function () { const text = '[prose]I am a prose paragraph.' const parsed = await parseWork({ text, bee }) const actual = (await parsed.get(METADATA)).author expect(actual).to.equal(undefined) }) it('throws on prose with inconsistent chapters', async function () { const text = await getInvalidChapteredProseTxt() await nodeAssert.rejects(parseWork({ text, bee }), { message: 'If a text consists of chapters, it should not contain text before the first chapter ("##")' }) }) it('Can parse complex prose', async function () { const text = await getChapteredProseTxt() const expected = [ { [TITLE]: ['I have 2 chapters'] }, { [PROSE]: [ { [CHAPTER]: [ { [PARAGRAPH]: ['This is my first paragraph.'] }, { [PARAGRAPH]: ['This is the second paragraph.'] } ] }, { [CHAPTER]: [ { [CHAPTER_TITLE]: ['Chapter 2'] }, { [PARAGRAPH]: ['Line breaks are allowed in a paragraph.\nSee?'] } ] } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) describe('Test parsing works with embeddings', function () { let referencedKey, referencedLocation, patch this.beforeEach(function () { referencedKey = 'a'.repeat(64) referencedLocation = 'content/1' patch = [[-1, 'Something to remove'], [0, ' the rest']] }) it('Can parse a poem with a simple embedding', async function () { const reference = [referencedKey, referencedLocation].join('/') const text = [`[poetry]I embed: <${reference}>`, 'and end soon'].join('\n') const expected = [ { [POEM]: [ { [VERSE]: [ { [LINE]: [ 'I embed: ', { [EMBEDDING_HASH]: referencedKey, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: [] } ] }, { [LINE]: ['and end soon'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('Can parse a poem with a patched embedding', async function () { const reference = [referencedKey, referencedLocation].join('/') + JSON.stringify(patch) const text = [`[poetry]I embed: <${reference}>`, 'and end soon'].join('\n') const expected = [ { [POEM]: [ { [VERSE]: [ { [LINE]: [ 'I embed: ', { [EMBEDDING_HASH]: referencedKey, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: [[-1, 'Something to remove'], [0, ' the rest']] } ] }, { [LINE]: ['and end soon'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('correctly handles multiple embeddings in the same text unit', async function () { const referenceKey2 = 'b'.repeat(64) const reference = [referencedKey, referencedLocation].join('/') const reference2 = [referenceKey2, referencedLocation].join('/') const text = [`[poetry]I embed: <${reference}><${reference2}>`, 'and end soon'].join('\n') const expected = [ { [POEM]: [ { [VERSE]: [ { [LINE]: [ 'I embed: ', { [EMBEDDING_HASH]: referencedKey, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: [] }, { [EMBEDDING_HASH]: referenceKey2, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: [] } ] }, { [LINE]: ['and end soon'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('correctly handles 2 patches in same text unit', async function () { const referenceKey2 = 'b'.repeat(64) const reference = [referencedKey, referencedLocation].join('/') const reference2 = [referenceKey2, referencedLocation].join('/') const patchedRef = `<${reference}${JSON.stringify(patch)}>` const patchedRef2 = `<${reference2}${JSON.stringify(patch)}>` const text = [`[poetry]I patch: ${patchedRef}${patchedRef2}`, 'and end soon'].join('\n') const expected = [ { [POEM]: [ { [VERSE]: [ { [LINE]: [ 'I patch: ', { [EMBEDDING_HASH]: referencedKey, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: patch }, { [EMBEDDING_HASH]: referenceKey2, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: patch } ] }, { [LINE]: ['and end soon'] } ] } ] } ] const parsedPoem = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsedPoem) expect(actual).to.deep.equal(expected) }) it('Can parse prose with a simple embedding', async function () { const reference = [referencedKey, referencedLocation].join('/') const text = `[prose]I embed: <${reference}> and end soon.` const expected = [ { [PROSE]: [ { [PARAGRAPH]: [ 'I embed: ', { [EMBEDDING_HASH]: referencedKey, [EMBEDDING_LOCATION]: contentLocation, [EMBEDDING_PATCH]: [] }, ' and end soon.' ] } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) it('throws on invalid patch ', async function () { const reference = [referencedKey, referencedLocation].join('/') + '[[Invalid patch]]' const text = [`[poetry]I embed: <${reference}>`, 'and end soon'].join('\n') await nodeAssert.rejects(parseWork({ text, bee }), { message: "Patch has invalid structure: '[[Invalid patch]]'" }) }) it('throws on invalid patch (invalid JSON)', async function () { const reference = [referencedKey, referencedLocation].join('/') + '[[{I\'m not valid json]]' const text = [`[poetry]I embed: <${reference}>`, 'and end soon'].join('\n') await nodeAssert.rejects(parseWork({ text, bee }), { message: "Patch has invalid structure: '[[{I'm not valid json]]'" }) }) }) describe('Test parsing collections', function () { let referencedKey1, referencedKey2, referencedLocation this.beforeEach(function () { referencedKey1 = 'a'.repeat(64) referencedKey2 = 'b'.repeat(64) referencedLocation = `${CONTENT}/1` }) it('Can parse a simple collection', async function () { const text = [ '[collection]', ` [${referencedKey1}] `, `[${referencedKey2}]` ].join('\n') const expected = [ { [COLLECTION]: [ { [LINK_HASH]: referencedKey1 }, { [LINK_HASH]: referencedKey2 } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) it('Can parse a collection with title', async function () { const text = [ '[collection]', '#My Collection\n', `[${referencedKey1}] `, `[${referencedKey2}]` ].join('\n') const expected = [ { [TITLE]: ['My Collection'] }, { [COLLECTION]: [ { [LINK_HASH]: referencedKey1 }, { [LINK_HASH]: referencedKey2 } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) it('Can parse a collection with links with locations', async function () { const text = [ '[collection]', `[${referencedKey1}/${referencedLocation}]`, `[${referencedKey2}/${STRUCTURE}/${VERSE}/1]` ].join('\n') const verseLocation = utils.createHexLexedReference( utils.getAbsoluteKey(STRUCTURE, VERSE), 1 ) const expected = [ { [COLLECTION]: [ { [LINK_HASH]: referencedKey1, [LINK_LOCATION]: contentLocation }, { [LINK_HASH]: referencedKey2, [LINK_LOCATION]: verseLocation } ] } ] const parsed = await parseWork({ text, bee }) const actual = await resolvePubeeToJson(parsed) expect(actual).to.deep.equal(expected) }) }) })