Files
openchamber/packages/ui/src/lib/browser/pageActions.test.ts
T

71 lines
2.6 KiB
TypeScript
Raw Normal View History

import { describe, expect, test } from 'bun:test';
import {
buildClickScript,
buildInspectScript,
buildScrollScript,
buildSnapshotScript,
buildTypeScript,
} from './pageActions';
/**
* These scripts are source text evaluated inside another page: nothing
* type-checks them, and a value interpolated without escaping either breaks the
* script or runs as code.
*/
const parses = (source: string): boolean => {
try {
new Function(source);
return true;
} catch {
return false;
}
};
describe('page action scripts', () => {
test('every script parses', () => {
expect(parses(buildSnapshotScript())).toBe(true);
expect(parses(buildSnapshotScript({ selector: '#main' }))).toBe(true);
expect(parses(buildClickScript({ selector: '#save' }))).toBe(true);
expect(parses(buildClickScript({ text: 'Save' }))).toBe(true);
expect(parses(buildTypeScript({ selector: '#q', value: 'hello', submit: true }))).toBe(true);
expect(parses(buildInspectScript({ selector: '#save' }))).toBe(true);
expect(parses(buildScrollScript({ direction: 'bottom' }))).toBe(true);
expect(parses(buildScrollScript({ selector: 'footer' }))).toBe(true);
});
test('a hostile selector is embedded as data, not as code', () => {
const hostile = `'); window.__owned = true; ('`;
const script = buildClickScript({ selector: hostile });
expect(parses(script)).toBe(true);
// Present only inside a quoted literal: that is what makes it inert.
expect(script).toContain(JSON.stringify(hostile));
});
test('a typed value is embedded as data too', () => {
const value = '"); alert(1); ("';
const script = buildTypeScript({ selector: '#q', value, submit: false });
expect(parses(script)).toBe(true);
expect(script).toContain(JSON.stringify(value));
});
test('whitespace regexes survive interpolation', () => {
// A doubled backslash here would produce a literal backslash-s and match
// nothing, silently collapsing no whitespace at all.
expect(buildSnapshotScript()).toContain('replace(/\\s+/g');
});
test('scrolling asks for instant behaviour, not the page preference', () => {
// A page with scroll-behavior: smooth would otherwise still be animating
// when the position is read.
expect(buildScrollScript({ direction: 'bottom' })).toContain("behavior: 'instant'");
expect(buildScrollScript({ selector: 'footer' })).toContain("behavior: 'instant'");
});
test('a scoped snapshot reads only the subtree it was given', () => {
const script = buildSnapshotScript({ selector: '#changelog' });
expect(script).toContain('"#changelog"');
expect(script).toContain('root.querySelectorAll');
});
});