From f9e94708bae30b027216dd4257953ee3bea9aab2 Mon Sep 17 00:00:00 2001 From: dinesh Date: Tue, 19 May 2026 11:27:35 +0530 Subject: [PATCH] feat: add context-card diffing utility and validation layer --- packages/shared/src/__tests__/cards.test.ts | 72 +++++++++++++++++++++ packages/shared/src/cards.ts | 50 ++++++++++++++ packages/shared/src/index.ts | 1 + 3 files changed, 123 insertions(+) create mode 100644 packages/shared/src/__tests__/cards.test.ts create mode 100644 packages/shared/src/cards.ts diff --git a/packages/shared/src/__tests__/cards.test.ts b/packages/shared/src/__tests__/cards.test.ts new file mode 100644 index 0000000..0c1a6d1 --- /dev/null +++ b/packages/shared/src/__tests__/cards.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { validateCardPlatforms, diffCardPlatforms } from '../cards'; + +describe('validateCardPlatforms', () => { + it('passes with valid platforms', () => { + const result = validateCardPlatforms(['github', 'linkedin']); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('fails with empty array', () => { + const result = validateCardPlatforms([]); + expect(result.valid).toBe(false); + expect(result.errors).toContain('At least one platform is required.'); + }); + + it('fails with unknown platform', () => { + const result = validateCardPlatforms(['github', 'myspace']); + expect(result.valid).toBe(false); + expect(result.errors.some(e => e.includes('myspace'))).toBe(true); + }); + + it('fails with duplicate platforms', () => { + const result = validateCardPlatforms(['github', 'github']); + expect(result.valid).toBe(false); + expect(result.errors.some(e => e.includes('Duplicate'))).toBe(true); + }); + + it('passes with exactly 10 platforms', () => { + const platforms = ['github','linkedin','twitter','youtube','twitch', + 'discord','devto','medium','dribbble','leetcode']; + const result = validateCardPlatforms(platforms); + expect(result.valid).toBe(true); + }); + + it('fails with more than 10 platforms', () => { + const platforms = ['github','linkedin','twitter','youtube','twitch', + 'discord','devto','medium','dribbble','leetcode','npm']; + const result = validateCardPlatforms(platforms); + expect(result.valid).toBe(false); + expect(result.errors.some(e => e.includes('Maximum 10'))).toBe(true); + }); + + it('fails with all invalid platforms', () => { + const result = validateCardPlatforms(['myspace', 'bebo']); + expect(result.valid).toBe(false); + expect(result.errors.length).toBeGreaterThanOrEqual(2); + }); +}); + +describe('diffCardPlatforms', () => { + it('correctly identifies added, removed, unchanged', () => { + const diff = diffCardPlatforms(['github', 'linkedin'], ['github', 'twitter']); + expect(diff.added).toEqual(['twitter']); + expect(diff.removed).toEqual(['linkedin']); + expect(diff.unchanged).toEqual(['github']); + }); + + it('handles empty old card', () => { + const diff = diffCardPlatforms([], ['github']); + expect(diff.added).toEqual(['github']); + expect(diff.removed).toEqual([]); + expect(diff.unchanged).toEqual([]); + }); + + it('handles identical cards', () => { + const diff = diffCardPlatforms(['github'], ['github']); + expect(diff.added).toEqual([]); + expect(diff.removed).toEqual([]); + expect(diff.unchanged).toEqual(['github']); + }); +}); \ No newline at end of file diff --git a/packages/shared/src/cards.ts b/packages/shared/src/cards.ts new file mode 100644 index 0000000..d9fa513 --- /dev/null +++ b/packages/shared/src/cards.ts @@ -0,0 +1,50 @@ +export type CardValidationResult = { + valid: boolean; + errors: string[]; +}; + +const PLATFORMS = new Set([ + 'github', 'linkedin', 'twitter', 'instagram', 'youtube', + 'twitch', 'discord', 'devto', 'hashnode', 'medium', + 'dribbble', 'behance', 'figma', 'stackoverflow', 'leetcode', + 'codepen', 'replit', 'npm', 'producthunt', 'website', +]); + +export function validateCardPlatforms(platforms: string[]): CardValidationResult { + const errors: string[] = []; + + if (platforms.length === 0) { + errors.push('At least one platform is required.'); + } + + if (platforms.length > 10) { + errors.push(`Maximum 10 platforms allowed, got ${platforms.length}.`); + } + + const seen = new Set(); + for (const p of platforms) { + if (!PLATFORMS.has(p)) { + errors.push(`Unknown platform: "${p}".`); + } + if (seen.has(p)) { + errors.push(`Duplicate platform: "${p}".`); + } + seen.add(p); + } + + return { valid: errors.length === 0, errors }; +} + +export function diffCardPlatforms( + oldCard: string[], + newCard: string[] +): { added: string[]; removed: string[]; unchanged: string[] } { + const oldSet = new Set(oldCard); + const newSet = new Set(newCard); + + return { + added: newCard.filter(p => !oldSet.has(p)), + removed: oldCard.filter(p => !newSet.has(p)), + unchanged: oldCard.filter(p => newSet.has(p)), + }; +} \ No newline at end of file diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index a57e7e7..409d3e7 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,2 +1,3 @@ export * from './platforms'; export * from './types'; +export * from './cards'; \ No newline at end of file