style(queue): 完善队列进度展示

队列执行弹窗增加状态点、进度文字、任务状态标记和更大的日志面板,并对日志内容进行 HTML 转义,避免任务输出影响页面结构。

优化进度条和日志区域样式,支持成功、执行中和失败状态的不同视觉反馈,同时保留自动滚动查看最新日志。

为队列表格时间与耗时格式化增加前端兜底函数,修正脚本中转义后的  变量输出,并更新测试确保生成脚本可直接执行。
This commit is contained in:
邹景立 2026-05-21 00:17:01 +08:00
parent 2405afdb16
commit 11377b6761
3 changed files with 110 additions and 34 deletions

View File

@ -15,12 +15,16 @@
layui.define(function (exports) { layui.define(function (exports) {
let template = [ let template = [
'<div class="ta-queue-box" data-queue-load="{{d.code}}">', '<div class="ta-queue-box" data-queue-state="0" data-queue-load="{{d.code}}">',
' <div class="ta-queue-status">', ' <div class="ta-queue-status">',
' <div class="ta-queue-status-main">',
' <span class="ta-queue-status-dot"></span>',
' <div class="ta-queue-status-title layui-elip notselect nowrap" data-message-title><b class="color-desc">...</b></div>', ' <div class="ta-queue-status-title layui-elip notselect nowrap" data-message-title><b class="color-desc">...</b></div>',
' </div>',
' <div class="ta-queue-status-code"># {{d.code}}</div>', ' <div class="ta-queue-status-code"># {{d.code}}</div>',
' </div>', ' </div>',
' <div class="layui-progress layui-progress-big ta-queue-progress" lay-showPercent="yes"><div class="layui-progress-bar transition layui-bg-blue" lay-percent="0.00%"></div></div>', ' <div class="ta-queue-progress-meta"><span>执行进度</span><b data-progress-text>0.00%</b></div>',
' <div class="layui-progress layui-progress-big ta-queue-progress"><div class="layui-progress-bar transition layui-bg-blue" lay-percent="0.00%"></div></div>',
' <div class="ta-queue-log">', ' <div class="ta-queue-log">',
' <div class="ta-queue-log-head"><span>执行日志</span><small>实时刷新</small></div>', ' <div class="ta-queue-log-head"><span>执行日志</span><small>实时刷新</small></div>',
' <code></code>', ' <code></code>',
@ -33,32 +37,49 @@ layui.define(function (exports) {
let style = document.createElement('style'); let style = document.createElement('style');
style.id = 'ta-queue-style'; style.id = 'ta-queue-style';
style.innerHTML = [ style.innerHTML = [
'.ta-queue-layer{border-radius:10px;overflow:hidden;}', '@keyframes taQueuePulse{0%{box-shadow:0 0 0 0 rgba(59,130,246,.28);}70%{box-shadow:0 0 0 8px rgba(59,130,246,0);}100%{box-shadow:0 0 0 0 rgba(59,130,246,0);}}',
'.ta-queue-layer .layui-layer-title{height:48px;line-height:48px;font-weight:600;border-bottom:1px solid #edf0f5;background:#fff;}', '.ta-queue-layer{border-radius:12px;overflow:hidden;box-shadow:0 18px 45px rgba(15,23,42,.22);}',
'.ta-queue-layer .layui-layer-content{overflow:hidden;background:#f6f8fb;}', '.ta-queue-layer .layui-layer-title{height:50px;line-height:50px;padding:0 50px 0 18px;color:#1f2937;font-size:15px;font-weight:700;border-bottom:1px solid var(--ta-border-color,#edf0f5);background:linear-gradient(180deg,#fff,#f8fbfc);}',
'.ta-queue-box{padding:18px 20px 20px;background:#f6f8fb;}', '.ta-queue-layer .layui-layer-setwin{top:17px;right:16px;}',
'.ta-queue-status{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:13px 15px;background:#fff;border:1px solid #edf0f5;border-radius:10px;box-shadow:0 4px 18px rgba(15,35,60,.04);}', '.ta-queue-layer .layui-layer-content{overflow:hidden;background:var(--ta-body-bg,#f4f7fb);}',
'.ta-queue-status-title{min-width:0;flex:1;font-weight:600;}', '.ta-queue-box{height:100%;min-height:0;box-sizing:border-box;display:flex;flex-direction:column;padding:18px 20px 20px;background:var(--ta-body-bg,#f4f7fb);}',
'.ta-queue-status-title b{font-weight:600;}', '.ta-queue-status{min-height:50px;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:14px;box-sizing:border-box;padding:12px 14px;background:var(--ta-surface,#fff);border:1px solid var(--ta-border-color,#edf0f5);border-radius:12px;box-shadow:0 8px 22px rgba(15,35,60,.06);}',
'.ta-queue-status-code{flex-shrink:0;max-width:220px;color:#8c98a8;font-size:12px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}', '.ta-queue-status-main{display:flex;align-items:center;gap:10px;min-width:0;flex:1;}',
'.ta-queue-progress{height:12px;margin:14px 2px 0;border-radius:999px;background:#e8edf5;overflow:hidden;}', '.ta-queue-status-dot{width:8px;height:8px;display:inline-block;flex:0 0 8px;border-radius:50%;background:#94a3b8;}',
'.ta-queue-progress .layui-progress-bar{border-radius:999px;}', '.ta-queue-status-title{min-width:0;flex:1;color:#334155;font-size:13px;font-weight:600;text-align:left!important;}',
'.ta-queue-progress .layui-progress-text{top:-6px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2);}', '.ta-queue-status-title b{font-weight:700;}',
'.ta-queue-log{margin-top:14px;border-radius:10px;background:#222b33;border:1px solid #1a222a;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(255,255,255,.02);}', '.ta-queue-status-code{flex-shrink:0;max-width:220px;padding:2px 8px;color:#8c98a8;font-size:12px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:999px;background:#f3f6fa;}',
'.ta-queue-log-head{height:36px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;background:#172027;color:#dce5ee;font-size:13px;}', '.ta-queue-box[data-queue-state="1"] .ta-queue-status-dot,.ta-queue-box[data-queue-state="2"] .ta-queue-status-dot{background:#3b82f6;animation:taQueuePulse 1.6s ease-out infinite;}',
'.ta-queue-log-head small{font-size:12px;font-weight:400;color:#7f8b98;}', '.ta-queue-box[data-queue-state="3"] .ta-queue-status{border-color:#b7ebc6;background:linear-gradient(135deg,#f6ffed,#fff);}',
'.ta-queue-log code{display:block;height:185px;margin:0;padding:12px 14px;border:0;background:#222b33;color:#dce5ee;font-family:Consolas,Monaco,Menlo,monospace;font-size:12px;line-height:22px;white-space:normal;overflow:auto;}', '.ta-queue-box[data-queue-state="3"] .ta-queue-status-dot{background:#16a34a;box-shadow:0 0 0 5px rgba(22,163,74,.12);}',
'.ta-queue-log code p{height:22px;line-height:22px;margin:0;color:#dce5ee;}', '.ta-queue-box[data-queue-state="4"] .ta-queue-status{border-color:#ffd6d6;background:linear-gradient(135deg,#fff5f5,#fff);}',
'.ta-queue-log code .color-desc{color:#8c98a8!important;}' '.ta-queue-box[data-queue-state="4"] .ta-queue-status-dot{background:#ff4d4f;box-shadow:0 0 0 5px rgba(255,77,79,.12);}',
'.ta-queue-box[data-queue-state="4"] .ta-queue-status-title,.ta-queue-box[data-queue-state="4"] .ta-queue-status-title b{color:#ff4d4f!important;}',
'.ta-queue-progress-meta{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;margin:12px 2px 6px;color:#7b8794;font-size:12px;line-height:18px;}',
'.ta-queue-progress-meta b{color:#475569;font-size:12px;font-weight:600;}',
'.ta-queue-progress{flex-shrink:0;height:10px;border-radius:999px;background:#e7edf5;overflow:hidden;}',
'.ta-queue-progress .layui-progress-bar{height:10px;border-radius:999px;box-shadow:0 2px 6px rgba(30,64,175,.22);}',
'.ta-queue-log{flex:1 1 auto;min-height:230px;display:flex;flex-direction:column;margin-top:14px;border-radius:12px;background:#202a33;border:1px solid #19232c;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(255,255,255,.02),0 10px 24px rgba(15,23,42,.12);}',
'.ta-queue-log-head{height:38px;flex:0 0 38px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;background:#15202a;color:#e5edf5;font-size:13px;font-weight:600;}',
'.ta-queue-log-head small{font-size:12px;font-weight:400;color:#94a3b8;}',
'.ta-queue-log code{flex:1 1 auto;display:block;height:auto;min-height:0;margin:0;padding:12px 14px;border:0;box-sizing:border-box;background:#202a33;color:#e5edf5;font-family:Consolas,Monaco,Menlo,monospace;font-size:12px;line-height:22px;white-space:normal;overflow:auto;}',
'.ta-queue-log code p{min-height:22px;height:auto;line-height:22px;margin:0;color:#e5edf5;overflow:visible;text-overflow:clip;white-space:pre-wrap;word-break:break-word;}',
'.ta-queue-log code .color-desc{color:#94a3b8!important;}'
].join(''); ].join('');
document.head.appendChild(style); document.head.appendChild(style);
} }
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, function (char) {
return {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'}[char];
});
}
function Queue(code, doScript, element) { function Queue(code, doScript, element) {
let queue = this; let queue = this;
injectStyle(); injectStyle();
(this.doAjax = true) && (this.doReload = false) || layer.open({ (this.doAjax = true) && (this.doReload = false) || layer.open({
type: 1, title: '任务执行进度', area: ['620px', '390px'], skin: 'ta-queue-layer', anim: 2, shadeClose: false, end: function () { type: 1, title: '任务执行进度', area: ['640px', '520px'], skin: 'ta-queue-layer', anim: 2, shadeClose: false, end: function () {
queue.doAjax = queue.doReload && doScript && $.layTable.reload(((element || {}).dataset || {}).tableId || true) && false; queue.doAjax = queue.doReload && doScript && $.layTable.reload(((element || {}).dataset || {}).tableId || true) && false;
}, content: laytpl(template).render({code: code}), success: function ($elem) { }, content: laytpl(template).render({code: code}), success: function ($elem) {
new Progress($elem, code, queue, doScript); new Progress($elem, code, queue, doScript);
@ -74,6 +95,7 @@ layui.define(function (exports) {
this.$code = this.$box.find('code'); this.$code = this.$box.find('code');
this.$title = this.$box.find('[data-message-title]'); this.$title = this.$box.find('[data-message-title]');
this.$progressText = this.$box.find('[data-progress-text]');
this.$percent = this.$box.find('.layui-progress div'); this.$percent = this.$box.find('.layui-progress div');
// 设置数据缓存 // 设置数据缓存
@ -84,6 +106,7 @@ layui.define(function (exports) {
// 更新任务显示状态 // 更新任务显示状态
this.SetState = function (status, message) { this.SetState = function (status, message) {
that.$box.attr('data-queue-state', status);
if (message.indexOf('javascript:') === -1) if (status === 1) { if (message.indexOf('javascript:') === -1) if (status === 1) {
that.$title.html('<b class="color-text">' + message + '</b>').addClass('text-center'); that.$title.html('<b class="color-text">' + message + '</b>').addClass('text-center');
that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red'); that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red');
@ -121,7 +144,7 @@ layui.define(function (exports) {
continue; continue;
} }
if (text.indexOf('javascript:') === -1) { if (text.indexOf('javascript:') === -1) {
lines.push(text.indexOf('>>>') > -1 ? text : percent + text); lines.push(escapeHtml(text.indexOf('>>>') > -1 ? text : percent + text));
} else if (!that.SetCache(code, idx) && doScript !== false) { } else if (!that.SetCache(code, idx) && doScript !== false) {
that.SetCache(code, idx, 1) that.SetCache(code, idx, 1)
$.form.goto(text); $.form.goto(text);
@ -130,14 +153,18 @@ layui.define(function (exports) {
if (!isFinite(progress)) { if (!isFinite(progress)) {
progress = 0; progress = 0;
} }
that.$code.html(lines.length ? '<p class="layui-elip">' + lines.join('</p><p class="layui-elip">') + '</p>' : '<p class="layui-elip color-desc">暂无执行日志</p>').animate({scrollTop: that.$code[0].scrollHeight + 'px'}, 200); progress = Math.max(0, Math.min(100, progress));
let percentText = progress.toFixed(2) + '%';
that.$code.html(lines.length ? '<p>' + lines.join('</p><p>') + '</p>' : '<p class="color-desc">暂无执行日志</p>').animate({scrollTop: that.$code[0].scrollHeight + 'px'}, 200);
that.$progressText.text(percentText);
if (status > 0) { if (status > 0) {
that.SetState(status, message); that.SetState(status, message);
that.$percent.attr('lay-percent', progress.toFixed(2) + '%') && layui.element.render(); that.$percent.attr('lay-percent', percentText) && layui.element.render();
status === 3 || status === 4 || setTimeout(that.LoadProgress, Math.floor(Math.random() * 200)); status === 3 || status === 4 || setTimeout(that.LoadProgress, Math.floor(Math.random() * 200));
} else { } else {
that.$box.attr('data-queue-state', '0');
that.$title.html('<b class="color-desc">' + message + '</b>').addClass('text-center'); that.$title.html('<b class="color-desc">' + message + '</b>').addClass('text-center');
that.$percent.attr('lay-percent', progress.toFixed(2) + '%') && layui.element.render(); that.$percent.attr('lay-percent', percentText) && layui.element.render();
setTimeout(that.LoadProgress, Math.floor(Math.random() * 500) + 200); setTimeout(that.LoadProgress, Math.floor(Math.random() * 500) + 200);
} }
return false; return false;

View File

@ -124,16 +124,19 @@ function (d) {
SCRIPT, self::json(BuilderLang::text('循环')), self::json(BuilderLang::text('单次')), self::json(BuilderLang::text('任务编号:')), self::json(BuilderLang::text('任务名称:'))))]) SCRIPT, self::json(BuilderLang::text('循环')), self::json(BuilderLang::text('单次')), self::json(BuilderLang::text('任务编号:')), self::json(BuilderLang::text('任务名称:'))))])
->column(['field' => 'exec_time', 'title' => '任务计划', 'minWidth' => 220, 'templet' => PageBuilder::js(sprintf(<<<'SCRIPT' ->column(['field' => 'exec_time', 'title' => '任务计划', 'minWidth' => 220, 'templet' => PageBuilder::js(sprintf(<<<'SCRIPT'
function (d) { function (d) {
d.html = %s + d.command + '<br>' + %s + formatQueueTime(d.exec_time, '<span class="color-desc">' + %s + '</span>'); %s
d.html = %s + d.command + '<br>' + %s + formatQueueTimeSafe(d.exec_time, '<span class="color-desc">' + %s + '</span>');
if (d.loops_time > 0) { if (d.loops_time > 0) {
return d.html + ' ( ' + %s + ' <b class="color-blue">' + d.loops_time + '</b> ' + %s + ' ) '; return d.html + ' ( ' + %s + ' <b class="color-blue">' + d.loops_time + '</b> ' + %s + ' ) ';
} else { } else {
return d.html + ' <span class="color-desc">( ' + %s + ' )</span> '; return d.html + ' <span class="color-desc">( ' + %s + ' )</span> ';
} }
} }
SCRIPT, self::json(BuilderLang::text('执行指令:')), self::json(BuilderLang::text('计划执行:')), self::json(BuilderLang::text('未计划')), self::json(BuilderLang::text('每')), self::json(BuilderLang::text('秒')), self::json(BuilderLang::text('单次任务'))))]) SCRIPT, self::renderQueueTimeFallbackScript(), self::json(BuilderLang::text('执行指令:')), self::json(BuilderLang::text('计划执行:')), self::json(BuilderLang::text('未计划')), self::json(BuilderLang::text('每')), self::json(BuilderLang::text('秒')), self::json(BuilderLang::text('单次任务'))))])
->column(['field' => 'loops_time', 'title' => '任务状态', 'minWidth' => 260, 'templet' => PageBuilder::js(sprintf(<<<'SCRIPT' ->column(['field' => 'loops_time', 'title' => '任务状态', 'minWidth' => 260, 'templet' => PageBuilder::js(sprintf(<<<'SCRIPT'
function (d) { function (d) {
%s
%s
d.html = ([ d.html = ([
'<span class="pull-left layui-badge layui-badge-middle layui-bg-gray">' + %s + '</span>', '<span class="pull-left layui-badge layui-badge-middle layui-bg-gray">' + %s + '</span>',
'<span class="pull-left layui-badge layui-badge-middle layui-bg-black">' + %s + '</span>', '<span class="pull-left layui-badge layui-badge-middle layui-bg-black">' + %s + '</span>',
@ -142,7 +145,7 @@ function (d) {
'<span class="pull-left layui-badge layui-badge-middle layui-bg-red">' + %s + '</span>' '<span class="pull-left layui-badge layui-badge-middle layui-bg-red">' + %s + '</span>'
][d.status] || '') + %s; ][d.status] || '') + %s;
if (String(d.enter_time || '') !== '' && String(d.enter_time) !== '0' && String(d.enter_time) !== '0.0000') { if (String(d.enter_time || '') !== '' && String(d.enter_time) !== '0' && String(d.enter_time) !== '0.0000') {
d.html += formatQueueTime(d.enter_time) + '<span class="color-desc">' + formatQueueCost(d.enter_time, d.outer_time, d.status) + '</span>'; d.html += formatQueueTimeSafe(d.enter_time) + '<span class="color-desc">' + formatQueueCostSafe(d.enter_time, d.outer_time, d.status) + '</span>';
d.html += ' ' + %s + ' <b class="color-blue">' + (d.attempts || 0) + '</b> ' + %s; d.html += ' ' + %s + ' <b class="color-blue">' + (d.attempts || 0) + '</b> ' + %s;
} else { } else {
d.html += '<span class="color-desc">' + %s + '</span>'; d.html += '<span class="color-desc">' + %s + '</span>';
@ -150,6 +153,8 @@ function (d) {
return d.html + '<br>' + %s + '<span class="color-blue">' + (d.exec_desc || '<span class="color-desc">' + %s + '</span>') + '</span>'; return d.html + '<br>' + %s + '<span class="color-blue">' + (d.exec_desc || '<span class="color-desc">' + %s + '</span>') + '</span>';
} }
SCRIPT, SCRIPT,
self::renderQueueTimeFallbackScript(),
self::renderQueueCostFallbackScript(),
self::json(BuilderLang::text('未知')), self::json(BuilderLang::text('未知')),
self::json(BuilderLang::text('等待')), self::json(BuilderLang::text('等待')),
self::json(BuilderLang::text('执行')), self::json(BuilderLang::text('执行')),
@ -180,6 +185,47 @@ SCRIPT,
->build(); ->build();
} }
private static function renderQueueTimeFallbackScript(): string
{
return sprintf(<<<'SCRIPT'
var formatQueueTimeSafe = window.formatQueueTime || function (value, emptyHtml) {
var num = Number(value || 0);
if (!isFinite(num) || num <= 0) return emptyHtml || '<span class="color-desc">' + %s + '</span>';
return layui.util.toDateString(Math.round(num * 1000), 'yyyy-MM-dd HH:mm:ss');
};
SCRIPT, self::json(BuilderLang::text('未执行')));
}
private static function renderQueueCostFallbackScript(): string
{
return sprintf(<<<'SCRIPT'
var formatQueueCostSafe = window.formatQueueCost || function (start, finish, status) {
var begin = Number(start || 0);
var end = Number(finish || 0);
var cost = 0;
var suffix = '';
if (!isFinite(begin) || begin <= 0) return '';
if (isFinite(end) && end > begin) {
cost = end - begin;
} else if (Number(status) === 2) {
cost = Date.now() / 1000 - begin;
suffix = %s;
}
if (cost <= 0) return suffix;
if (cost >= 60) return %s + (cost / 60).toFixed(2) + ' ' + %s + suffix;
if (cost >= 1) return %s + cost.toFixed(2) + ' ' + %s + suffix;
return %s + Math.round(cost * 1000) + ' ms' + suffix;
};
SCRIPT,
self::json(BuilderLang::text(',执行中')),
self::json(BuilderLang::text(',耗时 ')),
self::json(BuilderLang::text('分钟')),
self::json(BuilderLang::text(',耗时 ')),
self::json(BuilderLang::text('秒')),
self::json(BuilderLang::text(',耗时 '))
);
}
private static function renderScript(bool $super): string private static function renderScript(bool $super): string
{ {
$queueStatusUrl = apiuri('system/queue/status'); $queueStatusUrl = apiuri('system/queue/status');
@ -189,23 +235,23 @@ $(function () {
const queueStatusUrl = %s; const queueStatusUrl = %s;
const queueStatusEnabled = %s; const queueStatusEnabled = %s;
const queueI18n = %s; const queueI18n = %s;
const \$queueMessage = \$('[data-queue-message]'); const $queueMessage = $('[data-queue-message]');
let queueStatusTimer = 0; let queueStatusTimer = 0;
let queueStatusRequest = null; let queueStatusRequest = null;
const setQueueStatusText = function (text, color, tips) { const setQueueStatusText = function (text, color, tips) {
if (\$queueMessage.length < 1) return; if ($queueMessage.length < 1) return;
\$queueMessage.attr('data-tips-text', tips || text).html('<span class="' + color + '">' + text + '</span>'); $queueMessage.attr('data-tips-text', tips || text).html('<span class="' + color + '">' + text + '</span>');
}; };
const loadQueueServiceStatus = function (times, delay) { const loadQueueServiceStatus = function (times, delay) {
if (!queueStatusEnabled || \$queueMessage.length < 1) return; if (!queueStatusEnabled || $queueMessage.length < 1) return;
clearTimeout(queueStatusTimer); clearTimeout(queueStatusTimer);
if (queueStatusRequest && queueStatusRequest.readyState !== 4) queueStatusRequest.abort(); if (queueStatusRequest && queueStatusRequest.readyState !== 4) queueStatusRequest.abort();
queueStatusRequest = $.ajax({ queueStatusRequest = $.ajax({
url: $.menu.parseUri(queueStatusUrl), url: $.menu.parseUri(queueStatusUrl),
type: 'GET', type: 'GET',
success: function (html) { success: function (html) {
\$queueMessage.attr('data-tips-text', queueI18n.clickRefreshServiceStatus).html(html); $queueMessage.attr('data-tips-text', queueI18n.clickRefreshServiceStatus).html(html);
if (times > 1) queueStatusTimer = setTimeout(function () { loadQueueServiceStatus(times - 1, delay); }, delay); if (times > 1) queueStatusTimer = setTimeout(function () { loadQueueServiceStatus(times - 1, delay); }, delay);
}, },
error: function (xhr) { error: function (xhr) {
@ -249,7 +295,7 @@ $(function () {
}); });
return false; return false;
}); });
\$queueMessage.off('click.queue-status').on('click.queue-status', function () { $queueMessage.off('click.queue-status').on('click.queue-status', function () {
setQueueStatusText(queueI18n.checking, 'color-desc', queueI18n.refreshingServiceStatus); setQueueStatusText(queueI18n.checking, 'color-desc', queueI18n.refreshingServiceStatus);
loadQueueServiceStatus(2, 400); loadQueueServiceStatus(2, 400);
return false; return false;

View File

@ -51,6 +51,9 @@ class QueueControllerTest extends SqliteIntegrationTestCase
$this->assertStringContainsString('批量删除', $html); $this->assertStringContainsString('批量删除', $html);
$this->assertStringContainsString('const queueStatusUrl = "/api/system/queue/status.html";', $html); $this->assertStringContainsString('const queueStatusUrl = "/api/system/queue/status.html";', $html);
$this->assertStringContainsString('const queueStatusEnabled = false;', $html); $this->assertStringContainsString('const queueStatusEnabled = false;', $html);
$this->assertStringContainsString('const $queueMessage = $(\'[data-queue-message]\');', $html);
$this->assertStringContainsString('formatQueueTimeSafe', $html);
$this->assertStringNotContainsString('const \\$queueMessage', $html);
$this->assertStringNotContainsString('{$queueStatusUrl}', $html); $this->assertStringNotContainsString('{$queueStatusUrl}', $html);
$this->assertStringNotContainsString('{$enabled}', $html); $this->assertStringNotContainsString('{$enabled}', $html);
} }