@certrev/cert-block 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CertBadge.d.ts +18 -3
- package/dist/components/CertBadge.d.ts.map +1 -1
- package/dist/components/CertBadge.js +8 -2
- package/dist/components/CertBadge.js.map +1 -1
- package/dist/components/CertReview.d.ts +7 -0
- package/dist/components/CertReview.d.ts.map +1 -1
- package/dist/components/CertReview.js +1 -1
- package/dist/components/CertReview.js.map +1 -1
- package/dist/components/format.d.ts +14 -2
- package/dist/components/format.d.ts.map +1 -1
- package/dist/components/format.js +18 -5
- package/dist/components/format.js.map +1 -1
- package/dist/components/render-cert-block.d.ts +91 -0
- package/dist/components/render-cert-block.d.ts.map +1 -0
- package/dist/components/render-cert-block.js +306 -0
- package/dist/components/render-cert-block.js.map +1 -0
- package/dist/components/render-def.d.ts +178 -0
- package/dist/components/render-def.d.ts.map +1 -0
- package/dist/components/render-def.js +204 -0
- package/dist/components/render-def.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/webcomponent/render-badge-html.d.ts +8 -0
- package/dist/webcomponent/render-badge-html.d.ts.map +1 -1
- package/dist/webcomponent/render-badge-html.js +31 -4
- package/dist/webcomponent/render-badge-html.js.map +1 -1
- package/package.json +77 -76
- package/src/__tests__/__snapshots__/render-cert-block.test.ts.snap +13 -0
- package/src/__tests__/components.test.tsx +16 -1
- package/src/__tests__/render-cert-block.test.ts +282 -0
- package/src/__tests__/render-def.test.tsx +286 -0
- package/src/__tests__/webcomponent.test.tsx +10 -0
- package/src/components/CertBadge.tsx +56 -4
- package/src/components/CertReview.tsx +8 -0
- package/src/components/format.ts +22 -5
- package/src/components/render-cert-block.ts +424 -0
- package/src/components/render-def.ts +286 -0
- package/src/index.ts +35 -0
- package/src/webcomponent/render-badge-html.ts +46 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`renderCertBlock — locked-design snapshots > banner (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--banner" data-certrev-mode="banner" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);overflow:hidden;font-family:var(--bf);"><div data-certrev-modal-open style="display:flex;align-items:center;justify-content:space-between;gap:16px;padding:15px 22px;background:#0a1b3f;color:#fff;cursor:pointer;"><span style="display:inline-flex;align-items:center;gap:11px;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="20" height="20" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg> Expert reviewed</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" aria-label="View certificate" style="display:inline-flex;align-items:center;flex-shrink:0;color:inherit;opacity:.72;text-decoration:none;"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a></div><div style="padding:22px 24px 18px;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:22px;"><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Written by</div><div style="display:flex;align-items:center;gap:12px;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">TE</div><div><div style="font-size:15px;font-weight:700;">The Editorial Team</div><div style="font-size:13px;color:var(--navy-55);">Editorial</div></div></div></div><div><div style="font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);margin-bottom:11px;">Reviewed by</div><div class="certrev-cert__rev" role="button" tabindex="0" aria-expanded="false" data-certrev-bio-toggle aria-label="Read more about Jane Doe" style="display:flex;align-items:flex-start;gap:12px;cursor:pointer;border-radius:8px;transition:opacity .15s;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">JD</div><div><div style="font-size:15px;font-weight:700;">Jane Doe, MD<span class="certrev-cert__caret" style="display:inline-flex;vertical-align:middle;margin-left:4px;color:var(--navy-35);transition:transform .18s ease-out;"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></div><div style="font-size:13px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:13px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div><div class="certrev-cert__bio" hidden style="display:none;margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--navy-75);">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</div></div></div></div></div><div style="height:1px;background:var(--navy-10);margin:16px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-55);"><span>Certified <span style="margin-left:8px;">Apr 14, 2026</span></span><span>Compensated expert</span></div></div></div><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);padding:24px;font-family:var(--bf);"><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--navy-55);margin-bottom:14px;">Expert memo</div><div style="display:flex;gap:15px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div style="flex:1;min-width:0;"><div style="font-size:16px;line-height:1.2;"><span style="font-weight:700;">Jane Doe</span><span style="color:var(--navy-55);">, MD</span></div><p style="margin:11px 0 14px;font-size:15.5px;line-height:1.6;color:var(--navy-75);white-space:pre-line;">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</p></div></div><div style="height:1px;background:var(--navy-10);margin:18px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:16px 22px;flex-wrap:wrap;"><div style="display:inline-flex;align-items:center;gap:9px;font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-55);"><svg viewBox="0 0 375 375" width="15" height="15" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg><span>Verified by <span style="font-weight:700;color:var(--navy);">CertREV</span></span></div><div style="display:flex;flex-wrap:wrap;gap:22px;"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);text-decoration:none;cursor:pointer;">View Jane's profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);text-decoration:none;cursor:pointer;">View certificate</a></div></div><div style="font-size:13px;color:var(--navy-55);margin-top:8px;line-height:1.5;">Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div></div>"`;
|
|
4
|
+
|
|
5
|
+
exports[`renderCertBlock — locked-design snapshots > banner (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--banner" data-certrev-mode="banner" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);overflow:hidden;font-family:var(--bf);"><div data-certrev-modal-open style="display:flex;align-items:center;justify-content:space-between;gap:16px;padding:15px 22px;background:#0a1b3f;color:#fff;cursor:pointer;"><span style="display:inline-flex;align-items:center;gap:11px;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="20" height="20" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg> Expert reviewed</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" aria-label="View certificate" style="display:inline-flex;align-items:center;flex-shrink:0;color:inherit;opacity:.72;text-decoration:none;"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a></div><div style="padding:22px 24px 18px;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:22px;"><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Written by</div><div style="display:flex;align-items:center;gap:12px;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">TE</div><div><div style="font-size:15px;font-weight:700;">The Editorial Team</div><div style="font-size:13px;color:var(--navy-55);">Editorial</div></div></div></div><div><div style="font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);margin-bottom:11px;">Reviewed by</div><div class="certrev-cert__rev" role="button" tabindex="0" aria-expanded="false" data-certrev-bio-toggle aria-label="Read more about Jane Doe" style="display:flex;align-items:flex-start;gap:12px;cursor:pointer;border-radius:8px;transition:opacity .15s;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">JD</div><div><div style="font-size:15px;font-weight:700;">Jane Doe, MD<span class="certrev-cert__caret" style="display:inline-flex;vertical-align:middle;margin-left:4px;color:var(--navy-35);transition:transform .18s ease-out;"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></div><div style="font-size:13px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:13px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div><div class="certrev-cert__bio" hidden style="display:none;margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--navy-75);">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</div></div></div></div></div><div style="height:1px;background:var(--navy-10);margin:16px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-55);"><span>Certified <span style="margin-left:8px;">Apr 14, 2026</span></span><span>Compensated expert</span></div></div></div><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);padding:24px;font-family:var(--bf);"><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--navy-55);margin-bottom:14px;">Expert memo</div><div style="display:flex;gap:15px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div style="flex:1;min-width:0;"><div style="font-size:16px;line-height:1.2;"><span style="font-weight:700;">Jane Doe</span><span style="color:var(--navy-55);">, MD</span></div><p style="margin:11px 0 14px;font-size:15.5px;line-height:1.6;color:var(--navy-75);white-space:pre-line;">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</p></div></div><div style="height:1px;background:var(--navy-10);margin:18px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:16px 22px;flex-wrap:wrap;"><div style="display:inline-flex;align-items:center;gap:9px;font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-55);"><svg viewBox="0 0 375 375" width="15" height="15" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg><span>Verified by <span style="font-weight:700;color:var(--navy);">CertREV</span></span></div><div style="display:flex;flex-wrap:wrap;gap:22px;"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);text-decoration:none;cursor:pointer;">View Jane's profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);text-decoration:none;cursor:pointer;">View certificate</a></div></div><div style="font-size:13px;color:var(--navy-55);margin-top:8px;line-height:1.5;">Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div></div>"`;
|
|
6
|
+
|
|
7
|
+
exports[`renderCertBlock — locked-design snapshots > floating (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--floating" data-certrev-mode="floating" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div class="certrev-cert__pill" style="display:inline-flex;align-items:center;gap:14px;padding:12px 22px;border-radius:999px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);box-shadow:0 10px 26px var(--navy-10);"><span style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;opacity:.75;">Expert reviewed:</span><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Profile</a><span style="opacity:.5;">·</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Certificate</a></div></div>"`;
|
|
8
|
+
|
|
9
|
+
exports[`renderCertBlock — locked-design snapshots > floating (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--floating" data-certrev-mode="floating" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div class="certrev-cert__pill" style="display:inline-flex;align-items:center;gap:14px;padding:12px 22px;border-radius:999px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);box-shadow:0 10px 26px var(--navy-10);"><span style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;opacity:.75;">Expert reviewed:</span><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Profile</a><span style="opacity:.5;">·</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Certificate</a></div></div>"`;
|
|
10
|
+
|
|
11
|
+
exports[`renderCertBlock — locked-design snapshots > sidebar (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--sidebar" data-certrev-mode="sidebar" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div style="background:var(--surface);border:1px solid var(--navy-10);border-radius:var(--br,14px);overflow:hidden;box-shadow:0 4px 18px var(--navy-06);font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:center;gap:10px;padding:12px;background:#0a1b3f;color:#fff;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg> Expert reviewed</div><div style="padding:20px;"><div style="display:flex;align-items:flex-start;gap:13px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div><div style="font-size:16px;font-weight:700;">Jane Doe, MD</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div></div></div><p style="font-size:14px;line-height:1.55;color:var(--navy-75);margin:16px 0 0;">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</p><blockquote style="margin:16px 0 0;padding-left:16px;border-left:3px solid var(--ba,#0a1b3f);font-size:15px;line-height:1.5;color:var(--navy-75);white-space:pre-line;">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="font-size:12.5px;line-height:1.5;color:var(--navy-55);margin-top:16px;"><span style="font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;">Compensated expert</span> · Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div><div style="display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--navy-10);"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;border-right:1px solid var(--navy-10);cursor:pointer;">View Profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Certificate</a></div></div></div>"`;
|
|
12
|
+
|
|
13
|
+
exports[`renderCertBlock — locked-design snapshots > sidebar (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--sidebar" data-certrev-mode="sidebar" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;font-family:var(--bf);color:var(--navy)"><div style="background:var(--surface);border:1px solid var(--navy-10);border-radius:var(--br,14px);overflow:hidden;box-shadow:0 4px 18px var(--navy-06);font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:center;gap:10px;padding:12px;background:#0a1b3f;color:#fff;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z"></path><path fill="currentColor" d="M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z"></path><path fill="currentColor" d="M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z"></path><path fill="currentColor" d="M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"></path></svg> Expert reviewed</div><div style="padding:20px;"><div style="display:flex;align-items:flex-start;gap:13px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div><div style="font-size:16px;font-weight:700;">Jane Doe, MD</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div></div></div><p style="font-size:14px;line-height:1.55;color:var(--navy-75);margin:16px 0 0;">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</p><blockquote style="margin:16px 0 0;padding-left:16px;border-left:3px solid var(--ba,#0a1b3f);font-size:15px;line-height:1.5;color:var(--navy-75);white-space:pre-line;">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="font-size:12.5px;line-height:1.5;color:var(--navy-55);margin-top:16px;"><span style="font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;">Compensated expert</span> · Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div><div style="display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--navy-10);"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;border-right:1px solid var(--navy-10);cursor:pointer;">View Profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Certificate</a></div></div></div>"`;
|
|
@@ -46,12 +46,27 @@ describe('<CertBadge>', () => {
|
|
|
46
46
|
expect(html).toContain('border-inline-start-color:#7c3aed')
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
it('compact style collapses
|
|
49
|
+
it('compact style collapses the reviewer block — no memo, no dates, no photo', () => {
|
|
50
50
|
const html = renderToStaticMarkup(<CertBadge payload={payload} badgeStyle="compact" />)
|
|
51
51
|
expect(html).toContain('certrev-badge--compact')
|
|
52
52
|
expect(html).not.toContain('retinol claims')
|
|
53
53
|
expect(html).not.toContain('Certified')
|
|
54
54
|
expect(html).not.toContain('__photo')
|
|
55
|
+
// …but the compliance disclosures persist: a compact claim badge is still a claim face.
|
|
56
|
+
expect(html).toContain(
|
|
57
|
+
'Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.',
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('carries the always-on compliance baseline: cue + verbatim FTC scope line + strings-version stamp', () => {
|
|
62
|
+
const html = renderToStaticMarkup(<CertBadge payload={payload} />)
|
|
63
|
+
expect(html).toContain('data-certrev-strings-version="2026-07"')
|
|
64
|
+
expect(html).toContain(
|
|
65
|
+
'Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.',
|
|
66
|
+
)
|
|
67
|
+
expect(html).toContain('data-ftc-line')
|
|
68
|
+
// "Compensated expert" (tag-stripped — the cue links "expert" to the reviewer profile).
|
|
69
|
+
expect(html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ')).toContain('Compensated expert')
|
|
55
70
|
})
|
|
56
71
|
|
|
57
72
|
it('honors display flags: showMemo=false hides the memo; showExpertPhoto=false hides the photo', () => {
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderCertBlock — the LOCKED 3-mode CertREV cert design (banner / sidebar /
|
|
3
|
+
* floating) as an SSR-safe HTML string.
|
|
4
|
+
*
|
|
5
|
+
* Coverage:
|
|
6
|
+
* 1. SNAPSHOTS — each mode for a representative envelope + a themed (non-navy)
|
|
7
|
+
* variant, so any drift from the owner-approved locked design is caught.
|
|
8
|
+
* 2. LOCKED disclosures — the compensation cue, the byte-exact FTC scope line,
|
|
9
|
+
* and the GROUPED dated credential verification appear on every CLAIM face
|
|
10
|
+
* (banner + sidebar). Floating is a modal pointer, not a claim face.
|
|
11
|
+
* 3. THEMING — the resolved tokens reach the root as the guide's CSS vars; an
|
|
12
|
+
* invalid token fails safe to navy; the fixed logo chrome is present.
|
|
13
|
+
* 4. ESCAPING — a value carrying `< & "` is escaped (this is storefront HTML).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from 'vitest'
|
|
17
|
+
import {
|
|
18
|
+
CERT_SCOPE_LINE,
|
|
19
|
+
type CertBlockFacts,
|
|
20
|
+
type CertBlockMode,
|
|
21
|
+
COMPENSATED_EXPERT_CUE,
|
|
22
|
+
CREDENTIAL_VERIFIED_ATTRIBUTION,
|
|
23
|
+
RENDER_BLOCK_FONT_STACKS,
|
|
24
|
+
type ResolvedBlockTheme,
|
|
25
|
+
renderCertBlock,
|
|
26
|
+
} from '../index.js'
|
|
27
|
+
|
|
28
|
+
/** A representative, well-formed cert (mirrors the locked-reference sample facts). */
|
|
29
|
+
const FACTS: CertBlockFacts = {
|
|
30
|
+
authorName: 'The Editorial Team',
|
|
31
|
+
authorTitle: 'Editorial',
|
|
32
|
+
reviewerName: 'Jane Doe',
|
|
33
|
+
credential: 'MD',
|
|
34
|
+
credentialVerifiedAt: '2026-04-14T00:00:00.000Z',
|
|
35
|
+
certifiedAt: '2026-04-14T00:00:00.000Z',
|
|
36
|
+
memo: 'Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.',
|
|
37
|
+
bio: 'Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.',
|
|
38
|
+
profileUrl: 'https://certrev.com/expert/sample',
|
|
39
|
+
certificateUrl: 'https://certrev.com/verify/sample',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A themed brand (non-navy accent + dark ink + custom corners/surface/font). */
|
|
43
|
+
const THEMED: ResolvedBlockTheme = {
|
|
44
|
+
accentColor: '#E8603C',
|
|
45
|
+
surface: '#faf8f4',
|
|
46
|
+
cornerRadius: '22px',
|
|
47
|
+
fontSlot: 'grotesk',
|
|
48
|
+
accentFg: '#141414',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const MODES: CertBlockMode[] = ['banner', 'sidebar', 'floating']
|
|
52
|
+
const CLAIM_FACES: CertBlockMode[] = ['banner', 'sidebar']
|
|
53
|
+
|
|
54
|
+
/** Tag-strip a fragment of HTML to its visible text (collapse whitespace). */
|
|
55
|
+
function visibleText(html: string): string {
|
|
56
|
+
return html
|
|
57
|
+
.replace(/<[^>]+>/g, ' ')
|
|
58
|
+
.replace(/\s+/g, ' ')
|
|
59
|
+
.trim()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
// LOCKED disclosures are undroppable — omitted OR empty/whitespace defaults
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
describe('renderCertBlock — LOCKED disclosure strings are undroppable', () => {
|
|
67
|
+
for (const mode of CLAIM_FACES) {
|
|
68
|
+
it(`${mode}: an empty/whitespace override of the locked strings falls back to the byte-exact constants`, () => {
|
|
69
|
+
const html = renderCertBlock({
|
|
70
|
+
mode,
|
|
71
|
+
facts: { ...FACTS, compensationCue: '', scopeLine: '', credentialLine: ' ' },
|
|
72
|
+
})
|
|
73
|
+
expect(html).toContain(COMPENSATED_EXPERT_CUE)
|
|
74
|
+
expect(html).toContain(CERT_SCOPE_LINE)
|
|
75
|
+
expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Snapshots — the locked-design byte target
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
describe('renderCertBlock — locked-design snapshots', () => {
|
|
85
|
+
for (const mode of MODES) {
|
|
86
|
+
it(`${mode} (navy default) matches the locked snapshot`, () => {
|
|
87
|
+
expect(renderCertBlock({ mode, facts: FACTS })).toMatchSnapshot()
|
|
88
|
+
})
|
|
89
|
+
it(`${mode} (themed, non-navy accent) matches the locked snapshot`, () => {
|
|
90
|
+
expect(renderCertBlock({ mode, theme: THEMED, facts: FACTS })).toMatchSnapshot()
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// LOCKED disclosures — on every claim face
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
describe('the three LOCKED disclosures render on every CLAIM face', () => {
|
|
100
|
+
for (const mode of CLAIM_FACES) {
|
|
101
|
+
it(`${mode}: compensation cue + byte-exact FTC scope line + dated credential verification`, () => {
|
|
102
|
+
const html = renderCertBlock({ mode, facts: FACTS })
|
|
103
|
+
// (1) compensation cue
|
|
104
|
+
expect(visibleText(html)).toContain(COMPENSATED_EXPERT_CUE)
|
|
105
|
+
// (2) FTC scope line — byte-exact, including the load-bearing curly apostrophe U+2019
|
|
106
|
+
expect(html).toContain(CERT_SCOPE_LINE)
|
|
107
|
+
expect(CERT_SCOPE_LINE).toContain('’')
|
|
108
|
+
// (3) dated credential verification
|
|
109
|
+
expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
110
|
+
expect(html).toContain('on Apr 14, 2026')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it(`${mode}: the credential + its dated verification stay GROUPED, in order`, () => {
|
|
114
|
+
const html = renderCertBlock({ mode, facts: FACTS })
|
|
115
|
+
const iName = html.indexOf('Jane Doe, MD')
|
|
116
|
+
const iAttribution = html.indexOf(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
117
|
+
const iDate = html.indexOf('on Apr 14, 2026')
|
|
118
|
+
expect(iName).toBeGreaterThanOrEqual(0)
|
|
119
|
+
expect(iAttribution).toBeGreaterThan(iName)
|
|
120
|
+
expect(iDate).toBeGreaterThan(iAttribution)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it(`${mode}: the disclosures survive even when the caller omits the locked strings`, () => {
|
|
124
|
+
// Omit compensationCue / scopeLine / credentialLine — they default to the
|
|
125
|
+
// byte-verbatim mirrored constants (the compliance baseline is never dropped).
|
|
126
|
+
const html = renderCertBlock({ mode, facts: FACTS })
|
|
127
|
+
expect(html).toContain(CERT_SCOPE_LINE)
|
|
128
|
+
expect(visibleText(html)).toContain(COMPENSATED_EXPERT_CUE)
|
|
129
|
+
expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
it('the caller-supplied locked strings are rendered verbatim when provided', () => {
|
|
134
|
+
const html = renderCertBlock({
|
|
135
|
+
mode: 'banner',
|
|
136
|
+
facts: {
|
|
137
|
+
...FACTS,
|
|
138
|
+
compensationCue: COMPENSATED_EXPERT_CUE,
|
|
139
|
+
scopeLine: CERT_SCOPE_LINE,
|
|
140
|
+
credentialLine: CREDENTIAL_VERIFIED_ATTRIBUTION,
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
expect(html).toContain(CERT_SCOPE_LINE)
|
|
144
|
+
expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('the floating pill is a modal pointer — it makes no claim (no disclosures)', () => {
|
|
148
|
+
const html = renderCertBlock({ mode: 'floating', facts: FACTS })
|
|
149
|
+
expect(html).toContain('Expert reviewed:')
|
|
150
|
+
expect(html).not.toContain(CERT_SCOPE_LINE)
|
|
151
|
+
expect(html).not.toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
|
|
152
|
+
expect(visibleText(html)).not.toContain(COMPENSATED_EXPERT_CUE)
|
|
153
|
+
// but it still links to the two modal surfaces
|
|
154
|
+
expect(html).toContain('data-certrev-modal-open="expert"')
|
|
155
|
+
expect(html).toContain('data-certrev-modal-open ')
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Theming — CSS vars, fail-safe, fixed chrome
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
describe('theming', () => {
|
|
164
|
+
it('un-themed: the root carries the navy default vars + the strings-version stamp', () => {
|
|
165
|
+
const html = renderCertBlock({ mode: 'banner', facts: FACTS })
|
|
166
|
+
expect(html).toContain('--ba:#0a1b3f')
|
|
167
|
+
expect(html).toContain('--ba-fg:#ffffff')
|
|
168
|
+
expect(html).toContain('--br:14px')
|
|
169
|
+
expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.sans}`)
|
|
170
|
+
expect(html).toContain('--surface:#ffffff')
|
|
171
|
+
expect(html).toContain('data-certrev-strings-version="2026-07"')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('themed: the resolved tokens reach the root as CSS vars', () => {
|
|
175
|
+
const html = renderCertBlock({ mode: 'sidebar', theme: THEMED, facts: FACTS })
|
|
176
|
+
expect(html).toContain('--ba:#E8603C')
|
|
177
|
+
expect(html).toContain('--ba-fg:#141414')
|
|
178
|
+
expect(html).toContain('--br:22px')
|
|
179
|
+
expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.grotesk}`)
|
|
180
|
+
expect(html).toContain('--surface:#faf8f4')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('an invalid token fails SAFE to the navy default (never emitted into CSS)', () => {
|
|
184
|
+
const html = renderCertBlock({
|
|
185
|
+
mode: 'banner',
|
|
186
|
+
// injection attempts + malformed values across every token
|
|
187
|
+
theme: {
|
|
188
|
+
accentColor: 'red;}</style>',
|
|
189
|
+
surface: '#ffffffaa',
|
|
190
|
+
cornerRadius: 'calc(1px)',
|
|
191
|
+
fontSlot: 'comic-sans' as ResolvedBlockTheme['fontSlot'],
|
|
192
|
+
accentFg: 'blue' as ResolvedBlockTheme['accentFg'],
|
|
193
|
+
},
|
|
194
|
+
facts: FACTS,
|
|
195
|
+
})
|
|
196
|
+
expect(html).not.toContain('red;}')
|
|
197
|
+
expect(html).not.toContain('#ffffffaa')
|
|
198
|
+
// the injected radius is dropped (the card's own `calc(var(--br,14px) * 0.7)` is a fixed literal)
|
|
199
|
+
expect(html).not.toContain('calc(1px)')
|
|
200
|
+
expect(html).not.toContain('comic-sans')
|
|
201
|
+
expect(html).toContain('--ba:#0a1b3f')
|
|
202
|
+
expect(html).toContain('--ba-fg:#ffffff')
|
|
203
|
+
expect(html).toContain('--br:14px')
|
|
204
|
+
expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.sans}`)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('the fixed CertREV logo chrome renders on the banner + sidebar headers and the memo credit', () => {
|
|
208
|
+
const banner = renderCertBlock({ mode: 'banner', facts: FACTS })
|
|
209
|
+
const sidebar = renderCertBlock({ mode: 'sidebar', facts: FACTS })
|
|
210
|
+
// every mark tracks its container ink via currentColor (navy header + memo verified lockup)
|
|
211
|
+
expect(banner).toContain('aria-label="CertREV"')
|
|
212
|
+
expect(banner).toContain('<path fill="currentColor"')
|
|
213
|
+
// the memo footer carries the "Verified by CertREV" trust mark; the wordmark rides the body
|
|
214
|
+
// font now (the grotesk --font-wordmark slot is gone from the locked chrome)
|
|
215
|
+
expect(banner).toContain('Verified by')
|
|
216
|
+
expect(banner).not.toContain('font-family:var(--font-wordmark)')
|
|
217
|
+
expect(sidebar).toContain('aria-label="CertREV"')
|
|
218
|
+
expect(sidebar).toContain('<path fill="currentColor"')
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
223
|
+
// Escaping — storefront HTML built by hand
|
|
224
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
describe('escaping (this is storefront HTML)', () => {
|
|
227
|
+
const HOSTILE = 'Ev<script>il & "quoted" \'x\''
|
|
228
|
+
const hostileFacts: CertBlockFacts = {
|
|
229
|
+
...FACTS,
|
|
230
|
+
authorName: HOSTILE,
|
|
231
|
+
authorTitle: HOSTILE,
|
|
232
|
+
reviewerName: HOSTILE,
|
|
233
|
+
credential: HOSTILE,
|
|
234
|
+
memo: HOSTILE,
|
|
235
|
+
bio: HOSTILE,
|
|
236
|
+
profileUrl: 'javascript:alert(1)',
|
|
237
|
+
certificateUrl: 'javascript:alert(2)',
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const mode of CLAIM_FACES) {
|
|
241
|
+
it(`${mode}: every interpolated fact value is escaped; no raw injection survives`, () => {
|
|
242
|
+
const html = renderCertBlock({ mode, facts: hostileFacts })
|
|
243
|
+
// the injected markup never lands as live HTML
|
|
244
|
+
expect(html).not.toContain('<script>il')
|
|
245
|
+
expect(html).toContain('<script>')
|
|
246
|
+
expect(html).toContain('&')
|
|
247
|
+
expect(html).toContain('"')
|
|
248
|
+
expect(html).toContain(''')
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const mode of MODES) {
|
|
253
|
+
it(`${mode}: an unsafe URL scheme is dropped from every href`, () => {
|
|
254
|
+
const html = renderCertBlock({ mode, facts: hostileFacts })
|
|
255
|
+
expect(html).not.toContain('javascript:')
|
|
256
|
+
// the modal hook + a safe fallback href remain
|
|
257
|
+
expect(html).toContain('data-certrev-modal-open')
|
|
258
|
+
expect(html).toContain('href="#"')
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
264
|
+
// Reviewer name — honorific stripping (parity with the Liquid twin)
|
|
265
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
describe('reviewer name — honorific stripping (Liquid parity)', () => {
|
|
268
|
+
it('strips a leading honorific for the avatar initials + the first-name link', () => {
|
|
269
|
+
const html = renderCertBlock({ mode: 'banner', facts: { ...FACTS, reviewerName: 'Dr. Marcus Ellison' } })
|
|
270
|
+
// "Dr. Marcus Ellison" → "ME" avatar, never "DM"
|
|
271
|
+
expect(html).toContain('flex-shrink:0;">ME<')
|
|
272
|
+
expect(html).not.toContain('flex-shrink:0;">DM<')
|
|
273
|
+
// "View Marcus's profile", never "View Dr.'s profile" (apostrophe-agnostic)
|
|
274
|
+
expect(html).toMatch(/View Marcus.s profile/)
|
|
275
|
+
expect(html).not.toMatch(/View Dr\..s profile/)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('leaves an un-prefixed name untouched', () => {
|
|
279
|
+
const html = renderCertBlock({ mode: 'banner', facts: { ...FACTS, reviewerName: 'Jane Doe' } })
|
|
280
|
+
expect(html).toContain('flex-shrink:0;">JD<')
|
|
281
|
+
})
|
|
282
|
+
})
|