Files
learningcircuit--local-deep…/tests/js/components/subscription-manager-render-card.test.js
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

131 lines
5.1 KiB
JavaScript

/**
* Tests for components/subscription-manager.js — renderSubscriptionCard.
*
* The card is built by template-string interpolation, so the assertions
* focus on data correctness (escaping, branching) rather than CSS class
* spelling. Covers active vs paused, optional folder/notes, and the
* data-subscription-id round-trip used by the action buttons.
*
* Kept in its own file (instead of extending subscription-manager.test.js)
* so it can land alongside / independently of PR #4297 without merge
* conflicts on a freshly-created test file.
*/
// Load xss-protection.js first so window.escapeHtml is defined: the card
// renderer escapes user fields via the global helper (the class no longer
// ships its own escapeHtml). In production, base.html loads xss-protection.js
// before subscription-manager.js with matching `defer` ordering.
import '@js/security/xss-protection.js';
import '@js/components/subscription-manager.js';
let manager;
beforeAll(() => {
// The module wires up its singleton inside a DOMContentLoaded listener;
// dispatch it manually if happy-dom already fired before the dynamic
// import settled.
if (!window.subscriptionManager) {
document.dispatchEvent(new Event('DOMContentLoaded'));
}
manager = window.subscriptionManager;
});
function makeSubscription(overrides = {}) {
// A baseline subscription that exercises the always-present fields.
// Use a far-future next_refresh so the computed timeUntil is stable
// (e.g. "365d" rather than something time-sensitive).
const oneYearMs = 365 * 24 * 60 * 60 * 1000;
return {
id: 'sub-abc-123',
query_or_topic: 'AI safety',
refresh_interval_minutes: 60,
next_refresh: new Date(Date.now() + oneYearMs).toISOString(),
status: 'active',
...overrides,
};
}
describe('subscriptionManager.renderSubscriptionCard', () => {
it('includes core fields (id, query, interval) in the output', () => {
const html = manager.renderSubscriptionCard(makeSubscription());
expect(html).toContain('data-subscription-id="sub-abc-123"');
expect(html).toContain('AI safety');
expect(html).toContain('Every 60 min');
});
it('shows a Pause button + pause icon when status is active', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ status: 'active' })
);
expect(html).toContain('title="Pause"');
expect(html).toContain('bi-pause');
expect(html).not.toContain('title="Resume"');
expect(html).not.toContain('bi-play');
});
it('shows a Resume button + play icon when status is not active (paused)', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ status: 'paused' })
);
expect(html).toContain('title="Resume"');
expect(html).toContain('bi-play');
expect(html).not.toContain('title="Pause"');
});
it('omits the folder span when subscription has no folder', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ folder: undefined })
);
expect(html).not.toContain('bi-folder');
});
it('renders the folder span when a folder is set', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ folder: 'Research' })
);
expect(html).toContain('bi-folder');
expect(html).toContain('Research');
});
it('omits the notes paragraph when no notes are provided', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ notes: undefined })
);
// The notes branch is the only <p> in the card template.
expect(html).not.toContain('<p ');
});
it('renders notes when provided', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({ notes: 'Important topic' })
);
expect(html).toContain('Important topic');
expect(html).toContain('<p ');
});
it('escapes HTML in user-supplied fields (XSS protection)', () => {
const html = manager.renderSubscriptionCard(
makeSubscription({
query_or_topic: '<script>alert(1)</script>',
folder: '<img src=x>',
notes: '<svg onload=alert(1)>',
})
);
// Local escapeHtml uses div+textContent+innerHTML, which escapes
// `<` to `&lt;`. The literal `<script>` substring must not appear.
expect(html).not.toContain('<script>');
expect(html).not.toContain('<img src=x>');
expect(html).not.toContain('<svg onload=alert(1)>');
// The escaped form should be present.
expect(html).toContain('&lt;script&gt;');
});
it('embeds the formatTimeUntil output near "Next:"', () => {
// Sanity check that the formatTimeUntil method is being called and
// its output is interpolated into the card. The exact number depends
// on system time, so just assert that a unit suffix follows "Next:".
const html = manager.renderSubscriptionCard(makeSubscription());
expect(html).toMatch(/Next:\s*\d+[mhd]/);
});
});