hyperpubee
Version:
Self-publishing over the decentralised internet
524 lines (453 loc) • 14.9 kB
JavaScript
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)
})
})
})