Release v2.15.5
This commit is contained in:
1 parent
0e77998e92
commit
2fd059d35a
18 files changed
+385
-30
No files matched your search
@@ -1,6 +1,7 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
require('../src/common');
|
||||
|
||||
@@ -332,6 +333,116 @@ test('template designer warns when invalid region names block save', () => {
|
||||
assert.ok(script.includes('regionNameValidationToastShown = false;'));
|
||||
});
|
||||
|
||||
function createRegionCardHarness(lockRatio = '') {
|
||||
const script = fs.readFileSync(require.resolve('../src/web/public/js/templates/template-designer.js'), 'utf8');
|
||||
const fields = {};
|
||||
const cardListeners = {};
|
||||
const initialValues = { name: 'Region', type: 'text', key: 'Region', label: 'Region', x: '80', y: '80', z: '1', width: '300', height: '120', lock_ratio: lockRatio, animation_json: '{}' };
|
||||
for (const [field, value] of Object.entries(initialValues)) {
|
||||
fields[`[name="region_${field}[]"]`] = {
|
||||
value,
|
||||
listeners: {},
|
||||
addEventListener(type, listener) { this.listeners[type] = listener; }
|
||||
};
|
||||
}
|
||||
fields['[data-region-remove-button]'] = { addEventListener() {} };
|
||||
const card = {
|
||||
dataset: {},
|
||||
querySelector(selector) { return fields[selector] || null; },
|
||||
querySelectorAll() { return []; },
|
||||
addEventListener(type, listener) { cardListeners[type] = listener; }
|
||||
};
|
||||
const previews = [];
|
||||
let dirtyCount = 0;
|
||||
const context = vm.createContext({
|
||||
window: {},
|
||||
regionCardTemplate: { content: { firstElementChild: { cloneNode: () => card } } },
|
||||
populateRegionCard() {},
|
||||
setRegionRemovalState() {},
|
||||
getRegionName: () => 'Region',
|
||||
updateRegionLockBadge() {},
|
||||
markTemplateFormDirty() { dirtyCount += 1; },
|
||||
requestOverlayRender() { previews.push(context.readCard(card)); },
|
||||
getCanvasSize: () => ({ width: 1920, height: 1080 }),
|
||||
normalizeLockRatio: (value) => value
|
||||
});
|
||||
vm.runInContext(fs.readFileSync(require.resolve('../src/web/public/js/templates/template-designer-utils.js'), 'utf8'), context);
|
||||
Object.assign(context, {
|
||||
readCard: context.window.templateDesignerUtils.readCard,
|
||||
writeCard: context.window.templateDesignerUtils.writeCard,
|
||||
parseLockRatio: context.window.templateDesignerUtils.parseLockRatio,
|
||||
clamp: context.window.templateDesignerUtils.clamp
|
||||
});
|
||||
for (const name of ['fitRegionToCanvas', 'normalizeRegionCard', 'syncLockedDimensions', 'makeRegionCard']) {
|
||||
const match = script.match(new RegExp(' function ' + name + '\\([^]*?\\n }'));
|
||||
assert.ok(match, `Missing designer function: ${name}`);
|
||||
vm.runInContext(match[0], context);
|
||||
}
|
||||
context.makeRegionCard({ region_key: 'Region' });
|
||||
return {
|
||||
previews,
|
||||
get dirtyCount() { return dirtyCount; },
|
||||
edit(field, value, type = 'input') {
|
||||
const input = fields[`[name="region_${field}[]"]`];
|
||||
input.value = value;
|
||||
input.listeners[type]();
|
||||
},
|
||||
value(field) { return Number(fields[`[name="region_${field}[]"]`].value); },
|
||||
rawValue(field) { return fields[`[name="region_${field}[]"]`].value; },
|
||||
keydown: (event) => cardListeners.keydown(event)
|
||||
};
|
||||
}
|
||||
|
||||
test('region coordinate and stacking fields immediately refresh the preview and mark it dirty', () => {
|
||||
const harness = createRegionCardHarness();
|
||||
harness.edit('x', '140');
|
||||
harness.edit('y', '220');
|
||||
harness.edit('z', '3');
|
||||
assert.equal(harness.previews.length, 3);
|
||||
assert.equal(harness.previews.at(-1).x, 140);
|
||||
assert.equal(harness.previews.at(-1).y, 220);
|
||||
assert.equal(harness.previews.at(-1).z_index, 3);
|
||||
assert.equal(harness.dirtyCount, 3);
|
||||
});
|
||||
|
||||
test('region dimensions allow clearing and typing below the minimum until committed', () => {
|
||||
for (const field of ['width', 'height']) {
|
||||
const harness = createRegionCardHarness();
|
||||
for (const value of ['', '1', '10', '100']) {
|
||||
harness.edit(field, value);
|
||||
assert.equal(harness.rawValue(field), value);
|
||||
}
|
||||
harness.edit(field, '1', 'change');
|
||||
assert.equal(harness.value(field), 12);
|
||||
harness.edit(field, '9999', 'change');
|
||||
assert.equal(harness.value(field), field === 'width' ? 1920 : 1080);
|
||||
}
|
||||
});
|
||||
|
||||
test('locked region dimensions update their counterpart without replacing partial input', () => {
|
||||
const harness = createRegionCardHarness('2:1');
|
||||
harness.edit('width', '');
|
||||
assert.equal(harness.rawValue('width'), '');
|
||||
harness.edit('width', '1');
|
||||
assert.equal(harness.rawValue('width'), '1');
|
||||
harness.edit('width', '400');
|
||||
assert.equal(harness.value('height'), 200);
|
||||
harness.edit('height', '150');
|
||||
assert.equal(harness.value('width'), 300);
|
||||
harness.edit('height', '150', 'change');
|
||||
assert.equal(harness.value('height'), 150);
|
||||
assert.equal(harness.value('width'), 300);
|
||||
});
|
||||
|
||||
test('Enter in region inputs prevents implicit saves without blocking buttons or textarea newlines', () => {
|
||||
const harness = createRegionCardHarness();
|
||||
for (const [key, tagName, expected] of [['Enter', 'INPUT', true], ['Tab', 'INPUT', false], ['Enter', 'BUTTON', false], ['Enter', 'TEXTAREA', false]]) {
|
||||
let prevented = false;
|
||||
harness.keydown({ key, target: { tagName }, preventDefault() { prevented = true; } });
|
||||
assert.equal(prevented, expected);
|
||||
}
|
||||
});
|
||||
|
||||
test('normalizeBackgroundGradient accepts linear colors and clamps the angle', () => {
|
||||
assert.equal(normalizeBackgroundGradient(JSON.stringify({
|
||||
type: 'radial',
|
||||
|
||||
Reference in new issue
Block a user