boardChrome.test.ts4.4 KBView on GitHub /**
* The two pure pieces of the board UI: which node the frontmatter hider decorates, and what
* colour a lane is.
*
* Both are tested as functions rather than through a mounted editor/kanban — the decoration
* rule and the tone rule are the whole content, and a ProseMirror view or a DnD context would
* be scaffolding around one assertion.
*/
import { Node as PMNode, Schema } from '@tiptap/pm/model';
import { __testing } from '@/modules/documents/board/HideCardFrontmatterExtension';
import {
BOARD_OPTION_ICONS,
optionIconFor,
} from '@/modules/documents/board/option-icons';
const { frontmatterDecorations, FRONTMATTER_LANG } = __testing;
/** A minimal schema with the two node types the rule looks at. */
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
text: { group: 'inline' },
paragraph: { group: 'block', content: 'inline*' },
codeBlock: {
group: 'block',
content: 'text*',
attrs: { language: { default: null } },
code: true,
},
},
});
function doc(...nodes: PMNode[]): PMNode {
return schema.node('doc', null, nodes);
}
function code(language: string | null, text = '{"lane":"bug"}'): PMNode {
return schema.node('codeBlock', { language }, schema.text(text));
}
function para(text = 'The dashboard is stale.'): PMNode {
return schema.node('paragraph', null, schema.text(text));
}
describe('HideCardFrontmatterExtension', () => {
it('hides a LEADING frontmatter code block', () => {
const decorations = frontmatterDecorations(doc(code(FRONTMATTER_LANG), para()));
expect(decorations.find()).toHaveLength(1);
});
it('hides nothing when the card has no frontmatter', () => {
// A plain document promoted to a card, or a body-only write. Hiding here would blank the
// ticket's first paragraph.
expect(frontmatterDecorations(doc(para(), para())).find()).toHaveLength(0);
});
it('leaves a real code block alone, even at position 0', () => {
// Someone's actual code sample. Only the sentinel language is frontmatter.
expect(frontmatterDecorations(doc(code('ts'), para())).find()).toHaveLength(0);
});
it('leaves a frontmatter block that is NOT leading alone', () => {
// Frontmatter is only frontmatter at the top. A block further down is content.
expect(frontmatterDecorations(doc(para(), code(FRONTMATTER_LANG))).find()).toHaveLength(0);
});
it('hides nothing in an empty document', () => {
// An empty paragraph, which is what a brand-new card's body actually is — ProseMirror
// forbids an empty TEXT node, so the paragraph carries no children at all.
const empty = schema.node('doc', null, [schema.node('paragraph')]);
expect(frontmatterDecorations(empty).find()).toHaveLength(0);
});
});
describe('optionIconFor', () => {
const withIcons = {
optionIcons: { backlog: 'circle-dashed', done: 'circle-check', bogus: 'not-a-real-icon' },
};
it('draws the glyph the BOARD declared for that value', () => {
// Which shape means "finished" is a judgement about the values, and the board is the only
// thing that knows what its values mean.
expect(optionIconFor('done', withIcons)).toBe(BOARD_OPTION_ICONS['circle-check']);
expect(optionIconFor('backlog', withIcons)).toBe(BOARD_OPTION_ICONS['circle-dashed']);
});
it('carries the colour on the GLYPH — a column has none of its own', () => {
expect(optionIconFor('done', withIcons)!.tone).toMatch(/^hsl\(/);
});
it('draws NOTHING rather than a fallback glyph for a name Cedar does not know', () => {
// A wrong-but-present shape is worse than none: the label alone still reads, but a card
// showing a check because the icon name was misspelled reads as finished.
expect(optionIconFor('bogus', withIcons)).toBeNull();
});
it('draws nothing for a value with no icon, and for a field with none at all', () => {
// A board that never sets an icon is a perfectly good board.
expect(optionIconFor('in_progress', withIcons)).toBeNull();
expect(optionIconFor('done', {})).toBeNull();
expect(optionIconFor('done', undefined)).toBeNull();
});
it('every registered glyph has a component and a tone', () => {
// Jest's `expect` takes no message argument, so the name goes in the collected shape.
const broken = Object.entries(BOARD_OPTION_ICONS)
.filter(([, entry]) => !entry.Icon || typeof entry.tone !== 'string')
.map(([name]) => name);
expect(broken).toEqual([]);
});
});