├── addon ├── .gitignore ├── babel.config.json ├── types │ ├── test-loader.d.ts │ ├── local-types.d.ts │ └── index.d.ts ├── .prettierrc.js ├── addon-main.cjs ├── src │ ├── qunit-configuration.js │ ├── adapter.js │ ├── test-container-styles.css │ ├── test-loader.js │ ├── test-isolation-validation.js │ └── index.js ├── .editorconfig ├── .eslintignore ├── .prettierignore ├── vendor │ └── ember-cli │ │ └── test-support-suffix.js ├── CONTRIBUTING.md ├── LICENSE.md ├── .eslintrc.cjs ├── rollup.config.mjs ├── RELEASE.md ├── package.json └── README.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── test-app ├── app │ ├── models │ │ └── .gitkeep │ ├── routes │ │ └── .gitkeep │ ├── styles │ │ └── app.css │ ├── components │ │ └── .gitkeep │ ├── controllers │ │ └── .gitkeep │ ├── helpers │ │ └── .gitkeep │ ├── templates │ │ ├── components │ │ │ └── .gitkeep │ │ └── application.hbs │ ├── router.js │ ├── resolver.js │ ├── app.js │ └── index.html ├── tests │ ├── integration │ │ ├── .gitkeep │ │ ├── setup-test-test.js │ │ └── setup-rendering-test-test.js │ ├── helpers │ │ ├── resolver.js │ │ └── index.js │ ├── unit │ │ ├── ember-testing-test.js │ │ ├── utils │ │ │ └── patch-assert-helper.js │ │ ├── setup-ember-onerror-validation-test.js │ │ ├── disable-container-styles-test.js │ │ ├── unhandled-rejection-test.js │ │ ├── adapter-test.js │ │ └── test-isolation-validation-test.js │ ├── test-helper.js │ ├── index.html │ └── acceptance │ │ └── basic-test.js ├── .watchmanconfig ├── public │ └── robots.txt ├── types │ ├── local-types.d.ts │ ├── tsconfig.json │ └── index.d.ts ├── .template-lintrc.js ├── config │ ├── optional-features.json │ ├── targets.js │ ├── environment.js │ └── ember-try.js ├── .prettierrc.js ├── .editorconfig ├── .eslintignore ├── .prettierignore ├── .ember-cli ├── .gitignore ├── testem.js ├── .npmignore ├── ember-cli-build.js ├── .eslintrc.js ├── README.md └── package.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── test-buildtime-options-app ├── app │ ├── helpers │ │ └── .gitkeep │ ├── models │ │ └── .gitkeep │ ├── routes │ │ └── .gitkeep │ ├── styles │ │ └── app.css │ ├── components │ │ └── .gitkeep │ ├── controllers │ │ └── .gitkeep │ ├── templates │ │ ├── components │ │ │ └── .gitkeep │ │ └── application.hbs │ ├── router.js │ ├── resolver.js │ ├── app.js │ └── index.html ├── .watchmanconfig ├── public │ └── robots.txt ├── types │ ├── local-types.d.ts │ ├── tsconfig.json │ └── index.d.ts ├── .template-lintrc.js ├── config │ ├── optional-features.json │ ├── targets.js │ ├── environment.js │ └── ember-try.js ├── .prettierrc.js ├── tests │ ├── test-helper.js │ ├── unit │ │ ├── theme-test.js │ │ └── disable-container-styles-test.js │ └── index.html ├── .editorconfig ├── .eslintignore ├── .prettierignore ├── .ember-cli ├── .gitignore ├── ember-cli-build.js ├── testem.js ├── .npmignore ├── .eslintrc.js ├── README.md └── package.json ├── test-types ├── types │ └── index.d.ts ├── tsconfig.json ├── package.json └── src │ └── tests.ts ├── pnpm-workspace.yaml ├── .npmrc ├── .github ├── workflows │ ├── publish-to-branch.yml │ ├── publish.yml │ ├── ci.yml │ └── plan-release.yml └── dependabot.yml ├── .gitignore ├── package.json ├── .release-plan.json └── docs ├── TEST_ISOLATION_VALIDATION.md ├── legacy.md └── migration.md /addon/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | addon/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | addon/README.md -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | addon/RELEASE.md -------------------------------------------------------------------------------- /test-app/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | addon/CHANGELOG.md -------------------------------------------------------------------------------- /test-app/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | addon/CONTRIBUTING.md -------------------------------------------------------------------------------- /test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} -------------------------------------------------------------------------------- /test-buildtime-options-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [] 3 | } 4 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/types/test-loader.d.ts: -------------------------------------------------------------------------------- 1 | export function loadTests(): void; 2 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} -------------------------------------------------------------------------------- /test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /addon/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /addon/types/local-types.d.ts: -------------------------------------------------------------------------------- 1 | import 'ember-source/types'; 2 | import 'ember-source/types/preview'; 3 | -------------------------------------------------------------------------------- /test-types/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import "ember-source/types"; 2 | import "ember-source/types/preview"; 3 | -------------------------------------------------------------------------------- /test-app/types/local-types.d.ts: -------------------------------------------------------------------------------- 1 | import 'ember-source/types'; 2 | import 'ember-source/types/preview'; 3 | -------------------------------------------------------------------------------- /test-buildtime-options-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember", 3 | "include": ["src", "types"] 4 | } 5 | -------------------------------------------------------------------------------- /test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - addon 3 | - test-app 4 | - test-buildtime-options-app 5 | - test-types 6 | -------------------------------------------------------------------------------- /test-buildtime-options-app/types/local-types.d.ts: -------------------------------------------------------------------------------- 1 | import 'ember-source/types'; 2 | import 'ember-source/types/preview'; 3 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /addon/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /test-app/tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | export function setResolverRegistry(owner, registry) { 2 | for (let [key, value] of Object.entries(registry)) { 3 | owner.register(key, value); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | overrides: [ 5 | { 6 | files: '*.{js,ts}', 7 | options: { 8 | singleQuote: true, 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /test-buildtime-options-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /test-app/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "ember-qunit": [ 7 | "./index.d.ts" 8 | ], 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | overrides: [ 5 | { 6 | files: '*.{js,ts}', 7 | options: { 8 | singleQuote: true, 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /test-buildtime-options-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /test-buildtime-options-app/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "ember-qunit": [ 7 | "./index.d.ts" 8 | ], 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-app/tests/unit/ember-testing-test.js: -------------------------------------------------------------------------------- 1 | import { isTesting } from '@ember/debug'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('setupEmberTesting', function () { 5 | test('isTesting() is true in all test contexts', function (assert) { 6 | assert.true(isTesting()); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test-app/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export let registry = Object.create(null); 4 | export function setRegistry(newRegistry) { 5 | registry = newRegistry; 6 | } 7 | 8 | export default Resolver.extend({ 9 | resolve(fullName) { 10 | return registry[fullName] || this._super(...arguments); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /addon/src/qunit-configuration.js: -------------------------------------------------------------------------------- 1 | import * as QUnit from 'qunit'; 2 | 3 | QUnit.config.autostart = false; 4 | QUnit.config.urlConfig.push({ id: 'nocontainer', label: 'Hide container' }); 5 | QUnit.config.urlConfig.push({ id: 'devmode', label: 'Development mode' }); 6 | 7 | QUnit.config.testTimeout = QUnit.urlParams.devmode ? null : 60000; //Default Test Timeout 60 Seconds 8 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export let registry = Object.create(null); 4 | export function setRegistry(newRegistry) { 5 | registry = newRegistry; 6 | } 7 | 8 | export default Resolver.extend({ 9 | resolve(fullName) { 10 | return registry[fullName] || this._super(...arguments); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # these settings allow us to be good stewards of the ecosystem 2 | # end-consumers can flip these settings in their own projects, 3 | # but library authors should not allow for less-than-strictest 4 | # behavior. 5 | # 6 | # For more information: https://pnpm.io/npmrc 7 | auto-install-peers=false 8 | resolve-peers-from-workspace-root=false 9 | resolution-mode=highest 10 | manage-package-manager-versions=false 11 | -------------------------------------------------------------------------------- /test-buildtime-options-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-app/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /addon/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /addon/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /addon/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-branch.yml: -------------------------------------------------------------------------------- 1 | name: Publish to branch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | push-dist: 11 | name: Push dist 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: wyvox/action@v1 15 | - uses: kategengler/put-built-npm-package-contents-on-branch@v2.0.0 16 | with: 17 | branch: dist 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | working-directory: addon 20 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | versioning-strategy: increase 9 | ignore: 10 | - dependency-name: broccoli-merge-trees 11 | versions: 12 | - ">= 4.a, < 5" 13 | - dependency-name: eslint-config-prettier 14 | versions: 15 | - 7.1.0 16 | - dependency-name: ember-source 17 | versions: 18 | - 3.24.0 19 | - dependency-name: ember-cli 20 | versions: 21 | - 3.23.0 22 | -------------------------------------------------------------------------------- /test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { loadTests } from 'ember-qunit/test-loader'; 7 | import { start, setupEmberOnerrorValidation } from 'ember-qunit'; 8 | 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | 13 | setupEmberOnerrorValidation(); 14 | loadTests(); 15 | start(); 16 | -------------------------------------------------------------------------------- /test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /test-buildtime-options-app/tests/unit/theme-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { assert as debugAssert } from '@ember/debug'; 3 | 4 | module('theme', function () { 5 | function style(element) { 6 | return window.getComputedStyle(element); 7 | } 8 | 9 | test('the qunit-default themes are present when used', async function (assert) { 10 | let qunitHeader = document.getElementById('qunit-header'); 11 | 12 | debugAssert(`#qunit-header must exist`, qunitHeader); 13 | 14 | // Defaults 15 | assert.strictEqual(style(qunitHeader).backgroundColor, 'rgb(13, 51, 73)'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist/ 5 | tmp/ 6 | .eslintcache 7 | 8 | # dependencies 9 | bower_components/ 10 | node_modules/ 11 | 12 | # misc 13 | .DS_Store* 14 | .env* 15 | .pnp* 16 | .sass-cache 17 | .eslintcache 18 | connect.lock 19 | coverage/ 20 | libpeerconnection.log 21 | npm-debug.log* 22 | testem.log 23 | yarn-error.log 24 | 25 | # ember-try 26 | .node_modules.ember-try/ 27 | bower.json.ember-try 28 | npm-shrinkwrap.json.ember-try 29 | package.json.ember-try 30 | package-lock.json.ember-try 31 | yarn.lock.ember-try 32 | 33 | # broccoli-debug 34 | DEBUG/ 35 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /test-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-buildtime-options-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 5 | 6 | module.exports = function (defaults) { 7 | const app = new EmberApp(defaults, { 8 | name: 'test-app', 9 | 10 | '@embroider/macros': { 11 | setConfig: { 12 | 'ember-qunit': { 13 | disableContainerStyles: true, 14 | theme: 'qunit-default', 15 | }, 16 | }, 17 | }, 18 | }); 19 | 20 | const { maybeEmbroider } = require('@embroider/test-setup'); 21 | return maybeEmbroider(app, { 22 | skipBabel: [ 23 | { 24 | package: 'qunit', 25 | }, 26 | ], 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /test-buildtime-options-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /addon/vendor/ember-cli/test-support-suffix.js: -------------------------------------------------------------------------------- 1 | /* 2 | used to determine if the application should be booted immediately when `app-name.js` is evaluated 3 | when `runningTests` the `app-name.js` file will **not** import the applications `app/app.js` and 4 | call `Application.create(...)` on it. Additionally, applications can opt-out of this behavior by 5 | setting `autoRun` to `false` in their `ember-cli-build.js` 6 | */ 7 | runningTests = true; 8 | 9 | /* 10 | This file overrides a file built into ember-cli's build pipeline and prevents 11 | this built-in `Testem.hookIntoTestFramework` invocation: 12 | 13 | https://github.com/ember-cli/ember-cli/blob/v3.20.0/lib/broccoli/test-support-suffix.js#L3-L5 14 | */ 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "0.0.0", 4 | "private": true, 5 | "repository": "https://github.com/emberjs/ember-qunit", 6 | "license": "MIT", 7 | "scripts": { 8 | "lint": "pnpm --filter '*' run lint", 9 | "lint:fix": "pnpm --filter '*' run lint:fix", 10 | "prepare": "pnpm --filter ember-qunit build", 11 | "test": "pnpm --filter '*' run test" 12 | }, 13 | "devDependencies": { 14 | "release-plan": "^0.16.0" 15 | }, 16 | "packageManager": "pnpm@10.7.0", 17 | "volta": { 18 | "node": "^18.17.0", 19 | "pnpm": "10.7.0" 20 | }, 21 | "pnpm": { 22 | "peerDependencyRules": { 23 | "ignoreMissing": [ 24 | "webpack" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /addon/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-qunit` 7 | * `pnpm install` 8 | 9 | ## Linting 10 | 11 | * `pnpm lint` 12 | * `pnpm lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 26 | -------------------------------------------------------------------------------- /test-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-types", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test:types": "tsc --noEmit" 7 | }, 8 | "dependencies": { 9 | "ember-qunit": "workspace:*" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.26.10", 13 | "@ember/string": "^3.1.1", 14 | "@ember/test-helpers": "^3.0.3", 15 | "@glimmer/component": "^2.0.0", 16 | "@tsconfig/ember": "^2.0.0", 17 | "@types/qunit": "^2.19.12", 18 | "ember-cli-htmlbars": "^6.3.0", 19 | "ember-resolver": "^13.1.0", 20 | "ember-source": "^5.0.0", 21 | "qunit": "^2.24.1", 22 | "typescript": "^4.9.5" 23 | }, 24 | "volta": { 25 | "extends": "../package.json" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-app/.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.github/ 18 | /.gitignore 19 | /.prettierignore 20 | /.prettierrc.js 21 | /.template-lintrc.js 22 | /.travis.yml 23 | /.watchmanconfig 24 | /bower.json 25 | /config/ember-try.js 26 | /CONTRIBUTING.md 27 | /ember-cli-build.js 28 | /testem.js 29 | /tests/ 30 | /yarn-error.log 31 | /yarn.lock 32 | .gitkeep 33 | 34 | # ember-try 35 | /.node_modules.ember-try/ 36 | /bower.json.ember-try 37 | /npm-shrinkwrap.json.ember-try 38 | /package.json.ember-try 39 | /package-lock.json.ember-try 40 | /yarn.lock.ember-try 41 | -------------------------------------------------------------------------------- /test-buildtime-options-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /.release-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "ember-qunit": { 4 | "impact": "patch", 5 | "oldVersion": "9.0.3", 6 | "newVersion": "9.0.4", 7 | "tagName": "latest", 8 | "constraints": [ 9 | { 10 | "impact": "patch", 11 | "reason": "Appears in changelog section :bug: Bug Fix" 12 | } 13 | ], 14 | "pkgJSONPath": "./addon/package.json" 15 | } 16 | }, 17 | "description": "## Release (2025-09-12)\n\n* ember-qunit 9.0.4 (patch)\n\n#### :bug: Bug Fix\n* `ember-qunit`\n * [#1197](https://github.com/emberjs/ember-qunit/pull/1197) Add types for object based test.each() datasets ([@sukima](https://github.com/sukima))\n\n#### Committers: 1\n- Devin Weaver ([@sukima](https://github.com/sukima))\n" 18 | } 19 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.github/ 18 | /.gitignore 19 | /.prettierignore 20 | /.prettierrc.js 21 | /.template-lintrc.js 22 | /.travis.yml 23 | /.watchmanconfig 24 | /bower.json 25 | /config/ember-try.js 26 | /CONTRIBUTING.md 27 | /ember-cli-build.js 28 | /testem.js 29 | /tests/ 30 | /yarn-error.log 31 | /yarn.lock 32 | .gitkeep 33 | 34 | # ember-try 35 | /.node_modules.ember-try/ 36 | /bower.json.ember-try 37 | /npm-shrinkwrap.json.ember-try 38 | /package.json.ember-try 39 | /package-lock.json.ember-try 40 | /yarn.lock.ember-try 41 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/patch-assert-helper.js: -------------------------------------------------------------------------------- 1 | export default function patchAssert(assert) { 2 | // this catches the native promise unhandled rejection case because QUnit 3 | // dispatches these to `assert.pushResult`, so we handle the failure being 4 | // pushed and convert it to a passing assertion 5 | // 6 | // Also, on Ember < 2.17 this is called for the RSVP unhandled rejection 7 | // case (because it goes through Adapter.exception). 8 | assert.test._originalPushResult = assert.test.pushResult; 9 | assert.test.pushResult = function (resultInfo) { 10 | // Inverts the result so we can test failing assertions 11 | resultInfo.result = !resultInfo.result; 12 | resultInfo.message = `Failed: ${resultInfo.message}`; 13 | this._originalPushResult(resultInfo); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 5 | 6 | module.exports = function (defaults) { 7 | const app = new EmberApp(defaults, { 8 | name: 'test-app', 9 | }); 10 | 11 | const { maybeEmbroider } = require('@embroider/test-setup'); 12 | return maybeEmbroider(app, { 13 | packageRules: [ 14 | { 15 | // See: https://github.com/embroider-build/embroider/issues/522 16 | package: 'test-app', 17 | components: { 18 | '{{template-only}}': { 19 | safeToIgnore: true, 20 | }, 21 | '{{js-only}}': { 22 | safeToIgnore: true, 23 | }, 24 | '{{jax}}': { 25 | safeToIgnore: true, 26 | }, 27 | }, 28 | }, 29 | ], 30 | skipBabel: [ 31 | { 32 | package: 'qunit', 33 | }, 34 | ], 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /test-app/tests/unit/setup-ember-onerror-validation-test.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import { setupEmberOnerrorValidation } from 'ember-qunit'; 3 | import { getOnerror, setOnerror } from '@ember/-internals/error-handling'; 4 | 5 | module('setupEmberOnerrorValidation', function (hooks) { 6 | hooks.beforeEach(function (assert) { 7 | let originalPushResult = assert.pushResult; 8 | assert.pushResult = function (resultInfo) { 9 | // Inverts the result so we can test failing assertions 10 | resultInfo.result = !resultInfo.result; 11 | resultInfo.message = `Failed: ${resultInfo.message}`; 12 | originalPushResult(resultInfo); 13 | }; 14 | 15 | this.originalEmberOnerror = getOnerror(); 16 | setOnerror(function () { 17 | // intentionally swallowing here 18 | }); 19 | }); 20 | 21 | hooks.afterEach(function () { 22 | setOnerror(this.originalEmberOnerror); 23 | }); 24 | 25 | setupEmberOnerrorValidation(); 26 | }); 27 | -------------------------------------------------------------------------------- /test-app/tests/unit/disable-container-styles-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { assert as debugAssert } from '@ember/debug'; 3 | 4 | module('Default Options | disableContainerStyles', function () { 5 | function style(element) { 6 | return window.getComputedStyle(element); 7 | } 8 | 9 | test('the styles are present', async function (assert) { 10 | let qunitFixture = document.getElementById('qunit-fixture'); 11 | let emberTestingContainer = document.getElementById( 12 | 'ember-testing-container' 13 | ); 14 | let emberTesting = document.getElementById('ember-testing'); 15 | 16 | debugAssert(`#qunit-fixture must exist`, qunitFixture); 17 | debugAssert(`#ember-testing-container must exist`, emberTestingContainer); 18 | debugAssert(`#ember-testing must exist`, emberTesting); 19 | 20 | assert.strictEqual(style(qunitFixture).position, 'relative'); 21 | assert.strictEqual(style(emberTestingContainer).position, 'fixed'); 22 | assert.strictEqual(style(emberTesting).transformOrigin, '0px 0px'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /addon/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /test-buildtime-options-app/tests/unit/disable-container-styles-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { assert as debugAssert } from '@ember/debug'; 3 | 4 | module('disableContainerStyles', function () { 5 | function style(element) { 6 | return window.getComputedStyle(element); 7 | } 8 | 9 | test('the styles are not present', async function (assert) { 10 | let qunitFixture = document.getElementById('qunit-fixture'); 11 | let emberTestingContainer = document.getElementById( 12 | 'ember-testing-container' 13 | ); 14 | let emberTesting = document.getElementById('ember-testing'); 15 | 16 | debugAssert(`#qunit-fixture must exist`, qunitFixture); 17 | debugAssert(`#ember-testing-container must exist`, emberTestingContainer); 18 | debugAssert(`#ember-testing must exist`, emberTesting); 19 | 20 | // Defaults 21 | assert.strictEqual(style(qunitFixture).position, 'absolute'); 22 | assert.strictEqual(style(emberTestingContainer).position, 'static'); 23 | assert.strictEqual(style(emberTesting).transformOrigin, '500px 0px'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /addon/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | babelOptions: { 13 | root: __dirname, 14 | }, 15 | }, 16 | plugins: ['ember'], 17 | extends: [ 18 | 'eslint:recommended', 19 | 'plugin:ember/recommended', 20 | 'plugin:prettier/recommended', 21 | ], 22 | env: { 23 | browser: true, 24 | }, 25 | rules: { 26 | 'ember/no-test-support-import': 'off', 27 | 'ember/no-classic-classes': 'off', 28 | }, 29 | overrides: [ 30 | // node files 31 | { 32 | files: [ 33 | './.eslintrc.js', 34 | './.prettierrc.js', 35 | './.template-lintrc.js', 36 | './addon-main.js', 37 | ], 38 | parserOptions: { 39 | sourceType: 'script', 40 | }, 41 | env: { 42 | browser: false, 43 | node: true, 44 | }, 45 | plugins: ['node'], 46 | extends: ['plugin:node/recommended'], 47 | }, 48 | ], 49 | }; 50 | -------------------------------------------------------------------------------- /addon/src/adapter.js: -------------------------------------------------------------------------------- 1 | import TestAdapter from '@ember/test/adapter'; 2 | import * as QUnit from 'qunit'; 3 | 4 | export function nonTestDoneCallback() {} 5 | 6 | let Adapter = TestAdapter.extend({ 7 | init() { 8 | this.doneCallbacks = []; 9 | this.qunit = this.qunit || QUnit; 10 | }, 11 | 12 | asyncStart() { 13 | let currentTest = this.qunit.config.current; 14 | let done = 15 | currentTest && currentTest.assert 16 | ? currentTest.assert.async() 17 | : nonTestDoneCallback; 18 | this.doneCallbacks.push({ test: currentTest, done }); 19 | }, 20 | 21 | asyncEnd() { 22 | let currentTest = this.qunit.config.current; 23 | 24 | if (this.doneCallbacks.length === 0) { 25 | throw new Error( 26 | 'Adapter asyncEnd called when no async was expected. Please create an issue in ember-qunit.' 27 | ); 28 | } 29 | 30 | let { test, done } = this.doneCallbacks.pop(); 31 | 32 | // In future, we should explore fixing this at a different level, specifically 33 | // addressing the pairing of asyncStart/asyncEnd behavior in a more consistent way. 34 | if (test === currentTest) { 35 | done(); 36 | } 37 | }, 38 | }); 39 | 40 | export default Adapter; 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the primary branch with .release-plan.json modified, 2 | # runs release-plan. 3 | 4 | name: Publish Stable 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - main 11 | - master 12 | paths: 13 | - '.release-plan.json' 14 | 15 | concurrency: 16 | group: publish-${{ github.head_ref || github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | publish: 21 | name: "NPM Publish" 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: write 25 | pull-requests: write 26 | id-token: write 27 | attestations: write 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: pnpm/action-setup@v4 32 | - uses: actions/setup-node@v4 33 | with: 34 | node-version: 18 35 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 36 | registry-url: 'https://registry.npmjs.org' 37 | cache: pnpm 38 | - run: pnpm install --frozen-lockfile 39 | - name: Publish to NPM 40 | run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish 41 | env: 42 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 44 | -------------------------------------------------------------------------------- /test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-buildtime-options-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /test-buildtime-options-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 13 | ], 14 | }, 15 | }, 16 | plugins: ['ember'], 17 | extends: [ 18 | 'eslint:recommended', 19 | 'plugin:ember/recommended', 20 | 'plugin:prettier/recommended', 21 | ], 22 | env: { 23 | browser: true, 24 | }, 25 | rules: {}, 26 | overrides: [ 27 | // node files 28 | { 29 | files: [ 30 | './.eslintrc.js', 31 | './.prettierrc.js', 32 | './.stylelintrc.js', 33 | './.template-lintrc.js', 34 | './ember-cli-build.js', 35 | './testem.js', 36 | './blueprints/*/index.js', 37 | './config/**/*.js', 38 | './lib/*/index.js', 39 | './server/**/*.js', 40 | ], 41 | parserOptions: { 42 | sourceType: 'script', 43 | }, 44 | env: { 45 | browser: false, 46 | node: true, 47 | }, 48 | extends: ['plugin:n/recommended'], 49 | }, 50 | { 51 | // test files 52 | files: ['tests/**/*-test.{js,ts}'], 53 | extends: ['plugin:qunit/recommended'], 54 | }, 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /test-buildtime-options-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 13 | ], 14 | }, 15 | }, 16 | plugins: ['ember'], 17 | extends: [ 18 | 'eslint:recommended', 19 | 'plugin:ember/recommended', 20 | 'plugin:prettier/recommended', 21 | ], 22 | env: { 23 | browser: true, 24 | }, 25 | rules: {}, 26 | overrides: [ 27 | // node files 28 | { 29 | files: [ 30 | './.eslintrc.js', 31 | './.prettierrc.js', 32 | './.stylelintrc.js', 33 | './.template-lintrc.js', 34 | './ember-cli-build.js', 35 | './testem.js', 36 | './blueprints/*/index.js', 37 | './config/**/*.js', 38 | './lib/*/index.js', 39 | './server/**/*.js', 40 | ], 41 | parserOptions: { 42 | sourceType: 'script', 43 | }, 44 | env: { 45 | browser: false, 46 | node: true, 47 | }, 48 | extends: ['plugin:n/recommended'], 49 | }, 50 | { 51 | // test files 52 | files: ['tests/**/*-test.{js,ts}'], 53 | extends: ['plugin:qunit/recommended'], 54 | }, 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /addon/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import { Addon } from '@embroider/addon-dev/rollup'; 3 | 4 | const addon = new Addon({ 5 | srcDir: 'src', 6 | destDir: 'dist', 7 | }); 8 | 9 | export default { 10 | // This provides defaults that work well alongside `publicEntrypoints` below. 11 | // You can augment this if you need to. 12 | output: addon.output(), 13 | 14 | plugins: [ 15 | // These are the modules that users should be able to import from your 16 | // addon. Anything not listed here may get optimized away. 17 | addon.publicEntrypoints(['**/*.js']), 18 | 19 | // Follow the V2 Addon rules about dependencies. Your code can import from 20 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 21 | // package names. 22 | addon.dependencies(), 23 | 24 | // This babel config should *not* apply presets or compile away ES modules. 25 | // It exists only to provide development niceties for you, like automatic 26 | // template colocation. 27 | // 28 | // By default, this will load the actual babel config from the file 29 | // babel.config.json. 30 | babel({ 31 | babelHelpers: 'bundled', 32 | }), 33 | 34 | // addons are allowed to contain imports of .css files, which we want rollup 35 | // to leave alone and keep in the published output. 36 | addon.keepAssets(['**/*.css']), 37 | 38 | // Remove leftover build artifacts when starting a new build. 39 | addon.clean(), 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /test-app/tests/integration/setup-test-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import Service, { inject as injectService } from '@ember/service'; 3 | import { setupTest } from 'ember-qunit'; 4 | import hasEmberVersion from '@ember/test-helpers/has-ember-version'; 5 | 6 | module('setupTest tests', function (hooks) { 7 | if (!hasEmberVersion(2, 4)) { 8 | return; 9 | } 10 | 11 | setupTest(hooks); 12 | 13 | test('can be used for unit style testing', function (assert) { 14 | this.owner.register( 15 | 'service:foo', 16 | class extends Service { 17 | someMethod() { 18 | return 'hello thar!'; 19 | } 20 | } 21 | ); 22 | 23 | let subject = this.owner.lookup('service:foo'); 24 | 25 | assert.strictEqual(subject.someMethod(), 'hello thar!'); 26 | }); 27 | 28 | test('can access a shared service instance', function (assert) { 29 | this.owner.register('service:bar', class extends Service {}); 30 | this.owner.register( 31 | 'service:foo', 32 | class extends Service { 33 | @injectService bar; 34 | 35 | someMethod() { 36 | this.set('bar.someProp', 'derp'); 37 | } 38 | } 39 | ); 40 | 41 | let subject = this.owner.lookup('service:foo'); 42 | let bar = this.owner.lookup('service:bar'); 43 | 44 | assert.notOk(bar.get('someProp'), 'precond - initially undefined'); 45 | 46 | subject.someMethod(); 47 | 48 | assert.strictEqual(bar.get('someProp'), 'derp', 'property updated'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | This is the test-app for ember-qunit. 4 | 5 | ## Prerequisites 6 | 7 | You will need the following things properly installed on your computer. 8 | 9 | * [Git](https://git-scm.com/) 10 | * [Node.js](https://nodejs.org/) 11 | * [Yarn](https://yarnpkg.com/) 12 | * [Ember CLI](https://cli.emberjs.com/release/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd test-app` 19 | * `pnpm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `pnpm lint` 39 | * `pnpm lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://cli.emberjs.com/release/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test-buildtime-options-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | This is the test-app for ember-qunit. 4 | 5 | ## Prerequisites 6 | 7 | You will need the following things properly installed on your computer. 8 | 9 | * [Git](https://git-scm.com/) 10 | * [Node.js](https://nodejs.org/) 11 | * [Yarn](https://yarnpkg.com/) 12 | * [Ember CLI](https://cli.emberjs.com/release/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd test-app` 19 | * `pnpm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `pnpm lint` 39 | * `pnpm lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://cli.emberjs.com/release/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /addon/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | - breaking - Used when the PR is considered a breaking change. 18 | - enhancement - Used when the PR adds a new feature or enhancement. 19 | - bug - Used when the PR fixes a bug included in a previous release. 20 | - documentation - Used when the PR adds or updates documentation. 21 | - internal - Internal changes or things that don't fit in any other category. 22 | 23 | **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` 24 | 25 | ## Release 26 | 27 | Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/emberjs/ember-qunit/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR 28 | -------------------------------------------------------------------------------- /addon/src/test-container-styles.css: -------------------------------------------------------------------------------- 1 | /* Override QUnit's default styles that place #qunit-fixture outside the viewport */ 2 | #qunit-fixture { 3 | position: relative; 4 | left: auto; 5 | top: auto; 6 | width: auto; 7 | height: auto; 8 | } 9 | 10 | #ember-testing-container { 11 | position: fixed; 12 | 13 | background-color: #fff; 14 | background-image: 15 | linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), 16 | linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); 17 | background-position: 0 0, 10px 10px; 18 | background-size: 20px 20px; 19 | 20 | bottom: 0; 21 | right: 0; 22 | width: 640px; 23 | height: 384px; 24 | overflow: auto; 25 | z-index: 98; 26 | border: 1px solid #ccc; 27 | margin: 0 auto; 28 | 29 | /* Prevent leaking position fixed elements outside the testing container */ 30 | transform: translateZ(0); 31 | } 32 | 33 | #ember-testing-container.ember-testing-container-full-screen { 34 | width: 100%; 35 | height: 100%; 36 | overflow: auto; 37 | z-index: 98; 38 | border: none; 39 | right: 0; 40 | } 41 | 42 | #ember-testing-container.ember-testing-container-hidden { 43 | opacity: 0; 44 | pointer-events: none; 45 | } 46 | 47 | #ember-testing { 48 | width: 200%; 49 | height: 200%; 50 | transform: scale(0.5); 51 | transform-origin: top left; 52 | } 53 | 54 | .ember-testing-container-full-screen #ember-testing { 55 | position: absolute; 56 | width: 100%; 57 | height: 100%; 58 | transform: scale(1); 59 | } 60 | 61 | #qunit-tests > li:last-child { 62 | margin-bottom: 384px; 63 | } 64 | 65 | @supports (display: flex) or (display: -webkit-box) { 66 | @media (min-height: 500px) { 67 | #qunit-tests { 68 | overflow: auto; 69 | } 70 | 71 | #ember-testing-container { 72 | right: 30px; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test-app/tests/unit/unhandled-rejection-test.js: -------------------------------------------------------------------------------- 1 | import { Promise as RSVPPromise } from 'rsvp'; 2 | import { module, test } from 'qunit'; 3 | import patchAssert from './utils/patch-assert-helper'; 4 | 5 | const HAS_NATIVE_PROMISE = typeof Promise !== 'undefined'; 6 | const HAS_UNHANDLED_REJECTION_HANDLER = 'onunhandledrejection' in window; 7 | 8 | module('unhandle promise rejections', function (hooks) { 9 | let WINDOW_ONERROR; 10 | 11 | hooks.beforeEach(function (assert) { 12 | // capturing this outside of module scope to ensure we grab 13 | // the test frameworks own window.onerror to reset it 14 | WINDOW_ONERROR = window.onerror; 15 | 16 | patchAssert(assert); 17 | }); 18 | 19 | hooks.afterEach(function () { 20 | window.onerror = WINDOW_ONERROR; 21 | }); 22 | 23 | test('RSVP promises cause an unhandled rejection', function (assert) { 24 | assert.expect(2); 25 | let done = assert.async(); 26 | 27 | window.onerror = (message) => { 28 | assert.test._originalPushResult({ 29 | result: /whoops!/.test(message), 30 | actual: message, 31 | expected: 'to include `whoops!`', 32 | message: 33 | 'error should bubble out to window.onerror, and therefore fail tests (due to QUnit implementing window.onerror)', 34 | }); 35 | 36 | return true; // prevent "bubbling" and therefore failing the test 37 | }; 38 | 39 | // ensure we do not exit this test until the assertion has happened 40 | setTimeout(() => done(), 10); 41 | 42 | new RSVPPromise((resolve) => { 43 | setTimeout(resolve); 44 | }).then(function () { 45 | throw new Error('whoops!'); 46 | }); 47 | }); 48 | 49 | if (HAS_NATIVE_PROMISE && HAS_UNHANDLED_REJECTION_HANDLER) { 50 | test('native promises cause an unhandled rejection', function (assert) { 51 | assert.expect(1); 52 | let done = assert.async(); 53 | 54 | // ensure we do not exit this test until the assertion has happened 55 | setTimeout(() => done(), 10); 56 | 57 | new self.Promise((resolve) => { 58 | setTimeout(resolve); 59 | }).then(function () { 60 | throw new Error('whoops!'); 61 | }); 62 | }); 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /test-buildtime-options-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | usePnpm: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-4.4', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~4.4.0', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-4.8', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~4.4.0', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | dependencies: { 30 | 'ember-auto-import': '^2.2.0', 31 | }, 32 | devDependencies: { 33 | 'ember-source': await getChannelURL('release'), 34 | }, 35 | }, 36 | }, 37 | { 38 | name: 'ember-beta', 39 | npm: { 40 | dependencies: { 41 | 'ember-auto-import': '^2.2.0', 42 | }, 43 | devDependencies: { 44 | 'ember-source': await getChannelURL('beta'), 45 | }, 46 | }, 47 | }, 48 | { 49 | name: 'ember-canary', 50 | npm: { 51 | dependencies: { 52 | 'ember-auto-import': '^2.2.0', 53 | }, 54 | devDependencies: { 55 | 'ember-source': await getChannelURL('canary'), 56 | }, 57 | }, 58 | }, 59 | { 60 | name: 'ember-classic', 61 | env: { 62 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 63 | 'application-template-wrapper': true, 64 | 'default-async-observers': false, 65 | 'template-only-glimmer-components': false, 66 | }), 67 | }, 68 | npm: { 69 | devDependencies: { 70 | 'ember-source': '~3.28.0', 71 | }, 72 | ember: { 73 | edition: 'classic', 74 | }, 75 | }, 76 | }, 77 | embroiderSafe(), 78 | embroiderOptimized(), 79 | ], 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /test-app/tests/integration/setup-rendering-test-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ember/no-classic-components */ 2 | import { module, test } from 'qunit'; 3 | import Component, { setComponentTemplate } from '@ember/component'; 4 | import templateOnly from '@ember/component/template-only'; 5 | import { helper } from '@ember/component/helper'; 6 | import { hbs } from 'ember-cli-htmlbars'; 7 | import { setupRenderingTest } from 'ember-qunit'; 8 | import { render } from '@ember/test-helpers'; 9 | import { macroCondition, dependencySatisfies } from '@embroider/macros'; 10 | 11 | module('setupRenderingTest tests', function (hooks) { 12 | setupRenderingTest(hooks); 13 | 14 | test('can render a simple template', async function (assert) { 15 | await render(hbs`

