// 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 }} Kit', 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 %} ')).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); }); test('inlined storefront CSS/JS contain no Liquid delimiters (proxy pages render them as Liquid)', async () => { // vitest stubs CSS imports, so check the source files; the production build inlines them via ?raw const { readFileSync } = await import('node:fs'); for (const f of ['bundle-offers.css', 'bundle-offers.js']) { const src = readFileSync(new URL(`../../extensions/bundle-offers-theme/assets/${f}`, import.meta.url), 'utf8'); expect(src.length).toBeGreaterThan(1000); expect(src).not.toMatch(/\{\{|\{%/); } const { selfContained } = await import('../../app/lib/storefront.server.ts'); expect(selfContained('

x

')).toContain('window.BundleOffers'); });