fix: prevent crash in handleCreateNoteImmediate — slugify fallback + try/catch

- slugify now returns 'untitled' instead of empty string when input has only
  special characters, preventing invalid paths like '/vault//note.md'
- handleCreateNoteImmediate wrapped in try/catch — worst case shows a toast
  error instead of crashing the app
- Added Rust test for deeply nested directory creation in save_note_content
- Regression tests for slugify edge cases and handleCreateNoteImmediate with
  special-character types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-11 16:42:32 +01:00
parent b9139e2d57
commit d1021b9131
3 changed files with 81 additions and 14 deletions

View File

@@ -77,13 +77,21 @@ describe('slugify', () => {
expect(slugify('--hello--')).toBe('hello')
})
it('handles empty string', () => {
expect(slugify('')).toBe('')
it('handles empty string with fallback', () => {
expect(slugify('')).toBe('untitled')
})
it('collapses multiple separators into one hyphen', () => {
expect(slugify('hello world---foo')).toBe('hello-world-foo')
})
it('returns fallback for strings with only special characters', () => {
// slugify('+++') should not return empty string — it causes invalid paths
expect(slugify('+++')).not.toBe('')
expect(slugify('!!!')).not.toBe('')
expect(slugify('---')).not.toBe('')
expect(slugify('@#$')).not.toBe('')
})
})
describe('buildNewEntry', () => {
@@ -270,6 +278,20 @@ describe('resolveNewNote', () => {
expect(entry.path).toBe('/other/vault/note/test.md')
expect(entry.path).not.toContain('/Users/luca/Laputa')
})
it('produces a valid path for custom types with special characters', () => {
const { entry } = resolveNewNote('My Note', 'Q&A', '/vault')
expect(entry.path).not.toContain('//')
expect(entry.path).toMatch(/\.md$/)
expect(entry.filename).not.toBe('.md')
})
it('produces a valid path when type is all special characters', () => {
const { entry } = resolveNewNote('My Note', '+++', '/vault')
// folder should not be empty, path should not have double slashes
expect(entry.path).not.toContain('//')
expect(entry.path).toMatch(/\.md$/)
})
})
describe('resolveNewType', () => {
@@ -612,6 +634,34 @@ describe('useNoteActions hook', () => {
expect(tabContent).toContain('## Steps')
})
it('handleCreateNoteImmediate does not throw for custom types with special characters', () => {
const { result } = renderHook(() => useNoteActions(makeConfig()))
expect(() => {
act(() => {
result.current.handleCreateNoteImmediate('Q&A')
})
}).not.toThrow()
const [entry] = addEntry.mock.calls[0]
expect(entry.isA).toBe('Q&A')
expect(entry.path).not.toContain('//')
})
it('handleCreateNoteImmediate does not throw for types that slugify to empty string', () => {
const { result } = renderHook(() => useNoteActions(makeConfig()))
expect(() => {
act(() => {
result.current.handleCreateNoteImmediate('+++')
})
}).not.toThrow()
const [entry] = addEntry.mock.calls[0]
expect(entry.path).not.toContain('//')
expect(entry.filename).not.toBe('.md')
})
it('handleCreateNoteImmediate uses template for typed notes', () => {
const typeEntry = makeEntry({ isA: 'Type', title: 'Project', template: '## Custom Template\n\n' })
const { result } = renderHook(() => useNoteActions(makeConfig([typeEntry])))