Hello!

`); 16 | 17 | assert.strictEqual(this.element.textContent, 'Hello!'); 18 | }); 19 | 20 | test('can invoke template only components', async function (assert) { 21 | if ( 22 | macroCondition(dependencySatisfies('ember-source', '>= 6.0.0-alpha.0')) 23 | ) { 24 | this.owner.register( 25 | 'component:template-only', 26 | setComponentTemplate(hbs`template-only component here`, templateOnly()) 27 | ); 28 | } else { 29 | this.owner.register( 30 | 'template:components/template-only', 31 | hbs`template-only component here` 32 | ); 33 | } 34 | 35 | await render(hbs``); 36 | 37 | assert.strictEqual( 38 | this.element.textContent, 39 | 'template-only component here' 40 | ); 41 | }); 42 | 43 | test('can invoke JS only components', async function (assert) { 44 | this.owner.register( 45 | 'component:js-only', 46 | // eslint-disable-next-line ember/no-classic-classes 47 | Component.extend({ 48 | classNames: ['js-only'], 49 | }) 50 | ); 51 | 52 | await render(hbs``); 53 | 54 | assert.ok( 55 | this.element.querySelector('.js-only'), 56 | 'element found for js-only component' 57 | ); 58 | }); 59 | 60 | test('can invoke helper', async function (assert) { 61 | this.owner.register( 62 | 'helper:jax', 63 | helper(([name]) => `${name}-jax`) 64 | ); 65 | 66 | await render(hbs`{{jax "max"}}`); 67 | 68 | assert.strictEqual(this.element.textContent, 'max-jax'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test-app/tests/unit/adapter-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { QUnitAdapter, nonTestDoneCallback } from 'ember-qunit'; 3 | import patchAssert from './utils/patch-assert-helper'; 4 | 5 | const NATIVE_PROMISE = Promise; 6 | 7 | import { Promise } from 'rsvp'; 8 | 9 | module('QUnitAdapter'); 10 | 11 | test('asyncStart waits for asyncEnd to finish a test', function (assert) { 12 | assert.expect(1); 13 | const adapter = QUnitAdapter.create(); 14 | 15 | adapter.asyncStart(); 16 | setTimeout(function () { 17 | assert.ok(true); 18 | adapter.asyncEnd(); 19 | }, 50); 20 | }); 21 | 22 | test('asyncStart waits for equal numbers of asyncEnd to finish a test', function (assert) { 23 | assert.expect(1); 24 | const adapter = QUnitAdapter.create(); 25 | 26 | adapter.asyncStart(); 27 | adapter.asyncStart(); 28 | adapter.asyncEnd(); 29 | 30 | setTimeout(function () { 31 | assert.ok(true); 32 | adapter.asyncEnd(); 33 | }, 50); 34 | }); 35 | 36 | test('asyncStart should handle skipped tests that has no assert', function (assert) { 37 | assert.expect(2); 38 | let FakeQUnitWithoutAssert = { 39 | config: { 40 | current: {}, 41 | }, 42 | }; 43 | 44 | const adapter = QUnitAdapter.create({ qunit: FakeQUnitWithoutAssert }); 45 | 46 | adapter.asyncStart(); 47 | assert.strictEqual(adapter.doneCallbacks.length, 1); 48 | assert.deepEqual(adapter.doneCallbacks, [ 49 | { 50 | test: FakeQUnitWithoutAssert.config.current, 51 | done: nonTestDoneCallback, 52 | }, 53 | ]); 54 | }); 55 | 56 | module('QUnitAdapter - Balanced async with native Promise', function () { 57 | const adapter = QUnitAdapter.create(); 58 | 59 | test('asyncStart invoked', function (assert) { 60 | assert.expect(1); 61 | adapter.asyncStart(); 62 | 63 | assert.ok(true); 64 | 65 | patchAssert(assert); 66 | return NATIVE_PROMISE.reject('trolol'); 67 | }); 68 | 69 | test('asyncEnd invoked', function (assert) { 70 | assert.ok(true, 'fired!'); 71 | setTimeout(() => { 72 | adapter.asyncEnd(); 73 | }); 74 | }); 75 | }); 76 | 77 | module('QUnitAdapter - Balanced async with RSVP.Promise', function () { 78 | const adapter = QUnitAdapter.create(); 79 | 80 | test('asyncStart invoked', function (assert) { 81 | assert.expect(1); 82 | adapter.asyncStart(); 83 | 84 | assert.ok(true); 85 | 86 | patchAssert(assert); 87 | return Promise.reject('trolol'); 88 | }); 89 | 90 | test('asyncEnd invoked', function (assert) { 91 | assert.ok(true, 'fired!'); 92 | setTimeout(() => { 93 | adapter.asyncEnd(); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test-app/tests/unit/test-isolation-validation-test.js: -------------------------------------------------------------------------------- 1 | import { later, _backburner as backburner } from '@ember/runloop'; 2 | import { registerWaiter, unregisterWaiter } from '@ember/test'; 3 | import QUnit, { module, test } from 'qunit'; 4 | import { installTestNotIsolatedHook } from 'ember-qunit/test-isolation-validation'; 5 | import { getDebugInfo } from '@ember/test-helpers'; 6 | import patchAssert from './utils/patch-assert-helper'; 7 | 8 | backburner.DEBUG = true; 9 | 10 | if (getDebugInfo()) { 11 | module('test isolation validation', function (hooks) { 12 | let isWaiterPending = false; 13 | let waiter = () => { 14 | return !isWaiterPending; 15 | }; 16 | 17 | // eslint-disable-next-line ember/no-legacy-test-waiters 18 | registerWaiter(waiter); 19 | 20 | QUnit.on('testEnd', function () { 21 | // eslint-disable-next-line ember/no-legacy-test-waiters 22 | unregisterWaiter(this._waiter); 23 | }); 24 | 25 | hooks.beforeEach(function () { 26 | this.cancelId = 0; 27 | backburner.DEBUG = true; 28 | 29 | installTestNotIsolatedHook(); 30 | }); 31 | 32 | test('detectIfTestNotIsolated does not add failing assertion when test is isolated', function (assert) { 33 | assert.expect(1); 34 | 35 | assert.ok(true); 36 | }); 37 | 38 | test('detectIfTestNotIsolated adds failing assertion when test has pending timers', function (assert) { 39 | assert.expect(1); 40 | patchAssert(assert); 41 | 42 | this.cancelId = later(() => {}, 1000); 43 | }); 44 | 45 | test('detectIfTestNotIsolated adds failing assertion when test has test waiters', function (assert) { 46 | assert.expect(1); 47 | patchAssert(assert); 48 | 49 | isWaiterPending = true; 50 | }); 51 | 52 | test('detectIfTestNotIsolated allows for a small window (e.g. an autorun to flush)', function (assert) { 53 | assert.expect(0); 54 | 55 | isWaiterPending = true; 56 | setTimeout(() => (isWaiterPending = false), 0); 57 | }); 58 | 59 | module('timeouts', function (hooks) { 60 | hooks.afterEach(function (assert) { 61 | assert.test._originalPushResult({ 62 | result: 63 | assert.test.assertions[0].message.indexOf( 64 | 'Failed: Test took longer than 50ms' 65 | ) === 0, 66 | }); 67 | }); 68 | 69 | test('detectIfTestNotIsolated outputs debug info on test timeout', function (assert) { 70 | assert.expect(1); 71 | assert.timeout(50); 72 | 73 | assert.async(); 74 | 75 | let testPushFailure = assert.test.pushFailure; 76 | 77 | patchAssert(assert); 78 | 79 | assert.test.pushFailure = testPushFailure; 80 | 81 | later(() => {}, 100); 82 | }); 83 | }); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /addon/src/test-loader.js: -------------------------------------------------------------------------------- 1 | /* globals requirejs, require */ 2 | 3 | import * as QUnit from 'qunit'; 4 | 5 | export class TestLoader { 6 | static load() { 7 | new TestLoader().loadModules(); 8 | } 9 | 10 | constructor() { 11 | this._didLogMissingUnsee = false; 12 | } 13 | 14 | shouldLoadModule(moduleName) { 15 | return moduleName.match(/[-_]test$/); 16 | } 17 | 18 | listModules() { 19 | return Object.keys(requirejs.entries); 20 | } 21 | 22 | listTestModules() { 23 | let moduleNames = this.listModules(); 24 | let testModules = []; 25 | let moduleName; 26 | 27 | for (let i = 0; i < moduleNames.length; i++) { 28 | moduleName = moduleNames[i]; 29 | 30 | if (this.shouldLoadModule(moduleName)) { 31 | testModules.push(moduleName); 32 | } 33 | } 34 | 35 | return testModules; 36 | } 37 | 38 | loadModules() { 39 | let testModules = this.listTestModules(); 40 | let testModule; 41 | 42 | for (let i = 0; i < testModules.length; i++) { 43 | testModule = testModules[i]; 44 | this.require(testModule); 45 | this.unsee(testModule); 46 | } 47 | } 48 | 49 | require(moduleName) { 50 | try { 51 | require(moduleName); 52 | } catch (e) { 53 | this.moduleLoadFailure(moduleName, e); 54 | } 55 | } 56 | 57 | unsee(moduleName) { 58 | if (typeof require.unsee === 'function') { 59 | require.unsee(moduleName); 60 | } else if (!this._didLogMissingUnsee) { 61 | this._didLogMissingUnsee = true; 62 | if (typeof console !== 'undefined') { 63 | console.warn( 64 | 'unable to require.unsee, please upgrade loader.js to >= v3.3.0' 65 | ); 66 | } 67 | } 68 | } 69 | 70 | moduleLoadFailure(moduleName, error) { 71 | moduleLoadFailures.push(error); 72 | 73 | QUnit.module('TestLoader Failures'); 74 | QUnit.test(moduleName + ': could not be loaded', function () { 75 | throw error; 76 | }); 77 | } 78 | } 79 | 80 | let moduleLoadFailures = []; 81 | 82 | QUnit.done(function () { 83 | let length = moduleLoadFailures.length; 84 | 85 | try { 86 | if (length === 0) { 87 | // do nothing 88 | } else if (length === 1) { 89 | throw moduleLoadFailures[0]; 90 | } else { 91 | throw new Error('\n' + moduleLoadFailures.join('\n')); 92 | } 93 | } finally { 94 | // ensure we release previously captured errors. 95 | moduleLoadFailures = []; 96 | } 97 | }); 98 | 99 | /** 100 | Load tests following the default patterns: 101 | 102 | * The module name ends with `-test` 103 | 104 | @method loadTests 105 | */ 106 | export function loadTests() { 107 | new TestLoader().loadModules(); 108 | } 109 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - 'v*' 9 | pull_request: 10 | schedule: 11 | - cron: '0 3 * * *' # daily, at 3am 12 | 13 | concurrency: 14 | group: ci-${{ github.head_ref || github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | test: 19 | name: Tests 20 | runs-on: ubuntu-latest 21 | timeout-minutes: 10 22 | 23 | steps: 24 | - uses: wyvox/action@v1 25 | - run: pnpm lint 26 | - run: pnpm test:ember 27 | working-directory: test-app 28 | 29 | buildtime-options-tests: 30 | name: "Buildtime Options Tests" 31 | runs-on: ubuntu-latest 32 | timeout-minutes: 10 33 | 34 | steps: 35 | - uses: wyvox/action@v1 36 | - run: pnpm lint 37 | - run: pnpm test:ember 38 | working-directory: test-app 39 | - name: embroider test 40 | run: node_modules/.bin/ember try:one embroider-optimized --skip-cleanup 41 | working-directory: test-app 42 | 43 | 44 | floating-dependencies: 45 | name: 'Floating Dependencies' 46 | runs-on: ubuntu-latest 47 | timeout-minutes: 10 48 | 49 | steps: 50 | - uses: wyvox/action@v1 51 | with: 52 | pnpm-args: '--no-lockfile' 53 | - run: pnpm test:ember 54 | working-directory: test-app 55 | 56 | try-scenarios: 57 | name: 'Try: ${{ matrix.ember-try-scenario }}' 58 | runs-on: ubuntu-latest 59 | timeout-minutes: 10 60 | 61 | needs: test 62 | 63 | strategy: 64 | fail-fast: true 65 | matrix: 66 | ember-try-scenario: 67 | - ember-lts-4.4 68 | - ember-lts-4.8 69 | - ember-lts-4.12 70 | - ember-lts-5.4 71 | - ember-lts-5.8 72 | - ember-lts-5.12 73 | - ember-release 74 | - ember-beta 75 | - ember-canary 76 | - embroider-safe 77 | - embroider-optimized 78 | 79 | steps: 80 | - uses: wyvox/action@v1 81 | - name: test 82 | run: node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup 83 | working-directory: test-app 84 | 85 | types: 86 | runs-on: ubuntu-latest 87 | 88 | needs: test 89 | 90 | strategy: 91 | fail-fast: false 92 | matrix: 93 | ts-version: 94 | - 4.8 95 | - 4.9 96 | - 5.0 97 | - 5.1 98 | - 5.2 99 | - 5.3 100 | - 5.4 101 | - 5.5 102 | - 5.6 103 | - 5.7 104 | - 5.8 105 | - next 106 | 107 | steps: 108 | - uses: wyvox/action@v1 109 | - name: install TS version 110 | run: pnpm install --save-dev typescript@${{matrix.ts-version}} 111 | working-directory: addon 112 | - name: test types 113 | run: pnpm test:types 114 | working-directory: test-types 115 | -------------------------------------------------------------------------------- /docs/TEST_ISOLATION_VALIDATION.md: -------------------------------------------------------------------------------- 1 | # Debugging Async Leakage Using Test Isolation Validation 2 | 3 | ## Overview 4 | 5 | The `ember-qunit` test isolation validation feature allows you to opt-in to enabling automatic detection of async execution that extends beyond when the test is considered complete. This can be a particularly difficult problem to detect in your tests, and can contribute to non-deterministic test execution. 6 | 7 | ## Installation 8 | 9 | The test isolation validation functionality is available on `ember-qunit` version `4.2.0` or higher. 10 | 11 | ```bash 12 | ember install ember-qunit 13 | ``` 14 | 15 | ## How to use 16 | 17 | In order to enable test isolation validation, you'll need to configure the option, `setupTestIsolationValidation: true` in `ember-qunit`'s `start` function, which starts your application or addon's test run. 18 | 19 | ```js 20 | // tests/test-helper.js 21 | 22 | import Application from '../app'; 23 | import config from '../config/environment'; 24 | import { setApplication } from '@ember/test-helpers'; 25 | import { start } from 'ember-qunit'; 26 | 27 | setApplication(Application.create(config.APP)); 28 | 29 | start({ 30 | setupTestIsolationValidation: true 31 | }); 32 | ``` 33 | 34 | ## Finding non-isolated tests 35 | 36 | Once a test run is started with test isolation validation turned on, analysis will occur on a test-by-test basis. Once a failure is detected, a new assertion will be added that will fail the test, indicating that the test is not isolated. 37 | 38 | ![image](https://user-images.githubusercontent.com/180990/50046470-aeb91d00-0058-11e9-9b9c-d08190e04e6c.png) 39 | 40 | As indicated by the assertion message, important (and more useful) information is printed to the console. 41 | 42 | ![image](https://user-images.githubusercontent.com/180990/50046710-89c6a900-005c-11e9-96b1-e66ac6ef7907.png) 43 | 44 | As you can see, the following information is provided for each test that fails isolation validation: 45 | - The test module and test name 46 | - The category of failure, one of 47 | - Pending AJAX requests 48 | - Pending test waiters 49 | - Scheduled async 50 | - Scheduled autorun 51 | - The stack trace of the code that caused the isolation failure (Scheduled async or Scheduled autoruns only) 52 | 53 | In the case of pending **AJAX requests** and **test waiters**, we see those categorizations of the failures. In the case of **scheduled async** or **scheduled autoruns**, we get a bit more information - stack traces that point to the code that scheduled the async call. 54 | 55 | By viewing this information output to the console, we can traverse the stack from top to bottom, clicking the associated file/line number to get a more detailed view of the callee. 56 | 57 | In line 9 below, we can see that an async call was scheduled via `Ember.run.later`: 58 | 59 | ![image](https://user-images.githubusercontent.com/180990/50046897-f98a6300-005f-11e9-9cd0-dffe0663880a.png) 60 | 61 | Having this information will enable you to evaluate the best way to address the particular case of async leakage detected. 62 | -------------------------------------------------------------------------------- /test-buildtime-options-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "0.0.0", 4 | "description": "Test app for ember-qunit", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/emberjs/ember-qunit.git" 8 | }, 9 | "license": "MIT", 10 | "private": true, 11 | "scripts": { 12 | "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", 13 | "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", 14 | "lint:hbs": "ember-template-lint .", 15 | "lint:hbs:fix": "ember-template-lint . --fix", 16 | "lint:js": "eslint . --cache", 17 | "lint:js:fix": "eslint . --fix", 18 | "start": "pnpm sync:injected; ember serve", 19 | "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", 20 | "test:types": "tsc --noEmit --project types", 21 | "test:ember": "pnpm sync:injected; ember test", 22 | "test:ember-compatibility": "ember try:each", 23 | "sync:injected": "pnpm sync-dependencies-meta-injected" 24 | }, 25 | "dependencies": { 26 | "ember-qunit": "workspace:*" 27 | }, 28 | "dependenciesMeta": { 29 | "ember-qunit": { 30 | "injected": true 31 | } 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.26.10", 35 | "@babel/eslint-parser": "^7.27.0", 36 | "@ember/optional-features": "^2.2.0", 37 | "@ember/string": "^3.0.1", 38 | "@ember/test-helpers": "^3.0.3", 39 | "@ember/test-waiters": "^3.0.2", 40 | "@embroider/macros": "^1.16.12", 41 | "@embroider/test-setup": "^3.0.1", 42 | "@glimmer/component": "^2.0.0", 43 | "@glimmer/tracking": "^1.1.2", 44 | "@tsconfig/ember": "^2.0.0", 45 | "@types/qunit": "^2.19.12", 46 | "@types/rsvp": "^4.0.4", 47 | "concurrently": "^9.1.2", 48 | "ember-angle-bracket-invocation-polyfill": "^3.0.2", 49 | "ember-auto-import": "^2.10.0", 50 | "ember-cli": "~5.4.0", 51 | "ember-cli-babel": "^8.2.0", 52 | "ember-cli-dependency-checker": "^3.3.3", 53 | "ember-cli-htmlbars": "^6.3.0", 54 | "ember-cli-inject-live-reload": "^2.1.0", 55 | "ember-cli-terser": "^4.0.2", 56 | "ember-disable-prototype-extensions": "^1.1.3", 57 | "ember-load-initializers": "^2.1.2", 58 | "ember-resolver": "^13.1.0", 59 | "ember-source": "~5.0.0", 60 | "ember-source-channel-url": "^3.0.0", 61 | "ember-template-lint": "^7.0.1", 62 | "ember-try": "^4.0.0", 63 | "eslint": "^8.52.0", 64 | "eslint-plugin-disable-features": "^0.1.3", 65 | "eslint-plugin-ember": "^11.11.1", 66 | "eslint-plugin-n": "^16.1.0", 67 | "eslint-plugin-qunit": "^7.3.4", 68 | "expect-type": "^0.16.0", 69 | "loader.js": "^4.7.0", 70 | "pnpm-sync-dependencies-meta-injected": "^0.0.9", 71 | "prettier": "^2.8.8", 72 | "qunit": "^2.24.1", 73 | "qunit-dom": "^3.4.0", 74 | "typescript": "^4.9.5", 75 | "webpack": "^5.98.0" 76 | }, 77 | "engines": { 78 | "node": "14.* || 16.* || >= 18" 79 | }, 80 | "ember": { 81 | "edition": "octane" 82 | }, 83 | "volta": { 84 | "extends": "../package.json" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "0.0.0", 4 | "description": "Test app for ember-qunit", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/emberjs/ember-qunit.git" 8 | }, 9 | "license": "MIT", 10 | "private": true, 11 | "scripts": { 12 | "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", 13 | "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", 14 | "lint:hbs": "ember-template-lint .", 15 | "lint:hbs:fix": "ember-template-lint . --fix", 16 | "lint:js": "eslint . --cache", 17 | "lint:js:fix": "eslint . --fix", 18 | "start": "pnpm sync:injected; ember serve", 19 | "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", 20 | "test:types": "tsc --noEmit --project types", 21 | "test:ember": "pnpm sync:injected; ember test", 22 | "test:ember-compatibility": "ember try:each", 23 | "sync:injected": "pnpm sync-dependencies-meta-injected" 24 | }, 25 | "dependencies": { 26 | "ember-qunit": "workspace:*" 27 | }, 28 | "dependenciesMeta": { 29 | "ember-qunit": { 30 | "injected": true 31 | } 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.26.10", 35 | "@babel/eslint-parser": "^7.27.0", 36 | "@babel/plugin-proposal-decorators": "^7.22.15", 37 | "@ember/optional-features": "^2.2.0", 38 | "@ember/string": "^3.0.1", 39 | "@ember/test-helpers": "^3.0.3", 40 | "@ember/test-waiters": "^3.0.2", 41 | "@embroider/macros": "^1.16.12", 42 | "@embroider/test-setup": "^3.0.1", 43 | "@glimmer/component": "^2.0.0", 44 | "@glimmer/tracking": "^1.1.2", 45 | "@tsconfig/ember": "^2.0.0", 46 | "@types/qunit": "^2.19.12", 47 | "@types/rsvp": "^4.0.4", 48 | "concurrently": "^9.1.2", 49 | "ember-angle-bracket-invocation-polyfill": "^3.0.2", 50 | "ember-auto-import": "^2.10.0", 51 | "ember-cli": "~5.4.0", 52 | "ember-cli-babel": "^8.2.0", 53 | "ember-cli-dependency-checker": "^3.3.3", 54 | "ember-cli-htmlbars": "^6.3.0", 55 | "ember-cli-inject-live-reload": "^2.1.0", 56 | "ember-cli-terser": "^4.0.2", 57 | "ember-disable-prototype-extensions": "^1.1.3", 58 | "ember-load-initializers": "^2.1.2", 59 | "ember-resolver": "^13.1.0", 60 | "ember-source": "~5.0.0", 61 | "ember-source-channel-url": "^3.0.0", 62 | "ember-template-lint": "^7.0.1", 63 | "ember-try": "^4.0.0", 64 | "eslint": "^8.52.0", 65 | "eslint-plugin-disable-features": "^0.1.3", 66 | "eslint-plugin-ember": "^11.11.1", 67 | "eslint-plugin-n": "^16.1.0", 68 | "eslint-plugin-qunit": "^7.3.4", 69 | "expect-type": "^0.16.0", 70 | "loader.js": "^4.7.0", 71 | "pnpm-sync-dependencies-meta-injected": "^0.0.9", 72 | "prettier": "^2.8.8", 73 | "qunit": "^2.24.1", 74 | "qunit-dom": "^3.4.0", 75 | "typescript": "^4.9.5", 76 | "webpack": "^5.98.0" 77 | }, 78 | "engines": { 79 | "node": "14.* || 16.* || >= 18" 80 | }, 81 | "ember": { 82 | "edition": "octane" 83 | }, 84 | "volta": { 85 | "extends": "../package.json" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /addon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-qunit", 3 | "version": "9.0.4", 4 | "description": "QUnit helpers for testing Ember.js applications", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/emberjs/ember-qunit.git" 11 | }, 12 | "license": "MIT", 13 | "contributors": [ 14 | "Stefan Penner", 15 | "Ryan Florence", 16 | "Robert Jackson", 17 | "Dan Gebhardt", 18 | "Steve Calvert" 19 | ], 20 | "exports": { 21 | ".": { 22 | "types": "./types/index.d.ts", 23 | "default": "./dist/index.js" 24 | }, 25 | "./*": { 26 | "types": "./types/*.d.ts", 27 | "default": "./dist/*.js" 28 | }, 29 | "./addon-main.js": "./addon-main.cjs" 30 | }, 31 | "files": [ 32 | "dist", 33 | "types", 34 | "addon-main.cjs" 35 | ], 36 | "typesVersions": { 37 | "*": { 38 | "*": [ 39 | "types/*" 40 | ] 41 | } 42 | }, 43 | "scripts": { 44 | "build": "rollup --config", 45 | "lint": "concurrently 'npm:lint:*(!fix)' --names 'lint:'", 46 | "lint:fix": "concurrently 'npm:lint:*:fix' --names 'fix:'", 47 | "lint:js": "eslint . --cache", 48 | "lint:js:fix": "eslint . --fix", 49 | "start": "rollup --config --watch", 50 | "prepack": "rollup --config" 51 | }, 52 | "dependencies": { 53 | "@embroider/addon-shim": "^1.9.0", 54 | "@embroider/macros": "^1.16.12", 55 | "qunit-theme-ember": "^1.0.0" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.26.10", 59 | "@babel/eslint-parser": "^7.27.0", 60 | "@ember/test-helpers": "^3.0.3", 61 | "@embroider/addon-dev": "^3.0.0", 62 | "@glimmer/component": "^2.0.0", 63 | "@rollup/plugin-babel": "^5.3.0", 64 | "@tsconfig/ember": "^2.0.0", 65 | "@types/qunit": "^2.19.12", 66 | "@types/rsvp": "^4.0.4", 67 | "concurrently": "^9.1.2", 68 | "ember-source": "^5.0.0", 69 | "eslint": "^8.52.0", 70 | "eslint-config-prettier": "^8.6.0", 71 | "eslint-plugin-disable-features": "^0.1.3", 72 | "eslint-plugin-node": "^11.1.0", 73 | "eslint-plugin-prettier": "^4.2.1", 74 | "expect-type": "^0.16.0", 75 | "prettier": "^2.8.8", 76 | "qunit": "^2.24.1", 77 | "rollup": "^4.2.0", 78 | "typescript": "^4.9.5" 79 | }, 80 | "peerDependencies": { 81 | "@ember/test-helpers": ">=3.0.3", 82 | "qunit": "^2.13.0" 83 | }, 84 | "publishConfig": { 85 | "registry": "https://registry.npmjs.org" 86 | }, 87 | "changelog": { 88 | "repo": "emberjs/ember-qunit", 89 | "labels": { 90 | "breaking": ":boom: Breaking Change", 91 | "enhancement": ":rocket: Enhancement", 92 | "bug": ":bug: Bug Fix", 93 | "documentation": ":memo: Documentation", 94 | "internal": ":house: Internal" 95 | } 96 | }, 97 | "ember": { 98 | "edition": "octane" 99 | }, 100 | "ember-addon": { 101 | "version": 2, 102 | "type": "addon", 103 | "main": "addon-main.cjs", 104 | "app-js": {} 105 | }, 106 | "volta": { 107 | "extends": "../package.json" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | usePnpm: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-4.4', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~4.4.0', 15 | 'ember-cli': '~4.8.1', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'ember-lts-4.8', 21 | npm: { 22 | devDependencies: { 23 | 'ember-source': '~4.8.0', 24 | 'ember-cli': '~4.8.1', 25 | }, 26 | }, 27 | }, 28 | { 29 | name: 'ember-lts-4.12', 30 | npm: { 31 | devDependencies: { 32 | 'ember-source': '~4.12.0', 33 | 'ember-cli': '~4.12.2', 34 | }, 35 | }, 36 | }, 37 | { 38 | name: 'ember-lts-5.4', 39 | npm: { 40 | devDependencies: { 41 | 'ember-source': '~5.4.0', 42 | 'ember-cli': '~5.4.0', 43 | }, 44 | }, 45 | }, 46 | { 47 | name: 'ember-lts-5.8', 48 | npm: { 49 | devDependencies: { 50 | 'ember-source': '~5.8.0', 51 | 'ember-cli': '~5.8.0', 52 | }, 53 | }, 54 | }, 55 | { 56 | name: 'ember-lts-5.12', 57 | npm: { 58 | devDependencies: { 59 | 'ember-source': '~5.12.0', 60 | 'ember-cli': '~5.12.0', 61 | }, 62 | }, 63 | }, 64 | { 65 | name: 'ember-release', 66 | npm: { 67 | dependencies: { 68 | 'ember-auto-import': '^2.2.0', 69 | }, 70 | devDependencies: { 71 | 'ember-source': await getChannelURL('release'), 72 | 'ember-cli': '^5.1.0', 73 | }, 74 | }, 75 | }, 76 | { 77 | name: 'ember-beta', 78 | npm: { 79 | dependencies: { 80 | 'ember-auto-import': '^2.2.0', 81 | }, 82 | devDependencies: { 83 | 'ember-source': await getChannelURL('beta'), 84 | 'ember-cli': '^5.1.0', 85 | }, 86 | }, 87 | }, 88 | { 89 | name: 'ember-canary', 90 | npm: { 91 | dependencies: { 92 | 'ember-auto-import': '^2.2.0', 93 | }, 94 | devDependencies: { 95 | 'ember-source': await getChannelURL('canary'), 96 | 'ember-cli': '^5.1.0', 97 | }, 98 | }, 99 | }, 100 | { 101 | name: 'ember-classic', 102 | env: { 103 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 104 | 'application-template-wrapper': true, 105 | 'default-async-observers': false, 106 | 'template-only-glimmer-components': false, 107 | }), 108 | }, 109 | npm: { 110 | devDependencies: { 111 | 'ember-source': '~3.28.0', 112 | 'ember-cli': '^4.8.1', 113 | }, 114 | ember: { 115 | edition: 'classic', 116 | }, 117 | }, 118 | }, 119 | embroiderSafe(), 120 | embroiderOptimized(), 121 | ], 122 | }; 123 | }; 124 | -------------------------------------------------------------------------------- /.github/workflows/plan-release.yml: -------------------------------------------------------------------------------- 1 | name: Plan Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 9 | types: 10 | - labeled 11 | - unlabeled 12 | 13 | concurrency: 14 | group: plan-release # only the latest one of these should ever be running 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | is-this-a-release: 19 | name: "Is this a release?" 20 | runs-on: ubuntu-latest 21 | outputs: 22 | command: ${{ steps.check-release.outputs.command }} 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 2 28 | ref: 'main' 29 | # This will only cause the `is-this-a-release` job to have a "command" of `release` 30 | # when the .release-plan.json file was changed on the last commit. 31 | - id: check-release 32 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 33 | 34 | create-prepare-release-pr: 35 | name: Create Prepare Release PR 36 | runs-on: ubuntu-latest 37 | timeout-minutes: 5 38 | needs: is-this-a-release 39 | permissions: 40 | contents: write 41 | issues: read 42 | pull-requests: write 43 | # only run on push event or workflow dispatch if plan wasn't updated (don't create a release plan when we're releasing) 44 | # only run on labeled event if the PR has already been merged 45 | if: ((github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.is-this-a-release.outputs.command != 'release') || (github.event_name == 'pull_request_target' && github.event.pull_request.merged == true) 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | # We need to download lots of history so that 50 | # github-changelog can discover what's changed since the last release 51 | with: 52 | fetch-depth: 0 53 | ref: 'main' 54 | - uses: pnpm/action-setup@v4 55 | - uses: actions/setup-node@v4 56 | with: 57 | node-version: 18 58 | cache: pnpm 59 | - run: pnpm install --frozen-lockfile 60 | - name: "Generate Explanation and Prep Changelogs" 61 | id: explanation 62 | run: | 63 | set +e 64 | pnpm release-plan prepare 2> >(tee -a release-plan-stderr.txt >&2) 65 | 66 | if [ $? -ne 0 ]; then 67 | release_plan_output=$(cat release-plan-stderr.txt) 68 | else 69 | release_plan_output=$(jq .description .release-plan.json -r) 70 | rm release-plan-stderr.txt 71 | 72 | if [ $(jq '.solution | length' .release-plan.json) -eq 1 ]; then 73 | new_version=$(jq -r '.solution[].newVersion' .release-plan.json) 74 | echo "new_version=v$new_version" >> $GITHUB_OUTPUT 75 | fi 76 | fi 77 | echo 'text<> $GITHUB_OUTPUT 78 | echo "$release_plan_output" >> $GITHUB_OUTPUT 79 | echo 'EOF' >> $GITHUB_OUTPUT 80 | env: 81 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 82 | 83 | - uses: peter-evans/create-pull-request@v7 84 | with: 85 | commit-message: "Prepare Release ${{ steps.explanation.outputs.new_version}} using 'release-plan'" 86 | labels: "internal" 87 | branch: release-preview 88 | title: Prepare Release ${{ steps.explanation.outputs.new_version }} 89 | body: | 90 | This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍 91 | 92 | ----------------------------------------- 93 | 94 | ${{ steps.explanation.outputs.text }} 95 | -------------------------------------------------------------------------------- /addon/src/test-isolation-validation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as QUnit from 'qunit'; 3 | import { _cancelTimers as cancelTimers } from '@ember/runloop'; 4 | import { waitUntil, isSettled, getSettledState } from '@ember/test-helpers'; 5 | import { getDebugInfo } from '@ember/test-helpers'; 6 | 7 | /** 8 | * Detects if a specific test isn't isolated. A test is considered 9 | * not isolated if it: 10 | * 11 | * - has pending timers 12 | * - is in a runloop 13 | * - has pending AJAX requests 14 | * - has pending test waiters 15 | * 16 | * @function detectIfTestNotIsolated 17 | * @param {Object} testInfo 18 | * @param {string} testInfo.module The name of the test module 19 | * @param {string} testInfo.name The test name 20 | */ 21 | export function detectIfTestNotIsolated(test, message = '') { 22 | if (!isSettled()) { 23 | let { debugInfo } = getSettledState(); 24 | 25 | console.group(`${test.module.name}: ${test.testName}`); 26 | debugInfo.toConsole(); 27 | console.groupEnd(); 28 | 29 | test.expected++; 30 | test.assert.pushResult({ 31 | result: false, 32 | message: `${message} \nMore information has been printed to the console. Please use that information to help in debugging.\n\n`, 33 | }); 34 | } 35 | } 36 | 37 | /** 38 | * Installs a hook to detect if a specific test isn't isolated. 39 | * This hook is installed by patching into the `test.finish` method, 40 | * which allows us to be very precise as to when the detection occurs. 41 | * 42 | * @function installTestNotIsolatedHook 43 | * @param {number} delay the delay delay to use when checking for isolation validation 44 | */ 45 | export function installTestNotIsolatedHook(delay = 50) { 46 | if (!getDebugInfo()) { 47 | return; 48 | } 49 | 50 | let test = QUnit.config.current; 51 | let finish = test.finish; 52 | let pushFailure = test.pushFailure; 53 | 54 | test.pushFailure = function (message) { 55 | if (message.indexOf('Test took longer than') === 0) { 56 | detectIfTestNotIsolated(this, message); 57 | } else { 58 | return pushFailure.apply(this, arguments); 59 | } 60 | }; 61 | 62 | // We're hooking into `test.finish`, which utilizes internal ordering of 63 | // when a test's hooks are invoked. We do this mainly because we need 64 | // greater precision as to when to detect and subsequently report if the 65 | // test is isolated. 66 | // 67 | // We looked at using: 68 | // - `afterEach` 69 | // - the ordering of when the `afterEach` is called is not easy to guarantee 70 | // (ancestor `afterEach`es have to be accounted for too) 71 | // - `QUnit.on('testEnd')` 72 | // - is executed too late; the test is already considered done so 73 | // we're unable to push a new assert to fail the current test 74 | // - 'QUnit.done' 75 | // - it detaches the failure from the actual test that failed, making it 76 | // more confusing to the end user. 77 | test.finish = function () { 78 | let doFinish = () => finish.apply(this, arguments); 79 | 80 | if (isSettled()) { 81 | return doFinish(); 82 | } else { 83 | return waitUntil(isSettled, { timeout: delay }) 84 | .catch(() => { 85 | // we consider that when waitUntil times out, you're in a state of 86 | // test isolation violation. The nature of the error is irrelevant 87 | // in this case, and we want to allow the error to fall through 88 | // to the finally, where cleanup occurs. 89 | }) 90 | .finally(() => { 91 | detectIfTestNotIsolated( 92 | this, 93 | 'Test is not isolated (async execution is extending beyond the duration of the test).' 94 | ); 95 | 96 | // canceling timers here isn't perfect, but is as good as we can do 97 | // to attempt to prevent future tests from failing due to this test's 98 | // leakage 99 | cancelTimers(); 100 | 101 | return doFinish(); 102 | }); 103 | } 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /test-app/tests/acceptance/basic-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import EmberRouter from '@ember/routing/router'; 3 | import Route from '@ember/routing/route'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | import { setupApplicationTest } from 'ember-qunit'; 6 | import { 7 | visit, 8 | currentRouteName, 9 | currentURL, 10 | click, 11 | } from '@ember/test-helpers'; 12 | import { setResolverRegistry } from '../helpers/resolver'; 13 | 14 | module('setupApplicationTest tests', function (hooks) { 15 | class Router extends EmberRouter { 16 | location = 'none'; 17 | } 18 | 19 | Router.map(function () { 20 | this.route('widgets'); 21 | this.route('posts', function () { 22 | this.route('post', { path: ':post_id' }); 23 | }); 24 | }); 25 | 26 | setupApplicationTest(hooks); 27 | 28 | hooks.beforeEach(function () { 29 | setResolverRegistry(this.owner, { 30 | 'router:main': Router, 31 | 'template:application': hbs` 32 | 33 | {{outlet}} 34 | `, 35 | 'template:index': hbs`

