From e3bc6dbb653cc2ca172921886b33dc7c9868a7f2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 3 Sep 2026 19:05:20 +0300 Subject: [PATCH] drafts unit test --- src/stores/drafts.js | 8 +- test/unit/specs/stores/drafts.spec.js | 196 ++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 test/unit/specs/stores/drafts.spec.js diff --git a/src/stores/drafts.js b/src/stores/drafts.js index 0d2986e3c..1a57a71af 100644 --- a/src/stores/drafts.js +++ b/src/stores/drafts.js @@ -52,10 +52,6 @@ export const useDraftsStore = defineStore('drafts', { }, }, actions: { - async abandonDraft(id) { - this.drafts.delete(id) - await deleteDraftFromStorage([id]) - }, async loadDrafts() { const currentData = (await getStorageData()) ?? {} this.drafts = new Map(Object.entries(currentData)) @@ -67,6 +63,10 @@ export const useDraftsStore = defineStore('drafts', { await saveDraftToStorage(draftWithId) return id }, + async abandonDraft(id) { + this.drafts.delete(id) + await deleteDraftFromStorage([id]) + }, async abandonAllDrafts() { const ids = [...this.drafts.keys()] ids.forEach((id) => this.drafts.delete(id)) diff --git a/test/unit/specs/stores/drafts.spec.js b/test/unit/specs/stores/drafts.spec.js new file mode 100644 index 000000000..e9b7b9614 --- /dev/null +++ b/test/unit/specs/stores/drafts.spec.js @@ -0,0 +1,196 @@ +import { createTestingPinia } from '@pinia/testing' +import { setActivePinia } from 'pinia' + +import { useDraftsStore } from 'src/stores/drafts.js' + +import { storage } from 'src/lib/storage.js' + +describe('Drafts store', () => { + beforeEach(() => { + setActivePinia(createTestingPinia({ stubActions: false })) + + // Localforage does something weird that resets get/setItem and breaks + // mocking if we just spy one them without overriding implementation + vi.spyOn(storage, 'setItem').mockImplementation(() => ({})) + vi.spyOn(storage, 'getItem').mockImplementation(() => ({})) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + describe('Getters', () => { + it('draftsCount returns total number of drafts', async () => { + const store = useDraftsStore() + + await store.addOrSaveDraft({ id: 1, status: 'draft' }) + await store.addOrSaveDraft({ id: 2, status: 'draft' }) + await store.addOrSaveDraft({ id: 3, status: 'draft' }) + + expect(store).to.have.property('draftsCount', 3) + }) + + it('draftsArray returns array of drafts', async () => { + const store = useDraftsStore() + + await store.addOrSaveDraft({ id: 1, status: 'draft' }) + await store.addOrSaveDraft({ id: 2, status: 'draft' }) + await store.addOrSaveDraft({ id: 3, status: 'draft' }) + + expect(store.draftsArray).to.have.length(3) + expect(store.draftsArray).to.be.an('Array') + expect(store.draftsArray).to.have.deep.members([ + { id: 1, status: 'draft' }, + { id: 2, status: 'draft' }, + { id: 3, status: 'draft' }, + ]) + }) + + it('draftsByTypeAndRefId', async () => { + const store = useDraftsStore() + + await store.addOrSaveDraft({ id: 1, type: 'edit', refId: 'e1', status: 'draft' }) + await store.addOrSaveDraft({ id: 2, type: 'reply', refId: 'r1', status: 'draft' }) + await store.addOrSaveDraft({ id: 3, status: 'draft' }) + await store.addOrSaveDraft({ id: 4, type: 'edit', refId: 'e2', status: 'draft' }) + await store.addOrSaveDraft({ id: 5, type: 'reply', refId: 'r2', status: 'draft' }) + + expect(store.draftsByTypeAndRefId).to.be.a('function') + expect(store.draftsByTypeAndRefId('edit', 'e1')).to.eql([{ id: 1, type: 'edit', refId: 'e1', status: 'draft' }]) + expect(store.draftsByTypeAndRefId('reply', 'r1')).to.eql([{ id: 2, type: 'reply', refId: 'r1', status: 'draft' }]) + }) + }) + + describe('Actions', () => { + describe('loadDrafts', () => { + it('should load drafts from storage and populate cache', async () => { + const store = useDraftsStore() + storage.getItem.mockResolvedValueOnce({ + a: { id: 'a', status: 'draft' }, + b: { id: 'b', status: 'draft' }, + c: { id: 'c', status: 'draft' }, + }) + + await store.loadDrafts() + + expect(store.drafts.get('a')).to.have.property('status', 'draft') + expect(store.drafts.get('b')).to.have.property('status', 'draft') + expect(store.drafts.get('c')).to.have.property('status', 'draft') + + expect(storage.getItem).to.have.been.calledOnce + expect(storage.getItem).to.have.been.calledWith('pleroma-fe-drafts') + expect(storage.setItem).to.have.not.been.called + }) + + it('should handle case where there is no local draft storage yet', async () => { + const store = useDraftsStore() + storage.getItem.mockResolvedValueOnce(null) + + await store.loadDrafts() + + expect(store.drafts).to.have.property('size', 0) + + expect(storage.getItem).to.have.been.calledOnce + expect(storage.getItem).to.have.been.calledWith('pleroma-fe-drafts') + expect(storage.setItem).to.have.not.been.called + }) + }) + + describe('addOrSaveDraft', () => { + it('create draft', async () => { + const store = useDraftsStore() + vi.setSystemTime(new Date(1997, 2, 29)) + + const id = await store.addOrSaveDraft({ status: 'draft' }) + + expect(store.drafts).to.have.property('size', 1) + expect(id).to.eql('859586400000') + expect(store.drafts.get(id)).to.have.property('status', 'draft') + expect(storage.getItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', { + [id]: { + id, + status: 'draft', + } + }) + }) + + it('update draft', async () => { + const store = useDraftsStore() + await store.addOrSaveDraft({ id: '1', status: 'draft' }) + + expect(store.drafts.get('1')).to.have.property('status', 'draft') + + await store.addOrSaveDraft({ id: '1', status: 'updated' }) + + expect(store.drafts.get('1')).to.have.property('status', 'updated') + expect(storage.getItem).to.have.been.calledTwice + expect(storage.setItem).to.have.been.calledTwice + expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', { + '1': { + id: '1', + status: 'draft', + } + }) + }) + }) + + describe('abandonDraft', () => { + it('should remove draft from storage and cache', async () => { + const store = useDraftsStore() + store.drafts.set('a', { id: 'a', status: 'draft' }) + store.drafts.set('b', { id: 'b', status: 'draft' }) + store.drafts.set('c', { id: 'c', status: 'draft' }) + storage.getItem.mockResolvedValueOnce({ + a: { id: 'a', status: 'draft' }, + b: { id: 'b', status: 'draft' }, + c: { id: 'c', status: 'draft' }, + }) + + await store.abandonDraft('b') + + expect(store.drafts.get('a')).to.have.property('status', 'draft') + expect(store.drafts.get('b')).to.be.undefined + expect(store.drafts.get('c')).to.have.property('status', 'draft') + + expect(storage.getItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', { + 'a': { + id: 'a', + status: 'draft', + }, + 'c': { + id: 'c', + status: 'draft', + } + }) + }) + }) + + describe('abandonAllDrafts', () => { + it('should remove draft from storage and cache', async () => { + const store = useDraftsStore() + store.drafts.set('a', { id: 'a', status: 'draft' }) + store.drafts.set('b', { id: 'b', status: 'draft' }) + store.drafts.set('c', { id: 'c', status: 'draft' }) + storage.getItem.mockResolvedValueOnce({ + a: { id: 'a', status: 'draft' }, + b: { id: 'b', status: 'draft' }, + c: { id: 'c', status: 'draft' }, + }) + + await store.abandonAllDrafts() + + expect(store.drafts.get('a')).to.be.undefined + expect(store.drafts.get('b')).to.be.undefined + expect(store.drafts.get('c')).to.be.undefined + + expect(storage.getItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledOnce + expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', {}) + }) + }) + }) +})