open-design/apps/web/tests/components/plugins-home-facets.test.ts
Eli-tangerine 72c8e34bc9
Polish home onboarding and community presets (#2658)
Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-22 14:26:56 +08:00

299 lines
13 KiB
TypeScript

// Facet derivation contract for the plugins-home filter row. The
// home section is driven by artifact-kind primary tabs that mirror the
// artifact creation surface, plus scene buckets derived from the
// user-query taxonomy for the crowded template types.
import { describe, expect, it } from 'vitest';
import type { InstalledPluginRecord } from '@open-design/contracts';
import {
applyFacetSelection,
buildFacetCatalog,
extractCategories,
extractSubcategories,
isFeaturedPlugin,
resolveDefaultSelection,
} from '../../src/components/plugins-home/facets';
function fixture(overrides: {
id: string;
title?: string;
tags?: string[];
od?: Record<string, unknown>;
}): InstalledPluginRecord {
return {
id: overrides.id,
title: overrides.title ?? overrides.id,
version: '0.1.0',
sourceKind: 'bundled',
source: '/tmp',
trust: 'bundled',
capabilitiesGranted: ['prompt:inject'],
manifest: {
name: overrides.id,
version: '0.1.0',
...(overrides.tags ? { tags: overrides.tags } : {}),
...(overrides.od ? { od: overrides.od } : {}),
},
fsPath: '/tmp',
installedAt: 0,
updatedAt: 0,
};
}
describe('extractCategories', () => {
it('maps generation modes to artifact-kind primary tabs', () => {
expect(extractCategories(fixture({ id: 'prototype', od: { mode: 'prototype' } }))).toEqual(['prototype']);
expect(extractCategories(fixture({ id: 'deck', od: { mode: 'deck' } }))).toEqual(['deck']);
expect(extractCategories(fixture({ id: 'image', od: { mode: 'image' } }))).toEqual(['image']);
expect(extractCategories(fixture({ id: 'video', od: { mode: 'video' } }))).toEqual(['video']);
expect(extractCategories(fixture({ id: 'audio', od: { mode: 'audio' } }))).toEqual(['audio']);
});
it('groups live artifacts ahead of their underlying rendering mode', () => {
expect(
extractCategories(
fixture({
id: 'example-live-dashboard',
tags: ['live-dashboard'],
od: { mode: 'prototype' },
}),
),
).toEqual(['live-artifact']);
expect(
extractCategories(
fixture({
id: 'image-template-notion-team-dashboard-live-artifact',
tags: ['live-artifact'],
od: { mode: 'image' },
}),
),
).toEqual(['live-artifact']);
expect(
extractCategories(
fixture({
id: 'example-social-media-matrix-tracker-template',
tags: ['live-artifacts'],
od: { mode: 'template' },
}),
),
).toEqual(['live-artifact']);
});
it('splits HyperFrames from the broader video mode', () => {
expect(
extractCategories(fixture({ id: 'hf', tags: ['hyperframes'], od: { mode: 'video' } })),
).toEqual(['hyperframes']);
expect(
extractCategories(fixture({ id: 'composition', tags: ['video-composition'], od: { mode: 'video' } })),
).toEqual(['hyperframes']);
});
it('keeps non-artifact workflow and design-system plugins out of primary tabs', () => {
expect(extractCategories(fixture({ id: 'design-system', od: { mode: 'design-system' } }))).toEqual([]);
expect(extractCategories(fixture({ id: 'import', od: { taskKind: 'figma-migration', mode: 'scenario' } }))).toEqual([]);
expect(extractCategories(fixture({ id: 'export', tags: ['export', 'react'], od: { mode: 'export' } }))).toEqual([]);
expect(extractCategories(fixture({ id: 'utility', od: { mode: 'utility' } }))).toEqual([]);
});
it('normalises mode casing / formatting via slugify before matching', () => {
expect(extractCategories(fixture({ id: 'a', od: { mode: 'Prototype' } }))).toEqual(['prototype']);
expect(extractCategories(fixture({ id: 'b', od: { mode: 'slide_deck' } }))).toEqual([]);
expect(extractCategories(fixture({ id: 'c', od: { mode: 'deck' } }))).toEqual(['deck']);
});
});
describe('extractSubcategories', () => {
it('maps prototype templates to prompt-taxonomy scene buckets', () => {
expect(extractSubcategories(fixture({ id: 'dashboard', tags: ['dashboard'], od: { mode: 'prototype' } }))).toEqual(['business-dashboards']);
expect(extractSubcategories(fixture({ id: 'app', tags: ['mobile-app'], od: { mode: 'prototype' } }))).toEqual(['app-prototypes']);
expect(extractSubcategories(fixture({ id: 'landing', tags: ['saas-landing'], od: { mode: 'prototype' } }))).toEqual(['landing-marketing']);
expect(extractSubcategories(fixture({ id: 'dev', tags: ['engineering'], od: { mode: 'prototype' } }))).toEqual(['developer-tools']);
expect(extractSubcategories(fixture({ id: 'clinical', tags: ['case-report'], od: { mode: 'prototype' } }))).toEqual(['docs-reports']);
expect(extractSubcategories(fixture({ id: 'brand', tags: ['wireframe'], od: { mode: 'prototype' } }))).toEqual(['brand-design']);
});
it('maps deck templates to pitch, course, report, product, engineering, and creative scenes', () => {
expect(extractSubcategories(fixture({ id: 'pitch', tags: ['pitch-deck'], od: { mode: 'deck' } }))).toEqual(['pitch-business']);
expect(extractSubcategories(fixture({ id: 'course', tags: ['course-module'], od: { mode: 'deck' } }))).toEqual(['course-training']);
expect(extractSubcategories(fixture({ id: 'report', tags: ['weekly-report'], od: { mode: 'deck' } }))).toEqual(['reports-briefings']);
expect(extractSubcategories(fixture({ id: 'launch', tags: ['product-launch'], od: { mode: 'deck' } }))).toEqual(['product-sales']);
expect(extractSubcategories(fixture({ id: 'tech', tags: ['tech-sharing'], od: { mode: 'deck' } }))).toEqual(['engineering-talks']);
expect(extractSubcategories(fixture({ id: 'creative', tags: ['zhangzara'], od: { mode: 'deck' } }))).toEqual(['creative-decks']);
});
it('maps image templates to visual-scene buckets', () => {
expect(extractSubcategories(fixture({ id: 'ui', tags: ['app-web-design'], od: { mode: 'image' } }))).toEqual(['ui-product-mockups']);
expect(extractSubcategories(fixture({ id: 'brand', tags: ['typography'], od: { mode: 'image' } }))).toEqual(['brand-visuals']);
expect(extractSubcategories(fixture({ id: 'storyboard', tags: ['storyboard'], od: { mode: 'image' } }))).toEqual(['storyboards-motion-refs']);
expect(extractSubcategories(fixture({ id: 'social', tags: ['social-media-post'], od: { mode: 'image' } }))).toEqual(['social-content']);
expect(extractSubcategories(fixture({ id: 'portrait', tags: ['profile-avatar'], od: { mode: 'image' } }))).toEqual(['avatar-portrait']);
expect(extractSubcategories(fixture({ id: 'illustration', tags: ['illustration'], od: { mode: 'image' } }))).toEqual(['illustration-style']);
});
it('maps non-HyperFrames video templates to scene buckets', () => {
expect(extractSubcategories(fixture({ id: 'motion', tags: ['motion-graphics'], od: { mode: 'video' } }))).toEqual(['motion-effects']);
expect(extractSubcategories(fixture({ id: 'social', tags: ['short-form'], od: { mode: 'video' } }))).toEqual(['social-short-form']);
expect(extractSubcategories(fixture({ id: 'marketing', tags: ['product-promo'], od: { mode: 'video' } }))).toEqual(['marketing-product']);
expect(extractSubcategories(fixture({ id: 'data', tags: ['flowchart'], od: { mode: 'video' } }))).toEqual(['data-explainers']);
expect(extractSubcategories(fixture({ id: 'cinema', tags: ['cinematic'], od: { mode: 'video' } }))).toEqual(['cinematic-story']);
});
it('keeps Live Artifact, HyperFrames, and Audio flat with no second-level buckets', () => {
expect(
extractSubcategories(
fixture({
id: 'example-live-artifact',
tags: ['live-artifact'],
od: { mode: 'prototype' },
}),
),
).toEqual([]);
expect(extractSubcategories(fixture({ id: 'hf', tags: ['hyperframes'], od: { mode: 'video' } }))).toEqual([]);
expect(extractSubcategories(fixture({ id: 'audio', od: { mode: 'audio' } }))).toEqual([]);
});
});
describe('buildFacetCatalog', () => {
it('produces artifact-kind primary tabs in product order', () => {
const catalog = buildFacetCatalog([
fixture({ id: 'prototype', tags: ['dashboard'], od: { mode: 'prototype' } }),
fixture({ id: 'example-live-artifact', tags: ['live-artifact'], od: { mode: 'prototype' } }),
fixture({ id: 'deck', tags: ['pitch-deck'], od: { mode: 'deck' } }),
fixture({ id: 'image', tags: ['profile-avatar'], od: { mode: 'image' } }),
fixture({ id: 'video', tags: ['cinematic'], od: { mode: 'video' } }),
fixture({ id: 'hf', tags: ['hyperframes'], od: { mode: 'video' } }),
fixture({ id: 'audio', od: { mode: 'audio' } }),
fixture({ id: 'design-system', od: { mode: 'design-system' } }),
]);
expect(catalog.category.map((o) => [o.slug, o.count])).toEqual([
['prototype', 1],
['live-artifact', 1],
['deck', 1],
['image', 1],
['video', 1],
['hyperframes', 1],
['audio', 1],
]);
expect((catalog.subcategory.prototype ?? []).map((o) => o.slug)).toEqual([
'business-dashboards',
'app-prototypes',
'landing-marketing',
'developer-tools',
'docs-reports',
'brand-design',
]);
expect((catalog.subcategory.deck ?? []).map((o) => o.slug)).toEqual([
'pitch-business',
'course-training',
'reports-briefings',
'product-sales',
'engineering-talks',
'creative-decks',
]);
expect((catalog.subcategory.image ?? []).map((o) => o.slug)).toEqual([
'ui-product-mockups',
'brand-visuals',
'storyboards-motion-refs',
'social-content',
'avatar-portrait',
'illustration-style',
]);
expect((catalog.subcategory.video ?? []).map((o) => o.slug)).toEqual([
'motion-effects',
'social-short-form',
'marketing-product',
'data-explainers',
'cinematic-story',
]);
expect(catalog.subcategory['live-artifact']).toBeUndefined();
expect(catalog.subcategory.hyperframes).toBeUndefined();
expect(catalog.subcategory.audio).toBeUndefined();
});
});
describe('applyFacetSelection', () => {
const plugins = [
fixture({ id: 'prototype-dashboard', tags: ['dashboard'], od: { mode: 'prototype' } }),
fixture({ id: 'prototype-app', tags: ['mobile-app'], od: { mode: 'prototype' } }),
fixture({ id: 'example-live-artifact', tags: ['live-artifact'], od: { mode: 'prototype' } }),
fixture({ id: 'deck', tags: ['pitch-deck'], od: { mode: 'deck' } }),
fixture({ id: 'image', tags: ['profile-avatar'], od: { mode: 'image' } }),
fixture({ id: 'video', tags: ['cinematic'], od: { mode: 'video' } }),
fixture({ id: 'hf', tags: ['hyperframes'], od: { mode: 'video' } }),
fixture({ id: 'audio', od: { mode: 'audio' } }),
];
it('returns everything when no category is selected', () => {
expect(
applyFacetSelection(plugins, { category: null, subcategory: null }).map((p) => p.id),
).toEqual([
'prototype-dashboard',
'prototype-app',
'example-live-artifact',
'deck',
'image',
'video',
'hf',
'audio',
]);
});
it('filters by the selected artifact-kind category slug', () => {
expect(
applyFacetSelection(plugins, { category: 'prototype', subcategory: null }).map((p) => p.id),
).toEqual(['prototype-dashboard', 'prototype-app']);
expect(
applyFacetSelection(plugins, { category: 'live-artifact', subcategory: null }).map((p) => p.id),
).toEqual(['example-live-artifact']);
expect(
applyFacetSelection(plugins, { category: 'hyperframes', subcategory: null }).map((p) => p.id),
).toEqual(['hf']);
expect(
applyFacetSelection(plugins, { category: 'video', subcategory: null }).map((p) => p.id),
).toEqual(['video']);
});
it('filters by the selected scene bucket inside the selected artifact kind', () => {
expect(
applyFacetSelection(plugins, { category: 'prototype', subcategory: 'business-dashboards' }).map((p) => p.id),
).toEqual(['prototype-dashboard']);
expect(
applyFacetSelection(plugins, { category: 'prototype', subcategory: 'app-prototypes' }).map((p) => p.id),
).toEqual(['prototype-app']);
});
});
describe('isFeaturedPlugin', () => {
it('returns true for boolean featured picks and numeric curator ranks', () => {
expect(isFeaturedPlugin(fixture({ id: 'a', od: { featured: true } }))).toBe(true);
expect(isFeaturedPlugin(fixture({ id: 'ranked', od: { featured: 4 } }))).toBe(true);
expect(isFeaturedPlugin(fixture({ id: 'b', od: { featured: 'true' } }))).toBe(false);
expect(isFeaturedPlugin(fixture({ id: 'c' }))).toBe(false);
});
});
describe('resolveDefaultSelection', () => {
it('defaults the home catalog to Prototype when that bucket exists', () => {
const catalog = buildFacetCatalog([
fixture({ id: 'slides', od: { mode: 'deck' } }),
fixture({ id: 'prototype', od: { mode: 'prototype' } }),
]);
expect(resolveDefaultSelection(catalog)).toEqual({
category: 'prototype',
subcategory: null,
});
});
it('falls back to the first populated artifact kind when Prototype is unavailable', () => {
const catalog = buildFacetCatalog([
fixture({ id: 'slides', od: { mode: 'deck' } }),
]);
expect(resolveDefaultSelection(catalog)).toEqual({
category: 'deck',
subcategory: null,
});
});
});