Add canvas size disable controls
This commit is contained in:
1 parent
68e1f80190
commit
f0601f823d
22 files changed
+205
-87
No files matched your search
+57
-20
@@ -7,9 +7,10 @@ const path = require('node:path');
|
||||
require('../src/common');
|
||||
|
||||
const { buildCanvasSizePayload, MAX_CANVAS_SIZE_DIMENSION } = require('../src/data');
|
||||
const { fetchCanvasSizesData } = require('../src/data/canvas-sizes');
|
||||
const { buildCanvasSizeFormViewModel } = require('../src/web/routes/signage/canvas-sizes/form-view-model');
|
||||
const { buildDuplicateCanvasSizeName, buildDuplicateCanvasSize } = require('../src/web/routes/signage/canvas-sizes/duplicate');
|
||||
const registerContentRoutes = require('../src/web/routes/admin/content');
|
||||
const renderCanvasSizesPage = require('../src/web/routes/signage/canvas-sizes/list');
|
||||
const adminPageScript = fs.readFileSync(require.resolve('../src/web/public/js/admin/admin-page.js'), 'utf8');
|
||||
const canvasSizeFormTemplate = fs.readFileSync(require.resolve('../src/web/views/signage/canvas-sizes/form.hbs'), 'utf8');
|
||||
const canvasSizeListTemplate = fs.readFileSync(require.resolve('../src/web/views/signage/canvas-sizes/list.hbs'), 'utf8');
|
||||
@@ -40,23 +41,40 @@ test('canvas size edit form locks dimensions while the size is in use', () => {
|
||||
assert.ok(canvasSizeFormTemplate.includes('data-limit-input-value'));
|
||||
});
|
||||
|
||||
test('buildDuplicateCanvasSize copies canvas size fields for the create page', () => {
|
||||
const duplicate = buildDuplicateCanvasSize({
|
||||
id: 7,
|
||||
name: 'Full HD',
|
||||
width: 1920,
|
||||
height: 1080
|
||||
}, buildDuplicateCanvasSizeName('Full HD'));
|
||||
|
||||
assert.equal(duplicate.id, null);
|
||||
assert.equal(duplicate.name, 'Copy of Full HD');
|
||||
assert.equal(duplicate.width, 1920);
|
||||
assert.equal(duplicate.height, 1080);
|
||||
test('canvas size list replaces duplicate action with status and toggle controls', () => {
|
||||
assert.doesNotMatch(canvasSizeListTemplate, /\/canvas-sizes\/\{\{id\}\}\/duplicate/);
|
||||
assert.match(canvasSizeListTemplate, /data-label="Status"/);
|
||||
assert.match(canvasSizeListTemplate, /\/canvas-sizes\/\{\{id\}\}\/toggle/);
|
||||
assert.match(canvasSizeListTemplate, />\{\{#if enabled\}\}Disable\{\{else\}\}Enable\{\{\/if\}\}<\/button>/);
|
||||
});
|
||||
|
||||
test('canvas size list template includes duplicate action', () => {
|
||||
assert.match(canvasSizeListTemplate, /\/canvas-sizes\/\{\{id\}\}\/duplicate/);
|
||||
assert.match(canvasSizeListTemplate, />Dupe<\/a>/);
|
||||
test('canvas size list renderer preserves enabled state for status labels', () => {
|
||||
const html = renderCanvasSizesPage({
|
||||
canvasSizes: [
|
||||
{ id: 1, name: 'Enabled size', width: 1920, height: 1080, enabled: 1, template_count: 0 },
|
||||
{ id: 2, name: 'Disabled size', width: 1280, height: 720, enabled: 0, template_count: 0 }
|
||||
]
|
||||
}, '', null);
|
||||
|
||||
assert.match(html, /data-label="Status"><span class="badge text-bg-success">Enabled<\/span>/);
|
||||
assert.match(html, /data-label="Status"><span class="badge text-bg-secondary">Disabled<\/span>/);
|
||||
});
|
||||
|
||||
test('canvas-size selectors exclude disabled sizes unless retaining the current assignment', async () => {
|
||||
const queries = [];
|
||||
const pool = {
|
||||
async query(sql, params) {
|
||||
queries.push([sql, params]);
|
||||
return [[]];
|
||||
}
|
||||
};
|
||||
|
||||
await fetchCanvasSizesData(pool);
|
||||
await fetchCanvasSizesData(pool, 9);
|
||||
|
||||
assert.match(queries[0][0], /WHERE enabled = 1/);
|
||||
assert.match(queries[1][0], /WHERE enabled = 1 OR id = \?/);
|
||||
assert.deepEqual(queries[1][1], [9]);
|
||||
});
|
||||
|
||||
test('canvas size update returns a warning when an in-use canvas changes dimensions', async () => {
|
||||
@@ -71,11 +89,19 @@ test('canvas size update returns a warning when an in-use canvas changes dimensi
|
||||
};
|
||||
|
||||
const queries = [];
|
||||
const toggleUpdates = [];
|
||||
const auditEvents = [];
|
||||
let enabled = 1;
|
||||
const pool = {
|
||||
async query(sql) {
|
||||
queries.push(sql);
|
||||
async query(sql, params) {
|
||||
queries.push([sql, params]);
|
||||
if (sql.includes('UPDATE c_canvas_sizes SET enabled')) {
|
||||
enabled = params[0];
|
||||
toggleUpdates.push(params);
|
||||
return [{ affectedRows: 1 }];
|
||||
}
|
||||
if (sql.includes('FROM c_canvas_sizes WHERE id = ?')) {
|
||||
return [[{ id: 7, name: 'Full HD', width: 1920, height: 1080 }]];
|
||||
return [[{ id: 7, name: 'Full HD', width: 1920, height: 1080, enabled: enabled }]];
|
||||
}
|
||||
if (sql.includes('SELECT COUNT(*) AS ref_count FROM c_templates WHERE canvas_size_id = ?')) {
|
||||
return [[{ ref_count: 1 }]];
|
||||
@@ -92,7 +118,7 @@ test('canvas size update returns a warning when an in-use canvas changes dimensi
|
||||
getSortQuery: () => '',
|
||||
getSortDirectionQuery: () => 'asc',
|
||||
fetchCanvasSizeById: async (connection, id) => {
|
||||
const [rows] = await connection.query('SELECT id, name, width, height, created_at, modified_at, created_by, modified_by FROM c_canvas_sizes WHERE id = ?', [id]);
|
||||
const [rows] = await connection.query('SELECT id, name, width, height, enabled, created_at, modified_at, created_by, modified_by FROM c_canvas_sizes WHERE id = ?', [id]);
|
||||
return rows[0] || null;
|
||||
},
|
||||
buildCanvasSizePayload: buildCanvasSizePayload,
|
||||
@@ -124,6 +150,7 @@ test('canvas size update returns a warning when an in-use canvas changes dimensi
|
||||
removeUnusedUploadFiles: async () => {},
|
||||
syncPlaylistUploadsOnChange: async () => {},
|
||||
getAuditUserId: () => 1,
|
||||
recordRequestAuditEvent: async (_connection, _req, event) => auditEvents.push(event),
|
||||
redirectAfterSave: () => {},
|
||||
notifyPlayerScreens: async () => 0,
|
||||
broadcastDashboardState: async () => {},
|
||||
@@ -172,6 +199,16 @@ test('canvas size update returns a warning when an in-use canvas changes dimensi
|
||||
assert.equal(res.body, 'This canvas size is still used by one or more templates, so its dimensions cannot be changed.');
|
||||
assert.equal(res.redirectedTo, '');
|
||||
assert.equal(queries.length, 2);
|
||||
|
||||
const toggleHandler = handlers['/canvas-sizes/:id/toggle'][1];
|
||||
const toggleReq = { params: { id: '7' } };
|
||||
const toggleRes = { redirectedTo: '', redirect(url) { this.redirectedTo = url; } };
|
||||
await toggleHandler(toggleReq, toggleRes, (error) => { throw error; });
|
||||
assert.equal(toggleRes.redirectedTo, '/canvas-sizes?message=Canvas%20size%20disabled.');
|
||||
await toggleHandler(toggleReq, toggleRes, (error) => { throw error; });
|
||||
|
||||
assert.deepEqual(toggleUpdates.map((params) => params[0]), [0, 1]);
|
||||
assert.deepEqual(auditEvents.map((event) => event.eventType), ['canvas-size.disabled', 'canvas-size.enabled']);
|
||||
});
|
||||
|
||||
test('slide upload cleanup route removes pending media assets', async () => {
|
||||
|
||||
@@ -83,4 +83,20 @@ test('playlist rows use the longest video region duration', () => {
|
||||
assert.equal(model.playlistSlides[0].showVideoDurationButton, true);
|
||||
assert.equal(model.playlistSlides[0].videoDurationSeconds, 27.75);
|
||||
assert.equal(model.playlistSlides[0].durationSeconds, 27.75);
|
||||
});
|
||||
|
||||
test('disabled canvas sizes are hidden for new playlists but retained for existing assignments', () => {
|
||||
const canvasSizes = [
|
||||
{ id: 1, name: 'Full HD', width: 1920, height: 1080, enabled: 1 },
|
||||
{ id: 2, name: 'Legacy', width: 1280, height: 720, enabled: 0 }
|
||||
];
|
||||
const data = { playlistSlides: [], slides: [], canvasSizes: canvasSizes };
|
||||
|
||||
const createModel = buildPlaylistFormViewModel({}, data, '', null, {});
|
||||
assert.deepEqual(createModel.canvasSizes.map((size) => size.id), [1]);
|
||||
|
||||
const editModel = buildPlaylistFormViewModel({ id: 12, canvas_id: 2 }, data, '', null, { isEdit: true });
|
||||
assert.deepEqual(editModel.canvasSizes.map((size) => size.id), [1, 2]);
|
||||
assert.match(editModel.canvasSizes[1].label, /Disabled/);
|
||||
assert.equal(editModel.canvasSizes[1].selected, true);
|
||||
});
|
||||
@@ -38,6 +38,7 @@ function createResponse() {
|
||||
test('playlist create and update redirect to the canonical edit path', async () => {
|
||||
const calls = [];
|
||||
let groupInsertParams = null;
|
||||
let canvasSizeEnabled = 1;
|
||||
const connection = {
|
||||
async beginTransaction() {},
|
||||
async commit() {},
|
||||
@@ -76,7 +77,7 @@ test('playlist create and update redirect to the canonical edit path', async ()
|
||||
},
|
||||
common: {
|
||||
async fetchDuplicateName() { return null; },
|
||||
async fetchCanvasSizeById() { return { id: 7 }; },
|
||||
async fetchCanvasSizeById() { return { id: 7, enabled: canvasSizeEnabled }; },
|
||||
async fetchPlaylistById() { return { id: 4, canvas_id: 7 }; }
|
||||
},
|
||||
pages: {},
|
||||
@@ -161,6 +162,24 @@ test('playlist create and update redirect to the canonical edit path', async ()
|
||||
'insert-slide',
|
||||
{ kind: 'redirectAfterSave', url: '/playlists/4/edit', path: '/playlists/4' }
|
||||
]);
|
||||
|
||||
canvasSizeEnabled = 0;
|
||||
const disabledCreateRes = createResponse();
|
||||
await createHandlers[1]({
|
||||
path: '/playlists',
|
||||
body: { name: 'Disabled size playlist', canvas_size_id: '7', slide_id: [] }
|
||||
}, disabledCreateRes, (error) => { throw error; });
|
||||
assert.equal(disabledCreateRes.statusCode, 400);
|
||||
assert.equal(disabledCreateRes.body, 'Canvas size is disabled.');
|
||||
|
||||
const retainDisabledRes = createResponse();
|
||||
await updateHandlers[1]({
|
||||
path: '/playlists/4',
|
||||
params: { id: '4' },
|
||||
body: { name: 'Lunch Loop', canvas_size_id: '7', slide_id: [] }
|
||||
}, retainDisabledRes, (error) => { throw error; });
|
||||
assert.equal(retainDisabledRes.redirectedTo, '/playlists/4/edit');
|
||||
|
||||
for (const pickCount of ['0', '2']) {
|
||||
const invalidRes = createResponse();
|
||||
await updateHandlers[1]({
|
||||
|
||||
@@ -61,13 +61,22 @@ test('playlist shuffle migration upgrades an existing 2.15.0 installation', asyn
|
||||
test('playlist repeat migration upgrades an existing 2.15.1 installation', async () => {
|
||||
const pool = createPool([]);
|
||||
const pending = await getPendingMigrations(pool, { currentVersion: '2.15.1' });
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].version, '2.15.2');
|
||||
await pending[0].run(pool);
|
||||
const repeatMigration = pending.find(migration => migration.version === '2.15.2');
|
||||
assert.ok(repeatMigration);
|
||||
await repeatMigration.run(pool);
|
||||
assert.ok(pool.queries.some(([sql]) => sql.includes('ADD COLUMN group_mode')));
|
||||
assert.ok(pool.queries.some(([sql]) => sql.includes('ADD COLUMN group_repeat_count')));
|
||||
});
|
||||
|
||||
test('canvas size enabled migration upgrades an existing 2.15.2 installation', async () => {
|
||||
const pool = createPool([]);
|
||||
const pending = await getPendingMigrations(pool, { currentVersion: '2.15.2' });
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].version, '2.15.3');
|
||||
await pending[0].run(pool);
|
||||
assert.ok(pool.queries.some(([sql]) => sql.includes('ALTER TABLE c_canvas_sizes ADD COLUMN enabled')));
|
||||
});
|
||||
|
||||
test('playlist group schema migration is pending for version 2.14.3', async () => {
|
||||
const pool = createPool([
|
||||
{
|
||||
|
||||
@@ -124,6 +124,30 @@ test('buildTemplatePayload resolves canvas size and rejects duplicate region nam
|
||||
);
|
||||
});
|
||||
|
||||
test('disabled canvas sizes cannot be assigned to new templates but existing assignments can be retained', async () => {
|
||||
const pool = {
|
||||
async query() {
|
||||
return [[{ id: 4, name: 'Legacy', width: 1280, height: 720, enabled: 0 }]];
|
||||
}
|
||||
};
|
||||
const req = {
|
||||
body: {
|
||||
name: 'Legacy template',
|
||||
canvas_size_id: '4',
|
||||
regions_json: JSON.stringify([{ region_name: 'Body', region_type: 'text' }])
|
||||
},
|
||||
files: []
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => buildTemplatePayload(pool, req, null),
|
||||
(error) => error && error.statusCode === 400 && error.message === 'Canvas size is disabled.'
|
||||
);
|
||||
|
||||
const payload = await buildTemplatePayload(pool, req, { canvas_size_id: 4 });
|
||||
assert.equal(payload.canvasSizeId, 4);
|
||||
});
|
||||
|
||||
test('buildDuplicateTemplate copies template fields and strips row identity', () => {
|
||||
const duplicate = buildDuplicateTemplate({
|
||||
id: 9,
|
||||
|
||||
Reference in new issue
Block a user