(feat) Discord build share
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import sharp from 'sharp';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
BUILD_SOCIAL_CARD_HEIGHT,
|
||||
BUILD_SOCIAL_CARD_WIDTH,
|
||||
buildBuildSocialCardSvg,
|
||||
renderBuildSocialCardPng,
|
||||
type BuildSocialCardInput,
|
||||
} from './build-social-card';
|
||||
|
||||
const input: BuildSocialCardInput = {
|
||||
title: 'Vanguard Counter-Offensive',
|
||||
author: 'Agent Kelso',
|
||||
primaryWeapon: 'Police M4',
|
||||
secondaryWeapon: 'Super 90',
|
||||
firepower: 1280,
|
||||
toughness: 940,
|
||||
engineering: 615,
|
||||
supportingAttributes: [
|
||||
{ label: 'Critical Hit Chance', value: '18.5%' },
|
||||
{ label: 'Headshot Damage', value: '42%' },
|
||||
{ label: 'Armor', value: '12,400' },
|
||||
],
|
||||
visibility: 'public',
|
||||
directUrl: 'shd.build/builds/vanguard-counter-offensive',
|
||||
};
|
||||
|
||||
describe('build social card', () => {
|
||||
it('escapes XML injection and keeps inputs out of markup and URLs', () => {
|
||||
const attack = `</text><script href="https://attacker.invalid">alert('x') & more</script>`;
|
||||
const svg = buildBuildSocialCardSvg({
|
||||
...input,
|
||||
title: attack,
|
||||
author: `A\u0000gent <&> "one" 'two'`,
|
||||
directUrl: `javascript:alert(1)\n${attack}`,
|
||||
supportingAttributes: [{ label: attack, value: attack }],
|
||||
});
|
||||
|
||||
expect(svg).not.toContain('<script');
|
||||
expect(svg).not.toContain('href="https://attacker.invalid"');
|
||||
expect(svg).not.toContain('\u0000');
|
||||
expect(svg).toContain(
|
||||
'</text><script href="https://attacker.invalid">',
|
||||
);
|
||||
expect(svg).toContain(
|
||||
'A gent <&> "one" 'two'',
|
||||
);
|
||||
expect(svg).not.toMatch(/<[^>]+\s(?:href|xlink:href)\s*=/gu);
|
||||
});
|
||||
|
||||
it('wraps and truncates long Unicode text without splitting graphemes', () => {
|
||||
const family = '👨👩👧👦';
|
||||
const svg = buildBuildSocialCardSvg({
|
||||
...input,
|
||||
title: `${family.repeat(31)} ${family.repeat(31)} ${family.repeat(31)}`,
|
||||
author: `Zoë ${'e\u0301'.repeat(60)}`,
|
||||
directUrl: `shd.build/builds/${'路'.repeat(130)}`,
|
||||
});
|
||||
|
||||
const titleBlock =
|
||||
svg.match(/<text class="title">(.*?)<\/text>/u)?.[1] ?? '';
|
||||
expect(titleBlock.match(/<tspan/gu)).toHaveLength(2);
|
||||
expect(titleBlock).toContain('…');
|
||||
expect(titleBlock).not.toMatch(
|
||||
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/gu,
|
||||
);
|
||||
expect(svg).toContain('é');
|
||||
expect(svg).not.toContain('é');
|
||||
expect(svg).toContain(`${'路'.repeat(20)}`);
|
||||
expect(svg).toContain('…');
|
||||
});
|
||||
|
||||
it('builds deterministic SVG with bounded supporting facts', () => {
|
||||
const withExtraFacts = {
|
||||
...input,
|
||||
supportingAttributes: [
|
||||
...input.supportingAttributes!,
|
||||
{ label: 'Must not render', value: '999' },
|
||||
],
|
||||
};
|
||||
const first = buildBuildSocialCardSvg(withExtraFacts);
|
||||
const second = buildBuildSocialCardSvg(withExtraFacts);
|
||||
|
||||
expect(first).toBe(second);
|
||||
expect(first).toContain('width="1200" height="630"');
|
||||
expect(first).toContain('SHD</text><text');
|
||||
expect(first).toContain('.build');
|
||||
expect(first).toContain('font-family:"DejaVu Sans Condensed",sans-serif');
|
||||
expect(first).toContain('SUPPORTING ATTRIBUTES / 03');
|
||||
expect(first).not.toContain('Must not render');
|
||||
});
|
||||
|
||||
it('renders a valid 1200x630 PNG through Sharp', async () => {
|
||||
const png = await renderBuildSocialCardPng(input);
|
||||
const metadata = await sharp(png).metadata();
|
||||
|
||||
expect(png.subarray(0, 8)).toEqual(
|
||||
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
||||
);
|
||||
expect(metadata.format).toBe('png');
|
||||
expect(metadata.width).toBe(BUILD_SOCIAL_CARD_WIDTH);
|
||||
expect(metadata.height).toBe(BUILD_SOCIAL_CARD_HEIGHT);
|
||||
expect(png.byteLength).toBeLessThan(1_000_000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user