Hello World!

`, 36 | 'template:posts': hbs`

Posts Page

{{outlet}}`, 37 | 'template:posts/post': hbs`
{{this.model.post_id}}
`, 38 | 'route:posts/post': class extends Route { 39 | model(params) { 40 | return params; 41 | } 42 | }, 43 | }); 44 | }); 45 | 46 | test('can render', async function (assert) { 47 | await visit('/'); 48 | 49 | assert.strictEqual(currentRouteName(), 'index'); 50 | assert.strictEqual(currentURL(), '/'); 51 | 52 | assert.strictEqual( 53 | this.element.querySelector('.nav').textContent, 54 | 'posts | widgets' 55 | ); 56 | assert.strictEqual( 57 | this.element.querySelector('h1').textContent, 58 | 'Hello World!' 59 | ); 60 | }); 61 | 62 | test('can perform a basic template rendering for nested route', async function (assert) { 63 | await visit('/posts/1'); 64 | 65 | assert.strictEqual(currentRouteName(), 'posts.post'); 66 | assert.strictEqual(currentURL(), '/posts/1'); 67 | 68 | assert.strictEqual( 69 | this.element.querySelector('.nav').textContent, 70 | 'posts | widgets' 71 | ); 72 | assert.strictEqual(this.element.querySelector('.post-id').textContent, '1'); 73 | }); 74 | 75 | test('can visit multiple times', async function (assert) { 76 | await visit('/posts/1'); 77 | 78 | assert.strictEqual(currentRouteName(), 'posts.post'); 79 | assert.strictEqual(currentURL(), '/posts/1'); 80 | 81 | assert.strictEqual( 82 | this.element.querySelector('.nav').textContent, 83 | 'posts | widgets' 84 | ); 85 | assert.strictEqual(this.element.querySelector('.post-id').textContent, '1'); 86 | 87 | await visit('/'); 88 | 89 | assert.strictEqual(currentRouteName(), 'index'); 90 | assert.strictEqual(currentURL(), '/'); 91 | 92 | assert.strictEqual( 93 | this.element.querySelector('.nav').textContent, 94 | 'posts | widgets' 95 | ); 96 | assert.strictEqual( 97 | this.element.querySelector('h1').textContent, 98 | 'Hello World!' 99 | ); 100 | 101 | await visit('/posts/2'); 102 | 103 | assert.strictEqual(currentRouteName(), 'posts.post'); 104 | assert.strictEqual(currentURL(), '/posts/2'); 105 | 106 | assert.strictEqual( 107 | this.element.querySelector('.nav').textContent, 108 | 'posts | widgets' 109 | ); 110 | assert.strictEqual(this.element.querySelector('.post-id').textContent, '2'); 111 | }); 112 | 113 | test('can navigate amongst routes', async function (assert) { 114 | await visit('/'); 115 | 116 | assert.strictEqual(currentRouteName(), 'index'); 117 | assert.strictEqual(currentURL(), '/'); 118 | 119 | await click('a[href="/posts"]'); 120 | 121 | assert.strictEqual(currentRouteName(), 'posts.index'); 122 | assert.strictEqual(currentURL(), '/posts'); 123 | 124 | assert.strictEqual( 125 | this.element.querySelector('h1').textContent, 126 | 'Posts Page' 127 | ); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /addon/src/index.js: -------------------------------------------------------------------------------- 1 | /* globals Testem */ 2 | import { macroCondition, getOwnConfig, importSync } from '@embroider/macros'; 3 | import { setTesting } from '@ember/debug'; 4 | import { setAdapter } from 'ember-testing/lib/test/adapter'; 5 | 6 | /** 7 | * Load qunit-default theme by default, if no custom theme is specified. 8 | */ 9 | if ( 10 | macroCondition( 11 | getOwnConfig()?.theme === undefined || 12 | getOwnConfig()?.theme === 'qunit-default' || 13 | getOwnConfig()?.theme === 'ember' 14 | ) 15 | ) { 16 | importSync('qunit/qunit/qunit.css'); 17 | } 18 | 19 | if (macroCondition(!getOwnConfig()?.disableContainerStyles)) { 20 | importSync('./test-container-styles.css'); 21 | } 22 | 23 | if (macroCondition(getOwnConfig()?.theme === 'ember')) { 24 | importSync('qunit-theme-ember/qunit.css'); 25 | } 26 | 27 | export { default as QUnitAdapter, nonTestDoneCallback } from './adapter'; 28 | 29 | import './qunit-configuration'; 30 | 31 | if (typeof Testem !== 'undefined') { 32 | Testem.hookIntoTestFramework(); 33 | } 34 | 35 | import { _backburner } from '@ember/runloop'; 36 | import { resetOnerror, getTestMetadata } from '@ember/test-helpers'; 37 | import * as QUnit from 'qunit'; 38 | import QUnitAdapter from './adapter'; 39 | import { 40 | setupContext, 41 | teardownContext, 42 | setupRenderingContext, 43 | setupApplicationContext, 44 | validateErrorHandler, 45 | } from '@ember/test-helpers'; 46 | import { installTestNotIsolatedHook } from './test-isolation-validation'; 47 | 48 | let waitForSettled = true; 49 | 50 | export function setupTest(hooks, _options) { 51 | let options = { waitForSettled, ..._options }; 52 | 53 | hooks.beforeEach(function (assert) { 54 | let testMetadata = getTestMetadata(this); 55 | testMetadata.framework = 'qunit'; 56 | 57 | return setupContext(this, options).then(() => { 58 | let originalPauseTest = this.pauseTest; 59 | this.pauseTest = function QUnit_pauseTest() { 60 | assert.timeout(-1); // prevent the test from timing out 61 | return originalPauseTest.call(this); 62 | }; 63 | }); 64 | }); 65 | 66 | hooks.afterEach(function () { 67 | return teardownContext(this, options); 68 | }); 69 | } 70 | 71 | export function setupRenderingTest(hooks, _options) { 72 | let options = { waitForSettled, ..._options }; 73 | 74 | setupTest(hooks, options); 75 | 76 | hooks.beforeEach(function () { 77 | return setupRenderingContext(this); 78 | }); 79 | } 80 | 81 | export function setupApplicationTest(hooks, _options) { 82 | let options = { waitForSettled, ..._options }; 83 | 84 | setupTest(hooks, options); 85 | 86 | hooks.beforeEach(function () { 87 | return setupApplicationContext(this); 88 | }); 89 | } 90 | 91 | /** 92 | Uses current URL configuration to setup the test container. 93 | 94 | * If `?nocontainer` is set, the test container will be hidden. 95 | * If `?devmode` or `?fullscreencontainer` is set, the test container will be 96 | made full screen. 97 | 98 | @method setupTestContainer 99 | */ 100 | export function setupTestContainer() { 101 | let testContainer = document.getElementById('ember-testing-container'); 102 | if (!testContainer) { 103 | return; 104 | } 105 | 106 | let params = QUnit.urlParams; 107 | 108 | if (params.devmode || params.fullscreencontainer) { 109 | testContainer.classList.add('ember-testing-container-full-screen'); 110 | } 111 | 112 | if (params.nocontainer) { 113 | testContainer.classList.add('ember-testing-container-hidden'); 114 | } 115 | } 116 | 117 | /** 118 | Instruct QUnit to start the tests. 119 | @method startTests 120 | */ 121 | export function startTests() { 122 | QUnit.start(); 123 | } 124 | 125 | /** 126 | Sets up the `Ember.Test` adapter for usage with QUnit 2.x. 127 | 128 | @method setupTestAdapter 129 | */ 130 | export function setupTestAdapter() { 131 | setAdapter(QUnitAdapter.create()); 132 | } 133 | 134 | /** 135 | Ensures that `Ember.testing` is set to `true` before each test begins 136 | (including `before` / `beforeEach`), and reset to `false` after each test is 137 | completed. This is done via `QUnit.testStart` and `QUnit.testDone`. 138 | 139 | */ 140 | export function setupEmberTesting() { 141 | QUnit.testStart(() => { 142 | setTesting(true); 143 | }); 144 | 145 | QUnit.testDone(() => { 146 | setTesting(false); 147 | }); 148 | } 149 | 150 | /** 151 | Ensures that `Ember.onerror` (if present) is properly configured to re-throw 152 | errors that occur while `Ember.testing` is `true`. 153 | */ 154 | export function setupEmberOnerrorValidation() { 155 | QUnit.module('ember-qunit: Ember.onerror validation', function () { 156 | QUnit.test('Ember.onerror is functioning properly', function (assert) { 157 | assert.expect(1); 158 | let result = validateErrorHandler(); 159 | assert.ok( 160 | result.isValid, 161 | `Ember.onerror handler with invalid testing behavior detected. An Ember.onerror handler _must_ rethrow exceptions when \`Ember.testing\` is \`true\` or the test suite is unreliable. See https://git.io/vbine for more details.` 162 | ); 163 | }); 164 | }); 165 | } 166 | 167 | export function setupResetOnerror() { 168 | QUnit.testDone(resetOnerror); 169 | } 170 | 171 | export function setupTestIsolationValidation(delay) { 172 | waitForSettled = false; 173 | _backburner.DEBUG = true; 174 | QUnit.on('testStart', () => installTestNotIsolatedHook(delay)); 175 | } 176 | 177 | /** 178 | @method start 179 | @param {Object} [options] Options to be used for enabling/disabling behaviors 180 | @param {Boolean} [options.setupTestContainer] If `false` the test container will not 181 | be setup based on `devmode`, `dockcontainer`, or `nocontainer` URL params. 182 | @param {Boolean} [options.startTests] If `false` tests will not be automatically started 183 | (you must run `QUnit.start()` to kick them off). 184 | @param {Boolean} [options.setupTestAdapter] If `false` the default Ember.Test adapter will 185 | not be updated. 186 | @param {Boolean} [options.setupEmberTesting] `false` opts out of the 187 | default behavior of setting `Ember.testing` to `true` before all tests and 188 | back to `false` after each test will. 189 | @param {Boolean} [options.setupTestIsolationValidation] If `false` test isolation validation 190 | will be disabled. 191 | @param {Number} [options.testIsolationValidationDelay] When using 192 | setupTestIsolationValidation this number represents the maximum amount of 193 | time in milliseconds that is allowed _after_ the test is completed for all 194 | async to have been completed. The default value is 50. 195 | */ 196 | export function start(options = {}) { 197 | if (options.setupTestContainer !== false) { 198 | setupTestContainer(); 199 | } 200 | 201 | if (options.setupTestAdapter !== false) { 202 | setupTestAdapter(); 203 | } 204 | 205 | if (options.setupEmberTesting !== false) { 206 | setupEmberTesting(); 207 | } 208 | 209 | if ( 210 | typeof options.setupTestIsolationValidation !== 'undefined' && 211 | options.setupTestIsolationValidation !== false 212 | ) { 213 | setupTestIsolationValidation(options.testIsolationValidationDelay); 214 | } 215 | 216 | if (options.startTests !== false) { 217 | startTests(); 218 | } 219 | 220 | setupResetOnerror(); 221 | } 222 | -------------------------------------------------------------------------------- /addon/README.md: -------------------------------------------------------------------------------- 1 | # ember-qunit 2 | 3 | [![Latest NPM release][npm-badge]][npm-badge-url] 4 | [![CI Build Status][ci-badge]][ci-badge-url] 5 | 6 | [npm-badge]: https://img.shields.io/npm/v/ember-qunit.svg 7 | [npm-badge-url]: https://www.npmjs.com/package/ember-qunit 8 | [ci-badge]: https://github.com/emberjs/ember-qunit/workflows/CI/badge.svg 9 | [ci-badge-url]: https://github.com/emberjs/ember-qunit/actions?query=workflow%3ACI 10 | 11 | ember-qunit simplifies testing of Ember applications with 12 | [QUnit](https://qunitjs.com/) by providing QUnit-specific wrappers around the 13 | helpers contained in 14 | [ember-test-helpers](https://github.com/emberjs/ember-test-helpers). 15 | 16 | 17 | Requirements 18 | ------------------------------------------------------------------------------ 19 | 20 | * Ember.js v4.0 or above 21 | * [Ember Auto Import](https://github.com/embroider-build/ember-auto-import) v2 or above (or Embroider) 22 | * Node.js v16 or above 23 | - TypeScript 4.8 and 4.9 24 | - SemVer policy: [simple majors](https://www.semver-ts.org/#simple-majors) 25 | - The public API is defined by the [Usage](#usage) section below. 26 | * ember-cli ~v4.8.1, ~v4.12.2, >= v5.1 27 | 28 | If you need support for Node 14 please use v6.2 of this addon. 29 | 30 | If you need support for Node 13 or older Ember CLI versions please use v4.x 31 | of this addon. 32 | 33 | 34 | Installation 35 | ------------------------------------------------------------------------------ 36 | 37 | `ember-qunit` is an [Ember CLI](http://www.ember-cli.com/) addon, so install it 38 | as you would any other addon: 39 | 40 | ```sh 41 | $ ember install ember-qunit 42 | ``` 43 | 44 | Some other addons are detecting the test framework based on the installed 45 | addon names and are expecting `ember-cli-qunit` instead. If you have issues 46 | with this then `ember install ember-cli-qunit`, which should work exactly 47 | the same. 48 | 49 | Upgrading 50 | ------------------------------------------------------------------------------ 51 | 52 | For instructions how to upgrade to the latest version, please read our 53 | [Migration Guide](docs/migration.md). 54 | 55 | Usage 56 | ------------------------------------------------------------------------------ 57 | 58 | The following section describes the use of ember-qunit with the latest modern 59 | Ember testing APIs, as laid out in the RFCs 60 | [232](https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md) 61 | and 62 | [268](https://github.com/emberjs/rfcs/blob/master/text/0268-acceptance-testing-refactor.md). 63 | 64 | For the older APIs have a look at our [Legacy Guide](docs/legacy.md). 65 | 66 | ### Setting the Application 67 | 68 | Your `tests/test-helper.js` file should look similar to the following, to 69 | correctly setup the application required by `@ember/test-helpers`: 70 | 71 | ```javascript 72 | import Application from '../app'; 73 | import config from '../config/environment'; 74 | import { setApplication } from '@ember/test-helpers'; 75 | import { start } from 'ember-qunit'; 76 | 77 | setApplication(Application.create(config.APP)); 78 | 79 | start(); 80 | ``` 81 | 82 | Also make sure that you have set `ENV.APP.autoboot = false;` for the `test` 83 | environment in your `config/environment.js`. 84 | 85 | ### Load Tests 86 | 87 | In environments that use the AMD Loader, `loadTests()` will need to be called 88 | before `start()`. 89 | 90 | ```javascript 91 | import Application from '../app'; 92 | import config from '../config/environment'; 93 | import { setApplication } from '@ember/test-helpers'; 94 | import { start } from 'ember-qunit'; 95 | import { loadTests } from 'ember-qunit/test-loader'; 96 | 97 | setApplication(Application.create(config.APP)); 98 | 99 | loadTests() 100 | start(); 101 | ``` 102 | 103 | In ESM environments, this isn't needed as import.meta.glob can load test files. 104 | 105 | ### Setup Tests 106 | 107 | The `setupTest()` function can be used to setup a unit test for any kind 108 | of "module/unit" of your application that can be looked up in a container. 109 | 110 | It will setup your test context with: 111 | 112 | * `this.owner` to interact with Ember's [Dependency Injection](https://guides.emberjs.com/v3.0.0/applications/dependency-injection/) 113 | system 114 | * `this.set()`, `this.setProperties()`, `this.get()`, and `this.getProperties()` 115 | * `this.pauseTest()` method to allow easy pausing/resuming of tests 116 | 117 | For example, the following is a unit test for the `SidebarController`: 118 | 119 | ```javascript 120 | import { module, test } from 'qunit'; 121 | import { setupTest } from 'ember-qunit'; 122 | 123 | module('SidebarController', function(hooks) { 124 | setupTest(hooks); 125 | 126 | // Replace this with your real tests. 127 | test('exists', function() { 128 | let controller = this.owner.lookup('controller:sidebar'); 129 | assert.ok(controller); 130 | }); 131 | }); 132 | ``` 133 | 134 | 135 | ### Setup Rendering Tests 136 | 137 | The `setupRenderingTest()` function is specifically designed for tests that 138 | render arbitrary templates, including components and helpers. 139 | 140 | It will setup your test context the same way as `setupTest()`, and additionally: 141 | 142 | * Initializes Ember's renderer to be used with the 143 | [Rendering helpers](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#rendering-helpers), 144 | specifically `render()` 145 | * sets up the [DOM Interaction Helpers](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#dom-interaction-helpers) 146 | from `@ember/test-helpers` (`click()`, `fillIn()`, ...) 147 | 148 | ```javascript 149 | import { module, test } from 'qunit'; 150 | import { setupRenderingTest } from 'ember-qunit'; 151 | import { render, find } from '@ember/test-helpers'; 152 | import { hbs } from 'ember-cli-htmlbars'; 153 | 154 | module('GravatarImageComponent', function(hooks) { 155 | setupRenderingTest(hooks); 156 | 157 | test('renders', async function() { 158 | await render(hbs`{{gravatar-image}}`); 159 | assert.ok(find('img')); 160 | }); 161 | }); 162 | ``` 163 | 164 | ### Setup Application Tests 165 | 166 | The `setupApplicationTest()` function can be used to run tests that interact 167 | with the whole application, so in most cases acceptance tests. 168 | 169 | On top of `setupTest()` it will: 170 | 171 | * Boot your application instance 172 | * Set up all the [DOM Interaction Helpers](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#dom-interaction-helpers) 173 | (`click()`, `fillIn()`, ...) as well as the [Routing Helpers](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#routing-helpers) 174 | (`visit()`, `currentURL()`, ...) from `@ember/test-helpers` 175 | 176 | ```javascript 177 | import { module, test } from 'qunit'; 178 | import { setupApplicationTest } from 'ember-qunit'; 179 | import { visit, currentURL } from '@ember/test-helpers'; 180 | 181 | module('basic acceptance test', function(hooks) { 182 | setupApplicationTest(hooks); 183 | 184 | test('can visit /', async function(assert) { 185 | await visit('/'); 186 | assert.equal(currentURL(), '/'); 187 | }); 188 | }); 189 | ``` 190 | 191 | ## Configuration 192 | 193 | Configuration is optionally managed via [`@embroider/macros`](https://www.npmjs.com/package/@embroider/macros) 194 | 195 | To configure `ember-qunit`, in your `ember-cli-build.js`, add: 196 | ```js 197 | 'use strict'; 198 | 199 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 200 | 201 | module.exports = function (defaults) { 202 | const app = new EmberApp(defaults, { 203 | '@embroider/macros': { 204 | setConfig: { 205 | 'ember-qunit': { 206 | /** 207 | * default: false 208 | * 209 | * removes the CSS for the test-container (where the app and components are rendered to) 210 | */ 211 | disableContainerStyles: true, 212 | /** 213 | * default: 'qunit-default' 214 | * options: 'qunit-default' | 'ember' 215 | * 216 | * Sets the theme for the Web UI of the test runner. Use a different value to disable loading any theme, allowing you to provide your own external one. 217 | */ 218 | theme: 'qunit-default', 219 | }, 220 | }, 221 | }, 222 | /* ... */ 223 | }); 224 | 225 | /* ... */ 226 | }; 227 | ``` 228 | 229 | 230 | Contributing 231 | ------------------------------------------------------------------------------ 232 | 233 | ### Installation 234 | 235 | * `git clone ` 236 | * `cd ember-qunit` 237 | * `pnpm install` 238 | 239 | ### Running tests 240 | 241 | * `pnpm test` (Runs `ember try:each` to test your addon against multiple Ember versions) 242 | * `ember test` 243 | * `ember test --server` 244 | 245 | ### Running the dummy application 246 | 247 | * `ember serve` 248 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 249 | 250 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 251 | -------------------------------------------------------------------------------- /docs/legacy.md: -------------------------------------------------------------------------------- 1 | 2 | Legacy Guide 3 | ============================================================================== 4 | 5 | This document describes the use of `ember-qunit` with Ember's legacy testing 6 | APIs, which have been superseded by the newer testing system based on the RFCs 7 | [232](https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md) 8 | and 9 | [268](https://github.com/emberjs/rfcs/blob/master/text/0268-acceptance-testing-refactor.md). 10 | 11 | ## Usage 12 | 13 | - [Component Integration Tests](#component-integration-tests) 14 | - [Component Unit Tests](#component-unit-tests) 15 | - [Other Tests](#other-tests) 16 | - [Ember Data Tests](#ember-data-tests) 17 | 18 | ### Component Integration Tests 19 | 20 | ```js 21 | import { hbs } from 'ember-cli-htmlbars'; 22 | import { test, moduleForComponent } from 'ember-qunit'; 23 | 24 | moduleForComponent('x-foo', { 25 | integration: true 26 | }); 27 | 28 | test('it renders', function(assert) { 29 | assert.expect(2); 30 | 31 | // setup the outer context 32 | this.set('value', 'cat'); 33 | this.on('action', function(result) { 34 | assert.equal(result, 'bar', 'The correct result was returned'); 35 | }); 36 | 37 | // render the component 38 | this.render(hbs` 39 | {{ x-foo value=value action="result" }} 40 | `); 41 | 42 | assert.equal(this.$('div>.value').text(), 'cat', 'The component shows the correct value'); 43 | 44 | this.$('button').click(); 45 | }); 46 | ``` 47 | 48 | Component integration tests are the default mode for `moduleForComponent`. You can still explicitly activate them by passing `integration: true`. 49 | 50 | Integration tests have the advantage of testing your component as Ember would actually use them. It's helpful to think of this mode as simply testing the inputs and outputs of the component. These tests allow you interact with both the bound values that are passed into the component as well as its resulting actions. 51 | 52 | Component integration tests have the following features: 53 | - Your test context `this` acts as the outer context for the component. As a result, you can call `this.set` and `this.on` to setup values and event listeners that you can then have interact with the component. 54 | - You are required to render the component as a template, e.g. ``this.render(hbs`{{ your-component-name value=value action="updated" }}`)``. You can render other components as well as block content. 55 | - All of the normal Ember lifecycle hooks for a component are called (including the new ones from 1.13.x). 56 | - Testing the component's template is through `this.$()`. 57 | - You do not require dependencies through `needs:`. Doing so will force the test into unit mode. 58 | - You do not have direct access to the component instance. (`this.subject()` will raise an exception). 59 | 60 | ### Component Unit Tests 61 | 62 | [Ember Guide](http://guides.emberjs.com/v1.13.0/testing/testing-components/) 63 | 64 | ```js 65 | import { test, moduleForComponent } from 'ember-qunit'; 66 | 67 | moduleForComponent('x-foo', { 68 | unit: true, 69 | needs: ['helper:pluralize-string'] 70 | }); 71 | 72 | // run a test 73 | test('it renders', function(assert) { 74 | assert.expect(1); 75 | 76 | // creates the component instance 77 | var subject = this.subject(); 78 | 79 | // render the component on the page 80 | this.render(); 81 | assert.equal(this.$('.foo').text(), 'bar'); 82 | }); 83 | ``` 84 | 85 | Unit tests used to be the default mode for component tests. To flag a test as a unit test, either specify `unit: true` or include `needs: []` in the callbacks object. 86 | 87 | Unit tests have the advantage of giving you direct access to the component instance so you can test its internals. Unit tests have the following features: 88 | 89 | - You have access to the component instance through `this.subject()`. 90 | - If you want to render the component's template, call either `this.render()` or `this.$()`. 91 | - Testing the component's template is through `this.$()`. 92 | - You are required to specify any dependencies other than the component's template in the `needs: []` option. This includes helpers, services, partials, and any other components (with their templates) that are referenced. 93 | - Unit tests do not call most of the Ember lifecycle hooks. `didInsertElement` and `willDestroyElement` will be called, but the remaining hooks introduced in Ember 1.13.x will not be. 94 | - There is no outer context for the component so testing things such as actions will require directly stubbing the actions on the component. 95 | 96 | ### Other Tests 97 | 98 | [Controllers Guide](http://guides.emberjs.com/v1.13.0/testing/testing-controllers/) 99 | 100 | [Routes Guide](http://guides.emberjs.com/v1.13.0/testing/testing-routes/) 101 | 102 | ```js 103 | import { test, moduleFor } from 'ember-qunit'; 104 | 105 | moduleFor('controller:home'); 106 | 107 | test('It can calculate the result', function(assert) { 108 | assert.expect(1); 109 | 110 | var subject = this.subject(); 111 | 112 | subject.set('value', 'foo'); 113 | assert.equal(subject.get('result'), 'bar'); 114 | }); 115 | ``` 116 | 117 | `moduleFor` works for any object you could look up with the Ember Resolver (service, routes, controllers, etc.). 118 | 119 | Note: Controllers / Routes do not have access to rendering. You will need to either use a component test or an acceptance test. 120 | 121 | ### Ember Data Tests 122 | 123 | [Ember Guide](http://guides.emberjs.com/v1.13.0/testing/testing-models/) 124 | 125 | ```js 126 | import { test, moduleForModel } from 'ember-qunit'; 127 | 128 | moduleForModel('user', { 129 | needs: ['model:child'] 130 | }); 131 | 132 | test('It can set its child', function(assert) { 133 | assert.expect(1); 134 | var subject = this.subject(); 135 | 136 | var child = subject.store.createRecord('child'); 137 | subject.get('children').pushObject(child); 138 | 139 | assert.equal(subject.get('some-computed-value'), true); 140 | }); 141 | ``` 142 | 143 | ## Advanced Usage 144 | ### Setting the resolver 145 | 146 | ```js 147 | // if you don't have a custom resolver, do it like this: 148 | setResolver(Ember.DefaultResolver.create({ namespace: App })); 149 | 150 | // otherwise something like: 151 | import Resolver from './path/to/resolver'; 152 | import { setResolver } from 'ember-qunit'; 153 | setResolver(Resolver.create()); 154 | ``` 155 | 156 | ### Async Example 157 | 158 | Under the hood, if you use `Ember.RSVP.Promise`, ember-qunit will call 159 | QUnit's `start` and `stop` helpers to stop the test from tearing down 160 | and running other tests while your asynchronous code runs. ember-qunit 161 | also asserts that the promise gets fulfilled. 162 | 163 | In addition, you can also return promises in the test body: 164 | 165 | ```js 166 | // If you return a promise from a test callback it becomes an asyncTest. This 167 | // is a key difference between ember-qunit and standard QUnit. 168 | test('async is awesome', function(assert) { 169 | assert.expect(1); 170 | var myThing = MyThing.create(); 171 | // myThing.exampleMethod() returns a promise 172 | return myThing.exampleMethod().then(function() { 173 | assert.ok(myThing.get('finished')); 174 | }); 175 | }); 176 | ``` 177 | 178 | If an error is thrown in your promise or a promise 179 | within `test` becomes rejected, ember-qunit will fail the test. 180 | To assert that a promise should be rejected, you can "catch" 181 | the error and assert that you got there: 182 | 183 | ```js 184 | test('sometimes async gets rejected', function(assert) { 185 | assert.expect(1); 186 | var myThing = MyThing.create() 187 | 188 | return myThing.exampleMethod().then(function() { 189 | assert.ok(false, "promise should not be fulfilled"); 190 | })['catch'](function(err) { 191 | assert.equal(err.message, "User not Authorized"); 192 | }); 193 | }); 194 | ``` 195 | 196 | ## Test Helpers 197 | 198 | ### `moduleFor(fullName [, description [, callbacks]])` 199 | 200 | - `fullName`: (String) - The full name of the unit, ie 201 | `controller:application`, `route:index`. 202 | 203 | - `description`: (String) optional - The description of the module 204 | 205 | - `callbacks`: (Object) optional 206 | - QUnit callbacks (`beforeEach` and `afterEach`) 207 | - ember-test-helpers callback (`subject`) 208 | - `integration: true` or `unit: true` (default: `integration: true`) 209 | - `needs` specify any dependencies the tested module will require. 210 | 211 | ### `moduleForComponent(name, [description, callbacks])` 212 | 213 | - `name`: (String) - the short name of the component that you'd use in a 214 | template, ie `x-foo`, `ic-tabs`, etc. 215 | 216 | - `description`: (String) optional - The description of the module 217 | 218 | - `callbacks`: (Object) optional 219 | - QUnit callbacks (`beforeEach` and `afterEach`) 220 | - ember-test-helpers callback (`subject`) 221 | - `integration: true` or `unit: true` (default: `integration: true`) 222 | - `needs` specify any dependencies the tested module will require. (Including this will force your test into unit mode). 223 | 224 | ### `moduleForModel(name, [description, callbacks])` 225 | 226 | - `name`: (String) - the short name of the model you'd use in `store` 227 | operations ie `user`, `assignmentGroup`, etc. 228 | 229 | - `description`: (String) optional - The description of the module 230 | 231 | - `callbacks`: (Object) optional 232 | - QUnit callbacks (`beforeEach` and `afterEach`) 233 | - ember-test-helpers callback (`subject`) 234 | - `integration: true` or `unit: true` (default: `integration: true`) 235 | - `needs` specify any dependencies the tested module will require. 236 | -------------------------------------------------------------------------------- /test-app/types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for ember-qunit 5.0 2 | // Project: https://github.com/emberjs/ember-qunit#readme 3 | // Definitions by: Dan Freeman 4 | // Chris Krycho 5 | // James C. Davis 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | // Minimum TypeScript Version: 4.4 8 | 9 | import EmberTestAdapter from '@ember/test/adapter'; 10 | import { Resolver } from '@ember/owner'; 11 | import { TestContext } from '@ember/test-helpers'; 12 | 13 | /** 14 | * Sets a Resolver globally which will be used to look up objects from each test's container. 15 | */ 16 | export function setResolver(resolver: Resolver): void; 17 | 18 | /** 19 | * Options for configuring the test runner. Normally, you will not need to 20 | * customize this. It is exported primarily so that end user app code can name 21 | * it when passing it back to the framework. 22 | */ 23 | export interface SetupTestOptions { 24 | /** 25 | * The resolver to use when instantiating container-managed entities in the test. 26 | */ 27 | resolver?: Resolver | undefined; 28 | } 29 | 30 | /** 31 | * Sets up acceptance tests. 32 | * 33 | * The `setupApplicationTest` function is used for all acceptance tests. It 34 | * is invoked in the callback scope of a QUnit module (aka "nested module"). 35 | * 36 | * Once invoked, all subsequent hooks.beforeEach and test invocations will 37 | * have access to the following: 38 | * * `this.owner` - the owner object that been set on the test context. 39 | * * `this.pauseTest` and `this.resumeTest` - allow easy pausing/resuming of tests. 40 | * * `this.element` which returns the DOM element representing the application's root element. 41 | */ 42 | export function setupApplicationTest( 43 | hooks: NestedHooks, 44 | options?: SetupTestOptions 45 | ): void; 46 | 47 | /** 48 | * Sets up tests that need to render snippets of templates. 49 | * 50 | * The setupRenderingTest method is used for tests that need to render 51 | * snippets of templates. It is also invoked in the callback scope of a 52 | * QUnit module (aka "nested module"). 53 | * 54 | * Once invoked, all subsequent hooks.beforeEach and test invocations will 55 | * have access to the following: 56 | * * All of the methods / properties listed for `setupTest` 57 | * * this.render(...) - Renders the provided template snippet returning a 58 | * promise that resolves once rendering has completed 59 | * * An importable render function that de-sugars into this.render will be 60 | * the default output of blueprints 61 | * * this.element - Returns the native DOM element representing the element 62 | * that was rendered via this.render 63 | * * this.$(...) - When jQuery is present, executes a jQuery selector with 64 | * the current this.element as its root 65 | */ 66 | export function setupRenderingTest( 67 | hooks: NestedHooks, 68 | options?: SetupTestOptions 69 | ): void; 70 | 71 | /** 72 | * Sets up tests that do not need to render snippets of templates. 73 | * 74 | * The `setupTest` method is used for all types of tests except for those 75 | * that need to render snippets of templates. It is invoked in the callback 76 | * scope of a QUnit module (aka "nested module"). 77 | * 78 | * Once invoked, all subsequent hooks.beforeEach and test invocations will 79 | * have access to the following: 80 | * * this.owner - This exposes the standard "owner API" for the test environment. 81 | * * this.set / this.setProperties - Allows setting values on the test context. 82 | * * this.get / this.getProperties - Retrieves values from the test context. 83 | */ 84 | export function setupTest(hooks: NestedHooks, options?: SetupTestOptions): void; 85 | 86 | export class QUnitAdapter extends EmberTestAdapter {} 87 | 88 | export { module, test, skip, only, todo } from 'qunit'; 89 | 90 | export function setupEmberOnerrorValidation(): void; 91 | 92 | interface QUnitStartOptions { 93 | /** 94 | * If `false` tests will not be loaded automatically. 95 | */ 96 | loadTests?: boolean | undefined; 97 | 98 | /** 99 | * If `false` the test container will not be setup based on `devmode`, 100 | * `dockcontainer`, or `nocontainer` URL params. 101 | */ 102 | setupTestContainer?: boolean | undefined; 103 | 104 | /** 105 | * If `false` tests will not be automatically started (you must run 106 | * `QUnit.start()` to kick them off). 107 | */ 108 | startTests?: boolean | undefined; 109 | 110 | /** 111 | * If `false` the default Ember.Test adapter will not be updated. 112 | */ 113 | setupTestAdapter?: boolean | undefined; 114 | 115 | /** 116 | * `false` opts out of the default behavior of setting `Ember.testing` 117 | * to `true` before all tests and back to `false` after each test will. 118 | */ 119 | setupEmberTesting?: boolean | undefined; 120 | 121 | /** 122 | * If `false` test isolation validation will be disabled. 123 | */ 124 | setupTestIsolationValidation?: boolean | undefined; 125 | } 126 | 127 | export function start(options?: QUnitStartOptions): void; 128 | 129 | // SAFETY: all of the `TC extends TestContext` generics below are just wildly, 130 | // impossibly unsafe. QUnit cannot -- ever! -- guarantee that the test context 131 | // is properly set up in a type-safe way to match this. However, it is the only 132 | // way to handle setting state in a TS-visible way prior to Ember RFC 0785, 133 | // which is slooooowly rolling out across the ecosystem in conjunction with the 134 | // `