Files
standardnotes-app-web/packages/snjs/mocha/model_tests/appmodels.test.js

378 lines
15 KiB
JavaScript

/* eslint-disable camelcase */
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('app models', () => {
const sharedApplication = Factory.createApplicationWithFakeCrypto()
before(async function () {
localStorage.clear()
await Factory.initializeApplication(sharedApplication)
})
after(async function () {
localStorage.clear()
await Factory.safeDeinit(sharedApplication)
})
beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems
this.context = await Factory.createAppContext()
this.application = this.context.application
await this.context.launch()
})
afterEach(async function () {
await Factory.safeDeinit(this.application)
})
it('payloads should be defined', () => {
expect(sharedApplication.payloads).to.be.ok
})
it('item should be defined', () => {
expect(GenericItem).to.be.ok
})
it('item content should be assigned', () => {
const params = Factory.createNotePayload()
const item = CreateDecryptedItemFromPayload(params)
expect(item.content.title).to.equal(params.content.title)
})
it('should default updated_at to 1970 and created_at to the present', () => {
const params = Factory.createNotePayload()
const item = CreateDecryptedItemFromPayload(params)
const epoch = new Date(0)
expect(item.serverUpdatedAt - epoch).to.equal(0)
expect(item.created_at - epoch).to.be.above(0)
const presentThresholdMs = 10
expect(new Date() - item.created_at).to.be.below(presentThresholdMs)
})
it('handles delayed mapping', async function () {
const params1 = Factory.createNotePayload()
const params2 = Factory.createNotePayload()
const mutated = new DecryptedPayload({
...params1,
content: {
...params1.content,
references: [
{
uuid: params2.uuid,
content_type: params2.content_type,
},
],
},
})
await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
await this.application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged)
const item1 = this.application.items.findItem(params1.uuid)
const item2 = this.application.items.findItem(params2.uuid)
expect(item1.content.references.length).to.equal(1)
expect(item2.content.references.length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1)
})
it('mapping an item twice shouldnt cause problems', async function () {
const payload = Factory.createNotePayload()
const mutated = new DecryptedPayload({
...payload,
content: {
...payload.content,
foo: 'bar',
},
})
let items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
let item = items[0]
expect(item).to.be.ok
items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
item = items[0]
expect(item.content.foo).to.equal('bar')
expect(this.application.items.getDisplayableNotes().length).to.equal(1)
})
it('mapping item twice should preserve references', async function () {
const item1 = await Factory.createMappedNote(this.application)
const item2 = await Factory.createMappedNote(this.application)
await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
await this.application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
})
const refreshedItem = this.application.items.findItem(item1.uuid)
expect(refreshedItem.content.references.length).to.equal(1)
})
it('fixes relationship integrity', async function () {
var item1 = await Factory.createMappedNote(this.application)
var item2 = await Factory.createMappedNote(this.application)
await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
await this.application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
})
const refreshedItem1 = this.application.items.findItem(item1.uuid)
const refreshedItem2 = this.application.items.findItem(item2.uuid)
expect(refreshedItem1.content.references.length).to.equal(1)
expect(refreshedItem2.content.references.length).to.equal(1)
const damagedPayload = refreshedItem1.payload.copy({
content: {
...refreshedItem1.content,
// damage references of one object
references: [],
},
})
await this.application.mutator.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged)
const refreshedItem1_2 = this.application.items.findItem(item1.uuid)
const refreshedItem2_2 = this.application.items.findItem(item2.uuid)
expect(refreshedItem1_2.content.references.length).to.equal(0)
expect(refreshedItem2_2.content.references.length).to.equal(1)
})
it('creating and removing relationships between two items should have valid references', async function () {
var item1 = await Factory.createMappedNote(this.application)
var item2 = await Factory.createMappedNote(this.application)
await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
await this.application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
})
const refreshedItem1 = this.application.items.findItem(item1.uuid)
const refreshedItem2 = this.application.items.findItem(item2.uuid)
expect(refreshedItem1.content.references.length).to.equal(1)
expect(refreshedItem2.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(item1)).to.include(refreshedItem2)
expect(this.application.items.itemsReferencingItem(item2)).to.include(refreshedItem1)
await this.application.mutator.changeItem(item1, (mutator) => {
mutator.removeItemAsRelationship(item2)
})
await this.application.mutator.changeItem(item2, (mutator) => {
mutator.removeItemAsRelationship(item1)
})
const refreshedItem1_2 = this.application.items.findItem(item1.uuid)
const refreshedItem2_2 = this.application.items.findItem(item2.uuid)
expect(refreshedItem1_2.content.references.length).to.equal(0)
expect(refreshedItem2_2.content.references.length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0)
})
it('properly duplicates item with no relationships', async function () {
const item = await Factory.createMappedNote(this.application)
const duplicate = await this.application.mutator.duplicateItem(item)
expect(duplicate.uuid).to.not.equal(item.uuid)
expect(item.isItemContentEqualWith(duplicate)).to.equal(true)
expect(item.created_at.toISOString()).to.equal(duplicate.created_at.toISOString())
expect(item.content_type).to.equal(duplicate.content_type)
})
it('properly duplicates item with relationships', async function () {
const item1 = await Factory.createMappedNote(this.application)
const item2 = await Factory.createMappedNote(this.application)
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
expect(refreshedItem1.content.references.length).to.equal(1)
const duplicate = await this.application.mutator.duplicateItem(item1)
expect(duplicate.uuid).to.not.equal(item1.uuid)
expect(duplicate.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(2)
const refreshedItem1_2 = this.application.items.findItem(item1.uuid)
expect(refreshedItem1_2.isItemContentEqualWith(duplicate)).to.equal(true)
expect(refreshedItem1_2.created_at.toISOString()).to.equal(duplicate.created_at.toISOString())
expect(refreshedItem1_2.content_type).to.equal(duplicate.content_type)
})
it('removing references should update cross-refs', async function () {
const item1 = await Factory.createMappedNote(this.application)
const item2 = await Factory.createMappedNote(this.application)
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
const refreshedItem1_2 = await this.application.mutator.emitItemFromPayload(
refreshedItem1.payloadRepresentation({
deleted: true,
content: {
...refreshedItem1.payload.content,
references: [],
},
}),
PayloadEmitSource.LocalChanged,
)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(refreshedItem1_2.content.references.length).to.equal(0)
})
it('properly handles single item uuid alternation', async function () {
const item1 = await Factory.createMappedNote(this.application)
const item2 = await Factory.createMappedNote(this.application)
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
expect(refreshedItem1.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1)
const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid)
const refreshedItem1_2 = this.application.items.findItem(item1.uuid)
expect(refreshedItem1_2).to.not.be.ok
expect(this.application.items.getDisplayableNotes().length).to.equal(2)
expect(alternatedItem.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(alternatedItem.uuid).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1)
expect(alternatedItem.isReferencingItem(item2)).to.equal(true)
expect(alternatedItem.dirty).to.equal(true)
})
it('alterating uuid of item should fill its duplicateOf value', async function () {
const item1 = await Factory.createMappedNote(this.application)
const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid)
expect(alternatedItem.duplicateOf).to.equal(item1.uuid)
})
it('alterating itemskey uuid should update errored items encrypted with that key', async function () {
const item1 = await Factory.createMappedNote(this.application)
const itemsKey = this.application.items.getDisplayableItemsKeys()[0]
/** Encrypt item1 and emit as errored so it persists with items_key_id */
const encrypted = await this.application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: {
items: [item1.payload],
},
})
const errored = encrypted.copy({
errorDecrypting: true,
waitingForKey: true,
})
await this.application.payloads.emitPayload(errored)
expect(this.application.payloads.findOne(item1.uuid).errorDecrypting).to.equal(true)
expect(this.application.payloads.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid)
sinon.stub(this.application.encryption.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => {
// prevent auto decryption
})
const alternatedKey = await Factory.alternateUuidForItem(this.application, itemsKey.uuid)
const updatedPayload = this.application.payloads.findOne(item1.uuid)
expect(updatedPayload.items_key_id).to.equal(alternatedKey.uuid)
})
it('properly handles mutli item uuid alternation', async function () {
const item1 = await Factory.createMappedNote(this.application)
const item2 = await Factory.createMappedNote(this.application)
this.expectedItemCount += 2
await this.application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
})
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1)
const alternatedItem1 = await Factory.alternateUuidForItem(this.application, item1.uuid)
const alternatedItem2 = await Factory.alternateUuidForItem(this.application, item2.uuid)
expect(this.application.items.items.length).to.equal(this.expectedItemCount)
expect(item1.uuid).to.not.equal(alternatedItem1.uuid)
expect(item2.uuid).to.not.equal(alternatedItem2.uuid)
const refreshedAltItem1 = this.application.items.findItem(alternatedItem1.uuid)
expect(refreshedAltItem1.content.references.length).to.equal(1)
expect(refreshedAltItem1.content.references[0].uuid).to.equal(alternatedItem2.uuid)
expect(alternatedItem2.content.references.length).to.equal(0)
expect(this.application.items.itemsReferencingItem(alternatedItem2).length).to.equal(1)
expect(refreshedAltItem1.isReferencingItem(alternatedItem2)).to.equal(true)
expect(alternatedItem2.isReferencingItem(refreshedAltItem1)).to.equal(false)
expect(refreshedAltItem1.dirty).to.equal(true)
})
it('maintains referencing relationships when duplicating', async function () {
const tag = await Factory.createMappedTag(this.application)
const note = await Factory.createMappedNote(this.application)
const refreshedTag = await this.application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(note)
})
expect(refreshedTag.content.references.length).to.equal(1)
const noteCopy = await this.application.mutator.duplicateItem(note)
expect(note.uuid).to.not.equal(noteCopy.uuid)
expect(this.application.items.getDisplayableNotes().length).to.equal(2)
expect(this.application.items.getDisplayableTags().length).to.equal(1)
expect(note.content.references.length).to.equal(0)
expect(noteCopy.content.references.length).to.equal(0)
const refreshedTag_2 = this.application.items.findItem(tag.uuid)
expect(refreshedTag_2.content.references.length).to.equal(2)
})
it('maintains editor reference when duplicating note', async function () {
const component = await this.application.mutator.createItem(
ContentType.TYPES.Component,
{ area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } },
true,
)
const note = await Factory.insertItemWithOverride(this.application, ContentType.TYPES.Note, {
editorIdentifier: 'foo-editor',
})
expect(this.application.componentManager.editorForNote(note).uniqueIdentifier).to.equal(component.uuid)
const duplicate = await this.application.mutator.duplicateItem(note, true)
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier).to.equal(component.uuid)
})
})