Repository: dvanoni/notero
Pull request #599 (2024-10-12 18:36:46)

fix(sync): Improve warning when trying to sync note item without parent
Show new error message when trying to sync a note item that doesn't have a parent item (i.e. a standalone note).

Error message:

> Cannot sync note without a parent item.

github-actions[bot]: <!-- add-pr-comment:add-pr-comment -->

## 🤖 Build Bot

**✅ Successful build**

⬇️ Download: [notero-0.6.5-629](https://github.com/dvanoni/notero/actions/runs/11307908060/artifacts/2048977565)

Patch:
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 546ec7e..8f91ef9 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -62,6 +62,13 @@ export default tseslint.config(
           warnOnUnassignedImports: true,
         },
       ],
+      'no-console': 'error',
+    },
+  },
+  {
+    files: ['scripts/**'],
+    rules: {
+      'no-console': 'off',
     },
   },
 );
diff --git a/src/content/sync/sync-note.ts b/src/content/sync/sync-note.ts
index d4ee4f2..062111c 100644
--- a/src/content/sync/sync-note.ts
+++ b/src/content/sync/sync-note.ts
@@ -42,12 +42,19 @@ export async function syncNote(
   notion: Client,
   noteItem: Zotero.Item,
 ): Promise<void> {
+  if (noteItem.isTopLevelItem()) {
+    throw new LocalizableError(
+      'Cannot sync note without a parent item',
+      'notero-error-note-without-parent',
+    );
+  }
+
   const regularItem = noteItem.topLevelItem;
   const pageID = getNotionPageID(regularItem);
 
   if (!pageID) {
     throw new LocalizableError(
-      'Cannot sync note for item that is not synced',
+      'Cannot sync note because its parent item is not synced',
       'notero-error-note-parent-not-synced',
     );
   }
diff --git a/src/locale/en-US/notero.ftl b/src/locale/en-US/notero.ftl
index 5a1c553..24b0661 100644
--- a/src/locale/en-US/notero.ftl
+++ b/src/locale/en-US/notero.ftl
@@ -64,6 +64,7 @@ notero-error-missing-notion-token = Notion integration token not provided. Pleas
 notero-error-missing-pref = Missing value for { $pref }. Please enter it in Notero preferences.
 notero-error-no-notion-databases = No Notion databases are accessible.
 notero-error-note-conversion-failed = Failed to convert note content to Notion blocks.
-notero-error-note-parent-not-synced = Cannot sync note for item that is not synced.
+notero-error-note-parent-not-synced = Cannot sync note because its parent item is not synced.
 notero-error-note-sync-failed = Failed to sync note to Notion.
+notero-error-note-without-parent = Cannot sync note without a parent item.
 notero-error-notion-link-attachment = Failed to create Notion link attachment. This will result in duplicate Notion pages. Please ensure that the "read content" capability is enabled for the Notero integration at www.notion.so/my-integrations.
diff --git a/types/zotero.d.ts b/types/zotero.d.ts
index e1999f5..c818ddc 100644
--- a/types/zotero.d.ts
+++ b/types/zotero.d.ts
@@ -197,6 +197,8 @@ declare namespace Zotero {
 
     isRegularItem(): boolean;
 
+    isTopLevelItem(): boolean;
+
     setField(field: number | string, value: unknown, loadIn?: boolean): boolean;
 
     setNote(text: string): boolean;


Test patch:
diff --git a/src/content/sync/__tests__/sync-note.spec.ts b/src/content/sync/__tests__/sync-note.spec.ts
index 1a2c953..70f12b6 100644
--- a/src/content/sync/__tests__/sync-note.spec.ts
+++ b/src/content/sync/__tests__/sync-note.spec.ts
@@ -55,6 +55,7 @@ function setup({ syncedNotes }: { syncedNotes: SyncedNotes }) {
     },
   });
   const regularItem = createZoteroItemMock();
+  noteItem.isTopLevelItem.mockReturnValue(false);
   noteItem.topLevelItem = regularItem;
 
   vi.mocked(getNotionPageID).mockReturnValue(fakePageID);
@@ -74,6 +75,25 @@ function setup({ syncedNotes }: { syncedNotes: SyncedNotes }) {
 }
 
 describe('syncNote', () => {
+  it('throws an error when note has no parent', async () => {
+    const { noteItem, notion } = setup({ syncedNotes: {} });
+    noteItem.isTopLevelItem.mockReturnValue(true);
+    noteItem.topLevelItem = noteItem;
+
+    await expect(syncNote(notion, noteItem)).rejects.toThrow(
+      'Cannot sync note without a parent item',
+    );
+  });
+
+  it('throws an error when parent item is not synced', async () => {
+    const { noteItem, notion } = setup({ syncedNotes: {} });
+    vi.mocked(getNotionPageID).mockReturnValue(undefined);
+
+    await expect(syncNote(notion, noteItem)).rejects.toThrow(
+      'Cannot sync note because its parent item is not synced',
+    );
+  });
+
   it('creates a container block when item does not already have one', async () => {
     const { noteItem, notion } = setup({ syncedNotes: {} });