chore: add tests

This commit is contained in:
Julien Calixte
2026-06-06 22:13:14 +02:00
parent 8a8509a0f3
commit 1a2d8f4ebf
23 changed files with 1943 additions and 3 deletions

View File

@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest"
import { decodeBase64ToUTF8, encodeUTF8ToBase64 } from "./decodeBase64ToUTF8"
describe("base64 ↔ UTF-8 round-trip", () => {
it.each([
["ASCII", "Hello, world!"],
["multi-byte UTF-8", "Café résumé naïve"],
["CJK characters", "こんにちは世界"],
["emoji", "👋 🌍 🎉"],
["empty string", ""],
["newlines and whitespace", "line1\nline2\tend"],
["markdown content", "# Title\n\n- [[link]]\n- **bold**"]
])("round-trips %s", (_label, input) => {
expect(decodeBase64ToUTF8(encodeUTF8ToBase64(input))).toBe(input)
})
it("encodes ASCII to standard base64", () => {
expect(encodeUTF8ToBase64("hi")).toBe("aGk=")
})
it("decodes standard base64 to ASCII", () => {
expect(decodeBase64ToUTF8("aGk=")).toBe("hi")
})
})

View File

@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest"
import { getFileLanguage, isMarkdownPath } from "./fileLanguage"
describe("isMarkdownPath", () => {
it.each(["note.md", "dir/note.md", "note.mdx", "DIR/NOTE.MD"])(
"returns true for %s",
(path) => {
expect(isMarkdownPath(path)).toBe(true)
}
)
it.each(["note.txt", "script.ts", "no-extension", "", "image.png"])(
"returns false for %s",
(path) => {
expect(isMarkdownPath(path)).toBe(false)
}
)
})
describe("getFileLanguage", () => {
it.each([
["sh", "bash"],
["bash", "bash"],
["js", "javascript"],
["mjs", "javascript"],
["cjs", "javascript"],
["ts", "typescript"],
["mts", "typescript"],
["md", "markdown"],
["mdx", "markdown"],
["html", "html"],
["htm", "html"],
["css", "css"],
["scss", "css"],
["json", "json"],
["jsonc", "json"],
["als", "alloy"]
])("maps .%s to %s", (ext, lang) => {
expect(getFileLanguage(`file.${ext}`)).toBe(lang)
})
it("matches case-insensitively", () => {
expect(getFileLanguage("File.TS")).toBe("typescript")
})
it("returns null for unknown extensions", () => {
expect(getFileLanguage("file.xyz")).toBeNull()
})
it("returns null for files without an extension", () => {
expect(getFileLanguage("Makefile")).toBeNull()
})
it("returns null for empty input", () => {
expect(getFileLanguage("")).toBeNull()
})
})

View File

@@ -0,0 +1,54 @@
import MarkdownIt from "markdown-it"
import { describe, expect, it } from "vitest"
import { html5Media } from "./markdown-html5-media"
const renderer = () => MarkdownIt().use(html5Media)
describe("html5Media plugin", () => {
it("renders <video> for .mp4 links using image syntax", () => {
const html = renderer().render("![demo](movie.mp4)")
expect(html).toContain('<video src="movie.mp4"')
expect(html).toContain("</video>")
})
it("renders <audio> for .mp3 links using image syntax", () => {
const html = renderer().render("![demo](song.mp3)")
expect(html).toContain('<audio src="song.mp3"')
expect(html).toContain("</audio>")
})
it("renders <img> for unrecognized extensions", () => {
const html = renderer().render("![alt](pic.png)")
expect(html).toContain('<img src="pic.png"')
expect(html).not.toContain("<video")
expect(html).not.toContain("<audio")
})
it("recognizes all listed video extensions", () => {
for (const ext of ["mp4", "m4v", "ogv", "webm", "mpg", "mpeg"]) {
expect(renderer().render(`![v](clip.${ext})`)).toContain(
`<video src="clip.${ext}"`
)
}
})
it("recognizes all listed audio extensions", () => {
for (const ext of ["aac", "m4a", "mp3", "oga", "ogg", "wav"]) {
expect(renderer().render(`![a](sound.${ext})`)).toContain(
`<audio src="sound.${ext}"`
)
}
})
it("includes a title attribute when provided in image syntax", () => {
const html = renderer().render('![demo](movie.mp4 "My title")')
expect(html).toContain('title="My title"')
})
it("matches extensions case-insensitively", () => {
expect(renderer().render("![v](CLIP.MP4)")).toContain(
'<video src="CLIP.MP4"'
)
})
})

