Release 2.16.2
This commit is contained in:
1 parent
8ce8228091
commit
49df51d92f
32 files changed
+390
-172
No files matched your search
@@ -22,4 +22,16 @@ test('data-source toggles update in place instead of reloading the edit form', (
|
||||
assert.ok(adminPageScript.includes('data-async-data-source-toggle'));
|
||||
assert.ok(adminPageScript.includes("showToast(message || (willEnable ? 'Data source enabled.' : 'Data source disabled.'), 'success')"));
|
||||
assert.ok(!adminPageScript.includes("/^\\/data-sources\\/(?:api-sources|rss-feeds|weather)\\/\\d+$/.test(actionPath);"));
|
||||
assert.ok(adminPageScript.includes('data-source-toggle-owner'));
|
||||
assert.ok(adminPageScript.includes("control.disabled = !willEnable || control.getAttribute('data-source-item-enabled') === 'false';"));
|
||||
});
|
||||
|
||||
test('manual refresh targets poll and replace response panels instead of navigating the form', () => {
|
||||
const start = adminPageScript.indexOf("var refreshButton = target.closest('[data-manual-refresh-url]');");
|
||||
const end = adminPageScript.indexOf("var button = target.closest('button[name=\"save_action\"]", start);
|
||||
const handler = adminPageScript.slice(start, end);
|
||||
assert.match(handler, /getAttribute\('data-manual-refresh-target'\)/);
|
||||
assert.match(handler, /if \(refreshTargetSelector\)\s*\{\s*return response\.json\(\)/);
|
||||
assert.match(handler, /watchRefreshTask\(payload\.stateUrl, payload\.taskId, refreshTargetSelector, refreshSequence\)/);
|
||||
assert.ok(handler.indexOf('return response.json()') < handler.indexOf('window.location.assign(response.url)'));
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { buildApiGroupPayload, saveApiGroup } = require('../src/data/api-source-groups');
|
||||
const { buildApiGroupPayload, saveApiGroup, fetchApiGroupById } = require('../src/data/api-source-groups');
|
||||
const { getPendingMigrations } = require('../src/db/migrations');
|
||||
const { fetchApiSourcesData, fetchApiSourceById, testApiSourceAuthentication } = require('../src/data/api-sources');
|
||||
|
||||
@@ -8,7 +8,7 @@ function payload(requests, auth) {
|
||||
return buildApiGroupPayload(Object.assign({ name: 'Tiltify', auth_method: 'none', requests_json: JSON.stringify(requests) }, auth));
|
||||
}
|
||||
|
||||
function createPool(existing, slides) {
|
||||
function createPool(existing, slides, groupEnabled = 1) {
|
||||
const calls = [];
|
||||
const connection = {
|
||||
async beginTransaction() { calls.push(['begin']); },
|
||||
@@ -17,7 +17,7 @@ function createPool(existing, slides) {
|
||||
release() { calls.push(['release']); },
|
||||
async query(sql, params) {
|
||||
calls.push([sql, params]);
|
||||
if (sql.includes('SELECT id FROM i_api_source_groups WHERE id')) return [[{ id: 5 }]];
|
||||
if (sql.includes('SELECT id, enabled FROM i_api_source_groups WHERE id')) return [[{ id: 5, enabled: groupEnabled }]];
|
||||
if (sql.includes('SELECT id FROM i_api_source_groups WHERE name')) return [[]];
|
||||
if (sql.includes('SELECT id FROM i_api_sources')) return [existing.map(id => ({ id }))];
|
||||
if (sql.includes('SELECT content_json')) return [(slides || []).map(value => ({ content_json: value }))];
|
||||
@@ -81,14 +81,29 @@ test('2.16.1 migration preserves source IDs and backfills parent authentication'
|
||||
return [[]];
|
||||
} };
|
||||
const pending = await getPendingMigrations(pool, { currentVersion: '2.16.0' });
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].version, '2.16.1');
|
||||
await pending[0].run(pool);
|
||||
const migration = pending.find(value => value.version === '2.16.1');
|
||||
assert.ok(migration);
|
||||
await migration.run(pool);
|
||||
assert.ok(calls.some(([sql]) => sql.includes('INSERT IGNORE INTO i_api_source_groups (id,') && sql.includes('SELECT id,')));
|
||||
assert.ok(calls.some(([sql]) => sql.includes('SET auth_source_id = id WHERE auth_source_id IS NULL')));
|
||||
assert.ok(!calls.some(([sql]) => /UPDATE i_api_sources SET id\s*=/.test(sql)));
|
||||
});
|
||||
|
||||
test('2.16.2 adds an enabled master switch without rewriting individual requests', async () => {
|
||||
const calls = [];
|
||||
const pool = { async query(sql) {
|
||||
calls.push(sql);
|
||||
if (sql.includes('information_schema.COLUMNS')) return [[{ column_count: 0 }]];
|
||||
return [[]];
|
||||
} };
|
||||
const pending = await getPendingMigrations(pool, { currentVersion: '2.16.1' });
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].version, '2.16.2');
|
||||
await pending[0].run(pool);
|
||||
assert.ok(calls.some(sql => sql.includes('ALTER TABLE i_api_source_groups ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1')));
|
||||
assert.ok(!calls.some(sql => sql.includes('UPDATE i_api_sources')));
|
||||
});
|
||||
|
||||
test('editor/player API lists exclude credentials while refresh lookup resolves group authentication', async () => {
|
||||
const queries = [];
|
||||
const pool = { async query(sql) { queries.push(sql); return [[]]; } };
|
||||
@@ -116,4 +131,115 @@ test('authentication-only tests never call a data endpoint', async () => {
|
||||
assert.equal(publicAuth.ok, true);
|
||||
assert.equal(calls.length, 1);
|
||||
} finally { global.fetch = originalFetch; }
|
||||
});
|
||||
|
||||
test('API refresh queues a saved request without redirects and scopes task status to that request', async () => {
|
||||
const registerRoutes = require('../src/web/routes/data-sources/api-sources/routes');
|
||||
const routes = { get: {}, post: {} };
|
||||
const queued = [];
|
||||
let enabled = 1;
|
||||
let taskKey = 'api-source-refresh:43';
|
||||
const pool = { async query(sql, params) {
|
||||
if (sql.startsWith('SELECT * FROM i_api_source_groups')) return [[{ id: 5, name: 'Tiltify' }]];
|
||||
if (sql.includes('WHERE request.auth_source_id')) return [[{ id: 43, request_name: 'Event', enabled }]];
|
||||
if (sql.startsWith('SELECT id FROM i_api_sources WHERE id')) return [params[0] === 43 && params[1] === 5 ? [{ id: 43 }] : []];
|
||||
throw new Error('Unexpected query: ' + sql);
|
||||
} };
|
||||
registerRoutes({ get(path, ...handlers) { routes.get[path] = handlers; }, post(path, ...handlers) { routes.post[path] = handlers; } }, {
|
||||
pool, common: {}, requirePermission: permission => permission, getAuditUserId: () => 1,
|
||||
backgroundTaskQueue: { async enqueueTask(task) { queued.push(task); return { id: 101 }; } },
|
||||
dataSourceTasks: { async getTaskStatusById() { return { id: 101, key: taskKey, status: 'completed' }; } }
|
||||
});
|
||||
function response() {
|
||||
return { statusCode: 200, headers: {}, status(code) { this.statusCode = code; return this; }, set(key, value) { this.headers[key] = value; return this; }, json(body) { this.body = body; return this; }, redirect() { throw new Error('AJAX refresh must not redirect'); } };
|
||||
}
|
||||
const refresh = routes.post['/data-sources/api-sources/:id/requests/:requestId/refresh'];
|
||||
const state = routes.get['/data-sources/api-sources/:id/requests/:requestId/state'];
|
||||
assert.equal(refresh[0], 'api-sources.allow');
|
||||
assert.equal(state[0], 'api-sources.allow');
|
||||
const req = { params: { id: '5', requestId: '43' }, get: () => 'application/json', query: { refresh_task_id: '101' } };
|
||||
const next = error => { throw error; };
|
||||
const started = response();
|
||||
await refresh[1](req, started, next);
|
||||
assert.equal(started.statusCode, 202);
|
||||
assert.equal(started.body.taskId, 101);
|
||||
assert.equal(started.body.stateUrl, '/data-sources/api-sources/5/requests/43/state');
|
||||
assert.equal(queued[0].payload.sourceId, 43);
|
||||
const completed = response();
|
||||
await state[1](req, completed, next);
|
||||
assert.equal(completed.body.status, 'completed');
|
||||
assert.equal(completed.headers['Cache-Control'], 'no-store');
|
||||
taskKey = 'api-source-refresh:44';
|
||||
const wrongTask = response();
|
||||
await state[1](req, wrongTask, next);
|
||||
assert.equal(wrongTask.statusCode, 404);
|
||||
const wrongGroup = response();
|
||||
await state[1]({ ...req, params: { id: '6', requestId: '43' } }, wrongGroup, next);
|
||||
assert.equal(wrongGroup.statusCode, 404);
|
||||
enabled = 0;
|
||||
const disabled = response();
|
||||
await refresh[1](req, disabled, next);
|
||||
assert.equal(disabled.statusCode, 409);
|
||||
assert.equal(queued.length, 1);
|
||||
});
|
||||
|
||||
test('saving a disabled group does not re-enable it or erase individual request switches', async () => {
|
||||
const pool = createPool([5, 6], [], 0);
|
||||
const result = await saveApiGroup(pool, 5, payload([
|
||||
{ id: 5, name: 'Event', api_url: 'https://example.com/event', enabled: true },
|
||||
{ id: 6, name: 'Donors', api_url: 'https://example.com/donors', enabled: false }
|
||||
]), 1, true);
|
||||
assert.equal(result.enabled, 0);
|
||||
assert.deepEqual(result.requests.map(request => request.enabled), [1, 0]);
|
||||
const parentUpdate = pool.calls.find(([sql]) => sql.startsWith('UPDATE i_api_source_groups'));
|
||||
assert.doesNotMatch(parentUpdate[0], /\benabled\s*=/);
|
||||
});
|
||||
|
||||
test('disabled parent groups preserve raw request switches in the editor', async () => {
|
||||
const pool = { async query(sql) {
|
||||
if (sql.startsWith('SELECT * FROM i_api_source_groups')) return [[{ id: 5, enabled: 0 }]];
|
||||
return [[{ id: 43, request_name: 'Event', enabled: 0, request_enabled: 1, source_enabled: 0 }]];
|
||||
} };
|
||||
const group = await fetchApiGroupById(pool, 5);
|
||||
assert.equal(group.enabled, 0);
|
||||
assert.equal(group.requests[0].enabled, 1);
|
||||
assert.equal(group.requests[0].source_enabled, 0);
|
||||
});
|
||||
|
||||
test('the master toggle changes only the group and resumes only individually enabled requests', async () => {
|
||||
const registerRoutes = require('../src/web/routes/data-sources/api-sources/routes');
|
||||
const routes = {};
|
||||
const queries = [];
|
||||
const removed = [];
|
||||
const registered = [];
|
||||
const requests = [{ id: 43, request_name: 'Event', request_enabled: 1 }, { id: 44, request_name: 'Donors', request_enabled: 0 }];
|
||||
let enabled = 1;
|
||||
const pool = { async query(sql, params) {
|
||||
queries.push(sql);
|
||||
if (sql.startsWith('SELECT * FROM i_api_source_groups')) return [[{ id: 5, name: 'Tiltify', enabled }]];
|
||||
if (sql.includes('WHERE request.auth_source_id')) return [requests.map(request => ({ ...request, enabled: enabled && request.request_enabled, source_enabled: enabled, update_interval_value: 30, update_interval_unit: 'seconds' }))];
|
||||
if (sql.startsWith('UPDATE i_api_source_groups SET enabled')) { enabled = params[0]; return [{}]; }
|
||||
throw new Error('Unexpected query: ' + sql);
|
||||
} };
|
||||
registerRoutes({ get() {}, post(path, ...handlers) { routes[path] = handlers; } }, {
|
||||
pool, common: {}, requirePermission: permission => permission, getAuditUserId: () => 1,
|
||||
dataSourceTasks: { removeRecurringRefresh(type, id) { removed.push(id); }, registerRecurringRefresh(type, id) { registered.push(id); } }
|
||||
});
|
||||
const res = { redirect(url) { this.url = url; }, status(code) { this.statusCode = code; return this; }, json(body) { this.body = body; } };
|
||||
const next = error => { throw error; };
|
||||
const route = routes['/data-sources/api-sources/:id'];
|
||||
assert.equal(route[0], 'api-sources.update');
|
||||
const req = { params: { id: '5' }, body: { data_source_action: 'toggle', name: 'Unsaved draft must not be saved' } };
|
||||
await route[1](req, res, next);
|
||||
assert.equal(enabled, 0);
|
||||
assert.deepEqual(removed, [43, 44]);
|
||||
assert.deepEqual(registered, []);
|
||||
assert.deepEqual(requests.map(request => request.request_enabled), [1, 0]);
|
||||
await routes['/data-sources/api-sources/:id/requests/:requestId/refresh'][1]({ params: { id: '5', requestId: '43' } }, res, next);
|
||||
assert.equal(res.statusCode, 409);
|
||||
assert.match(res.body.error, /source is disabled/);
|
||||
await route[1](req, res, next);
|
||||
assert.equal(enabled, 1);
|
||||
assert.deepEqual(registered, [43]);
|
||||
assert.ok(!queries.some(sql => sql.startsWith('UPDATE i_api_sources')));
|
||||
});
|
||||
@@ -244,7 +244,7 @@ test('saved requests show visible full-width response trees with independent exp
|
||||
const html = renderEdit({ id: 42, name: 'Campaign', requests: [
|
||||
{ id: 43, name: 'Event', last_response_status: 200, last_response_json: '{"data":{"total":42}}' },
|
||||
{ id: 44, name: 'Donors', last_response_status: 200, last_response_json: '{"data":[{"name":"Donor"}]}' }
|
||||
] }, {}, '', { permissionKeys: ['api-sources.update'] });
|
||||
] }, {}, '', { permissionKeys: ['api-sources.update', 'api-sources.allow'] });
|
||||
assert.equal((html.match(/data-api-request-response/g) || []).length, 2);
|
||||
assert.equal((html.match(/data-json-toggle-expand-all/g) || []).length, 2);
|
||||
assert.equal((html.match(/data-json-toggle-collapse-all/g) || []).length, 2);
|
||||
@@ -253,6 +253,27 @@ test('saved requests show visible full-width response trees with independent exp
|
||||
assert.match(html, /HTTP 200/);
|
||||
assert.match(html, /data-api-request-title>Request - Event<\/h3>/);
|
||||
assert.match(html, /data-api-request-title>Request - Donors<\/h3>/);
|
||||
for (const id of [43, 44]) {
|
||||
assert.ok(html.includes('id="api-request-response-' + id + '"'));
|
||||
assert.ok(html.includes('data-manual-refresh-target="#api-request-response-' + id + '"'));
|
||||
}
|
||||
});
|
||||
|
||||
test('the Connection master toggle disables refresh actions without changing request switches', () => {
|
||||
const renderEdit = require('../src/web/routes/data-sources/api-sources/edit');
|
||||
const user = { permissionKeys: ['api-sources.update', 'api-sources.allow'] };
|
||||
const requests = [{ id: 43, name: 'Event', enabled: 1 }];
|
||||
const enabled = renderEdit({ id: 5, name: 'Tiltify', enabled: 1, requests }, {}, '', user);
|
||||
const disabled = renderEdit({ id: 5, name: 'Tiltify', enabled: 0, requests }, {}, '', user);
|
||||
assert.match(enabled, /form="api-source-toggle-form" data-async-data-source-toggle data-enabled="true"/);
|
||||
assert.match(disabled, /form="api-source-toggle-form" data-async-data-source-toggle data-enabled="false"/);
|
||||
assert.match(disabled, /data-request-field="enabled" checked/);
|
||||
assert.match(disabled, /data-source-item-enabled="true" disabled/);
|
||||
assert.match(disabled, /data-async-command><input type="hidden" name="data_source_action" value="toggle"/);
|
||||
for (const html of [enabled, disabled]) {
|
||||
assert.ok(html.indexOf('data-api-auth-test-url') < html.indexOf('data-async-data-source-toggle'));
|
||||
assert.ok(html.indexOf('data-async-data-source-toggle') < html.indexOf('data-api-request-add'));
|
||||
}
|
||||
});
|
||||
|
||||
test('connection diagnostics follow the tested section and can be dismissed and reopened', async () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ const vm = require('node:vm');
|
||||
|
||||
function loadChartData() {
|
||||
const context = { window: {} };
|
||||
vm.runInNewContext(fs.readFileSync('src/web/public/js/shared/placeholder-utils.js', 'utf8'), context);
|
||||
vm.runInNewContext(fs.readFileSync('src/web/public/js/regions/chart-data.js', 'utf8'), context);
|
||||
return context.window.pulseChartData;
|
||||
}
|
||||
@@ -64,6 +65,17 @@ test('uses an API source default items path and ignores non-numeric rows', () =>
|
||||
assert.deepEqual(Array.from(result.datasets[0].data), [1200, 4]);
|
||||
});
|
||||
|
||||
test('API charts accept a single object items path without falling back on missing paths', () => {
|
||||
const chartData = loadChartData();
|
||||
const sources = [{ id: 4, items_path: 'data', responseJson: { data: { name: 'Campaign', amount_raised: { value: '200' } } } }];
|
||||
const config = { source_kind: 'api', source_id: 4, label_path: 'name', value_path: 'amount_raised.value' };
|
||||
const result = chartData.fromContent(config, sources);
|
||||
assert.deepEqual(Array.from(result.labels), ['Campaign']);
|
||||
assert.deepEqual(Array.from(result.datasets[0].data), [200]);
|
||||
const missing = chartData.fromContent({ ...config, items_path: 'missing' }, sources);
|
||||
assert.deepEqual(Array.from(missing.labels), []);
|
||||
});
|
||||
|
||||
test('normalizes multiple series for line charts', () => {
|
||||
const chartData = loadChartData();
|
||||
const result = chartData.fromContent({
|
||||
|
||||
@@ -116,6 +116,15 @@ test('refreshApiSource skips notifications when the API snapshot is unchanged',
|
||||
assert.deepEqual(notifyCalls, []);
|
||||
});
|
||||
|
||||
test('refreshApiSource skips disabled requests before outbound calls or snapshot writes', async () => {
|
||||
const connection = createConnection();
|
||||
const result = await refreshApiSource({ async getConnection() { return connection; } }, {
|
||||
async fetchApiSourceResponse() { throw new Error('Disabled sources must not be fetched'); }
|
||||
}, { id: 7, enabled: 0 }, 1);
|
||||
assert.deepEqual(result, { skipped: true, reason: 'disabled' });
|
||||
assert.deepEqual(connection.calls, [['release']]);
|
||||
});
|
||||
|
||||
test('refreshRssFeed notifies players only when the RSS items change', async () => {
|
||||
const connection = createConnection({
|
||||
rssItemSnapshots: [JSON.stringify({ title: 'Old item' })],
|
||||
|
||||
@@ -195,6 +195,57 @@ test('server and browser rich-text rendering stay in sync', () => {
|
||||
assert.equal(browserContext.renderEditorJsContent(value), renderEditorJsContent(value));
|
||||
});
|
||||
|
||||
test('shared API item selection treats object paths as one item and preserves array/root behavior', () => {
|
||||
const helpers = require('../src/web/public/js/shared/placeholder-utils');
|
||||
const campaign = { name: 'Campaign', amount_raised: { value: '200' }, goal: { value: '1000' } };
|
||||
const response = { data: campaign };
|
||||
assert.deepEqual(helpers.resolveApiItems(response, 'data'), [campaign]);
|
||||
assert.deepEqual(helpers.resolveApiItems(response, ''), [response]);
|
||||
assert.deepEqual(helpers.resolveApiItems(response, 'missing'), []);
|
||||
assert.deepEqual(helpers.resolveApiItems(response, 'data.name'), []);
|
||||
assert.deepEqual(helpers.resolveApiItems(response, 'data.absent.nested'), []);
|
||||
assert.deepEqual(helpers.resolveApiItems({ data: null }, 'data'), []);
|
||||
assert.deepEqual(helpers.resolveApiItems({ data: {} }, 'data'), [{}]);
|
||||
const rows = [campaign];
|
||||
for (const key of ['items', 'results', 'data']) {
|
||||
assert.equal(helpers.resolveApiItems({ [key]: rows }, ''), rows);
|
||||
assert.equal(helpers.resolveApiItems({ [key]: rows }, key), rows);
|
||||
}
|
||||
assert.equal(helpers.resolveApiItems(rows, ''), rows);
|
||||
assert.deepEqual(helpers.resolveApiItems({ data: rows }, 'data[0]'), [campaign]);
|
||||
const sandbox = { window: {} };
|
||||
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8'), sandbox);
|
||||
assert.equal(JSON.stringify(sandbox.window.placeholderUtils.resolveApiItems(response, 'data')), JSON.stringify([campaign]));
|
||||
});
|
||||
|
||||
test('API editor and player resolve object paths, root overrides, and numeric placeholders consistently', () => {
|
||||
let editor;
|
||||
const campaign = { name: 'Campaign', amount_raised: { value: '200' }, goal: { value: '1000' } };
|
||||
const response = { data: campaign };
|
||||
const sources = [{ id: 4, items_path: 'data', responseJson: response }];
|
||||
const context = {
|
||||
window: {
|
||||
pulseRegionTypes: { register(_type, definition) { editor = definition; } },
|
||||
pulsePlayerRegionTypes: { register() {} }
|
||||
},
|
||||
initialData: { apiSources: sources },
|
||||
escapeHtml: String
|
||||
};
|
||||
for (const file of ['../src/web/public/js/shared/placeholder-utils.js', '../src/web/public/js/regions/type/api.js', '../src/player/regions/api.js']) {
|
||||
vm.runInNewContext(fs.readFileSync(require.resolve(file), 'utf8'), context);
|
||||
}
|
||||
assert.equal(editor.getItems(4, sources)[0], campaign);
|
||||
assert.equal(editor.getItemCount(4, sources), 1);
|
||||
assert.equal(context.getApiItem(4, 1), campaign);
|
||||
assert.equal(context.getApiItem(4, 2), null);
|
||||
assert.equal(editor.getItems(4, sources, '')[0], response);
|
||||
assert.equal(context.getApiItem(4, 1, ''), response);
|
||||
assert.equal(editor.getItemCount(4, sources, 'missing'), 0);
|
||||
assert.equal(context.getApiItem(4, 1, 'missing'), null);
|
||||
assert.equal(context.substituteApiVariables('{{amount_raised.value}}', campaign, 4), '200');
|
||||
assert.match(context.substituteApiVariables('{{progress(amount_raised.value,goal.value)}}', campaign, 4), /width:20%/);
|
||||
});
|
||||
|
||||
test('shared API progress placeholder renders a clamped bar from numeric fields', () => {
|
||||
const sandbox = { window: {} };
|
||||
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8'), sandbox);
|
||||
@@ -237,6 +288,25 @@ test('shared API progress placeholder renders a clamped bar from numeric fields'
|
||||
assert.match(literalMarkup, /aria-valuenow="30"/);
|
||||
});
|
||||
|
||||
test('progress placeholders ignore rich-text formatting inside numeric and field arguments', () => {
|
||||
const sandbox = { window: {} };
|
||||
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8'), sandbox);
|
||||
const placeholderUtils = sandbox.window.placeholderUtils;
|
||||
const literal = 'progress(<span style="font-family: \'Ubuntu Regular\', Helvetica, sans-serif; font-size: 96px; color: #ffffff;">200</span>,<span style="font-family: \'Ubuntu Regular\', Helvetica, sans-serif; font-size: 96px; color: #ffffff;">1000</span>,#F47B20,#2D2D2D,pill)';
|
||||
assert.equal(placeholderUtils.isProgressPlaceholderExpression(literal), true);
|
||||
const markup = placeholderUtils.renderProgressPlaceholder({}, literal);
|
||||
assert.match(markup, /aria-valuenow="20"/);
|
||||
assert.match(markup, /width:20%/);
|
||||
assert.match(markup, /background-color:#f47b20/);
|
||||
assert.match(markup, /background-color:#2d2d2d/);
|
||||
assert.doesNotMatch(markup, /Ubuntu|font-size:96px/);
|
||||
const fields = placeholderUtils.renderProgressPlaceholder(
|
||||
{ raised: { value: 200 }, target: { amount: 1000 } },
|
||||
'progress(<strong><span>raised.value</span></strong>,<em>target.amount</em>,pill)'
|
||||
);
|
||||
assert.match(fields, /aria-valuenow="20"/);
|
||||
});
|
||||
|
||||
test('shared placeholder transforms support arithmetic', () => {
|
||||
const sandbox = { window: {} };
|
||||
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8'), sandbox);
|
||||
|
||||
Reference in new issue
Block a user