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([]);
  });
});