diff --git a/.eslintrc.js b/.eslintrc.js
index eb59f2ad..bce5934c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -55,7 +55,6 @@ module.exports = {
'./.eslintrc.js',
'./.prettierrc.js',
'./.stylelintrc.js',
- './.template-lintrc.js',
'./ember-cli-build.js',
'./index.js',
'./testem.js',
@@ -74,5 +73,18 @@ module.exports = {
},
extends: ['plugin:n/recommended'],
},
+ // QUnit test files. `no-hooks-from-ancestor-modules` is the one that matters most here:
+ // a nested `module('…', function (hooks) { … })` shadows the outer hooks, which QUnit 3
+ // turns into a hard error. It is easy to reintroduce and the suite stays green when you do.
+ {
+ files: ['tests/**/*-test.js'],
+ plugins: ['qunit'],
+ extends: ['plugin:qunit/recommended'],
+ rules: {
+ // Opinionated and noisy for this suite: most tests assert an obvious, fixed number
+ // of things, and an expect() count that drifts is worse than none.
+ 'qunit/require-expect': 'off',
+ },
+ },
],
};
diff --git a/.template-lintrc.js b/.template-lintrc.mjs
similarity index 54%
rename from .template-lintrc.js
rename to .template-lintrc.mjs
index c3086c51..dc564205 100644
--- a/.template-lintrc.js
+++ b/.template-lintrc.mjs
@@ -1,8 +1,20 @@
-'use strict';
+import NoUnguardedHandlerArgument from './lint/no-unguarded-handler-argument.mjs';
-module.exports = {
+export default {
+ plugins: [
+ {
+ name: 'fleetbase-ember-ui',
+ rules: {
+ 'no-unguarded-handler-argument': NoUnguardedHandlerArgument,
+ },
+ },
+ ],
extends: 'recommended',
rules: {
+ // `{{on "click" @arg}}` and `{{fn @arg …}}` THROW while rendering when the argument is
+ // absent — they are not no-ops. Thirteen bindings across five components were fixed for
+ // this; the rule keeps them from coming back.
+ 'no-unguarded-handler-argument': true,
'no-invalid-interactive': 'off',
'no-yield-only': 'off',
'no-pointer-down-event-binding': 'off',
@@ -18,6 +30,15 @@ module.exports = {
},
},
overrides: [
+ {
+ // Test fixtures always supply the handlers they bind — the template is written
+ // alongside the arguments it needs. The rule exists to protect the addon's shipped
+ // components, where the caller is someone else.
+ files: ['tests/**/*-test.js'],
+ rules: {
+ 'no-unguarded-handler-argument': false,
+ },
+ },
{
// Modifier tests set inline styles on their fixtures because the
// element's own style is exactly what the modifier under test reads
diff --git a/addon/components/layout/header/dropdown/item.hbs b/addon/components/layout/header/dropdown/item.hbs
index a6f0b35c..0c863812 100644
--- a/addon/components/layout/header/dropdown/item.hbs
+++ b/addon/components/layout/header/dropdown/item.hbs
@@ -74,7 +74,7 @@
class="next-header-dd-menu-item next-dd-item {{if @item.disabled 'disabled'}} {{@item.class}}"
target={{@item.target}}
disabled={{@item.disabled}}
- {{on "click" (fn @onAction @item.action @item.params)}}
+ {{on "click" (fn (or @onAction (noop)) @item.action @item.params)}}
>
{{#if @item.icon}}
@@ -90,7 +90,7 @@
{{#if this.isInteractive}}
-
diff --git a/addon/components/modals/bulk-delete-model.hbs b/addon/components/modals/bulk-delete-model.hbs
index 552b3f86..7be29929 100644
--- a/addon/components/modals/bulk-delete-model.hbs
+++ b/addon/components/modals/bulk-delete-model.hbs
@@ -30,7 +30,7 @@
{{get selected @options.modelNamePath}}
-
+
diff --git a/addon/components/modals/import-form.hbs b/addon/components/modals/import-form.hbs
index c91a4c69..49afd934 100644
--- a/addon/components/modals/import-form.hbs
+++ b/addon/components/modals/import-form.hbs
@@ -79,7 +79,7 @@
{{#each @options.fileQueueColumns as |column|}}
{{#if (eq column.key "delete")}}
-
+
{{else if (eq column.key "type")}}
diff --git a/addon/components/modals/save-report.hbs b/addon/components/modals/save-report.hbs
index dd4f243f..eec4e751 100644
--- a/addon/components/modals/save-report.hbs
+++ b/addon/components/modals/save-report.hbs
@@ -15,7 +15,7 @@
{{#if @options.showScheduling}}
-
+
Schedule this report
diff --git a/addon/components/overlay/header.hbs b/addon/components/overlay/header.hbs
index 7ccb9867..1caf2f36 100644
--- a/addon/components/overlay/header.hbs
+++ b/addon/components/overlay/header.hbs
@@ -113,7 +113,7 @@
class="next-content-overlay-panel-maximize-button {{@maximizeButtonClass}}"
{{set-height @maximizeButtonHeight}}
{{set-width @maximizeButtonWidth}}
- {{on "click" @overlay.maximize}}
+ {{on "click" (or @overlay.maximize (noop))}}
>
@@ -124,7 +124,7 @@
class="next-content-overlay-panel-minimize-button {{@minimizeButtonClass}}"
{{set-height @minimizeButtonHeight}}
{{set-width @minimizeButtonWidth}}
- {{on "click" @overlay.minimize}}
+ {{on "click" (or @overlay.minimize (noop))}}
>
diff --git a/addon/components/table/cell/link-list.hbs b/addon/components/table/cell/link-list.hbs
index a5315aea..ec87361d 100644
--- a/addon/components/table/cell/link-list.hbs
+++ b/addon/components/table/cell/link-list.hbs
@@ -3,7 +3,7 @@
{{#let (get @row @column.valuePath) as |items|}}
{{#each items as |item|}}
-
+
{{get item (or @column.cellComponentLabelPath "name")}}
diff --git a/lint/no-unguarded-handler-argument.mjs b/lint/no-unguarded-handler-argument.mjs
new file mode 100644
index 00000000..cc8ffb8c
--- /dev/null
+++ b/lint/no-unguarded-handler-argument.mjs
@@ -0,0 +1,104 @@
+import { Rule } from 'ember-template-lint';
+
+/**
+ * Flags `{{on "event" @handler}}` and `{{fn @handler …}}` where the handler position holds a BARE
+ * argument.
+ *
+ * These are not no-ops when the argument is absent — Glimmer throws while rendering:
+ *
+ * {{on "click" @onFoo}} → "You must pass a function as the second argument to the `on` modifier"
+ * {{fn @onFoo item}} → "You must pass a function as the `fn` helper's first argument"
+ *
+ * so a component with an unguarded binding cannot be rendered at all without that argument. Five
+ * components in this addon were untestable for exactly this reason.
+ *
+ * Guarded forms are accepted:
+ * {{on "click" (or @onFoo (noop))}} — the pattern this codebase settled on
+ * {{#if @item.onClick}}{{on "click" @item.onClick}}{{/if}}
+ * {{on "click" this.handler}} — a component's own action always exists
+ */
+export default class NoUnguardedHandlerArgument extends Rule {
+ logNode({ node, message }) {
+ this.log({ message, node });
+ }
+
+ /** Is this node a bare `@arg` / `@arg.path` reference? */
+ isBareArgument(node) {
+ return node && node.type === 'PathExpression' && node.head && node.head.type === 'AtHead';
+ }
+
+ /** Walk out to see whether an enclosing `{{#if}}`/`{{#unless}}` already tests this argument. */
+ isGuardedByBlock(argumentName) {
+ return this.guardStack.some((guard) => guard === argumentName || argumentName.startsWith(`${guard}.`));
+ }
+
+ static pathName(node) {
+ return node.original || '';
+ }
+
+ visitor() {
+ this.guardStack = [];
+
+ const enterBlock = (node) => {
+ const name = node.path && node.path.original;
+ if (name !== 'if' && name !== 'unless') {
+ return;
+ }
+ const [condition] = node.params;
+ if (this.isBareArgument(condition)) {
+ this.guardStack.push(NoUnguardedHandlerArgument.pathName(condition));
+ } else {
+ this.guardStack.push(null);
+ }
+ };
+
+ const exitBlock = (node) => {
+ const name = node.path && node.path.original;
+ if (name === 'if' || name === 'unless') {
+ this.guardStack.pop();
+ }
+ };
+
+ const check = (node, position, describe) => {
+ const candidate = node.params[position];
+ if (!this.isBareArgument(candidate)) {
+ return;
+ }
+ const argumentName = NoUnguardedHandlerArgument.pathName(candidate);
+ if (this.isGuardedByBlock(argumentName)) {
+ return;
+ }
+ this.logNode({
+ node: candidate,
+ message:
+ `${describe} receives the bare argument \`${argumentName}\`, which throws while rendering when the ` +
+ `argument is absent. Guard it — \`(or ${argumentName} (noop))\` — or wrap the binding in ` +
+ `\`{{#if ${argumentName}}}\`.`,
+ });
+ };
+
+ return {
+ BlockStatement: { enter: enterBlock, exit: exitBlock },
+
+ ElementModifierStatement(node) {
+ // {{on "click" @onFoo}} — the handler is the second parameter
+ if (node.path && node.path.original === 'on' && node.params.length >= 2) {
+ check(node, 1, 'The `on` modifier');
+ }
+ },
+
+ SubExpression(node) {
+ // (fn @onFoo item) — the function is the first parameter
+ if (node.path && node.path.original === 'fn' && node.params.length >= 1) {
+ check(node, 0, 'The `fn` helper');
+ }
+ },
+
+ MustacheStatement(node) {
+ if (node.path && node.path.original === 'fn' && node.params.length >= 1) {
+ check(node, 0, 'The `fn` helper');
+ }
+ },
+ };
+ }
+};
diff --git a/package.json b/package.json
index 8468aa34..46607788 100644
--- a/package.json
+++ b/package.json
@@ -163,7 +163,7 @@
"eslint-plugin-ember": "^11.11.1",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-prettier": "^5.0.1",
- "eslint-plugin-qunit": "^8.0.1",
+ "eslint-plugin-qunit": "^8.2.6",
"loader.js": "^4.7.0",
"prettier": "^3.0.3",
"qunit": "^2.20.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 987506bb..d3780ea2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -373,7 +373,7 @@ importers:
specifier: ^5.0.1
version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.3)
eslint-plugin-qunit:
- specifier: ^8.0.1
+ specifier: ^8.2.6
version: 8.2.6(eslint@8.57.1)
loader.js:
specifier: ^4.7.0
diff --git a/tests/integration/components/drawer-test.js b/tests/integration/components/drawer-test.js
index 3971f521..52ae574a 100644
--- a/tests/integration/components/drawer-test.js
+++ b/tests/integration/components/drawer-test.js
@@ -198,7 +198,7 @@ module('Integration | Component | drawer', function (hooks) {
loaded[0].minimize();
await settled();
- assert.true(loaded[0].isMinimized === false, 'the snapshot is not live; callers must re-read via a fresh callback');
+ assert.false(loaded[0].isMinimized, 'the snapshot is not live; callers must re-read via a fresh callback');
assert.dom(drawer()).hasClass('drawer-is-minimized', 'the drawer itself did update');
});
diff --git a/tests/integration/components/dropdown-button-test.js b/tests/integration/components/dropdown-button-test.js
index c8d5f4a3..f7c48318 100644
--- a/tests/integration/components/dropdown-button-test.js
+++ b/tests/integration/components/dropdown-button-test.js
@@ -241,7 +241,7 @@ module('Integration | Component | dropdown-button', function (hooks) {
await render(TEMPLATE);
- assert.ok(find(`${TRIGGER} .ember-attacher`) || find(TRIGGER), 'the trigger renders with a tooltip attached');
+ assert.dom(TRIGGER).exists('the trigger renders when help text is permitted');
});
test('visibility and disabled state follow their arguments', async function (assert) {
diff --git a/tests/integration/components/layout/sidebar/navigator-test.js b/tests/integration/components/layout/sidebar/navigator-test.js
index 9679c73a..b1b0d909 100644
--- a/tests/integration/components/layout/sidebar/navigator-test.js
+++ b/tests/integration/components/layout/sidebar/navigator-test.js
@@ -325,17 +325,11 @@ module('Integration | Component | layout/sidebar/navigator', function (hooks) {
],
},
]);
+ const syncCalls = [];
this.set('shouldSyncInitialActiveParent', ({ activePath, routeName, currentURL }) => {
- if (routeName === 'console.settings.index') {
- assert.deepEqual(
- activePath.map((item) => item.label),
- ['Settings', 'General']
- );
- assert.strictEqual(currentURL, '/settings');
- return false;
- }
+ syncCalls.push({ labels: activePath.map((item) => item.label), routeName, currentURL });
- return true;
+ return routeName !== 'console.settings.index';
});
await render(hbs`
`);
@@ -343,6 +337,13 @@ module('Integration | Component | layout/sidebar/navigator', function (hooks) {
assert.dom('.next-sidebar-navigator-back').doesNotExist('initial render stays at root when predicate returns false');
assert.dom('.next-sidebar-navigator-view-in').includesText('Settings');
+ // Asserted outside the predicate: inside it, a change to the routeName would have skipped
+ // the branch and quietly asserted nothing.
+ const settingsCall = syncCalls.find((call) => call.routeName === 'console.settings.index');
+ assert.ok(settingsCall, 'the predicate is consulted for the active route');
+ assert.deepEqual(settingsCall.labels, ['Settings', 'General'], 'it receives the full active path');
+ assert.strictEqual(settingsCall.currentURL, '/settings', 'and the current url');
+
const router = this.owner.lookup('service:router');
router.currentRouteName = 'console.settings.security';
router.currentURL = '/settings/security';
diff --git a/tests/integration/components/metadata-editor-test.js b/tests/integration/components/metadata-editor-test.js
index 8e503c11..5828f68e 100644
--- a/tests/integration/components/metadata-editor-test.js
+++ b/tests/integration/components/metadata-editor-test.js
@@ -200,7 +200,7 @@ module('Integration | Component | metadata-editor', function (hooks) {
await render(TEMPLATE);
await click(rows()[0].querySelector('input[type="checkbox"]'));
- assert.strictEqual(changes[changes.length - 1].active, true);
+ assert.true(changes[changes.length - 1].active);
});
test('changing the type coerces the existing value', async function (assert) {
@@ -228,7 +228,7 @@ module('Integration | Component | metadata-editor', function (hooks) {
await render(TEMPLATE);
await fillIn(rows()[0].querySelector('select'), 'boolean');
- assert.strictEqual(changes[changes.length - 1].thing, true);
+ assert.true(changes[changes.length - 1].thing);
});
test('boolean rows are omitted from the output when allowBoolean is false', async function (assert) {
diff --git a/tests/integration/components/metadata-viewer-test.js b/tests/integration/components/metadata-viewer-test.js
index 6de146c2..eea68a56 100644
--- a/tests/integration/components/metadata-viewer-test.js
+++ b/tests/integration/components/metadata-viewer-test.js
@@ -55,9 +55,11 @@ module('Integration | Component | metadata-viewer', function (hooks) {
const [tags, nested] = rows();
assert.strictEqual(tags[0], 'tags');
- assert.true(tags[1].includes('"a"') && tags[1].includes('"b"'), 'the array is shown as json');
+ assert.true(tags[1].includes('"a"'), 'the array is shown as json');
+ assert.true(tags[1].includes('"b"'), 'including every element');
assert.strictEqual(nested[0], 'nested');
- assert.true(nested[1].includes('"x"') && nested[1].includes('1'), 'so is the object');
+ assert.true(nested[1].includes('"x"'), 'so is the object');
+ assert.true(nested[1].includes('1'), 'including its value');
assert.false(nested[1].includes('[object Object]'), 'nothing falls through to a raw stringification');
});
diff --git a/tests/integration/components/money-input-test.js b/tests/integration/components/money-input-test.js
index 36dfb500..6da854f2 100644
--- a/tests/integration/components/money-input-test.js
+++ b/tests/integration/components/money-input-test.js
@@ -130,7 +130,8 @@ module('Integration | Component | money-input', function (hooks) {
await render(TEMPLATE);
- assert.true(input().value.includes('£') || input().value.includes('25'), 'the amount is formatted for the currency');
+ assert.true(input().value.includes('25'), `the amount is present (got ${input().value})`);
+ assert.true(input().value.includes('£'), `and carries the currency symbol (got ${input().value})`);
});
test('a comma-decimal currency uses its own separator', async function (assert) {
diff --git a/tests/integration/components/pill-test.js b/tests/integration/components/pill-test.js
index 34be015e..93c2988d 100644
--- a/tests/integration/components/pill-test.js
+++ b/tests/integration/components/pill-test.js
@@ -157,7 +157,8 @@ module('Integration | Component | pill', function (hooks) {
`);
- assert.ok(find('.tip') || find('.fleetbase-pill'), 'the pill renders with a tooltip attached');
+ assert.dom('.fleetbase-pill').exists('the pill renders');
+ assert.ok(find('.tip'), 'with a tooltip attached to it');
});
});
diff --git a/tests/integration/components/query-builder/computed-columns-test.js b/tests/integration/components/query-builder/computed-columns-test.js
index 79d58c21..16e8dfa4 100644
--- a/tests/integration/components/query-builder/computed-columns-test.js
+++ b/tests/integration/components/query-builder/computed-columns-test.js
@@ -126,7 +126,7 @@ module('Integration | Component | query-builder/computed-columns', function (hoo
const badges = items().map((item) => item.querySelector('.rounded.text-xs').textContent.trim());
assert.deepEqual(badges, ['Text', 'Integer', 'Decimal', 'Date', 'Date & Time', 'Boolean']);
- assert.strictEqual(findAll('.computed-column-item svg').length >= 6, true, 'each row carries a type icon');
+ assert.true(findAll('.computed-column-item svg').length >= 6, 'each row carries a type icon');
});
test('an unrecognised type falls back to a question icon and no label', async function (assert) {
diff --git a/tests/integration/components/query-builder/conditions-test.js b/tests/integration/components/query-builder/conditions-test.js
index a21d32fc..525d537d 100644
--- a/tests/integration/components/query-builder/conditions-test.js
+++ b/tests/integration/components/query-builder/conditions-test.js
@@ -187,12 +187,11 @@ module('Integration | Component | query-builder/conditions', function (hooks) {
await render(TEMPLATE);
const valueInput = find('.condition-content input[type="text"]');
- if (valueInput) {
- await fillIn(valueInput, 'active');
- assert.strictEqual(changes[changes.length - 1].flat[0].value, 'active', 'the typed value is reported');
- } else {
- assert.dom('.condition-group').exists('the seeded condition rendered');
- }
+ assert.ok(valueInput, 'the seeded condition renders a value input');
+
+ await fillIn(valueInput, 'active');
+
+ assert.strictEqual(changes[changes.length - 1].flat[0].value, 'active', 'the typed value is reported');
});
module('field and operator selection', function () {
diff --git a/tests/integration/components/tab-navigation-test.js b/tests/integration/components/tab-navigation-test.js
index e35ecc52..78da0945 100644
--- a/tests/integration/components/tab-navigation-test.js
+++ b/tests/integration/components/tab-navigation-test.js
@@ -168,7 +168,7 @@ module('Integration | Component | tab-navigation', function (hooks) {
await render(TEMPLATE);
const container = find('.tab-list').parentElement;
- assert.true(container.className.includes('pills') || find('[role="tablist"]') !== null, 'the requested style is used');
+ assert.dom('[role="tablist"]').exists('the requested style renders a tablist');
});
});
@@ -499,7 +499,7 @@ module('Integration | Component | tab-navigation', function (hooks) {
});
// The style and size land on data-attributes, resolved by the component's own getters.
- module('style and size', function () {
+ module('style and size arguments', function () {
test('it defaults to the github style at medium size', async function (assert) {
await render(TEMPLATE);
diff --git a/tests/integration/components/table-test.js b/tests/integration/components/table-test.js
index 630ac6d4..d1910866 100644
--- a/tests/integration/components/table-test.js
+++ b/tests/integration/components/table-test.js
@@ -734,17 +734,6 @@ module('Integration | Component | table imperative api', function (hooks) {
assert.strictEqual(actions._stickyOffset, 150, 'and the one before it clears the default width');
});
- test('sticky cells are positioned in the dom', async function (assert) {
- await render(TEMPLATE);
-
- const stickyHeader = find('thead th.is-sticky');
- if (stickyHeader) {
- assert.strictEqual(stickyHeader.style.top, '0px', 'sticky headers are pinned vertically');
- } else {
- assert.ok(true, 'no sticky header markup in this configuration');
- }
- });
-
test('scrolling the wrapper updates the shadow classes', async function (assert) {
await render(TEMPLATE);
diff --git a/tests/integration/components/table/cell/checkbox-test.js b/tests/integration/components/table/cell/checkbox-test.js
index 8ec4c240..909afa39 100644
--- a/tests/integration/components/table/cell/checkbox-test.js
+++ b/tests/integration/components/table/cell/checkbox-test.js
@@ -33,7 +33,8 @@ module('Integration | Component | table/cell/checkbox', function (hooks) {
await render(hbs`
`);
const id = find('input[type="checkbox"]').getAttribute('id');
- assert.ok(id && id.length > 0, 'an id is still present');
+ assert.ok(id, 'an id is still present');
+ assert.true(id.length > 0, 'and it is not empty');
assert.notStrictEqual(id, 'undefined', 'the fallback is a real guid, not a stringified undefined');
});
diff --git a/tests/integration/components/table/cell/dropdown-test.js b/tests/integration/components/table/cell/dropdown-test.js
index 559806d6..0d535514 100644
--- a/tests/integration/components/table/cell/dropdown-test.js
+++ b/tests/integration/components/table/cell/dropdown-test.js
@@ -210,7 +210,10 @@ module('Integration | Component | table/cell/dropdown', function (hooks) {
assert.strictEqual(cell().style.zIndex, '1', 'the cell is raised while the menu is open');
await click(trigger());
- assert.strictEqual(cell().style.zIndex, before || '0', 'and lowered again on close');
+
+ // Normalised outside the assertion: an unset z-index reads back as ''.
+ const restored = before || '0';
+ assert.strictEqual(cell().style.zIndex, restored, 'and lowered again on close');
});
});
});
diff --git a/tests/integration/components/template-builder/canvas-test.js b/tests/integration/components/template-builder/canvas-test.js
index 3ade8b79..c9eb9a58 100644
--- a/tests/integration/components/template-builder/canvas-test.js
+++ b/tests/integration/components/template-builder/canvas-test.js
@@ -132,19 +132,14 @@ module('Integration | Component | template-builder/canvas', function (hooks) {
`);
- const child = find('[data-test-child]');
- if (child) {
- await click(child);
- assert.strictEqual(deselects, 0, 'only a direct background click clears the selection');
- } else {
- // The canvas does not yield, so simulate the bubbling case directly.
- const canvas = find('.tb-canvas');
- const inner = document.createElement('span');
- canvas.appendChild(inner);
- await click(inner);
-
- assert.strictEqual(deselects, 0, 'only a direct background click clears the selection');
- }
+ // The canvas does not yield, so append a child directly to exercise the bubbling case.
+ const canvas = find('.tb-canvas');
+ const inner = document.createElement('span');
+ canvas.appendChild(inner);
+
+ await click(inner);
+
+ assert.strictEqual(deselects, 0, 'a click that merely bubbles through does not clear the selection');
});
test('clicking the background without a handler does not throw', async function (assert) {
diff --git a/tests/integration/components/template-builder/element-renderer-test.js b/tests/integration/components/template-builder/element-renderer-test.js
index 61b2666f..fc0d1cd3 100644
--- a/tests/integration/components/template-builder/element-renderer-test.js
+++ b/tests/integration/components/template-builder/element-renderer-test.js
@@ -210,7 +210,7 @@ module('Integration | Component | template-builder/element-renderer', function (
await render(TEMPLATE);
const style = styleOf('.tb-element-text');
- assert.true(style.includes('border: 2px solid rgb(0, 0, 0)') || style.includes('border: 2px solid #000000'), 'default style and colour are used');
+ assert.true(style.includes('border: 2px solid #000000'), `default style and colour are used (got ${style})`);
assert.true(style.includes('border-radius: 4px'));
});
diff --git a/tests/integration/components/template-builder/properties-panel-test.js b/tests/integration/components/template-builder/properties-panel-test.js
index 8cd31c2b..66ccfcf6 100644
--- a/tests/integration/components/template-builder/properties-panel-test.js
+++ b/tests/integration/components/template-builder/properties-panel-test.js
@@ -289,13 +289,13 @@ module('Integration | Component | template-builder/properties-panel', function (
await render(TEMPLATE);
- const numberInput = find('input[type="number"]');
- if (numberInput) {
- await fillIn(numberInput, '300');
- assert.strictEqual(templateUpdates.length, 1, 'the template change is reported');
- } else {
- assert.dom(this.element).exists('the canvas settings panel rendered');
- }
+ const nameInput = find('input[type="text"].tb-input');
+ assert.ok(nameInput, 'the canvas settings panel offers a template name field');
+
+ await fillIn(nameInput, 'Invoice A4');
+
+ assert.strictEqual(templateUpdates.length, 1, 'the template change is reported');
+ assert.strictEqual(templateUpdates[0].name, 'Invoice A4', 'with the new value');
});
});
@@ -308,11 +308,7 @@ module('Integration | Component | template-builder/properties-panel', function (
await render(TEMPLATE);
const pickerButton = findAll('button').find((b) => /variable/i.test(b.textContent) || /\{\}/.test(b.textContent));
- if (!pickerButton) {
- assert.strictEqual(opened.length, 0, 'no variable picker affordance for this element type');
-
- return;
- }
+ assert.ok(pickerButton, 'a text element offers a variable picker affordance');
await click(pickerButton);
assert.strictEqual(opened.length, 1, 'the picker is opened for a target property');
@@ -618,7 +614,7 @@ module('Integration | Component | template-builder/properties-panel', function (
assert.dom('[data-test-panel="yes"]').exists();
});
- module('table columns', function () {
+ module('table column editing', function () {
function columnInputs() {
return findAll('input[placeholder="Column label"]');
}
@@ -788,7 +784,7 @@ module('Integration | Component | template-builder/properties-panel', function (
});
});
- module('the image source', function () {
+ module('the image source picker', function () {
function clearButton() {
return findAll('button[title="Clear"]')[0];
}
diff --git a/tests/integration/components/visible-column-picker-test.js b/tests/integration/components/visible-column-picker-test.js
index 2a7cbb17..d25ae546 100644
--- a/tests/integration/components/visible-column-picker-test.js
+++ b/tests/integration/components/visible-column-picker-test.js
@@ -126,7 +126,7 @@ module('Integration | Component | visible-column-picker', function (hooks) {
await click(TRIGGER);
await click(checkboxes()[0]);
- assert.false(this.columns[0].hidden === false, 'the column is now hidden');
+ assert.notStrictEqual(this.columns[0].hidden, false, 'the column is now hidden');
assert.true(this.columns[0].hidden);
assert.deepEqual(changes, [['name:hidden', 'status:hidden', 'internal_id:shown']]);
});
diff --git a/tests/integration/helpers/unwrap-coordinates-test.js b/tests/integration/helpers/unwrap-coordinates-test.js
index a43eec9b..9b744965 100644
--- a/tests/integration/helpers/unwrap-coordinates-test.js
+++ b/tests/integration/helpers/unwrap-coordinates-test.js
@@ -116,7 +116,8 @@ module('Integration | Helper | unwrap-coordinates', function (hooks) {
await render(hbs`{{capture-value (unwrap-coordinates this.input)}}`);
const coordinates = captured[0].coordinates;
- assert.true(coordinates.lat < 85.06 && coordinates.lat > 85.04, `the latitude is clamped to the mercator maximum (got ${coordinates.lat})`);
+ assert.true(coordinates.lat < 85.06, `the latitude is clamped below the mercator maximum (got ${coordinates.lat})`);
+ assert.true(coordinates.lat > 85.04, `and not past it (got ${coordinates.lat})`);
assert.true(Math.abs(coordinates.lng - 10) < TOLERANCE, 'the longitude is untouched');
});
diff --git a/tests/unit/services/modals-manager-test.js b/tests/unit/services/modals-manager-test.js
index f45632f7..47b22182 100644
--- a/tests/unit/services/modals-manager-test.js
+++ b/tests/unit/services/modals-manager-test.js
@@ -51,7 +51,7 @@ module('Unit | Service | modals-manager', function (hooks) {
assert.strictEqual(this.manager.options.title, 'Custom', 'the caller wins');
assert.strictEqual(this.manager.options.size, 'lg');
- assert.strictEqual(this.manager.options.backdrop, true, 'untouched defaults are preserved');
+ assert.true(this.manager.options.backdrop, 'untouched defaults are preserved');
assert.strictEqual(this.manager.options.confirmButtonDefaultText, 'Yes');
});
@@ -374,7 +374,7 @@ module('Unit | Service | modals-manager', function (hooks) {
assert.strictEqual(calls.length, 1);
assert.strictEqual(calls[0], modal.options, 'the callback receives the modal options');
- assert.strictEqual(calls[0].backdrop, true, 'the options include the merged defaults');
+ assert.true(calls[0].backdrop, 'the options include the merged defaults');
});
test('done invokes onFinish regardless of the action', async function (assert) {
@@ -683,7 +683,7 @@ module('Unit | Service | modals-manager', function (hooks) {
});
});
- module('userSelectOption', function () {
+ module('userSelectOption callbacks', function () {
test('it offers the prompt with no options and resolves null when declined', async function (assert) {
const selection = this.manager.userSelectOption('Pick a warehouse');
await settled();
diff --git a/tests/unit/utils/decorators/uses-transition-test.js b/tests/unit/utils/decorators/uses-transition-test.js
index b1bec0e8..3d4680e6 100644
--- a/tests/unit/utils/decorators/uses-transition-test.js
+++ b/tests/unit/utils/decorators/uses-transition-test.js
@@ -60,7 +60,7 @@ module('Unit | Utility | decorators/uses-transition', function (hooks) {
const value = this.build({ fade: 'truthy string' }).usesTransition;
assert.strictEqual(typeof value, 'boolean');
- assert.strictEqual(value, true);
+ assert.true(value);
});
test('transitions are disabled entirely under FastBoot', function (assert) {
diff --git a/tests/unit/utils/get-active-url-param-test.js b/tests/unit/utils/get-active-url-param-test.js
index b48b6ce8..49579132 100644
--- a/tests/unit/utils/get-active-url-param-test.js
+++ b/tests/unit/utils/get-active-url-param-test.js
@@ -17,18 +17,18 @@ module('Unit | Utility | get-active-url-param', function (hooks) {
});
test('it returns boolean true', function (assert) {
- assert.strictEqual(getActiveUrlParam(), true, 'the value is exactly true, not merely truthy');
+ assert.true(getActiveUrlParam(), 'the value is exactly true, not merely truthy');
});
test('it ignores any arguments it is given', function (assert) {
- assert.strictEqual(getActiveUrlParam('anything'), true);
- assert.strictEqual(getActiveUrlParam(null, undefined, 0), true);
+ assert.true(getActiveUrlParam('anything'));
+ assert.true(getActiveUrlParam(null, undefined, 0));
});
test('it does not depend on the current query string', function (assert) {
window.history.replaceState(null, '', '?active=false&other=1');
- assert.strictEqual(getActiveUrlParam(), true, 'the result is independent of the URL');
+ assert.true(getActiveUrlParam(), 'the result is independent of the URL');
});
test('it is stable across repeated calls', function (assert) {
diff --git a/tests/unit/utils/is-fast-boot-test.js b/tests/unit/utils/is-fast-boot-test.js
index 73d837c9..5cb62017 100644
--- a/tests/unit/utils/is-fast-boot-test.js
+++ b/tests/unit/utils/is-fast-boot-test.js
@@ -15,7 +15,7 @@ module('Unit | Utility | is-fastboot', function (hooks) {
setupTest(hooks);
test('it returns false when no fastboot service is registered', function (assert) {
- assert.strictEqual(isFastBoot(ownedContext(this.owner)), false, 'a browser app has no fastboot service');
+ assert.false(isFastBoot(ownedContext(this.owner)), 'a browser app has no fastboot service');
});
test('it reports true when the fastboot service says so', function (assert) {
diff --git a/tests/unit/utils/options-test.js b/tests/unit/utils/options-test.js
index d3e8705b..7dd34200 100644
--- a/tests/unit/utils/options-test.js
+++ b/tests/unit/utils/options-test.js
@@ -10,7 +10,7 @@ module('Unit | Utility | options', function () {
test('it returns the boolean true, not a truthy value', function (assert) {
const result = options();
- assert.strictEqual(result, true);
+ assert.true(result);
assert.strictEqual(typeof result, 'boolean');
});
@@ -24,6 +24,6 @@ module('Unit | Utility | options', function () {
test('it returns the same value on repeated invocations', function (assert) {
assert.strictEqual(options(), options(), 'the util is pure and stateless');
- assert.strictEqual(options.call({ scope: 'other' }), true, 'the receiver does not matter');
+ assert.true(options.call({ scope: 'other' }), 'the receiver does not matter');
});
});
diff --git a/tests/unit/utils/permission-check-test.js b/tests/unit/utils/permission-check-test.js
index d3c2d180..db15c12e 100644
--- a/tests/unit/utils/permission-check-test.js
+++ b/tests/unit/utils/permission-check-test.js
@@ -94,7 +94,7 @@ module('Unit | Utility | permission-check', function () {
const result = checkPermission(abilities, 'fleet-ops view order', subject);
- assert.strictEqual(result, true, 'the truthy result is coerced to true');
+ assert.true(result, 'the truthy result is coerced to true');
assert.deepEqual(calls, [['fleet-ops view order', subject]], 'the permission string and subject are forwarded verbatim');
});
@@ -253,8 +253,8 @@ module('Unit | Utility | permission-check', function () {
test('it coerces a non-boolean defaultWhenUnknown', function (assert) {
const abilitiesService = abilitiesWithCan(() => true);
- assert.strictEqual(evaluatePermission({ abilitiesService, defaultWhenUnknown: 'yes' }), true, 'truthy defaults coerce to true');
- assert.strictEqual(evaluatePermission({ abilitiesService, defaultWhenUnknown: 0 }), false, 'falsy defaults coerce to false');
+ assert.true(evaluatePermission({ abilitiesService, defaultWhenUnknown: 'yes' }), 'truthy defaults coerce to true');
+ assert.false(evaluatePermission({ abilitiesService, defaultWhenUnknown: 0 }), 'falsy defaults coerce to false');
});
test('it never calls the abilities service when resolution fails', function (assert) {
diff --git a/tests/unit/utils/remove-nullish-test.js b/tests/unit/utils/remove-nullish-test.js
index e2cfdfdc..ddd0bed6 100644
--- a/tests/unit/utils/remove-nullish-test.js
+++ b/tests/unit/utils/remove-nullish-test.js
@@ -15,7 +15,7 @@ module('Unit | Utility | remove-nullish', function () {
assert.deepEqual(Object.keys(result).sort(), ['empty', 'nan', 'negZero', 'no', 'zero'], 'only null/undefined are nullish');
assert.strictEqual(result.zero, 0);
assert.strictEqual(result.empty, '');
- assert.strictEqual(result.no, false);
+ assert.false(result.no);
assert.true(Number.isNaN(result.nan));
});
diff --git a/tests/unit/utils/report-builder-test.js b/tests/unit/utils/report-builder-test.js
index 4dbf2e15..6d760f1c 100644
--- a/tests/unit/utils/report-builder-test.js
+++ b/tests/unit/utils/report-builder-test.js
@@ -115,7 +115,7 @@ module('Unit | Utility | report-builder', function () {
test('it round-trips primitives and arrays', function (assert) {
assert.strictEqual(deepClone(0), 0, 'zero is not treated as nullish');
assert.strictEqual(deepClone(''), '', 'an empty string is not treated as nullish');
- assert.strictEqual(deepClone(false), false);
+ assert.false(deepClone(false));
assert.strictEqual(deepClone('text'), 'text');
assert.deepEqual(deepClone([1, [2]]), [1, [2]]);
assert.true(Array.isArray(deepClone([])), 'array-ness survives the round trip');
@@ -204,8 +204,8 @@ module('Unit | Utility | report-builder', function () {
module('default export', function () {
test('it is a function returning boolean true', function (assert) {
assert.strictEqual(typeof reportBuilder, 'function');
- assert.strictEqual(reportBuilder(), true);
- assert.strictEqual(reportBuilder('ignored', 1), true, 'arguments are ignored');
+ assert.true(reportBuilder());
+ assert.true(reportBuilder('ignored', 1), 'arguments are ignored');
});
});
});