Files
standardnotes-app-web/packages/snjs/mocha/item_manager.test.js
2023-08-06 15:23:31 -05:00

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)
})
})
})