mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 09:06:42 +02:00
feat(wallpaper-generator): add device wallpaper generation package
Add new @manacore/wallpaper-generator package for creating device wallpapers from QR codes and images. Features: - 30 device presets (phones, tablets, desktops) - 3 layout types (center, corner, pattern) - 5 gradient presets + solid color backgrounds - Browser (Canvas) and Node.js (Sharp) renderers - Svelte WallpaperModal UI component Integrations: - @manacore/qr-export: toWallpaper() function - @manacore/spiral-db: toWallpaper() function - QRExportModal: "Als Wallpaper" button Also includes the full @manacore/qr-export package which was previously untracked. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c480231128
commit
e5109da732
37 changed files with 5393 additions and 676 deletions
153
packages/qr-export/src/encoder.test.ts
Normal file
153
packages/qr-export/src/encoder.test.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { encode, decode, estimateSize, willFitInQR, MANA_QR_PREFIX } from './encoder';
|
||||
import { createManaQRExport, contact, event, todo } from './builder';
|
||||
import type { ManaQRExport } from './types';
|
||||
|
||||
describe('encoder', () => {
|
||||
const sampleExport: ManaQRExport = {
|
||||
v: 1,
|
||||
ts: 1708185600,
|
||||
u: {
|
||||
n: 'Till',
|
||||
z: 'Europe/Berlin',
|
||||
l: 'de',
|
||||
w: 'Berlin',
|
||||
},
|
||||
c: [
|
||||
{ n: 'Mama', p: '+491701234567', r: 1 },
|
||||
{ n: 'Papa', p: '+491707654321', r: 1 },
|
||||
],
|
||||
e: [
|
||||
{ t: 'Zahnarzt', s: 1708272000, d: 60, l: 'Praxis Dr. Weber' },
|
||||
{ t: 'Team Meeting', s: 1708358400, d: 30 },
|
||||
],
|
||||
t: [
|
||||
{ t: 'Steuererklärung abgeben', p: 1, d: 14 },
|
||||
{ t: 'Backup machen', p: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
describe('encode', () => {
|
||||
it('should encode data with correct prefix', () => {
|
||||
const result = encode(sampleExport);
|
||||
|
||||
expect(result.data.startsWith(MANA_QR_PREFIX)).toBe(true);
|
||||
expect(result.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should report fitsInQR correctly for small data', () => {
|
||||
const result = encode(sampleExport);
|
||||
|
||||
expect(result.fitsInQR).toBe(true);
|
||||
expect(result.size).toBeLessThan(2500);
|
||||
});
|
||||
|
||||
it('should compress data significantly', () => {
|
||||
const jsonSize = JSON.stringify(sampleExport).length;
|
||||
const result = encode(sampleExport);
|
||||
|
||||
// Encoded should be smaller than raw JSON
|
||||
expect(result.size).toBeLessThan(jsonSize);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decode', () => {
|
||||
it('should decode encoded data correctly', () => {
|
||||
const encoded = encode(sampleExport);
|
||||
const decoded = decode(encoded.data);
|
||||
|
||||
expect(decoded.success).toBe(true);
|
||||
if (decoded.success) {
|
||||
expect(decoded.data.u.n).toBe('Till');
|
||||
expect(decoded.data.c).toHaveLength(2);
|
||||
expect(decoded.data.e).toHaveLength(2);
|
||||
expect(decoded.data.t).toHaveLength(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail for invalid prefix', () => {
|
||||
const result = decode('INVALID:xyz');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error).toBe('INVALID_PREFIX');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail for invalid base64', () => {
|
||||
const result = decode(MANA_QR_PREFIX + '!!!invalid!!!');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error).toBe('INVALID_BASE64');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('roundtrip', () => {
|
||||
it('should preserve all data through encode/decode', () => {
|
||||
const encoded = encode(sampleExport);
|
||||
const decoded = decode(encoded.data);
|
||||
|
||||
expect(decoded.success).toBe(true);
|
||||
if (decoded.success) {
|
||||
expect(decoded.data).toEqual(sampleExport);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('estimateSize', () => {
|
||||
it('should estimate size within reasonable range', () => {
|
||||
const estimated = estimateSize(sampleExport);
|
||||
const actual = encode(sampleExport).size;
|
||||
|
||||
// Estimate should be within 50% of actual
|
||||
expect(estimated).toBeGreaterThan(actual * 0.5);
|
||||
expect(estimated).toBeLessThan(actual * 1.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('willFitInQR', () => {
|
||||
it('should return true for small data', () => {
|
||||
expect(willFitInQR(sampleExport)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('builder', () => {
|
||||
it('should build export with fluent API', () => {
|
||||
const result = createManaQRExport()
|
||||
.user({ n: 'Test', z: 'UTC', l: 'en' })
|
||||
.addContact(contact('Alice', '+1234567890', 1))
|
||||
.addEvent(event('Meeting', new Date('2024-02-20T10:00:00Z'), 60))
|
||||
.addTodo(todo('Task 1', 1, 7))
|
||||
.build();
|
||||
|
||||
expect(result.v).toBe(1);
|
||||
expect(result.u.n).toBe('Test');
|
||||
expect(result.c).toHaveLength(1);
|
||||
expect(result.e).toHaveLength(1);
|
||||
expect(result.t).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should encode to valid QR string', () => {
|
||||
const result = createManaQRExport()
|
||||
.userName('Test')
|
||||
.timezone('UTC')
|
||||
.addContact({ n: 'Bob', r: 3 })
|
||||
.encode();
|
||||
|
||||
expect(result.data.startsWith(MANA_QR_PREFIX)).toBe(true);
|
||||
expect(result.fitsInQR).toBe(true);
|
||||
});
|
||||
|
||||
it('should estimate size correctly', () => {
|
||||
const builder = createManaQRExport().userName('Test').addContact({ n: 'Alice', r: 1 });
|
||||
|
||||
const estimated = builder.estimateSize();
|
||||
const _actual = builder.encode().size;
|
||||
|
||||
expect(estimated).toBeGreaterThan(0);
|
||||
expect(builder.willFit()).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue