or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

component-rules.mdcomputed-property-rules.mdember-utils.mdindex.mdlegacy-configuration.mdmigration-rules.mdmodern-configuration.mdplugin-configuration.mdroute-rules.mdservice-rules.mdtest-rules.md
tile.json

test-rules.mddocs/

Test Rules

Comprehensive testing rules for modern Ember testing patterns and test helper usage. These rules help maintain best practices in Ember testing and encourage the use of modern testing APIs.

Capabilities

Modern Test Helper Rules

Rules encouraging the use of modern Ember test helpers over legacy patterns.

/**
 * Prefer @ember/test-helpers over legacy test helpers
 * Encourages modern testing patterns
 */
'ember/prefer-ember-test-helpers': ESLintRule;

/**
 * Disallow legacy test waiters
 * Enforces modern async test patterns
 */
'ember/no-legacy-test-waiters': ESLintRule;

/**
 * Disallow invalid test waiters
 * Ensures proper test waiter configuration
 */
'ember/no-invalid-test-waiters': ESLintRule;

Usage Examples:

// ❌ Bad - legacy test helpers (triggers prefer-ember-test-helpers)
import { test } from 'qunit';
import { visit, click, fillIn, find } from '@ember/test-helpers';
import wait from '@ember/test-helpers/wait';

// ✅ Good - modern test helpers
import { test } from 'qunit';
import { visit, click, fillIn, find, waitFor, settled } from '@ember/test-helpers';

// ❌ Bad - legacy test waiter (triggers no-legacy-test-waiters)
import { registerWaiter } from '@ember/test';

registerWaiter(() => {
  return !App.hasPendingTimers();
});

// ✅ Good - modern test waiter
import { registerWaiter } from '@ember/test-waiters';

const waiter = registerWaiter('my-waiter');

Async Testing Rules

Rules for proper async test patterns and avoiding deprecated async helpers.

/**
 * Disallow andThen in favor of async/await
 * Encourages modern async testing patterns
 */
'ember/no-test-and-then': ESLintRule;

/**
 * Disallow settled() after test helper calls
 * Prevents redundant settled() calls
 */
'ember/no-settled-after-test-helper': ESLintRule;

/**
 * Disallow pauseTest() in production code
 * Prevents debugging code from being committed
 */
'ember/no-pause-test': ESLintRule;

Usage Examples:

// ❌ Bad - using andThen (triggers no-test-and-then)
test('user can login', function(assert) {
  visit('/login');
  fillIn('#email', 'user@example.com');
  click('#submit');
  
  andThen(() => {
    assert.equal(currentURL(), '/dashboard');
  });
});

// ✅ Good - using async/await
test('user can login', async function(assert) {
  await visit('/login');
  await fillIn('#email', 'user@example.com');
  await click('#submit');
  
  assert.equal(currentURL(), '/dashboard');
});

// ❌ Bad - unnecessary settled() call (triggers no-settled-after-test-helper)
await click('.button');
await settled(); // Redundant - click already waits

// ✅ Good - no redundant settled()
await click('.button');

// ❌ Bad - pauseTest in code (triggers no-pause-test)
test('debugging test', async function(assert) {
  await visit('/');
  pauseTest(); // Should not be committed
});

// ✅ Good - no pauseTest in committed code
test('proper test', async function(assert) {
  await visit('/');
  assert.dom('[data-test-element]').exists();
});

Test Module and Import Rules

Rules for proper test module organization and import patterns.

/**
 * Disallow moduleFor* test patterns
 * Encourages modern setupTest patterns
 */
'ember/no-test-module-for': ESLintRule;

/**
 * Disallow import/export in test files where inappropriate
 * Maintains test file isolation
 */
'ember/no-test-import-export': ESLintRule;

/**
 * Disallow test support imports in non-test files
 * Keeps test utilities separate from production code
 */
'ember/no-test-support-import': ESLintRule;

Usage Examples:

// ❌ Bad - legacy moduleFor (triggers no-test-module-for)
import { moduleForComponent, test } from 'ember-qunit';

moduleForComponent('my-component', 'Integration | Component | my component', {
  integration: true
});

// ✅ Good - modern setupTest
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';

module('Integration | Component | my-component', function(hooks) {
  setupRenderingTest(hooks);
});

// ❌ Bad - importing test support in production (triggers no-test-support-import)
// In app/components/my-component.js
import { setupTest } from 'ember-qunit'; // Test helper in production code

// ✅ Good - test support only in tests
// In tests/unit/components/my-component-test.js
import { setupTest } from 'ember-qunit';

Test Rendering Rules

Rules for proper test rendering patterns and avoiding deprecated render methods.

/**
 * Disallow this.render() in tests
 * Encourages render() from @ember/test-helpers
 */
'ember/no-test-this-render': ESLintRule;

/**
 * Require valid CSS selectors in test helpers
 * Ensures proper element selection in tests
 */
'ember/require-valid-css-selector-in-test-helpers': ESLintRule;

Usage Examples:

// ❌ Bad - this.render() (triggers no-test-this-render)
test('renders component', async function(assert) {
  await this.render(hbs`<MyComponent />`);
  assert.dom('[data-test-component]').exists();
});

// ✅ Good - render() from test helpers
import { render } from '@ember/test-helpers';

test('renders component', async function(assert) {
  await render(hbs`<MyComponent />`);
  assert.dom('[data-test-component]').exists();
});

// ❌ Bad - invalid CSS selector (triggers require-valid-css-selector-in-test-helpers)
await click('[data-test-button'); // Missing closing bracket
await find('div > > span'); // Invalid selector syntax

// ✅ Good - valid CSS selectors
await click('[data-test-button]');
await find('div > span');

Test Setup and Teardown Rules

Rules for proper test setup and teardown patterns.

/**
 * Disallow noop setupOnerror in before hooks
 * Ensures proper error handling in tests
 */
'ember/no-noop-setup-on-error-in-before': ESLintRule;

Usage Examples:

// ❌ Bad - noop setupOnerror (triggers no-noop-setup-on-error-in-before)
import { setupOnerror } from '@ember/test-helpers';

module('Integration | Component | my-component', function(hooks) {
  setupRenderingTest(hooks);
  
  hooks.before(function() {
    setupOnerror(() => {}); // Noop error handler
  });
});

// ✅ Good - proper error handling or no setup
module('Integration | Component | my-component', function(hooks) {
  setupRenderingTest(hooks);
  
  hooks.before(function() {
    setupOnerror((error) => {
      console.error('Test error:', error);
      throw error;
    });
  });
});

Test Comment and Documentation Rules

Rules for maintaining proper test documentation and avoiding test comments.

/**
 * Require replacing test comments with actual tests
 * Prevents leaving TODO comments in test files
 */
'ember/no-replace-test-comments': ESLintRule;

Usage Examples:

// ❌ Bad - test comments (triggers no-replace-test-comments)
test('user can login', function(assert) {
  // TODO: implement this test
  assert.ok(true);
});

// ✅ Good - actual test implementation
test('user can login', async function(assert) {
  await visit('/login');
  await fillIn('[data-test-email]', 'user@example.com');
  await fillIn('[data-test-password]', 'password');
  await click('[data-test-submit]');
  
  assert.equal(currentURL(), '/dashboard');
  assert.dom('[data-test-welcome]').includesText('Welcome');
});

Test Restriction Rules

Rules for restricting certain patterns in test files to maintain consistency.

/**
 * Restrict specific resolver test patterns
 * Enforces consistent resolver testing approaches
 */
'ember/no-restricted-resolver-tests': ESLintRule;

Usage Examples:

// Configuration example for restricted resolver tests
// eslint.config.js
{
  rules: {
    'ember/no-restricted-resolver-tests': ['error', {
      'restricted': ['owner.lookup', 'owner.register']
    }]
  }
}

// ❌ Bad - restricted resolver pattern
test('service lookup', function(assert) {
  let service = this.owner.lookup('service:my-service');
  assert.ok(service);
});

// ✅ Good - proper service testing
test('service functionality', function(assert) {
  let service = this.owner.lookup('service:my-service');
  assert.equal(service.computeValue(5), 25);
});