Files
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

211 lines
7.7 KiB
JavaScript

/**
* Tests for utils/log-helpers.js
*
* The log panel filters and dedups thousands of log entries during a
* research run. These helpers power that — an off-by-one in
* checkLogVisibility means warnings disappear; a collision in
* hashString means dedupe drops real entries.
*/
import '@js/utils/log-helpers.js';
const {
checkLogVisibility,
emptyCounts,
hashString,
normalizeMessage,
normalizeTimestamps,
} = window.LdrLogHelpers;
describe('checkLogVisibility', () => {
it("filter 'all' shows every type", () => {
for (const type of ['info', 'milestone', 'warning', 'error']) {
expect(checkLogVisibility(type, 'all')).toBe(true);
}
});
it("filter 'info' only shows info entries (regression: was a no-op)", () => {
// The pre-fix implementation returned true for every type under
// the 'info' filter, which made clicking the Info filter button
// effectively a no-op. This test pins down the correct, narrow
// behaviour: only entries whose type is 'info' are visible.
expect(checkLogVisibility('info', 'info')).toBe(true);
expect(checkLogVisibility('milestone', 'info')).toBe(false);
expect(checkLogVisibility('warning', 'info')).toBe(false);
expect(checkLogVisibility('error', 'info')).toBe(false);
});
it("filter 'milestone' only shows milestones", () => {
expect(checkLogVisibility('milestone', 'milestone')).toBe(true);
expect(checkLogVisibility('info', 'milestone')).toBe(false);
expect(checkLogVisibility('warning', 'milestone')).toBe(false);
expect(checkLogVisibility('error', 'milestone')).toBe(false);
});
it("accepts plural 'milestones' as an alias", () => {
expect(checkLogVisibility('milestone', 'milestones')).toBe(true);
expect(checkLogVisibility('info', 'milestones')).toBe(false);
});
it("filter 'warning' shows warnings and errors", () => {
expect(checkLogVisibility('warning', 'warning')).toBe(true);
expect(checkLogVisibility('error', 'warning')).toBe(true);
expect(checkLogVisibility('info', 'warning')).toBe(false);
expect(checkLogVisibility('milestone', 'warning')).toBe(false);
});
it("accepts plural 'warnings' as an alias", () => {
expect(checkLogVisibility('warning', 'warnings')).toBe(true);
expect(checkLogVisibility('error', 'warnings')).toBe(true);
expect(checkLogVisibility('info', 'warnings')).toBe(false);
});
it("filter 'error' only shows errors", () => {
expect(checkLogVisibility('error', 'error')).toBe(true);
expect(checkLogVisibility('warning', 'error')).toBe(false);
expect(checkLogVisibility('info', 'error')).toBe(false);
expect(checkLogVisibility('milestone', 'error')).toBe(false);
});
it("accepts plural 'errors' as an alias", () => {
expect(checkLogVisibility('error', 'errors')).toBe(true);
expect(checkLogVisibility('warning', 'errors')).toBe(false);
});
it('unknown filter defaults to showing everything', () => {
expect(checkLogVisibility('info', 'bogus')).toBe(true);
expect(checkLogVisibility('error', '')).toBe(true);
expect(checkLogVisibility('milestone', undefined)).toBe(true);
});
});
describe('emptyCounts', () => {
it('returns a fresh object on each call (mutations do not leak)', () => {
const a = emptyCounts();
const b = emptyCounts();
expect(a).not.toBe(b);
a.info = 99;
expect(b.info).toBe(0);
});
it('initialises every category to zero with the expected keys', () => {
const counts = emptyCounts();
expect(counts).toEqual({
info: 0,
milestone: 0,
warning: 0,
error: 0,
});
});
});
describe('hashString', () => {
it('returns "0" for empty/null/undefined', () => {
expect(hashString('')).toBe('0');
expect(hashString(null)).toBe('0');
expect(hashString(undefined)).toBe('0');
});
it('is deterministic: same input → same output', () => {
const a = hashString('log entry one');
const b = hashString('log entry one');
expect(a).toBe(b);
});
it('produces different hashes for different inputs', () => {
expect(hashString('log entry one')).not.toBe(hashString('log entry two'));
expect(hashString('a')).not.toBe(hashString('b'));
});
it('handles long strings', () => {
const long = 'x'.repeat(10000);
expect(hashString(long)).toMatch(/^-?\d+$/);
});
it('handles unicode characters', () => {
// Should not throw and should differ from its ASCII analogue
expect(hashString('café')).toMatch(/^-?\d+$/);
expect(hashString('café')).not.toBe(hashString('cafe'));
});
});
describe('normalizeMessage', () => {
it('returns empty string for falsy input', () => {
expect(normalizeMessage('')).toBe('');
expect(normalizeMessage(null)).toBe('');
expect(normalizeMessage(undefined)).toBe('');
});
it('trims whitespace', () => {
expect(normalizeMessage(' hello ')).toBe('hello');
expect(normalizeMessage('\t\nhello\r\n')).toBe('hello');
});
it('lowercases', () => {
expect(normalizeMessage('HELLO World')).toBe('hello world');
});
it('trims and lowercases in combination', () => {
expect(normalizeMessage(' HELLO ')).toBe('hello');
});
});
describe('normalizeTimestamps', () => {
it('is a no-op when all logs share the same date', () => {
const logs = [
{time: '2026-04-01T10:00:00Z', message: 'a'},
{time: '2026-04-01T11:00:00Z', message: 'b'},
{time: '2026-04-01T12:00:00Z', message: 'c'},
];
const before = JSON.parse(JSON.stringify(logs));
normalizeTimestamps(logs);
expect(logs).toEqual(before);
});
it('re-stamps an outlier date to the majority date', () => {
const logs = [
{time: '2026-04-01T10:00:00.000Z', message: 'a'},
{time: '2026-04-01T11:00:00.000Z', message: 'b'},
{time: '2026-04-01T12:00:00.000Z', message: 'c'},
{time: '2026-04-02T15:30:00.000Z', message: 'outlier'},
];
const originalTime = logs[3].time;
normalizeTimestamps(logs);
const outlier = logs[3];
// The outlier's date portion should now be 2026-04-01
expect(outlier.time.startsWith('2026-04-01')).toBe(true);
// The time should have changed from the original
expect(outlier.time).not.toBe(originalTime);
// ID should be regenerated from the new timestamp + hash
expect(outlier.id).toMatch(/^2026-04-01.*-/);
});
it('does nothing when logs is empty', () => {
const logs = [];
expect(() => normalizeTimestamps(logs)).not.toThrow();
expect(logs).toEqual([]);
});
it('does nothing when every log has an unparseable time', () => {
const logs = [
{time: 'not-a-date', message: 'a'},
{time: 'also-not-a-date', message: 'b'},
];
// Should not throw; mostCommonDate would remain null
expect(() => normalizeTimestamps(logs)).not.toThrow();
});
it('handles a tie by picking one date deterministically (first seen wins via iteration order)', () => {
// Two days with equal count — either is acceptable, just shouldn't crash
const logs = [
{time: '2026-04-01T10:00:00Z', message: 'a'},
{time: '2026-04-02T10:00:00Z', message: 'b'},
];
expect(() => normalizeTimestamps(logs)).not.toThrow();
// Both should end up on the same day
const d1 = logs[0].time.split('T')[0];
const d2 = logs[1].time.split('T')[0];
expect(d1).toBe(d2);
});
});