Files
Scarlett 94d48f41f2 feat(plugins-home): order the template gallery by usage (OPEND-449) (#5106)
* feat(plugins-home): order the template gallery by usage (OPEND-449)

Non-prototype facets (Slides, Image, Video, …) now lead by real usage — a
trailing 28-day blend of distinct-users + runs from `run_finished`. The
Prototype facet keeps its curated editorial order; the default mode-seeds
(Web Prototype, Simple Deck) and no-preview / manually-sunk tiles drop to the
bottom of their facet and the All view. Ordering lives in one shared comparator
(`comparePluginGalleryOrder`) used by the grid and the Home rail; the All view
keeps its visual-appeal order with sunk tiles pushed last.

A weekly in-repo workflow rebuilds the popularity snapshot from PostHog and
opens a self-PR; a committed snapshot ships the ordering immediately.

* test(plugins-home): make Live Artifact grouping assertions order-insensitive

The rail + Community-section tests hardcoded the curated Live Artifact preset
order. Facet order is now usage/sink-driven (OPEND-449), so these grouping/
routing tests assert membership via a sorted comparison instead.

---------

Co-authored-by: Scarlett Huang <scarletthuang@Scarletts-MacBook-Air.local>
2026-07-17 09:50:05 +00:00

67 lines
2.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
PLUGIN_POPULARITY,
PLUGIN_NO_PREVIEW,
} from '../../src/components/plugins-home/pluginPopularity.generated';
import {
ALWAYS_LAST,
POPULARITY_LEADS_CURATION,
comparePluginGalleryOrder,
isSunkToBottom,
pluginPopularityScore,
} from '../../src/components/plugins-home/pluginPopularity';
// Assertions are written against invariants, not specific mid-list ids, so the
// weekly data refresh does not turn this red.
const scoredIds = Object.keys(PLUGIN_POPULARITY);
const UNSCORED = '__retired_or_unknown_plugin__';
// Two scored ids that are NOT sunk, highest and lowest, to check the ordering.
const rankable = scoredIds.filter((id) => !isSunkToBottom(id)).sort((a, b) => PLUGIN_POPULARITY[b]! - PLUGIN_POPULARITY[a]!);
describe('pluginPopularity', () => {
it('scores every listed template in (0, 1] and returns null for unknown/retired ids', () => {
expect(pluginPopularityScore(UNSCORED)).toBeNull();
expect(scoredIds.length).toBeGreaterThan(0);
for (const id of scoredIds) {
const score = pluginPopularityScore(id);
expect(score).not.toBeNull();
expect(score!).toBeGreaterThan(0);
expect(score!).toBeLessThanOrEqual(1);
}
});
it('sinks the default mode-seeds and the no-preview tiles to the bottom', () => {
for (const id of [...ALWAYS_LAST, ...PLUGIN_NO_PREVIEW]) {
expect(isSunkToBottom(id)).toBe(true);
}
expect(isSunkToBottom('example-open-design-landing')).toBe(false);
});
it('orders a sunk tile after a normal one regardless of usage', () => {
const normal = rankable[0]!;
const sunk = ALWAYS_LAST[0]!; // example-web-prototype — the top-usage seed
expect(comparePluginGalleryOrder(normal, sunk, false, false)).toBeLessThan(0);
expect(comparePluginGalleryOrder(sunk, normal, false, false)).toBeGreaterThan(0);
});
it('orders non-prototype tiles by usage (higher score first)', () => {
const hi = rankable[0]!;
const lo = rankable[rankable.length - 1]!;
if (PLUGIN_POPULARITY[hi]! !== PLUGIN_POPULARITY[lo]!) {
expect(comparePluginGalleryOrder(hi, lo, false, false)).toBeLessThan(0);
expect(comparePluginGalleryOrder(lo, hi, false, false)).toBeGreaterThan(0);
}
});
it('skips usage for curation-governed (prototype) tiles, deferring to curated order', () => {
const hi = rankable[0]!;
const lo = rankable[rankable.length - 1]!;
// both governed by curation -> popularity ignored -> defer (0)
expect(comparePluginGalleryOrder(hi, lo, true, true)).toBe(0);
});
it('keeps the usage-ordering knob on (OPEND-449)', () => {
expect(POPULARITY_LEADS_CURATION).toBe(true);
});
});