mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
// Visual-appeal ranking contract for the plugins-home gallery.
|
|
//
|
|
// The home grid relies on `sortByVisualAppeal` (and the underlying
|
|
// `pluginVisualScore`) to surface cinematic decks / image / video
|
|
// templates above plain scenario plugins. These tests lock the
|
|
// ordering so the first viewport keeps leading with rich previews
|
|
// instead of regressing back to alphabetical bundled noise.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import type { InstalledPluginRecord } from '@open-design/contracts';
|
|
import {
|
|
pluginVisualScore,
|
|
sortByVisualAppeal,
|
|
} from '../../src/components/plugins-home/visualScore';
|
|
|
|
function fixture(overrides: {
|
|
id: string;
|
|
title?: string;
|
|
description?: string;
|
|
tags?: string[];
|
|
od?: Record<string, unknown>;
|
|
author?: string;
|
|
}): 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.description ? { description: overrides.description } : {}),
|
|
...(overrides.tags ? { tags: overrides.tags } : {}),
|
|
...(overrides.author
|
|
? { author: { name: overrides.author } }
|
|
: {}),
|
|
...(overrides.od ? { od: overrides.od } : {}),
|
|
},
|
|
fsPath: '/tmp',
|
|
installedAt: 0,
|
|
updatedAt: 0,
|
|
};
|
|
}
|
|
|
|
describe('pluginVisualScore', () => {
|
|
it('boosts featured plugins above everything else', () => {
|
|
const plain = fixture({ id: 'plain' });
|
|
const featured = fixture({ id: 'featured', od: { featured: true } });
|
|
expect(pluginVisualScore(featured)).toBeGreaterThan(
|
|
pluginVisualScore(plain) + 500,
|
|
);
|
|
});
|
|
|
|
it('uses numeric featured ranks to order curated picks', () => {
|
|
const lead = fixture({ id: 'lead', od: { featured: 2 } });
|
|
const later = fixture({ id: 'later', od: { featured: 19 } });
|
|
expect(pluginVisualScore(lead)).toBeGreaterThan(pluginVisualScore(later));
|
|
});
|
|
|
|
it('ranks media-rich plugins above plain scenarios', () => {
|
|
const text = fixture({ id: 'text' });
|
|
const deckHtml = fixture({
|
|
id: 'deck',
|
|
od: { mode: 'deck', preview: { type: 'html', entry: './index.html' } },
|
|
});
|
|
const image = fixture({
|
|
id: 'image',
|
|
od: { surface: 'image', mode: 'image', preview: { type: 'image', poster: 'a.png' } },
|
|
});
|
|
const video = fixture({
|
|
id: 'video',
|
|
od: { surface: 'video', mode: 'video', preview: { type: 'video', video: 'a.mp4' } },
|
|
});
|
|
expect(pluginVisualScore(video)).toBeGreaterThan(pluginVisualScore(image));
|
|
expect(pluginVisualScore(image)).toBeGreaterThan(pluginVisualScore(deckHtml));
|
|
expect(pluginVisualScore(deckHtml)).toBeGreaterThan(pluginVisualScore(text));
|
|
});
|
|
|
|
it('credits design-system plugins between decks and plain text', () => {
|
|
const text = fixture({ id: 'text' });
|
|
const ds = fixture({ id: 'ds', od: { mode: 'design-system' } });
|
|
expect(pluginVisualScore(ds)).toBeGreaterThan(pluginVisualScore(text));
|
|
});
|
|
|
|
it('penalises atom kind so they never accidentally lead the grid', () => {
|
|
const atom = fixture({ id: 'a', od: { kind: 'atom' } });
|
|
expect(pluginVisualScore(atom)).toBeLessThan(0);
|
|
});
|
|
});
|
|
|
|
describe('sortByVisualAppeal', () => {
|
|
it('places the cinematic deck first, plain scenarios last', () => {
|
|
const records = [
|
|
fixture({ id: 'plain' }),
|
|
fixture({
|
|
id: 'guizang-ppt',
|
|
od: { mode: 'deck', preview: { type: 'html', entry: './index.html' } },
|
|
}),
|
|
fixture({
|
|
id: 'photo',
|
|
od: {
|
|
surface: 'image',
|
|
mode: 'image',
|
|
preview: { type: 'image', poster: 'p.png' },
|
|
},
|
|
}),
|
|
fixture({
|
|
id: 'reel',
|
|
od: {
|
|
surface: 'video',
|
|
mode: 'video',
|
|
preview: { type: 'video', video: 'r.mp4', poster: 'r.png' },
|
|
featured: true,
|
|
},
|
|
}),
|
|
];
|
|
const sorted = sortByVisualAppeal(records).map((r) => r.id);
|
|
expect(sorted[0]).toBe('reel');
|
|
expect(sorted[sorted.length - 1]).toBe('plain');
|
|
});
|
|
|
|
it('puts curated home picks before generic featured media', () => {
|
|
const records = [
|
|
fixture({
|
|
id: 'featured-video',
|
|
od: {
|
|
surface: 'video',
|
|
mode: 'video',
|
|
preview: { type: 'video', video: 'r.mp4', poster: 'r.png' },
|
|
featured: true,
|
|
},
|
|
}),
|
|
fixture({
|
|
id: 'example-open-design-landing',
|
|
od: { mode: 'prototype', preview: { type: 'html', entry: './index.html' } },
|
|
}),
|
|
];
|
|
const sorted = sortByVisualAppeal(records).map((r) => r.id);
|
|
expect(sorted[0]).toBe('example-open-design-landing');
|
|
});
|
|
|
|
it('keeps numeric featured rank ahead of media bonuses', () => {
|
|
const records = [
|
|
fixture({
|
|
id: 'hyperframes',
|
|
od: {
|
|
surface: 'video',
|
|
mode: 'video',
|
|
preview: { type: 'video', video: 'r.mp4', poster: 'r.png' },
|
|
featured: 0.13,
|
|
},
|
|
}),
|
|
fixture({
|
|
id: 'guizang',
|
|
od: {
|
|
mode: 'deck',
|
|
preview: { type: 'html', entry: './index.html' },
|
|
featured: 0.01,
|
|
},
|
|
}),
|
|
fixture({ id: 'huashu', od: { mode: 'prototype', featured: 0.03 } }),
|
|
fixture({ id: 'kami', od: { mode: 'deck', featured: 0.06 } }),
|
|
];
|
|
const sorted = sortByVisualAppeal(records).map((r) => r.id);
|
|
expect(sorted).toEqual(['guizang', 'huashu', 'kami', 'hyperframes']);
|
|
});
|
|
|
|
it('keeps the original list reference unchanged (returns a new array)', () => {
|
|
const records = [
|
|
fixture({ id: 'a' }),
|
|
fixture({ id: 'b', od: { mode: 'deck' } }),
|
|
];
|
|
const before = records.map((r) => r.id).join(',');
|
|
sortByVisualAppeal(records);
|
|
expect(records.map((r) => r.id).join(',')).toBe(before);
|
|
});
|
|
|
|
it('breaks ties deterministically by title, then by original position', () => {
|
|
const records = [
|
|
fixture({ id: 'beta', title: 'Beta' }),
|
|
fixture({ id: 'alpha', title: 'Alpha' }),
|
|
];
|
|
const sorted = sortByVisualAppeal(records).map((r) => r.id);
|
|
expect(sorted).toEqual(['alpha', 'beta']);
|
|
});
|
|
});
|