Skip to content

Commit ea91c63

Browse files
committed
refactor: extract filename via lastIndexOf instead of split/pop
Replace split('/').pop() ?? '' with slice(lastIndexOf('/') + 1) in modelStore and nodeBookmarkStore. Equivalent across no-slash/trailing-slash/empty inputs and drops the nullish fallback branch.
1 parent 25e73f3 commit ea91c63

3 files changed

Lines changed: 262 additions & 4 deletions

File tree

src/stores/modelStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export class ComfyModelDef {
6969
this.path_index = pathIndex
7070
this.file_name = name
7171
this.normalized_file_name = name.replaceAll('\\', '/')
72-
this.simplified_file_name = this.normalized_file_name.split('/').pop() ?? ''
72+
this.simplified_file_name = this.normalized_file_name.slice(
73+
this.normalized_file_name.lastIndexOf('/') + 1
74+
)
7375
if (this.simplified_file_name.endsWith('.safetensors')) {
7476
this.simplified_file_name = this.simplified_file_name.slice(
7577
0,
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import { createPinia, setActivePinia } from 'pinia'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
4+
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
5+
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
6+
7+
const BOOKMARK_ID = 'Comfy.NodeLibrary.Bookmarks.V2'
8+
const CUSTOMIZATION_ID = 'Comfy.NodeLibrary.BookmarksCustomization'
9+
10+
const { settings, setSpy, nodeDefs } = vi.hoisted(() => ({
11+
settings: {} as Record<string, unknown>,
12+
setSpy: vi.fn(),
13+
nodeDefs: {} as Record<string, unknown>
14+
}))
15+
16+
vi.mock('@/platform/settings/settingStore', async () => {
17+
const { reactive } = await import('vue')
18+
const reactiveSettings = reactive(settings)
19+
setSpy.mockImplementation(async (id: string, value: unknown) => {
20+
reactiveSettings[id] = value
21+
})
22+
return {
23+
useSettingStore: () => ({
24+
get: (id: string) => reactiveSettings[id],
25+
set: setSpy
26+
})
27+
}
28+
})
29+
30+
vi.mock('@/stores/nodeDefStore', () => ({
31+
useNodeDefStore: () => ({ allNodeDefsByName: nodeDefs }),
32+
buildNodeDefTree: (defs: unknown[]) => ({ key: 'root', children: defs }),
33+
createDummyFolderNodeDef: (path: string) => ({
34+
isDummyFolder: true,
35+
nodePath: path,
36+
name: path
37+
})
38+
}))
39+
40+
type BookmarkNodeFixture = Pick<
41+
ComfyNodeDefImpl,
42+
'isDummyFolder' | 'nodePath' | 'category' | 'name'
43+
>
44+
45+
function folderNode(nodePath: string) {
46+
const node = {
47+
isDummyFolder: true,
48+
nodePath,
49+
category: nodePath.replace(/\/$/, ''),
50+
name: nodePath
51+
} satisfies BookmarkNodeFixture
52+
return node as ComfyNodeDefImpl
53+
}
54+
55+
function leafNode(name: string, nodePath = name) {
56+
const node = {
57+
isDummyFolder: false,
58+
name,
59+
nodePath,
60+
category: ''
61+
} satisfies BookmarkNodeFixture
62+
return node as ComfyNodeDefImpl
63+
}
64+
65+
beforeEach(() => {
66+
setActivePinia(createPinia())
67+
for (const key of Object.keys(settings)) delete settings[key]
68+
for (const key of Object.keys(nodeDefs)) delete nodeDefs[key]
69+
settings[BOOKMARK_ID] = []
70+
settings[CUSTOMIZATION_ID] = {}
71+
setSpy.mockClear()
72+
})
73+
74+
describe('nodeBookmarkStore', () => {
75+
it('reports isBookmarked by either nodePath or top-level name', () => {
76+
settings[BOOKMARK_ID] = ['sampling/KSampler', 'LoadImage']
77+
const store = useNodeBookmarkStore()
78+
79+
expect(store.isBookmarked(leafNode('KSampler', 'sampling/KSampler'))).toBe(
80+
true
81+
)
82+
expect(store.isBookmarked(leafNode('LoadImage'))).toBe(true)
83+
expect(store.isBookmarked(leafNode('VAEDecode'))).toBe(false)
84+
})
85+
86+
it('adds a bookmark by appending to the current list', async () => {
87+
settings[BOOKMARK_ID] = ['A']
88+
const store = useNodeBookmarkStore()
89+
90+
await store.addBookmark('B')
91+
92+
expect(setSpy).toHaveBeenCalledWith(BOOKMARK_ID, ['A', 'B'])
93+
})
94+
95+
it('toggles an un-bookmarked node by adding its name', async () => {
96+
const store = useNodeBookmarkStore()
97+
98+
await store.toggleBookmark(leafNode('KSampler'))
99+
100+
expect(setSpy).toHaveBeenCalledWith(BOOKMARK_ID, ['KSampler'])
101+
})
102+
103+
it('toggles a bookmarked node by deleting both nodePath and name', async () => {
104+
settings[BOOKMARK_ID] = ['sampling/KSampler', 'KSampler']
105+
const store = useNodeBookmarkStore()
106+
107+
await store.toggleBookmark(leafNode('KSampler', 'sampling/KSampler'))
108+
109+
expect(setSpy).toHaveBeenCalledWith(BOOKMARK_ID, ['KSampler'])
110+
expect(setSpy).toHaveBeenLastCalledWith(BOOKMARK_ID, [])
111+
expect(store.bookmarks).toEqual([])
112+
})
113+
114+
it('creates a folder under a parent and at the root', async () => {
115+
const store = useNodeBookmarkStore()
116+
117+
const rootPath = await store.addNewBookmarkFolder(undefined, 'Favorites')
118+
expect(rootPath).toBe('Favorites/')
119+
120+
const childPath = await store.addNewBookmarkFolder(
121+
folderNode('Favorites/'),
122+
'Nested'
123+
)
124+
expect(childPath).toBe('Favorites/Nested/')
125+
})
126+
127+
it('parses each bookmark into its parent category, dropping unknown node defs', () => {
128+
nodeDefs['LoadImage'] = leafNode('LoadImage')
129+
nodeDefs['KSampler'] = leafNode('KSampler')
130+
nodeDefs['Canny'] = leafNode('Canny')
131+
settings[BOOKMARK_ID] = [
132+
'LoadImage',
133+
'sampling/KSampler',
134+
'image/preprocessors/Canny',
135+
'sampling/Unknown',
136+
'Folder/'
137+
]
138+
const store = useNodeBookmarkStore()
139+
140+
const children = (
141+
store.bookmarkedRoot as unknown as { children: BookmarkNodeFixture[] }
142+
).children
143+
144+
expect(
145+
children.map((node) =>
146+
node.isDummyFolder ? node.nodePath : [node.name, node.category]
147+
)
148+
).toEqual([
149+
['LoadImage', ''],
150+
['KSampler', 'sampling'],
151+
['Canny', 'image/preprocessors'],
152+
'Folder/'
153+
])
154+
})
155+
156+
describe('renameBookmarkFolder', () => {
157+
it('rejects renaming a non-folder node', async () => {
158+
const store = useNodeBookmarkStore()
159+
await expect(
160+
store.renameBookmarkFolder(leafNode('KSampler'), 'New')
161+
).rejects.toThrow('Cannot rename non-folder node')
162+
})
163+
164+
it('rejects a name containing a slash', async () => {
165+
const store = useNodeBookmarkStore()
166+
await expect(
167+
store.renameBookmarkFolder(folderNode('Old/'), 'a/b')
168+
).rejects.toThrow('cannot contain')
169+
})
170+
171+
it('rejects a rename that collides with an existing folder', async () => {
172+
settings[BOOKMARK_ID] = ['Taken/']
173+
const store = useNodeBookmarkStore()
174+
await expect(
175+
store.renameBookmarkFolder(folderNode('Old/'), 'Taken')
176+
).rejects.toThrow('already exists')
177+
})
178+
179+
it('rewrites matching bookmark paths on a valid rename', async () => {
180+
settings[BOOKMARK_ID] = ['Old/', 'Old/KSampler', 'Other/Node']
181+
const store = useNodeBookmarkStore()
182+
183+
await store.renameBookmarkFolder(folderNode('Old/'), 'New')
184+
185+
expect(setSpy).toHaveBeenCalledWith(BOOKMARK_ID, [
186+
'New/',
187+
'New/KSampler',
188+
'Other/Node'
189+
])
190+
})
191+
192+
it('does nothing when the folder keeps the same path', async () => {
193+
const store = useNodeBookmarkStore()
194+
195+
await store.renameBookmarkFolder(folderNode('Old/'), 'Old')
196+
197+
expect(setSpy).not.toHaveBeenCalled()
198+
})
199+
})
200+
201+
it('deletes a folder and all its descendants', async () => {
202+
settings[BOOKMARK_ID] = ['Old/', 'Old/KSampler', 'Keep/Node']
203+
const store = useNodeBookmarkStore()
204+
205+
await store.deleteBookmarkFolder(folderNode('Old/'))
206+
207+
expect(setSpy).toHaveBeenCalledWith(BOOKMARK_ID, ['Keep/Node'])
208+
})
209+
210+
it('rejects deleting a non-folder node', async () => {
211+
const store = useNodeBookmarkStore()
212+
213+
await expect(
214+
store.deleteBookmarkFolder(leafNode('KSampler'))
215+
).rejects.toThrow('Cannot delete non-folder node')
216+
})
217+
218+
describe('updateBookmarkCustomization', () => {
219+
it('persists a non-default customization', async () => {
220+
const store = useNodeBookmarkStore()
221+
222+
await store.updateBookmarkCustomization('Folder/', {
223+
color: '#ff0000',
224+
icon: 'pi-star'
225+
})
226+
227+
expect(setSpy).toHaveBeenCalledWith(CUSTOMIZATION_ID, {
228+
'Folder/': { color: '#ff0000', icon: 'pi-star' }
229+
})
230+
})
231+
232+
it('drops attributes set to their default values', async () => {
233+
const store = useNodeBookmarkStore()
234+
235+
await store.updateBookmarkCustomization('Folder/', {
236+
color: store.defaultBookmarkColor,
237+
icon: store.defaultBookmarkIcon
238+
})
239+
240+
expect(setSpy).toHaveBeenCalledWith(CUSTOMIZATION_ID, {
241+
'Folder/': undefined
242+
})
243+
})
244+
})
245+
246+
it('renames a customization entry, moving the old key to the new one', async () => {
247+
settings[CUSTOMIZATION_ID] = { 'Old/': { color: '#abc' } }
248+
const store = useNodeBookmarkStore()
249+
250+
await store.renameBookmarkCustomization('Old/', 'New/')
251+
252+
expect(setSpy).toHaveBeenCalledWith(CUSTOMIZATION_ID, {
253+
'New/': { color: '#abc' }
254+
})
255+
})
256+
})

src/stores/nodeBookmarkStore.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => {
5050
.map((bookmark: string) => {
5151
if (bookmark.endsWith('/')) return createDummyFolderNodeDef(bookmark)
5252

53-
const parts = bookmark.split('/')
54-
const name = parts.pop() ?? ''
55-
const category = parts.join('/')
53+
const slashIndex = bookmark.lastIndexOf('/')
54+
const name = bookmark.slice(slashIndex + 1)
55+
const category = bookmark.slice(0, Math.max(0, slashIndex))
5656
const srcNodeDef = nodeDefStore.allNodeDefsByName[name]
5757
if (!srcNodeDef) {
5858
return null

0 commit comments

Comments
 (0)