185 lines
11 KiB
JavaScript
185 lines
11 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const vm = require('node:vm');
|
|
|
|
const slideFormEditorSource = fs.readFileSync(require.resolve('../src/web/public/js/slides/slide-form-editor.js'), 'utf8');
|
|
const slideFormSource = fs.readFileSync(require.resolve('../src/web/public/js/slides/slide-form.js'), 'utf8');
|
|
const slideFormTemplateSource = fs.readFileSync(require.resolve('../src/web/views/signage/slides/form.hbs'), 'utf8');
|
|
const mediaPickerSource = fs.readFileSync(require.resolve('../src/web/public/js/shared/ui/media-picker.js'), 'utf8');
|
|
const slideThumbnailsSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnails.js'), 'utf8');
|
|
const slideHtmlRegionSource = fs.readFileSync(require.resolve('../src/web/public/js/regions/type/html.js'), 'utf8');
|
|
const slideWebpageRegionSource = fs.readFileSync(require.resolve('../src/web/public/js/regions/type/webpage.js'), 'utf8');
|
|
|
|
test('slide editor disables pasted data images in TinyMCE', () => {
|
|
assert.ok(slideFormEditorSource.includes('paste_data_images: false'));
|
|
assert.ok(slideFormEditorSource.includes('paste_block_drop: true'));
|
|
assert.ok(slideFormEditorSource.includes('resize: false'));
|
|
});
|
|
|
|
test('slide editor enables server-backed image uploads', () => {
|
|
assert.ok(slideFormEditorSource.includes("plugins: 'lists code advlist fullscreen table image'"));
|
|
assert.ok(slideFormEditorSource.includes('automatic_uploads: true'));
|
|
assert.ok(slideFormEditorSource.includes('images_file_types: imageUploadFileTypes'));
|
|
assert.ok(slideFormEditorSource.includes('images_upload_handler: uploadEditorImage'));
|
|
assert.ok(slideFormEditorSource.includes('table image chip | fullscreen'));
|
|
assert.ok(slideFormEditorSource.includes('relative_urls: false'));
|
|
assert.ok(slideFormEditorSource.includes('remove_script_host: false'));
|
|
});
|
|
|
|
test('slide editor keeps normal TinyMCE image insertion unchanged', () => {
|
|
assert.doesNotMatch(slideFormEditorSource, /bindPlaceholderImageSupport|placeholderFallbackSource/);
|
|
assert.ok(slideFormEditorSource.includes('images_upload_handler: uploadEditorImage'));
|
|
});
|
|
|
|
test('slide editor preserves player-local media paths and image placeholders without web-editor URL prefixes', () => {
|
|
assert.ok(slideFormEditorSource.includes('convert_urls: false'));
|
|
assert.doesNotMatch(slideFormEditorSource, /convert_urls:\s*true/);
|
|
});
|
|
|
|
test('slide editor inserts tables with zero padding and spacing by default', () => {
|
|
assert.ok(slideFormEditorSource.includes("table_default_attributes: {"));
|
|
assert.ok(slideFormEditorSource.includes("cellpadding: '0'"));
|
|
assert.ok(slideFormEditorSource.includes("cellspacing: '0'"));
|
|
assert.ok(slideFormEditorSource.includes('td, th { border: 1px solid currentColor; padding: 0; vertical-align: top; }'));
|
|
});
|
|
|
|
test('slide editor uses a smaller wysiwyg image limit', () => {
|
|
assert.ok(slideFormEditorSource.includes('var imageUploadMaxBytes = Math.max(1, Number(settings.imageUploadMaxBytes || 2 * 1024 * 1024));'));
|
|
assert.ok(slideFormEditorSource.includes('Image must be '));
|
|
assert.ok(slideFormEditorSource.includes('Larger images should use the dedicated Image region.'));
|
|
assert.ok(slideFormEditorSource.includes("xhr.setRequestHeader('X-Upload-Context', imageUploadContext);"));
|
|
});
|
|
|
|
test('slide editor tracks uploaded image paths for cleanup', () => {
|
|
assert.ok(slideFormEditorSource.includes('var editorImageUploadPaths = new Set();'));
|
|
assert.ok(slideFormEditorSource.includes('getImageUploadCleanupPaths'));
|
|
assert.ok(slideFormEditorSource.includes('getCommittedImageUploadCleanupPaths'));
|
|
assert.ok(slideFormEditorSource.includes('getPendingImageUploadPaths'));
|
|
assert.ok(slideFormEditorSource.includes('clearImageUploadPaths'));
|
|
});
|
|
|
|
test('slide editor marks the owning form dirty after TinyMCE edits', () => {
|
|
assert.ok(slideFormEditorSource.includes("var markFormDirty = typeof settings.markFormDirty === 'function' ? settings.markFormDirty : function () {};"));
|
|
assert.match(slideFormEditorSource, /if \(hydrated\)\s*\{\s*markFormDirty\(\);/);
|
|
assert.match(slideFormSource, /markFormDirty:\s*function \(\)\s*\{\s*slideForm\.dataset\.dirty = 'true';/);
|
|
});
|
|
|
|
test('slide editor keeps image-only rich text from being treated as empty', () => {
|
|
assert.ok(slideFormEditorSource.includes('/<img\\b/i.test(raw)'));
|
|
});
|
|
|
|
test('slide form queues editor image cleanup on save and close', () => {
|
|
assert.ok(slideFormSource.includes('getImageUploadCleanupPaths'));
|
|
assert.ok(slideFormSource.includes('getCommittedImageUploadCleanupPaths'));
|
|
assert.ok(slideFormSource.includes("regionMediaController.queueUploadCleanup(slideFormEditorController.getImageUploadCleanupPaths())"));
|
|
});
|
|
|
|
test('media library selections keep image and video remove controls', () => {
|
|
assert.ok(mediaPickerSource.includes("var removeAction = isVideo ? 'region-video' : isQrImage ? 'region-qr-image' : 'region-image';"));
|
|
assert.ok(mediaPickerSource.includes('data-remove-\' + removeAction'));
|
|
assert.ok(mediaPickerSource.includes('slide-image-region-preview-remove'));
|
|
assert.ok(mediaPickerSource.includes("document.querySelectorAll('.modal.show')"));
|
|
assert.ok(mediaPickerSource.includes('nestedParentFocusTrap.deactivate()'));
|
|
assert.ok(mediaPickerSource.includes('pickerZIndex - 5'));
|
|
const pickerModal = fs.readFileSync(require.resolve('../src/web/views/shared/media-picker-modal.hbs'), 'utf8');
|
|
assert.ok(pickerModal.indexOf('data-media-picker-confirm') < pickerModal.indexOf('data-media-picker-cancel'));
|
|
assert.match(pickerModal, /class="btn btn-warning" data-media-picker-cancel>Cancel/);
|
|
});
|
|
|
|
test('slide thumbnail previews treat image-only text as visible content', () => {
|
|
assert.ok(slideThumbnailsSource.includes('/<img\\b/i.test(raw)'));
|
|
assert.ok(slideThumbnailsSource.includes('getCachedImagePath'));
|
|
assert.ok(slideThumbnailsSource.includes('getThumbnailItem'));
|
|
});
|
|
|
|
test('popup preview falls back to iframe sizing rules', () => {
|
|
const popupPreviewSource = fs.readFileSync(require.resolve('../src/web/views/signage/slides/popup-preview.hbs'), 'utf8');
|
|
const stylesheet = fs.readFileSync(require.resolve('../src/web/public/css/theme-custom.css'), 'utf8');
|
|
|
|
assert.ok(popupPreviewSource.includes('slideEditorScripts'));
|
|
assert.ok(popupPreviewSource.includes('/assets/js/shared/slide-preview-renderer.js'));
|
|
assert.ok(popupPreviewSource.includes('window.pulseSlidePreviewRenderer.render(payload)'));
|
|
assert.ok(popupPreviewSource.includes('background: var(--bs-body-bg);'));
|
|
assert.ok(popupPreviewSource.includes('color: var(--bs-body-color);'));
|
|
assert.match(stylesheet, /\.slide-preview-popup-body\s*\{[^}]*background:\s*var\(--bs-body-bg\);[^}]*color:\s*var\(--bs-body-color\);/s);
|
|
assert.match(stylesheet, /\.slide-preview-popup-stage\s*\{[^}]*background:\s*var\(--bs-body-bg\);/s);
|
|
assert.ok(popupPreviewSource.includes('.slide-preview-chart-region canvas'));
|
|
assert.ok(popupPreviewSource.includes('background: transparent !important'));
|
|
assert.ok(popupPreviewSource.includes('#popup-preview-canvas iframe'));
|
|
assert.ok(popupPreviewSource.includes('#popup-preview-canvas .slide-preview-webpage-region iframe'));
|
|
assert.ok(popupPreviewSource.includes('#popup-preview-canvas .slide-preview-html-region iframe'));
|
|
});
|
|
|
|
test('browser preview helpers normalize object-shaped HTML and webpage values', () => {
|
|
assert.ok(slideHtmlRegionSource.includes('normalizePreviewValue'));
|
|
assert.ok(slideWebpageRegionSource.includes('normalizePreviewValue'));
|
|
});
|
|
|
|
test('webpage preview updates only through the explicit button', () => {
|
|
assert.ok(slideWebpageRegionSource.includes('input-group flex-nowrap'));
|
|
assert.ok(slideWebpageRegionSource.includes('data-webpage-preview-update'));
|
|
assert.ok(slideFormSource.includes('input[type="url"][name^="region_webpage_"]'));
|
|
assert.ok(slideFormSource.includes('requestPreviewRender();'));
|
|
});
|
|
|
|
test('HTML editor shares bounded size controls without changing its content', async () => {
|
|
const source = { dataset: {}, style: {}, value: '<div>Keep this HTML</div>', getAttribute: () => '708' };
|
|
let clickHandler;
|
|
const templateFields = {
|
|
addEventListener: (event, handler) => { clickHandler = handler; },
|
|
querySelector: (selector) => selector.includes('.html-editor-source') ? source : null,
|
|
querySelectorAll: (selector) => selector === '.html-editor-source' ? [source] : []
|
|
};
|
|
const sandbox = { window: {} };
|
|
vm.runInNewContext(slideFormEditorSource.replace('export function', 'function'), sandbox);
|
|
const controller = sandbox.createSlideFormEditorController({ templateFields });
|
|
await controller.renderEditors();
|
|
assert.equal(source.style.height, '360px');
|
|
|
|
function resize(action, count = 1) {
|
|
const button = {
|
|
getAttribute: () => action,
|
|
closest: () => ({ getAttribute: () => '708' })
|
|
};
|
|
for (let index = 0; index < count; index += 1) {
|
|
clickHandler({ target: { closest: () => button }, preventDefault() {} });
|
|
}
|
|
}
|
|
|
|
resize('increase');
|
|
assert.equal(source.style.height, '440px');
|
|
resize('decrease');
|
|
assert.equal(source.style.height, '360px');
|
|
resize('decrease', 20);
|
|
assert.equal(source.style.height, '240px');
|
|
resize('increase', 20);
|
|
assert.equal(source.style.height, '960px');
|
|
assert.equal(source.value, '<div>Keep this HTML</div>');
|
|
assert.match(slideHtmlRegionSource, /style="resize:none;"/);
|
|
assert.match(slideHtmlRegionSource, /sandboxed iframe preview\.<\/div>'\s*\+\s*'<div class="btn-group/);
|
|
});
|
|
|
|
test('slide template chooser searches and previews templates while preserving the submitted select', () => {
|
|
assert.equal((slideFormTemplateSource.match(/modalId="template-picker-modal"/g) || []).length, 1);
|
|
assert.match(slideFormTemplateSource, /name="template_id" id="template-select" class="visually-hidden"/);
|
|
assert.match(slideFormTemplateSource, /id="template-picker-open"/);
|
|
assert.match(slideFormTemplateSource, /template-picker-current"><strong id="template-picker-selected-name"/);
|
|
assert.doesNotMatch(slideFormTemplateSource, /id="template-picker-selected-size"/);
|
|
assert.match(slideFormTemplateSource, /modalId="template-picker-modal"/);
|
|
assert.match(slideFormTemplateSource, /id="template-picker-search"/);
|
|
assert.match(slideFormTemplateSource, /id="template-picker-canvas-size"/);
|
|
assert.match(slideFormTemplateSource, /All canvas sizes/);
|
|
assert.match(slideFormTemplateSource, /id="template-picker-grid"/);
|
|
assert.doesNotMatch(slideFormTemplateSource, /id="template-picker-none"|>No template<\/button>/);
|
|
assert.ok(slideFormSource.includes('function createTemplatePickerCard(template)'));
|
|
assert.ok(slideFormSource.includes('function populateTemplateCanvasSizes()'));
|
|
assert.doesNotMatch(slideFormSource, /createTemplatePickerCard\(null\)/);
|
|
assert.ok(slideFormSource.includes('function renderTemplatePicker()'));
|
|
assert.ok(slideFormSource.includes("templateSelect.dispatchEvent(new Event('change', { bubbles: true }))"));
|
|
});
|
|
|
|
test('popup preview is disabled and guarded until a template is selected', () => {
|
|
assert.match(slideFormSource, /if \(openPreviewPopupButton\) openPreviewPopupButton\.disabled = !template;/);
|
|
assert.match(slideFormSource, /function openPreviewPopup\(\) \{\s*if \(!getSelectedTemplate\(\)\)\s*\{\s*return;/);
|
|
}); |