import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import zlib from 'node:zlib'; import { DEFAULTS, cleanPrefs, wikiToMarkdownLinks } from '../src/lib/prefs.js'; import { formatDate, dailyId, dailyBody } from '../src/lib/daily.js'; import { weekOf, monthGrid, weekdayNames } from '../src/lib/database.js'; const require = createRequire(import.meta.url); test('cleanPrefs fills defaults and drops values of the wrong kind', () => { assert.deepEqual(cleanPrefs(undefined), DEFAULTS); assert.deepEqual(cleanPrefs('nope'), DEFAULTS); const p = cleanPrefs({ density: 'huge', sidebarWidth: 9999, codeSize: '110', autoPair: 'yes', hiddenKinds: ['quote', 4], trashDays: -3, dateFormat: 'DD/MM' }); assert.equal(p.density, 'default'); assert.equal(p.sidebarWidth, 420); assert.equal(p.codeSize, 110); assert.equal(p.autoPair, true); assert.deepEqual(p.hiddenKinds, ['quote']); assert.equal(p.trashDays, 0); assert.equal(p.dateFormat, 'YYYY-MM-DD'); assert.equal(cleanPrefs({ historyKeep: '' }).historyKeep, DEFAULTS.historyKeep); }); test('daily note names follow the chosen format', () => { const d = new Date(2026, 0, 5); assert.equal(formatDate(d), '2026-01-05'); assert.equal(formatDate(d, 'DD-MM-YYYY'), '05-01-2026'); assert.equal(formatDate(d, 'MMMM D, YYYY', 'en-US'), 'January 5, 2026'); assert.equal(formatDate(d, 'YYYY-MM-DD dddd', 'en-US'), '2026-01-05 Monday'); assert.equal(dailyId('Daily', d, 'YYYY.MM.DD'), 'Daily/2026.01.05'); assert.equal(dailyId('Daily', d), 'Daily/2026-01-05'); }); test('the daily template fills in the date and weekday', () => { const d = new Date(2026, 0, 5); assert.equal(dailyBody('## {{weekday}} {{ date }}', d, 'DD-MM-YYYY', 'en-US'), '## Monday 05-01-2026'); assert.equal(dailyBody('', d), ''); }); test('calendar weeks can start on Sunday', () => { assert.equal(weekOf('2026-01-07')[0].iso, '2026-01-05'); // Monday assert.equal(weekOf('2026-01-07', true)[0].iso, '2026-01-04'); // Sunday assert.equal(weekOf('2026-01-04', true)[0].iso, '2026-01-04'); assert.deepEqual(weekdayNames(true).slice(0, 2), ['Sun', 'Mon']); const g = monthGrid(2026, 1, true); // February 2026 starts on a Sunday: a Sunday-first grid has no lead days assert.equal(g[0][0].iso, '2026-02-01'); assert.equal(g.length, 4); }); test('Markdown export can write links as Markdown links', () => { const md = 'See [[My Page]] and [[Other|that one]], ![[photo.png]], [[Page#Some Heading]].\n`[[kept]]`\n```\n[[kept too]]\n```'; const out = wikiToMarkdownLinks(md); assert.match(out, /\[My Page\]\(My%20Page\.md\)/); assert.match(out, /\[that one\]\(Other\.md\)/); assert.match(out, /!\[\]\(photo\.png\)/); assert.match(out, /\(Page\.md#some-heading\)/); assert.match(out, /`\[\[kept\]\]`/); assert.match(out, /\n\[\[kept too\]\]\n/); }); test('a backup is a readable zip without history and trash', async () => { const { zipFolder, backup, due, crc32 } = require('../electron/backup.cjs'); assert.equal(crc32(Buffer.from('123456789')), 0xcbf43926); const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-bk-')); const out = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-bk-out-')); fs.mkdirSync(path.join(root, 'A'), { recursive: true }); fs.mkdirSync(path.join(root, '.notes', 'history', 'A'), { recursive: true }); fs.writeFileSync(path.join(root, 'A.md'), '# Hello\n'.repeat(50)); fs.writeFileSync(path.join(root, 'A', 'Sub é.md'), 'x'); fs.writeFileSync(path.join(root, '.notes', 'history', 'A', '1.md'), 'old'); const file = path.join(out, 'x.zip'); assert.equal(await zipFolder(root, file), 2); // read it back: end record -> central directory -> entries const buf = fs.readFileSync(file); const end = buf.length - 22; assert.equal(buf.readUInt32LE(end), 0x06054b50); const n = buf.readUInt16LE(end + 10); let at = buf.readUInt32LE(end + 16); const found = {}; for (let i = 0; i < n; i++) { const method = buf.readUInt16LE(at + 10), crc = buf.readUInt32LE(at + 16), csize = buf.readUInt32LE(at + 20), size = buf.readUInt32LE(at + 24); const nameLen = buf.readUInt16LE(at + 28), off = buf.readUInt32LE(at + 42); const name = buf.toString('utf8', at + 46, at + 46 + nameLen); const dataAt = off + 30 + buf.readUInt16LE(off + 26) + buf.readUInt16LE(off + 28); const raw = buf.subarray(dataAt, dataAt + csize); const data = method === 8 ? zlib.inflateRawSync(raw) : raw; assert.equal(data.length, size); assert.equal(crc32(data), crc); found[name] = data.toString(); at += 46 + nameLen; } assert.deepEqual(Object.keys(found).sort(), ['A.md', 'A/Sub é.md']); assert.equal(found['A/Sub é.md'], 'x'); // keeping only the newest few for (const f of ['2020-01-01 0000', '2020-01-02 0000', '2020-01-03 0000']) fs.writeFileSync(path.join(out, `${path.basename(root)} ${f}.zip`), 'z'); await backup(root, out, 2); assert.equal(fs.readdirSync(out).filter((f) => f.startsWith(path.basename(root))).length, 2); assert.equal(due('off', 0), false); assert.equal(due('daily', 0), true); assert.equal(due('daily', Date.now() - 3600e3), false); assert.equal(due('weekly', Date.now() - 3 * 864e5), false); }); test('history limits can be changed', async () => { const { Vault, setLimits } = require('../electron/vault.cjs'); const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-hist-')); const v = new Vault(dir); setLimits({ historyKeep: 3, historyGapMin: 1 }); try { for (let i = 0; i < 6; i++) { await v.write('P', {}, 'v' + i); await v.snapshot('P', 'text ' + i, { force: true }); } assert.equal((await v.listHistory('P')).length, 3); } finally { setLimits({ historyKeep: 100, historyGapMin: 5 }); } });