diff --git a/src/art/manifest.js b/src/art/manifest.js new file mode 100644 index 0000000..bfa90d0 --- /dev/null +++ b/src/art/manifest.js @@ -0,0 +1,20 @@ +export function validateArtManifest(m) { + if (!Array.isArray(m)) return ['manifest must be an array']; + const errors = []; + m.forEach((a, i) => { + if (!a || typeof a !== 'object') { errors.push(`entry[${i}] must be an object`); return; } + if (!a.id) errors.push(`entry[${i}].id required`); + if (!a.src) errors.push(`entry[${i}].src required`); + if (!Array.isArray(a.tags)) errors.push(`entry[${i}].tags must be an array`); + }); + return errors; +} + +// Seed set of placeholder pencil assets. Real convention art is added here as data. +export default [ + { id: 'detective-alley', src: '/art/detective-alley.png', label: 'Detective in alley', tags: ['detective', 'alley', 'rain', 'coat', 'tired'] }, + { id: 'neon-street', src: '/art/neon-street.png', label: 'Neon street', tags: ['street', 'neon', 'rain', 'city', 'night'] }, + { id: 'worn-portrait', src: '/art/worn-portrait.png', label: 'Worn portrait', tags: ['portrait', 'face', 'worn', 'close-up'] }, + { id: 'doorway-figure', src: '/art/doorway-figure.png', label: 'Figure in a doorway', tags: ['doorway', 'figure', 'shadow', 'coat'] }, + { id: 'rain-pier', src: '/art/rain-pier.png', label: 'Pier in the rain', tags: ['pier', 'rain', 'water', 'night', 'wide'] }, +]; diff --git a/tests/art/manifest.test.js b/tests/art/manifest.test.js new file mode 100644 index 0000000..5ffa5d2 --- /dev/null +++ b/tests/art/manifest.test.js @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest'; +import artManifest, { validateArtManifest } from '../../src/art/manifest.js'; + +describe('art manifest', () => { + it('flags malformed entries', () => { + expect(validateArtManifest([{ id: '', src: 'x', tags: [] }])).toContain('entry[0].id required'); + expect(validateArtManifest([{ id: 'a', src: '', tags: [] }])).toContain('entry[0].src required'); + expect(validateArtManifest([{ id: 'a', src: 'x' }])).toContain('entry[0].tags must be an array'); + }); + + it('ships a valid, non-empty seed manifest with unique ids', () => { + expect(validateArtManifest(artManifest)).toEqual([]); + expect(artManifest.length).toBeGreaterThanOrEqual(3); + const ids = artManifest.map((a) => a.id); + expect(new Set(ids).size).toBe(ids.length); + expect(artManifest.every((a) => a.src.startsWith('/art/'))).toBe(true); + }); +});