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
131 lines
5.1 KiB
JavaScript
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 `<`. 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('<script>');
|
|
});
|
|
|
|
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]/);
|
|
});
|
|
});
|