Rust discount function (one automatic discount evaluating all offers in priority order, parity with the Odoo engine), React Router admin with Polaris, theme app extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
2.7 KiB
JavaScript
37 lines
2.7 KiB
JavaScript
// Proxy page Liquid: merchant text can't inject Liquid/HTML; every bundle type renders; writes the output to
|
|
// $BO_LIQUID_OUT (if set) so it can be run through theme-check.
|
|
import { test, expect } from 'vitest';
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { bundlePageLiquid, bundlesListLiquid, esc } from '../../app/lib/storefront.server.ts';
|
|
|
|
const base = { id: 'x', code: 'o1', handle: 'diwali-kit', oldHandles: [], name: 'Diwali {{ shop.name }} <b>Kit</b>', badge: 'Diwali {% raw %}', description: 'Line 1\nLine 2', i18n: { fr: { name: 'Coffret Diwali' } }, sequence: 10, active: true, published: true, countries: [],
|
|
discountType: 'percent', discountValue: 20, mixQty: 3, buyQty: 2, getQty: 1, getPercent: 100, startsAt: null, endsAt: new Date('2026-11-09T04:59:59Z'), maxPerOrder: 0,
|
|
showHome: true, showProduct: true, showCartHints: true, imageUrl: null, limitedByPlan: false, createdAt: new Date(), updatedAt: new Date(), shopId: 's',
|
|
lines: [{ productId: '1', variantId: '11', quantity: 1, handle: 'diya', title: 'Diya', imageUrl: 'https://cdn.shopify.com/a.jpg' }, { productId: '2', variantId: null, quantity: 2, handle: "camphor'}}", title: 'Camphor', imageUrl: null }],
|
|
scopes: [{ role: 'mix', kind: 'collection', refId: '50', handle: 'sweets' }, { role: 'mix', kind: 'variant', refId: '71', parentId: '7', handle: 'ladoo' },
|
|
{ role: 'buy', kind: 'collection', refId: '60', handle: 'garlands' }, { role: 'get', kind: 'product', refId: '8', handle: 'marigold' }] };
|
|
const shop = { currency: 'CAD', timezone: 'America/Toronto', branding: true };
|
|
|
|
test('escaping neutralises Liquid and HTML', () => {
|
|
expect(esc('{{ x }} {% y %} <a>')).not.toMatch(/[{}%<>]/);
|
|
});
|
|
|
|
test('bundle pages render for every type without merchant-controlled Liquid', () => {
|
|
const out = process.env.BO_LIQUID_OUT;
|
|
if (out) mkdirSync(join(out, 'snippets'), { recursive: true });
|
|
for (const type of ['fixed', 'mix', 'bxgy']) {
|
|
const liquid = bundlePageLiquid({ ...base, type }, shop, null);
|
|
expect(liquid).not.toContain('{{ shop.name');
|
|
expect(liquid).not.toContain('{% raw');
|
|
expect(liquid).not.toContain("camphor'}}");
|
|
if (type === 'fixed') expect(liquid).toContain("all_products['camphor']");
|
|
expect(liquid).toContain(type === 'fixed' ? 'Add bundle to cart' : 'Add to cart');
|
|
if (out) writeFileSync(join(out, 'snippets', `page-${type}.liquid`), liquid);
|
|
}
|
|
expect(bundlePageLiquid({ ...base, type: 'mix' }, shop, 'fr')).toContain('Coffret Diwali');
|
|
const list = bundlesListLiquid([{ ...base, type: 'fixed' }, { ...base, type: 'mix', discountType: 'fixed_price' }], shop, null);
|
|
expect(list).toContain('data-bo-money="20"');
|
|
if (out) writeFileSync(join(out, 'snippets', 'list.liquid'), list);
|
|
});
|