diff --git a/src/modules/template/controller.ts b/src/modules/template/controller.ts index 98eef63..1a09173 100644 --- a/src/modules/template/controller.ts +++ b/src/modules/template/controller.ts @@ -62,7 +62,12 @@ function removeTemplate(keyName: string | undefined): void { addon.data.template.data?.deleteKey(keyName); } -function importTemplateFromClipboard(text?: string) { +function importTemplateFromClipboard( + text?: string, + options: { + quiet?: boolean; + } = {}, +) { if (!text) { text = Zotero.Utilities.Internal.getClipboard("text/plain") || ""; } @@ -83,7 +88,10 @@ function importTemplateFromClipboard(text?: string) { showHint("The copied template is invalid"); return; } - if (!window.confirm(`Import template "${template.name}"?`)) { + if ( + !options.quiet && + !window.confirm(`Import template "${template.name}"?`) + ) { return; } setTemplate({ name: template.name, text: template.content }); @@ -91,4 +99,5 @@ function importTemplateFromClipboard(text?: string) { if (addon.data.template.editor.window) { addon.data.template.editor.window.refresh(); } + return template.name; } diff --git a/test/tests/startup.spec.ts b/test/tests/startup.spec.ts index 2b579f9..80911f9 100644 --- a/test/tests/startup.spec.ts +++ b/test/tests/startup.spec.ts @@ -1,7 +1,7 @@ -import { config } from "../../package.json"; +import { getAddon } from "../utils/global"; describe("Startup", function () { it("should have plugin instance defined", function () { - assert.isNotEmpty(Zotero[config.addonRef]); + assert.isNotEmpty(getAddon()); }); }); diff --git a/test/tests/template.spec.ts b/test/tests/template.spec.ts new file mode 100644 index 0000000..a060ae8 --- /dev/null +++ b/test/tests/template.spec.ts @@ -0,0 +1,326 @@ +import { ClipboardHelper } from "zotero-plugin-toolkit"; +import { getAddon } from "../utils/global"; +import { resetAll } from "../utils/status"; + +describe("Template", function () { + const addon = getAddon(); + this.beforeAll(async function () { + await resetAll(); + }); + + this.afterEach(async function () {}); + + it("hooks.onImportTemplateFromClipboard", async function () { + const key = importTemplate(); + assert.isNotEmpty(key); + addon.api.template.removeTemplate(key); + }); + + it("api.template.getTemplateText", async function () { + const key = importTemplate(); + assert.isNotEmpty(addon.api.template.getTemplateText(key!)); + addon.api.template.removeTemplate(key); + }); + + it("api.template.getTemplateText", async function () { + const key = importTemplate(); + assert.isTrue(addon.api.template.getTemplateKeys().includes(key!)); + addon.api.template.removeTemplate(key); + }); + + it("api.template.removeTemplate", async function () { + const key = importTemplate(); + assert.isNotEmpty(key); + addon.api.template.removeTemplate(key!); + assert.isFalse(addon.api.template.getTemplateKeys().includes(key!)); + }); + + it("api.template.renderTemplatePreview", async function () { + const key = importTemplate(); + const preview = await addon.api.template.renderTemplatePreview(key!); + const expected = + '

Test Document

\n

\n

Headers

\n

H1 Header

\n

H2 Header

\n

H3 Header

\n

H4 Header

\n
H5 Header
\n
H6 Header
\n

Emphasis

\n

This text is italicized. This text is also italicized.

\n

This text is bold. This text is also bold.

\n

This text is bold and italicized. This text is also bold and italicized.

\n

Links

\n

Link with title Link without title

\n

Images

\n

\n

Blockquotes

\n
\n

This is a blockquote.

\n
\n

Nested blockquote.

\n
\n

Back to the outer blockquote.

\n
\n

Lists

\n

Unordered List

\n\n

Ordered List

\n
    \n
  1. \n

    First item

    \n
      \n
    1. \n

      Subitem 1.1

      \n
        \n
      1. \nSubitem 1.1.1\n
      2. \n
      \n
    2. \n
    \n
  2. \n
  3. \nSecond item\n
  4. \n
\n

Code

\n

Inline Code

\n

Here is some inline code.

\n

Code Block

\n
def hello_world():\n    print("Hello, world!")\n
\n

Horizontal Rules

\n
\n

This is text between horizontal rules

\n
\n

Tables

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n

Header 1

\n
\n

Header 2

\n
\n

Header 3

\n
\n

Row 1

\n
\n

Data 1.2

\n
\n

Data 1.3

\n
\n

Row 2

\n
\n

Data 2.2

\n
\n

Data 2.3

\n
\n

Math

\n

Inline Math

\n

This is an inline math equation: E=mc2E = mc^2.

\n

Block Math

\n

Below is a block math equation:

\n
abf(x)dx=F(b)F(a)\\int_a^b f(x) \\, dx = F(b) - F(a)
\n

Complex Math

\n

Solve the quadratic equation:

\n
x=b±b24ac2ax = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}
\n

Nested Elements

\n

Nested Lists and Blockquotes

\n\n

Nested Code and Lists

\n
    \n
  1. \n

    Ordered list item

    \n\n
  2. \n
\n

Special Characters

\n

Escape sequences for special characters: * _ ` [ ] ( ) # + - .

\n

HTML in Markdown

\n

This is a HTML block inside Markdown.

\n

Highlight Text

\n

Highlight text is here

\n

Colored Text

\n

Colored text is here

\n

Task Lists

\n\n

Strikethrough

\n

This text is strikethrough.

\n

Recursive Elements

\n

Recursive Links and Emphasis

\n

Bold link

\n

Recursive Emphasis

\n

Bold and nested italic within bold.

\n

Edge Cases

\n

Empty Link

\n

\n

Lone Asterisk

\n\n

Broken Lists

\n\n

Long Text Wrapping

\n

This is a very long paragraph that does not have any line breaks and is intended to test how the Markdown engine handles text wrapping when there are no explicit line breaks within the text.

\n
\n

Conclusion

\n

This document contains a wide range of Markdown elements, including headers, lists, blockquotes, inline and block code, tables, images, links, math, and special characters. It also tests recursive and edge cases to ensure the Markdown engine is robust.

'; + // debug(preview); + // new ClipboardHelper().addText(expected).copy(); + assert.equal(preview, expected); + addon.api.template.removeTemplate(key); + }); + + it("api.template.runTextTemplate", async function () { + addon.api.template.setTemplate({ + name: "[text]Test", + text: "

Test

\n

${targetNoteItem.id}

", + }); + const note = new Zotero.Item("note"); + await note.saveTx(); + const html = await addon.api.template.runTextTemplate("[text]Test", { + targetNoteId: note.id, + }); + assert.equal(html, `

Test

\n

${note.id}

`); + await Zotero.Items.erase(note.id); + addon.api.template.removeTemplate("[text]Test"); + }); + + it("api.template.runItemTemplate", async function () { + // Also test the use of Markdown pragma + addon.api.template.setTemplate({ + name: "[item]Test", + text: ` +// @beforeloop-begin +// @use-markdown +# Hi! This only renders once +// @beforeloop-end +// @default-begin +

Title: ]\${topItem.getField("title")}

+\${{ + const note = Zotero.Items.get(targetNoteItem.id); + return "

" + note.id + "

"; +}}$ +// @default-end +// @afterloop-begin +> Done! But Markdown is not rendered correctly. Try to add +\`// @use-markdown\` pragma before this line. +// @afterloop-end +`, + }); + const items = []; + for (let i = 0; i < 3; i++) { + const item = new Zotero.Item("book"); + item.setField("title", `Title ${i}`); + await item.saveTx(); + items.push(item); + } + const note = new Zotero.Item("note"); + await note.saveTx(); + const html = await addon.api.template.runItemTemplate("[item]Test", { + itemIds: items.map((item) => item.id), + targetNoteId: note.id, + }); + // new ClipboardHelper().addText(html).copy(); + const expected = + '

Hi! This only renders once

\n

Title: ]Title 0

\n

5

\n

Title: ]Title 1

\n

5

\n

Title: ]Title 2

\n

5

\n> Done! But Markdown is not rendered correctly. Try to add \n'; + assert.equal(html, expected); + for (const item of items) { + await Zotero.Items.erase(item.id); + } + await Zotero.Items.erase(note.id); + addon.api.template.removeTemplate("[item]Test"); + }); +}); + +function importTemplate() { + const shareCode = ` +# This template is specifically for importing/sharing, using better +# notes 'import from clipboard': copy the content and +# goto Zotero menu bar, click Tools->New Template from Clipboard. +# Do not copy-paste this to better notes template editor directly. +name: "[text]TestGen" +zoteroVersion: "7.0.12-beta.1+31bbf2acf" +pluginVersion: "2.2.3-beta.2" +savedAt: "2025-01-06T09:12:14.939Z" +content: |- +

Test Document

+

+

Headers

+

H1 Header

+

H2 Header

+

H3 Header

+

H4 Header

+
H5 Header
+
H6 Header
+

Emphasis

+

This text is italicized. This text is also italicized.

+

This text is bold. This text is also bold.

+

This text is bold and italicized. This text is also bold and italicized.

+

Links

+

Link with title Link without title

+

Images

+

+

Blockquotes

+
+

This is a blockquote.

+
+

Nested blockquote.

+
+

Back to the outer blockquote.

+
+

Lists

+

Unordered List

+ +

Ordered List

+
    +
  1. +

    First item

    +
      +
    1. +

      Subitem 1.1

      +
        +
      1. + Subitem 1.1.1 +
      2. +
      +
    2. +
    +
  2. +
  3. + Second item +
  4. +
+

Code

+

Inline Code

+

Here is some inline code.

+

Code Block

+
def hello_world():
+      print("Hello, world!")
+  
+

Horizontal Rules

+
+

This is text between horizontal rules

+
+

Tables

+ + + + + + + + + + + + + + + + + + +
+

Header 1

+
+

Header 2

+
+

Header 3

+
+

Row 1

+
+

Data 1.2

+
+

Data 1.3

+
+

Row 2

+
+

Data 2.2

+
+

Data 2.3

+
+

Math

+

Inline Math

+

This is an inline math equation: $E = mc^2$.

+

Block Math

+

Below is a block math equation:

+
$$\\\\int_a^b f(x) \\\\, dx = F(b) - F(a)$$
+

Complex Math

+

Solve the quadratic equation:

+
$$x = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$$
+

Nested Elements

+

Nested Lists and Blockquotes

+ +

Nested Code and Lists

+
    +
  1. +

    Ordered list item

    + +
  2. +
+

Special Characters

+

Escape sequences for special characters: * _ \\\` [ ] ( ) # + - .

+

HTML in Markdown

+

This is a HTML block inside Markdown.

+

Highlight Text

+

Highlight text is here

+

Colored Text

+

Colored text is here

+

Task Lists

+ +

Strikethrough

+

This text is strikethrough.

+

Recursive Elements

+

Recursive Links and Emphasis

+

Bold link

+

Recursive Emphasis

+

Bold and nested italic within bold.

+

Edge Cases

+

Empty Link

+

+

Lone Asterisk

+ +

Broken Lists

+ +

Long Text Wrapping

+

This is a very long paragraph that does not have any line breaks and is intended to test how the Markdown engine handles text wrapping when there are no explicit line breaks within the text.

+
+

Conclusion

+

This document contains a wide range of Markdown elements, including headers, lists, blockquotes, inline and block code, tables, images, links, math, and special characters. It also tests recursive and edge cases to ensure the Markdown engine is robust.

+`; + return getAddon().hooks.onImportTemplateFromClipboard(shareCode, { + quiet: true, + }); +} diff --git a/test/utils/global.ts b/test/utils/global.ts new file mode 100644 index 0000000..5f0a9e9 --- /dev/null +++ b/test/utils/global.ts @@ -0,0 +1,8 @@ +import { config } from "../../package.json"; + +export { getAddon }; + +function getAddon(): import("../../src/addon").default { + // @ts-ignore - plugin instance + return Zotero[config.addonRef]; +}