Add 230 Vitest unit tests covering the dependency-light, pure modules of packages/docmost-client/src/lib, imported directly from source: - node-ops: tree addressing, immutability/clone guarantees, table ops, throw-vs-noop contracts (87) - transforms: commentsToFootnotes reading-order renumbering, insertMarkerAfter mark-preserving split, setCalloutRange regex statefulness (43) - json-edit: applyTextEdits literal $&/$1, error distinction, immutability (17) - page-lock: async per-page mutex ordering and error isolation (6) - filters: filterPage/filterComment truthiness traps, filterSearchResult (19) - markdown-converter: per-node golden matrix + edge cases (41) - markdown-document envelope: round-trip, CRLF, malformed-JSON throws (17) No source files changed. The pre-existing test/markdown-document.test.ts is left intact; new envelope coverage lives in markdown-document-envelope.test.ts. Full suite: 16 files / 279 tests green.
235 lines
6.5 KiB
TypeScript
235 lines
6.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
filterComment,
|
|
filterGroup,
|
|
filterPage,
|
|
filterSearchResult,
|
|
filterSpace,
|
|
filterWorkspace,
|
|
} from '../packages/docmost-client/src/lib/filters.js';
|
|
|
|
describe('filterPage', () => {
|
|
const basePage = {
|
|
id: 'pg1',
|
|
slugId: 'slug-1',
|
|
title: 'Title',
|
|
parentPageId: 'parent-1',
|
|
spaceId: 'space-1',
|
|
isLocked: false,
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
// Extra fields that must be dropped by the filter.
|
|
secret: 'should-not-leak',
|
|
};
|
|
|
|
it('omits the content key when no content arg is given', () => {
|
|
const result = filterPage(basePage);
|
|
expect(result).not.toHaveProperty('content');
|
|
expect(result).not.toHaveProperty('secret');
|
|
expect(result.id).toBe('pg1');
|
|
});
|
|
|
|
it('includes an empty-string content (truthiness trap: empty string is kept)', () => {
|
|
const result = filterPage(basePage, '');
|
|
expect(result).toHaveProperty('content');
|
|
expect(result.content).toBe('');
|
|
});
|
|
|
|
it('includes a non-empty content string', () => {
|
|
const result = filterPage(basePage, '# Markdown');
|
|
expect(result.content).toBe('# Markdown');
|
|
});
|
|
|
|
it('omits content when it is not a string', () => {
|
|
// A non-string (e.g. an object passed by mistake) must be dropped.
|
|
const result = filterPage(basePage, { junk: true } as unknown as string);
|
|
expect(result).not.toHaveProperty('content');
|
|
});
|
|
|
|
it('omits subpages when undefined', () => {
|
|
const result = filterPage(basePage);
|
|
expect(result).not.toHaveProperty('subpages');
|
|
});
|
|
|
|
it('omits subpages when empty array', () => {
|
|
const result = filterPage(basePage, undefined, []);
|
|
expect(result).not.toHaveProperty('subpages');
|
|
});
|
|
|
|
it('maps non-empty subpages to { id, title } only', () => {
|
|
const result = filterPage(basePage, undefined, [
|
|
{ id: 's1', title: 'Sub 1', extra: 'drop-me' },
|
|
{ id: 's2', title: 'Sub 2' },
|
|
]);
|
|
expect(result.subpages).toEqual([
|
|
{ id: 's1', title: 'Sub 1' },
|
|
{ id: 's2', title: 'Sub 2' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('filterComment', () => {
|
|
const baseComment = {
|
|
id: 'c1',
|
|
pageId: 'pg1',
|
|
content: 'original content',
|
|
creatorId: 'u1',
|
|
createdAt: '2024-01-01',
|
|
};
|
|
|
|
it('overrides comment.content with markdownContent when provided', () => {
|
|
const result = filterComment(baseComment, 'markdown version');
|
|
expect(result.content).toBe('markdown version');
|
|
});
|
|
|
|
it('falls back to comment.content when markdownContent is undefined', () => {
|
|
const result = filterComment(baseComment, undefined);
|
|
expect(result.content).toBe('original content');
|
|
});
|
|
|
|
it('keeps an empty-string markdownContent (uses ?? not ||)', () => {
|
|
// `??` only falls back on null/undefined, so "" must be preserved.
|
|
const result = filterComment(baseComment, '');
|
|
expect(result.content).toBe('');
|
|
});
|
|
|
|
it('applies defaults for selection/type/parentCommentId/editedAt/resolvedAt/resolvedById', () => {
|
|
const result = filterComment(baseComment);
|
|
expect(result.selection).toBeNull();
|
|
expect(result.type).toBe('page');
|
|
expect(result.parentCommentId).toBeNull();
|
|
expect(result.editedAt).toBeNull();
|
|
expect(result.resolvedAt).toBeNull();
|
|
expect(result.resolvedById).toBeNull();
|
|
});
|
|
|
|
it('passes through provided optional values', () => {
|
|
const result = filterComment({
|
|
...baseComment,
|
|
selection: 'some text',
|
|
type: 'inline',
|
|
parentCommentId: 'c0',
|
|
editedAt: '2024-02-01',
|
|
resolvedAt: '2024-03-01',
|
|
resolvedById: 'u9',
|
|
});
|
|
expect(result.selection).toBe('some text');
|
|
expect(result.type).toBe('inline');
|
|
expect(result.parentCommentId).toBe('c0');
|
|
expect(result.editedAt).toBe('2024-02-01');
|
|
expect(result.resolvedAt).toBe('2024-03-01');
|
|
expect(result.resolvedById).toBe('u9');
|
|
});
|
|
|
|
it('returns null for creatorName when creator is absent', () => {
|
|
const result = filterComment(baseComment);
|
|
expect(result.creatorName).toBeNull();
|
|
});
|
|
|
|
it('reads the nested creator?.name when present', () => {
|
|
const result = filterComment({
|
|
...baseComment,
|
|
creator: { name: 'Alice' },
|
|
});
|
|
expect(result.creatorName).toBe('Alice');
|
|
});
|
|
});
|
|
|
|
describe('filterSearchResult', () => {
|
|
const baseResult = {
|
|
id: 'r1',
|
|
title: 'Result',
|
|
parentPageId: 'p0',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
rank: 0.9,
|
|
highlight: '<em>Result</em>',
|
|
};
|
|
|
|
it('reads nested space?.id and space?.name when space is present', () => {
|
|
const result = filterSearchResult({
|
|
...baseResult,
|
|
space: { id: 'sp1', name: 'Space One' },
|
|
});
|
|
expect(result.spaceId).toBe('sp1');
|
|
expect(result.spaceName).toBe('Space One');
|
|
});
|
|
|
|
it('returns undefined for space fields when space is absent (no throw)', () => {
|
|
const result = filterSearchResult(baseResult);
|
|
expect(result.spaceId).toBeUndefined();
|
|
expect(result.spaceName).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('flat pluckers (no branching)', () => {
|
|
it('filterWorkspace plucks the expected shape', () => {
|
|
const result = filterWorkspace({
|
|
id: 'w1',
|
|
name: 'WS',
|
|
description: 'desc',
|
|
defaultSpaceId: 'sp1',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
extra: 'drop',
|
|
});
|
|
expect(result).toEqual({
|
|
id: 'w1',
|
|
name: 'WS',
|
|
description: 'desc',
|
|
defaultSpaceId: 'sp1',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
});
|
|
});
|
|
|
|
it('filterSpace plucks the expected shape', () => {
|
|
const result = filterSpace({
|
|
id: 'sp1',
|
|
name: 'Space',
|
|
description: 'desc',
|
|
slug: 'space',
|
|
visibility: 'open',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
extra: 'drop',
|
|
});
|
|
expect(result).toEqual({
|
|
id: 'sp1',
|
|
name: 'Space',
|
|
description: 'desc',
|
|
slug: 'space',
|
|
visibility: 'open',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
});
|
|
});
|
|
|
|
it('filterGroup plucks the expected shape', () => {
|
|
const result = filterGroup({
|
|
id: 'g1',
|
|
name: 'Group',
|
|
description: 'desc',
|
|
workspaceId: 'w1',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
extra: 'drop',
|
|
});
|
|
expect(result).toEqual({
|
|
id: 'g1',
|
|
name: 'Group',
|
|
description: 'desc',
|
|
workspaceId: 'w1',
|
|
createdAt: '2024-01-01',
|
|
updatedAt: '2024-01-02',
|
|
deletedAt: null,
|
|
});
|
|
});
|
|
});
|