403 lines
14 KiB
JavaScript
403 lines
14 KiB
JavaScript
import * as Factory from './lib/factory.js'
|
|
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
|
|
|
chai.use(chaiAsPromised)
|
|
const expect = chai.expect
|
|
|
|
describe('item manager', function () {
|
|
let context
|
|
let application
|
|
|
|
beforeEach(async function () {
|
|
localStorage.clear()
|
|
|
|
context = await Factory.createAppContextWithFakeCrypto()
|
|
application = context.application
|
|
|
|
await context.launch()
|
|
})
|
|
|
|
afterEach(async function () {
|
|
await context.deinit()
|
|
localStorage.clear()
|
|
|
|
context = undefined
|
|
application = undefined
|
|
})
|
|
|
|
const createNote = async () => {
|
|
return application.mutator.createItem(ContentType.TYPES.Note, {
|
|
title: 'hello',
|
|
text: 'world',
|
|
})
|
|
}
|
|
|
|
const createTag = async (notes = []) => {
|
|
const references = notes.map((note) => {
|
|
return {
|
|
uuid: note.uuid,
|
|
content_type: note.content_type,
|
|
}
|
|
})
|
|
return application.mutator.createItem(ContentType.TYPES.Tag, {
|
|
title: 'thoughts',
|
|
references: references,
|
|
})
|
|
}
|
|
|
|
it('find items with valid uuid', async function () {
|
|
const item = await createNote()
|
|
|
|
const results = await application.items.findItems([item.uuid])
|
|
expect(results.length).to.equal(1)
|
|
expect(results[0]).to.equal(item)
|
|
})
|
|
|
|
it('find items with invalid uuid no blanks', async function () {
|
|
const results = await application.items.findItems([Factory.generateUuidish()])
|
|
expect(results.length).to.equal(0)
|
|
})
|
|
|
|
it('find items with invalid uuid include blanks', async function () {
|
|
const results = await application.items.findItemsIncludingBlanks([Factory.generateUuidish()])
|
|
expect(results.length).to.equal(1)
|
|
expect(results[0]).to.not.be.ok
|
|
})
|
|
|
|
it('item state', async function () {
|
|
await createNote()
|
|
|
|
expect(application.items.items.length).to.equal(1 + BaseItemCounts.DefaultItems)
|
|
expect(application.items.getDisplayableNotes().length).to.equal(1)
|
|
})
|
|
|
|
it('find item', async function () {
|
|
const item = await createNote()
|
|
|
|
const foundItem = application.items.findItem(item.uuid)
|
|
expect(foundItem).to.be.ok
|
|
})
|
|
|
|
it('reference map', async function () {
|
|
const note = await createNote()
|
|
const tag = await createTag([note])
|
|
|
|
expect(application.items.collection.referenceMap.directMap.get(tag.uuid)).to.eql([note.uuid])
|
|
})
|
|
|
|
it('inverse reference map', async function () {
|
|
const note = await createNote()
|
|
const tag = await createTag([note])
|
|
|
|
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
|
})
|
|
|
|
it('inverse reference map should not have duplicates', async function () {
|
|
const note = await createNote()
|
|
const tag = await createTag([note])
|
|
await application.mutator.changeItem(tag)
|
|
|
|
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
|
})
|
|
|
|
it('items that reference item', async function () {
|
|
const note = await createNote()
|
|
const tag = await createTag([note])
|
|
|
|
const itemsThatReference = application.items.itemsReferencingItem(note)
|
|
expect(itemsThatReference.length).to.equal(1)
|
|
expect(itemsThatReference[0]).to.equal(tag)
|
|
})
|
|
|
|
it('observer', async function () {
|
|
const observed = []
|
|
application.items.addObserver(ContentType.TYPES.Any, ({ changed, inserted, removed, source, sourceKey }) => {
|
|
observed.push({ changed, inserted, removed, source, sourceKey })
|
|
})
|
|
const note = await createNote()
|
|
const tag = await createTag([note])
|
|
expect(observed.length).to.equal(2)
|
|
|
|
const firstObserved = observed[0]
|
|
expect(firstObserved.inserted).to.eql([note])
|
|
|
|
const secondObserved = observed[1]
|
|
expect(secondObserved.inserted).to.eql([tag])
|
|
})
|
|
|
|
it('dirty items should not include errored items', async function () {
|
|
const note = await application.mutator.setItemDirty(await createNote())
|
|
const errorred = new EncryptedPayload({
|
|
...note.payload,
|
|
content: '004:...',
|
|
errorDecrypting: true,
|
|
})
|
|
|
|
await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
|
|
|
const dirtyItems = application.items.getDirtyItems()
|
|
|
|
expect(dirtyItems.length).to.equal(0)
|
|
})
|
|
|
|
it('dirty items should include errored items if they are being deleted', async function () {
|
|
const note = await application.mutator.setItemDirty(await createNote())
|
|
const errorred = new DeletedPayload({
|
|
...note.payload,
|
|
content: undefined,
|
|
errorDecrypting: true,
|
|
deleted: true,
|
|
})
|
|
|
|
await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
|
|
|
const dirtyItems = application.items.getDirtyItems()
|
|
|
|
expect(dirtyItems.length).to.equal(1)
|
|
})
|
|
|
|
it('system smart views', async function () {
|
|
expect(application.items.systemSmartViews.length).to.be.above(0)
|
|
})
|
|
|
|
it('find tag by title', async function () {
|
|
const tag = await createTag()
|
|
|
|
expect(application.items.findTagByTitle(tag.title)).to.be.ok
|
|
})
|
|
|
|
it('find tag by title should be case insensitive', async function () {
|
|
const tag = await createTag()
|
|
|
|
expect(application.items.findTagByTitle(tag.title.toUpperCase())).to.be.ok
|
|
})
|
|
|
|
it('find or create tag by title', async function () {
|
|
const title = 'foo'
|
|
|
|
expect(await application.mutator.findOrCreateTagByTitle({ title: title })).to.be.ok
|
|
})
|
|
|
|
it('note count', async function () {
|
|
await createNote()
|
|
expect(application.items.noteCount).to.equal(1)
|
|
})
|
|
|
|
it('remove all items from memory', async function () {
|
|
const observed = []
|
|
application.items.addObserver(ContentType.TYPES.Any, ({ changed, inserted, removed, ignored }) => {
|
|
observed.push({ changed, inserted, removed, ignored })
|
|
})
|
|
await createNote()
|
|
await application.items.removeAllItemsFromMemory()
|
|
|
|
const deletionEvent = observed[1]
|
|
expect(deletionEvent.removed[0].deleted).to.equal(true)
|
|
expect(application.items.items.length).to.equal(0)
|
|
})
|
|
|
|
it('remove item locally', async function () {
|
|
const observed = []
|
|
application.items.addObserver(ContentType.TYPES.Any, ({ changed, inserted, removed, ignored }) => {
|
|
observed.push({ changed, inserted, removed, ignored })
|
|
})
|
|
const note = await createNote()
|
|
await application.items.removeItemFromMemory(note)
|
|
|
|
expect(observed.length).to.equal(1)
|
|
expect(application.items.findItem(note.uuid)).to.not.be.ok
|
|
})
|
|
|
|
it('emitting a payload from within observer should queue to end', async function () {
|
|
/**
|
|
* From within an item observer, we want to emit some changes and await them.
|
|
* We expect that the end result is that whatever was most recently emitted,
|
|
* is propagated to listeners after any pending observation events. That is, when you
|
|
* emit items, it should be done serially, so that emitting while you're emitting does
|
|
* not interrupt the current emission, but instead queues it. This is so that changes
|
|
* are not propagated out of order.
|
|
*/
|
|
const payload = Factory.createNotePayload()
|
|
const changedTitle = 'changed title'
|
|
let didEmit = false
|
|
let latestVersion
|
|
application.items.addObserver(ContentType.TYPES.Note, ({ changed, inserted }) => {
|
|
const all = changed.concat(inserted)
|
|
if (!didEmit) {
|
|
didEmit = true
|
|
const changedPayload = payload.copy({
|
|
content: {
|
|
...payload.content,
|
|
title: changedTitle,
|
|
},
|
|
})
|
|
application.mutator.emitItemFromPayload(changedPayload)
|
|
}
|
|
latestVersion = all[0]
|
|
})
|
|
await application.mutator.emitItemFromPayload(payload)
|
|
expect(latestVersion.title).to.equal(changedTitle)
|
|
})
|
|
|
|
describe('searchTags', async function () {
|
|
it('should return tag with query matching title', async function () {
|
|
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'tag' })
|
|
|
|
const results = application.items.searchTags('tag')
|
|
expect(results).lengthOf(1)
|
|
expect(results[0].title).to.equal(tag.title)
|
|
})
|
|
it('should return all tags with query partially matching title', async function () {
|
|
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag one' })
|
|
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag two' })
|
|
|
|
const results = application.items.searchTags('tag')
|
|
expect(results).lengthOf(2)
|
|
expect(results[0].title).to.equal(firstTag.title)
|
|
expect(results[1].title).to.equal(secondTag.title)
|
|
})
|
|
it('should be case insensitive', async function () {
|
|
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'Tag' })
|
|
|
|
const results = application.items.searchTags('tag')
|
|
expect(results).lengthOf(1)
|
|
expect(results[0].title).to.equal(tag.title)
|
|
})
|
|
it('should return tag with query matching delimiter separated component', async function () {
|
|
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'parent.child' })
|
|
|
|
const results = application.items.searchTags('child')
|
|
expect(results).lengthOf(1)
|
|
expect(results[0].title).to.equal(tag.title)
|
|
})
|
|
it('should return tags with matching query including delimiter', async function () {
|
|
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'parent.child' })
|
|
|
|
const results = application.items.searchTags('parent.chi')
|
|
expect(results).lengthOf(1)
|
|
expect(results[0].title).to.equal(tag.title)
|
|
})
|
|
|
|
it('should return tags in natural order', async function () {
|
|
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag 100' })
|
|
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag 2' })
|
|
const thirdTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag b' })
|
|
const fourthTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag a' })
|
|
|
|
const results = application.items.searchTags('tag')
|
|
expect(results).lengthOf(4)
|
|
expect(results[0].title).to.equal(secondTag.title)
|
|
expect(results[1].title).to.equal(firstTag.title)
|
|
expect(results[2].title).to.equal(fourthTag.title)
|
|
expect(results[3].title).to.equal(thirdTag.title)
|
|
})
|
|
|
|
it('should not return tags associated with note', async function () {
|
|
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag one' })
|
|
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag two' })
|
|
|
|
const note = await createNote()
|
|
await application.mutator.changeItem(firstTag, (mutator) => {
|
|
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
|
})
|
|
|
|
const results = application.items.searchTags('tag', note)
|
|
expect(results).lengthOf(1)
|
|
expect(results[0].title).to.equal(secondTag.title)
|
|
})
|
|
})
|
|
|
|
describe('smart views', async function () {
|
|
it('all view should not include archived notes by default', async function () {
|
|
const normal = await createNote()
|
|
|
|
await application.mutator.changeItem(normal, (mutator) => {
|
|
mutator.archived = true
|
|
})
|
|
|
|
application.items.setPrimaryItemDisplayOptions({
|
|
views: [application.items.allNotesSmartView],
|
|
})
|
|
|
|
expect(application.items.getDisplayableNotes().length).to.equal(0)
|
|
})
|
|
|
|
it('archived view should not include trashed notes by default', async function () {
|
|
const normal = await createNote()
|
|
|
|
await application.mutator.changeItem(normal, (mutator) => {
|
|
mutator.archived = true
|
|
mutator.trashed = true
|
|
})
|
|
|
|
application.items.setPrimaryItemDisplayOptions({
|
|
views: [application.items.archivedSmartView],
|
|
})
|
|
|
|
expect(application.items.getDisplayableNotes().length).to.equal(0)
|
|
})
|
|
|
|
it('trashed view should include archived notes by default', async function () {
|
|
const normal = await createNote()
|
|
|
|
await application.mutator.changeItem(normal, (mutator) => {
|
|
mutator.archived = true
|
|
mutator.trashed = true
|
|
})
|
|
|
|
application.items.setPrimaryItemDisplayOptions({
|
|
views: [application.items.trashSmartView],
|
|
})
|
|
|
|
expect(application.items.getDisplayableNotes().length).to.equal(1)
|
|
})
|
|
})
|
|
|
|
describe('getSortedTagsForNote', async function () {
|
|
it('should return tags associated with a note in natural order', async function () {
|
|
const tags = [
|
|
await application.mutator.findOrCreateTagByTitle({ title: 'tag 100' }),
|
|
await application.mutator.findOrCreateTagByTitle({ title: 'tag 2' }),
|
|
await application.mutator.findOrCreateTagByTitle({ title: 'tag b' }),
|
|
await application.mutator.findOrCreateTagByTitle({ title: 'tag a' }),
|
|
]
|
|
|
|
const note = await createNote()
|
|
|
|
tags.map(async (tag) => {
|
|
await application.mutator.changeItem(tag, (mutator) => {
|
|
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
|
})
|
|
})
|
|
|
|
const results = application.items.getSortedTagsForItem(note)
|
|
|
|
expect(results).lengthOf(tags.length)
|
|
expect(results[0].title).to.equal(tags[1].title)
|
|
expect(results[1].title).to.equal(tags[0].title)
|
|
expect(results[2].title).to.equal(tags[3].title)
|
|
expect(results[3].title).to.equal(tags[2].title)
|
|
})
|
|
})
|
|
|
|
describe('getTagParentChain', function () {
|
|
it('should return parent tags for a tag', async function () {
|
|
const [parent, child, grandchild, _other] = await Promise.all([
|
|
application.mutator.findOrCreateTagByTitle({ title: 'parent' }),
|
|
application.mutator.findOrCreateTagByTitle({ title: 'parent.child' }),
|
|
application.mutator.findOrCreateTagByTitle({ title: 'parent.child.grandchild' }),
|
|
application.mutator.findOrCreateTagByTitle({ title: 'some other tag' }),
|
|
])
|
|
|
|
await application.mutator.setTagParent(parent, child)
|
|
await application.mutator.setTagParent(child, grandchild)
|
|
|
|
const results = application.items.getTagParentChain(grandchild)
|
|
|
|
expect(results).lengthOf(2)
|
|
expect(results[0].uuid).to.equal(parent.uuid)
|
|
expect(results[1].uuid).to.equal(child.uuid)
|
|
})
|
|
})
|
|
})
|