View File

@@ -0,0 +1,42 @@
import MarkdownIt from "markdown-it"
import { describe, expect, it } from "vitest"
import { markdownItPlugin } from "./markdown-it-regexp"
describe("markdownItPlugin", () => {
it("calls the replacer when the pattern matches at start of inline content", () => {
const plugin = markdownItPlugin(/@(\w+)/, (match) => {
return `<span class="mention">${match[1]}</span>`
})
const md = MarkdownIt().use(plugin)
expect(md.render("@alice posted a note")).toContain(
'<span class="mention">alice</span>'
)
})
it("leaves non-matching text untouched", () => {
const plugin = markdownItPlugin(/@(\w+)/, (match) => `MATCH:${match[1]}`)
const md = MarkdownIt().use(plugin)
expect(md.render("no mention here")).toBe("<p>no mention here</p>\n")
})
it("supports case-insensitive matching when the input regex has the i flag", () => {
const plugin = markdownItPlugin(/@hello/i, () => "<b>hi</b>")
const md = MarkdownIt().use(plugin)
expect(md.render("@HELLO world")).toContain("<b>hi</b>")
})
it("each plugin instance gets a unique rule id (no collisions)", () => {
const pluginA = markdownItPlugin(/@a/, () => "RULE_A")
const pluginB = markdownItPlugin(/@b/, () => "RULE_B")
const md = MarkdownIt().use(pluginA).use(pluginB)
const result = md.render("@a and @b")
expect(result).toContain("RULE_A")
expect(result).toContain("RULE_B")
})
})

View File

@@ -0,0 +1,31 @@
import MarkdownIt from "markdown-it"
import { describe, expect, it } from "vitest"
import { markdownItTablerIcons } from "./markdown-it-tabler-icons"
describe("markdownItTablerIcons", () => {
const renderer = () => MarkdownIt().use(markdownItTablerIcons)
it("renders a tabler icon for :icon-name: syntax", () => {
expect(renderer().render(":home:")).toContain(
'<i class="ti ti-home"></i>'
)
})
it("supports hyphenated icon names", () => {
expect(renderer().render(":arrow-right:")).toContain(
'<i class="ti ti-arrow-right"></i>'
)
})
it("renders icons inline alongside surrounding text", () => {
const html = renderer().render("Click :check: to confirm")
expect(html).toContain("Click")
expect(html).toContain('<i class="ti ti-check"></i>')
expect(html).toContain("to confirm")
})
it("does not fire on text without colon delimiters", () => {
expect(renderer().render("home")).toBe("<p>home</p>\n")
})
})

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from "vitest"
import {
filenameToNoteTitle,
pathToNotePathTitle,
pathToNoteTitle
} from "./noteTitle"
describe("filenameToNoteTitle", () => {
it("replaces hyphens with spaces", () => {
expect(filenameToNoteTitle("my-cool-note")).toBe("my cool note")
})
it("wraps slashes with spaces", () => {
expect(filenameToNoteTitle("dir/sub/file")).toBe("dir / sub / file")
})
it("returns empty input unchanged", () => {
expect(filenameToNoteTitle("")).toBe("")
})
})
describe("pathToNotePathTitle", () => {
it("strips the file extension", () => {
expect(pathToNotePathTitle("note.md")).toBe("note")
})
it("filters out README segments", () => {
expect(pathToNotePathTitle("folder/README.md")).toBe("folder")
})
it("preserves multi-level paths and replaces hyphens", () => {
expect(pathToNotePathTitle("dir/my-sub-dir/my-note.md")).toBe(
"dir/my sub dir/my note"
)
})
it("handles paths with multiple dots correctly", () => {
expect(pathToNotePathTitle("a/b.c.md")).toBe("a/b.c")
})
})
describe("pathToNoteTitle", () => {
it("returns the last segment of a multi-level path", () => {
expect(pathToNoteTitle("dir/sub/my-note.md")).toBe("my note")
})
it("returns the title for a root-level file", () => {
expect(pathToNoteTitle("my-note.md")).toBe("my note")
})
it("returns empty string for README files at root", () => {
expect(pathToNoteTitle("README.md")).toBe("")
})
it("returns the parent dir name when the file is a README", () => {
expect(pathToNoteTitle("folder/README.md")).toBe("folder")
})
})

