feat: add snjs package
This commit is contained in:
124
packages/snjs/lib/Migrations/Applicators/TagsToFolders.spec.ts
Normal file
124
packages/snjs/lib/Migrations/Applicators/TagsToFolders.spec.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { ItemManager } from '@Lib/Services'
|
||||
import { TagsToFoldersMigrationApplicator } from './TagsToFolders'
|
||||
|
||||
const itemManagerMock = (tagTitles: string[]) => {
|
||||
const mockTag = (title: string) => ({
|
||||
title,
|
||||
uuid: title,
|
||||
parentId: undefined,
|
||||
})
|
||||
|
||||
const mock = {
|
||||
getItems: jest.fn().mockReturnValue(tagTitles.map(mockTag)),
|
||||
findOrCreateTagParentChain: jest.fn(),
|
||||
changeItem: jest.fn(),
|
||||
}
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
describe('folders component to hierarchy', () => {
|
||||
it('should produce a valid hierarchy in the simple case', async () => {
|
||||
const titles = ['a', 'a.b', 'a.b.c']
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(2)
|
||||
expect(findOrCreateTagParentChainCalls[0][0]).toEqual(['a'])
|
||||
expect(findOrCreateTagParentChainCalls[1][0]).toEqual(['a', 'b'])
|
||||
|
||||
expect(changeItemCalls.length).toEqual(2)
|
||||
expect(changeItemCalls[0][0].uuid).toEqual('a.b')
|
||||
expect(changeItemCalls[1][0].uuid).toEqual('a.b.c')
|
||||
})
|
||||
|
||||
it('should not touch flat hierarchies', async () => {
|
||||
const titles = ['a', 'x', 'y', 'z']
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(0)
|
||||
|
||||
expect(changeItemCalls.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('should work despite cloned tags', async () => {
|
||||
const titles = ['a.b', 'c', 'a.b']
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(2)
|
||||
expect(findOrCreateTagParentChainCalls[0][0]).toEqual(['a'])
|
||||
expect(findOrCreateTagParentChainCalls[1][0]).toEqual(['a'])
|
||||
|
||||
expect(changeItemCalls.length).toEqual(2)
|
||||
expect(changeItemCalls[0][0].uuid).toEqual('a.b')
|
||||
expect(changeItemCalls[0][0].uuid).toEqual('a.b')
|
||||
})
|
||||
|
||||
it('should produce a valid hierarchy cases with missing intermediate tags or unordered', async () => {
|
||||
const titles = ['y.2', 'w.3', 'y']
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(2)
|
||||
expect(findOrCreateTagParentChainCalls[0][0]).toEqual(['w'])
|
||||
expect(findOrCreateTagParentChainCalls[1][0]).toEqual(['y'])
|
||||
|
||||
expect(changeItemCalls.length).toEqual(2)
|
||||
expect(changeItemCalls[0][0].uuid).toEqual('w.3')
|
||||
expect(changeItemCalls[1][0].uuid).toEqual('y.2')
|
||||
})
|
||||
|
||||
it('skip prefixed names', async () => {
|
||||
const titles = ['.something', '.something...something']
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(0)
|
||||
expect(changeItemCalls.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('skip not-supported names', async () => {
|
||||
const titles = [
|
||||
'something.',
|
||||
'something..',
|
||||
'something..another.thing',
|
||||
'a.b.c',
|
||||
'a',
|
||||
'something..another.thing..anyway',
|
||||
]
|
||||
|
||||
const itemManager = itemManagerMock(titles)
|
||||
await TagsToFoldersMigrationApplicator.run(itemManager as unknown as ItemManager)
|
||||
|
||||
const findOrCreateTagParentChainCalls = itemManager.findOrCreateTagParentChain.mock.calls
|
||||
const changeItemCalls = itemManager.changeItem.mock.calls
|
||||
|
||||
expect(findOrCreateTagParentChainCalls.length).toEqual(1)
|
||||
expect(findOrCreateTagParentChainCalls[0][0]).toEqual(['a', 'b'])
|
||||
|
||||
expect(changeItemCalls.length).toEqual(1)
|
||||
expect(changeItemCalls[0][0].uuid).toEqual('a.b.c')
|
||||
})
|
||||
})
|
||||
50
packages/snjs/lib/Migrations/Applicators/TagsToFolders.ts
Normal file
50
packages/snjs/lib/Migrations/Applicators/TagsToFolders.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { SNTag, TagMutator, TagFolderDelimitter } from '@standardnotes/models'
|
||||
import { ItemManager } from '@Lib/Services'
|
||||
import { lastElement, sortByKey, withoutLastElement } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
export class TagsToFoldersMigrationApplicator {
|
||||
public static isApplicableToCurrentData(itemManager: ItemManager): boolean {
|
||||
const tags = itemManager.getItems<SNTag>(ContentType.Tag)
|
||||
for (const tag of tags) {
|
||||
if (tag.title.includes(TagFolderDelimitter) && !tag.parentId) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
public static async run(itemManager: ItemManager): Promise<void> {
|
||||
const tags = itemManager.getItems(ContentType.Tag) as SNTag[]
|
||||
const sortedTags = sortByKey(tags, 'title')
|
||||
|
||||
for (const tag of sortedTags) {
|
||||
const hierarchy = tag.title.split(TagFolderDelimitter)
|
||||
const hasSimpleTitle = hierarchy.length === 1
|
||||
const hasParent = !!tag.parentId
|
||||
const hasUnsupportedTitle = hierarchy.some((title) => title.length === 0)
|
||||
|
||||
if (hasParent || hasSimpleTitle || hasUnsupportedTitle) {
|
||||
continue
|
||||
}
|
||||
|
||||
const parents = withoutLastElement(hierarchy)
|
||||
const newTitle = lastElement(hierarchy)
|
||||
|
||||
if (!newTitle) {
|
||||
return
|
||||
}
|
||||
|
||||
const parent = await itemManager.findOrCreateTagParentChain(parents)
|
||||
|
||||
await itemManager.changeItem(tag, (mutator: TagMutator) => {
|
||||
mutator.title = newTitle
|
||||
|
||||
if (parent) {
|
||||
mutator.makeChildOf(parent)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user