refactor: shared escapeHtml util; escape prop-viewer src/alt
This commit is contained in:
@@ -1,14 +1,6 @@
|
||||
import { generateNpc } from '../npc/generator.js';
|
||||
import { getGenrePack, listGenrePacks } from '../npc/packs/index.js';
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
import { escapeHtml } from '../core/escape-html.js';
|
||||
|
||||
export class NpcTray extends HTMLElement {
|
||||
constructor() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { escapeHtml } from '../core/escape-html.js';
|
||||
|
||||
export class PropViewer extends HTMLElement {
|
||||
constructor() { super(); this._src = ''; this._label = ''; }
|
||||
connectedCallback() { if (!this.hasAttribute('hidden')) this.setAttribute('hidden', ''); this.render(); }
|
||||
@@ -14,7 +16,7 @@ export class PropViewer extends HTMLElement {
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div class="prop-overlay" data-role="overlay">
|
||||
<img class="prop-img" data-role="prop-img" src="${this._src}" alt="${this._label}" />
|
||||
<img class="prop-img" data-role="prop-img" src="${escapeHtml(this._src)}" alt="${escapeHtml(this._label)}" />
|
||||
</div>`;
|
||||
this.querySelector('[data-role=overlay]').addEventListener('click', () => this.dismiss());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function escapeHtml(s) {
|
||||
return String(s ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
@@ -26,4 +26,11 @@ describe('<prop-viewer>', () => {
|
||||
el.querySelector('[data-role=overlay]').click();
|
||||
expect(el.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it('escapes a label containing a quote (no attribute break)', () => {
|
||||
el.show('/art/x.png', 'x" onerror="boom');
|
||||
const img = el.querySelector('[data-role=prop-img]');
|
||||
expect(img.getAttribute('alt')).toBe('x" onerror="boom');
|
||||
expect(img.hasAttribute('onerror')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { escapeHtml } from '../../src/core/escape-html.js';
|
||||
|
||||
describe('escapeHtml', () => {
|
||||
it('escapes the five HTML-significant characters', () => {
|
||||
expect(escapeHtml(`a & b < c > d " e ' f`)).toBe('a & b < c > d " e ' f');
|
||||
});
|
||||
it('replaces & first so entities are not double-escaped', () => {
|
||||
expect(escapeHtml('<')).toBe('<');
|
||||
expect(escapeHtml('<')).toBe('&lt;');
|
||||
});
|
||||
it('coerces null/undefined to empty string', () => {
|
||||
expect(escapeHtml(null)).toBe('');
|
||||
expect(escapeHtml(undefined)).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user