34
src/utils/slugify.spec.ts Normal file
View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest"
import { slugify } from "./slugify"
describe("slugify", () => {
it("lowercases and replaces spaces with hyphens", () => {
expect(slugify("Hello World")).toBe("hello-world")
})
it("strips diacritics via NFD normalization", () => {
expect(slugify("Café Résumé")).toBe("cafe-resume")
})
it("collapses non-alphanumeric runs into a single hyphen", () => {
expect(slugify("a !! b c__d")).toBe("a-b-c-d")
})
it("trims leading and trailing hyphens", () => {
expect(slugify("---hello---")).toBe("hello")
expect(slugify("!!!hello!!!")).toBe("hello")
})
it("returns empty string for empty input", () => {
expect(slugify("")).toBe("")
})
it("returns empty string when input is only special characters", () => {
expect(slugify("!@#$%^")).toBe("")
})
it("preserves digits", () => {
expect(slugify("Note 42")).toBe("note-42")
})
})

59
src/utils/youtube.spec.ts Normal file
View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from "vitest"
import { extractYouTubeId } from "./youtube"
describe("extractYouTubeId", () => {
it("returns null for empty input", () => {
expect(extractYouTubeId("")).toBeNull()
})
it("returns the trimmed string when input is not a valid URL", () => {
expect(extractYouTubeId(" dQw4w9WgXcQ ")).toBe("dQw4w9WgXcQ")
})
it("extracts id from youtu.be short URLs", () => {
expect(extractYouTubeId("https://youtu.be/dQw4w9WgXcQ")).toBe(
"dQw4w9WgXcQ"
)
})
it("extracts id from youtube.com/watch?v=", () => {
expect(
extractYouTubeId("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
).toBe("dQw4w9WgXcQ")
})
it("extracts id from youtube.com/embed/", () => {
expect(extractYouTubeId("https://www.youtube.com/embed/dQw4w9WgXcQ")).toBe(
"dQw4w9WgXcQ"
)
})
it("extracts id from youtube.com/shorts/", () => {
expect(extractYouTubeId("https://www.youtube.com/shorts/abc123XYZ")).toBe(
"abc123XYZ"
)
})
it("extracts id from youtube.com/live/", () => {
expect(extractYouTubeId("https://www.youtube.com/live/abc123XYZ")).toBe(
"abc123XYZ"
)
})
it("prefers v= param over path segments", () => {
expect(
extractYouTubeId(
"https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=PLxyz"
)
).toBe("dQw4w9WgXcQ")
})
it("returns null for non-YouTube hosts", () => {
expect(extractYouTubeId("https://example.com/watch?v=dQw4w9WgXcQ")).toBeNull()
})
it("returns null for youtube.com root URL with no id", () => {
expect(extractYouTubeId("https://www.youtube.com/")).toBeNull()
})
})