home-widget-registry.test.ts3.7 KBView on GitHub
/**
 * The widget registry's one job that is not a lookup: surviving ids it does not know.
 *
 * Settings store IDS, and the only UI for removing an id is the rail itself — so a rail that
 * threw (or blanked) on a retired id would be unrecoverable from the product. That makes
 * `resolveHomeWidgets` skipping unknowns a correctness property, not a nicety.
 */

import {
  DEFAULT_HOME_WIDGETS,
  HOME_WIDGETS,
  LISTED_HOME_WIDGETS,
  resolveHomeWidgets,
} from '@/modules/home/widgets/registry';

describe('resolveHomeWidgets', () => {
  it('resolves ids in the STORED order, not the registry order', () => {
    // The rail is a list the user arranged; registry order is an implementation detail.
    const ids = ['statistics', 'meetings'];
    expect(resolveHomeWidgets(ids).map((w) => w.id)).toEqual(ids);
  });

  it('skips an id this build no longer knows rather than throwing', () => {
    expect(resolveHomeWidgets(['meetings', 'retired-widget', 'pipeline']).map((w) => w.id)).toEqual([
      'meetings',
      'pipeline',
    ]);
  });

  it('still resolves an UNLISTED widget for a rail that already stores its id', () => {
    // `unlisted` withholds a widget from the picker, not from the rails already naming it —
    // resolving it away would silently take the tile off the screen of everyone who has it.
    expect(resolveHomeWidgets(['meetings', 'agents', 'pipeline']).map((w) => w.id)).toEqual([
      'meetings',
      'agents',
      'pipeline',
    ]);
  });

  it('answers empty for an empty rail, which is a real choice', () => {
    expect(resolveHomeWidgets([])).toEqual([]);
  });
});

describe('HOME_WIDGETS', () => {
  it('has unique ids — they are the settings key', () => {
    const ids = HOME_WIDGETS.map((w) => w.id);
    expect(new Set(ids).size).toBe(ids.length);
  });

  it('gives every widget a title, icon and description for the picker', () => {
    for (const widget of LISTED_HOME_WIDGETS) {
      expect({ id: widget.id, ok: !!widget.title && !!widget.icon && !!widget.description }).toEqual(
        { id: widget.id, ok: true },
      );
    }
  });

  it('defaults to the three widgets everyone gets, all of them resolvable', () => {
    expect(DEFAULT_HOME_WIDGETS).toEqual(['meetings', 'pipeline', 'statistics']);
    // A default that names an id the registry has retired would silently give a brand-new
    // user a shorter rail than intended, with nothing in the UI to explain it.
    expect(resolveHomeWidgets(DEFAULT_HOME_WIDGETS).map((w) => w.id)).toEqual([
      ...DEFAULT_HOME_WIDGETS,
    ]);
  });

  it('keeps the unfinished agents tile out of the picker and out of the defaults', () => {
    // TEMPORARY, and this test is the reminder: the tile is the agents surface's only entry
    // point, so not offering it is what keeps the surface out of everyone else's way. It
    // stays in the registry so the rails that already name it go on rendering it.
    expect(HOME_WIDGETS.some((w) => w.id === 'agents')).toBe(true);
    expect(LISTED_HOME_WIDGETS.some((w) => w.id === 'agents')).toBe(false);
    expect(DEFAULT_HOME_WIDGETS).not.toContain('agents');
  });

  it('marks every metric widget as needing the shared overview', () => {
    // Four separate stat tiles were merged into ONE "Statistics" widget: four bordered cards
    // to say four numbers cost four cards' worth of a 21rem rail, and were not comparable.
    const metrics = ['pipeline', 'statistics'];
    for (const id of metrics) {
      expect({ id, needsOverview: resolveHomeWidgets([id])[0]?.needsOverview }).toEqual({
        id,
        needsOverview: true,
      });
    }
    expect(resolveHomeWidgets(['responseTime', 'followUpTime', 'emailsSent', 'actionStats'])).toEqual(
      [],
    );
  });
});