-
-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement testing RFC #1211
Merged
rwjblue
merged 23 commits into
emberjs:master
from
cafreeman:cfreeman/implement-testing-rfc
Apr 21, 2022
Merged
Implement testing RFC #1211
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
9128019
add `rerender` helper
cafreeman e8ad8ba
render can now accept a component
cafreeman 25457a3
handle ember version for component manager
cafreeman 2324627
cleanup + more render tests
cafreeman d0767e9
streamline renderSettled ponyfill
cafreeman 3abf41f
backward compatibility + more tests
cafreeman d9f1529
more render tests
cafreeman b18fddb
add render pending to settled state
cafreeman 49d28f5
more backward compat for render
cafreeman d784f15
fix SummaryInfo type
cafreeman 386b47a
more specific versions in ponyfill files
cafreeman db471ec
even more backward compat
cafreeman c876c94
add rerender test
cafreeman f1adf7d
specify version for component manager
cafreeman 27e9e56
add warning when calling this.set with component
cafreeman ad5cd23
Update addon-test-support/@ember/test-helpers/rerender.ts
cafreeman 90cef79
Apply suggestions from code review
cafreeman bdf695f
upgrade @embroider/macros
cafreeman 8d935fb
PR feedback
cafreeman a749f81
throw when using this.set with a component
cafreeman 2c6b47f
update set assertion
cafreeman 1fab9a9
more assertions for setting on test context
cafreeman baa2bc9
add note isComponent helper in docs
cafreeman File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
addon-test-support/@ember/test-helpers/-internal/get-component-manager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Ember from 'ember'; | ||
import { | ||
macroCondition, | ||
importSync, | ||
dependencySatisfies, | ||
} from '@embroider/macros'; | ||
import type { InternalComponentManager } from '@glimmer/interfaces'; | ||
|
||
let getComponentManager: ( | ||
definition: object, | ||
owner: object | ||
) => InternalComponentManager | null; | ||
|
||
if (macroCondition(dependencySatisfies('ember-source', '>=3.27.0-alpha.1'))) { | ||
let _getComponentManager = | ||
//@ts-ignore | ||
importSync('@glimmer/manager').getInternalComponentManager; | ||
|
||
getComponentManager = (definition: object, owner: object) => { | ||
return _getComponentManager(definition, true); | ||
}; | ||
} else if ( | ||
macroCondition(dependencySatisfies('ember-source', '>=3.25.0-beta.1')) | ||
) { | ||
let _getComponentManager = (Ember as any).__loader.require( | ||
'@glimmer/manager' | ||
).getInternalComponentManager; | ||
|
||
getComponentManager = (definition: object, owner: object) => { | ||
return _getComponentManager(definition, true); | ||
}; | ||
} else { | ||
let _getComponentManager = (Ember as any).__loader.require( | ||
'@glimmer/runtime' | ||
).getComponentManager; | ||
|
||
getComponentManager = (definition: object, owner: object) => { | ||
return _getComponentManager(owner, definition); | ||
}; | ||
} | ||
|
||
export default getComponentManager; |
26 changes: 26 additions & 0 deletions
26
addon-test-support/@ember/test-helpers/-internal/is-component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { macroCondition, dependencySatisfies } from '@embroider/macros'; | ||
|
||
import getComponentManager from './get-component-manager'; | ||
|
||
/** | ||
* We should ultimately get a new API from @glimmer/runtime that provides this functionality | ||
* (see https://github.com/emberjs/rfcs/pull/785 for more info). | ||
* @private | ||
* @param {Object} maybeComponent The thing you think might be a component | ||
* @param {Object} owner Owner, we need this for old versions of getComponentManager | ||
* @returns {boolean} True if it's a component, false if not | ||
*/ | ||
function isComponent(maybeComponent: object, owner: object): boolean { | ||
if (macroCondition(dependencySatisfies('ember-source', '>=3.25.0-beta.1'))) { | ||
return !!getComponentManager(maybeComponent, owner); | ||
} else { | ||
return ( | ||
!!getComponentManager(maybeComponent, owner) || | ||
['@ember/component', '@ember/component/template-only'].includes( | ||
maybeComponent.toString() | ||
) | ||
); | ||
} | ||
} | ||
|
||
export default isComponent; |
31 changes: 31 additions & 0 deletions
31
addon-test-support/@ember/test-helpers/-internal/render-settled.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Ember from 'ember'; | ||
import { | ||
macroCondition, | ||
importSync, | ||
dependencySatisfies, | ||
} from '@embroider/macros'; | ||
|
||
let renderSettled: () => Promise<void>; | ||
|
||
// TODO need to add the actual version `@ember/renderer` landed once we know it | ||
if (macroCondition(dependencySatisfies('ember-source', '>=4.9999999.0'))) { | ||
//@ts-ignore | ||
renderSettled = importSync('@ember/renderer').renderSettled; | ||
} else if ( | ||
macroCondition(dependencySatisfies('ember-source', '>=3.27.0-alpha.1')) | ||
) { | ||
//@ts-ignore | ||
renderSettled = importSync('@ember/-internals/glimmer').renderSettled; | ||
} else if ( | ||
macroCondition(dependencySatisfies('ember-source', '>=3.6.0-alpha.1')) | ||
) { | ||
renderSettled = (Ember as any).__loader.require( | ||
'@ember/-internals/glimmer' | ||
).renderSettled; | ||
} else { | ||
renderSettled = (Ember as any).__loader.require( | ||
'ember-glimmer' | ||
).renderSettled; | ||
} | ||
|
||
export default renderSettled; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import renderSettled from './-internal/render-settled'; | ||
|
||
/** | ||
Returns a promise which will resolve when rendering has completed. In | ||
this context, rendering is completed when all auto-tracked state that is | ||
consumed in the template (including any tracked state in models, services, | ||
etc. that are then used in a template) has been updated in the DOM. | ||
|
||
For example, in a test you might want to update some tracked state and | ||
then run some assertions after rendering has completed. You _could_ use | ||
`await settled()` in that location, but in some contexts you don't want to | ||
wait for full settledness (which includes test waiters, pending AJAX/fetch, | ||
run loops, etc) but instead only want to know when that updated value has | ||
been rendered in the DOM. **THAT** is what `await rerender()` is _perfect_ | ||
for. | ||
@public | ||
@returns {Promise<void>} a promise which fulfills when rendering has completed | ||
*/ | ||
export default function rerender() { | ||
return renderSettled(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,18 @@ import { hbs, TemplateFactory } from 'ember-cli-htmlbars'; | |
import getRootElement from './dom/get-root-element'; | ||
import { Owner } from './build-owner'; | ||
import getTestMetadata, { ITestMetadata } from './test-metadata'; | ||
import { deprecate } from '@ember/debug'; | ||
import { assert, deprecate } from '@ember/debug'; | ||
import { runHooks } from './-internal/helper-hooks'; | ||
import hasEmberVersion from './has-ember-version'; | ||
import isComponent from './-internal/is-component'; | ||
import { macroCondition, dependencySatisfies } from '@embroider/macros'; | ||
import { ComponentRenderMap, SetUsage } from './setup-context'; | ||
import type { ComponentInstance } from '@glimmer/interfaces'; | ||
|
||
const OUTLET_TEMPLATE = hbs`{{outlet}}`; | ||
const EMPTY_TEMPLATE = hbs``; | ||
const INVOKE_PROVIDED_COMPONENT = hbs`<this.ProvidedComponent />`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chriskrycho here is this mystic |
||
const DYNAMIC_INVOKE_PROVIDED_COMPONENT = hbs`{{component this.ProvidedComponent}}`; | ||
|
||
export interface RenderingTestContext extends TestContext { | ||
render(template: TemplateFactory): Promise<void>; | ||
|
@@ -84,17 +90,17 @@ export interface RenderOptions { | |
Renders the provided template and appends it to the DOM. | ||
|
||
@public | ||
@param {CompiledTemplate} template the template to render | ||
@param {Template|Component} templateOrComponent the component (or template) to render | ||
@param {RenderOptions} options options hash containing engine owner ({ owner: engineOwner }) | ||
@returns {Promise<void>} resolves when settled | ||
*/ | ||
export function render( | ||
template: TemplateFactory, | ||
templateOrComponent: TemplateFactory | ComponentInstance, | ||
options?: RenderOptions | ||
): Promise<void> { | ||
let context = getContext(); | ||
|
||
if (!template) { | ||
if (!templateOrComponent) { | ||
throw new Error('you must pass a template to `render()`'); | ||
} | ||
|
||
|
@@ -115,9 +121,79 @@ export function render( | |
let OutletTemplate = lookupOutletTemplate(owner); | ||
let ownerToRenderFrom = options?.owner || owner; | ||
|
||
templateId += 1; | ||
let templateFullName = `template:-undertest-${templateId}`; | ||
ownerToRenderFrom.register(templateFullName, template); | ||
if (macroCondition(dependencySatisfies('ember-source', '<3.24.0'))) { | ||
// Pre 3.24, we just don't support rendering components at all, so we error | ||
// if we find anything that isn't a template. | ||
const isTemplate = | ||
('__id' in templateOrComponent && '__meta' in templateOrComponent) || | ||
('id' in templateOrComponent && 'meta' in templateOrComponent); | ||
|
||
if (!isTemplate) { | ||
throw new Error( | ||
`Using \`render\` with something other than a pre-compiled template is not supported until Ember 3.24 (you are on ${Ember.VERSION}).` | ||
); | ||
} | ||
|
||
templateId += 1; | ||
let templateFullName = `template:-undertest-${templateId}`; | ||
ownerToRenderFrom.register(templateFullName, templateOrComponent); | ||
templateOrComponent = lookupTemplate( | ||
ownerToRenderFrom, | ||
templateFullName | ||
); | ||
} else { | ||
if (isComponent(templateOrComponent, owner)) { | ||
// We use this to track when `render` is used with a component so that we can throw an | ||
// assertion if `this.{set,setProperty} is used in the same test | ||
ComponentRenderMap.set(context, true); | ||
|
||
rwjblue marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const setCalls = SetUsage.get(context); | ||
|
||
if (setCalls !== undefined) { | ||
assert( | ||
`You cannot call \`this.set\` or \`this.setProperties\` when passing a component to \`render\`, but they were called for the following properties:\n${setCalls | ||
.map((key) => ` - ${key}`) | ||
.join('\n')}` | ||
); | ||
} | ||
|
||
if ( | ||
macroCondition( | ||
dependencySatisfies('ember-source', '>=3.25.0-beta.1') | ||
) | ||
) { | ||
// In 3.25+, we can treat components as one big object and just pass them around/invoke them | ||
// wherever, so we just assign the component to the `ProvidedComponent` property and invoke it | ||
// in the test's template | ||
context = { | ||
ProvidedComponent: templateOrComponent, | ||
}; | ||
templateOrComponent = INVOKE_PROVIDED_COMPONENT; | ||
} else { | ||
// Below 3.25, however, we *cannot* treat components as one big object and instead have to | ||
// register their class and template independently and then invoke them with the `component` | ||
// helper so they can actually be found by the resolver and rendered | ||
templateId += 1; | ||
let name = `-undertest-${templateId}`; | ||
let componentFullName = `component:${name}`; | ||
let templateFullName = `template:components/${name}`; | ||
context = { | ||
ProvidedComponent: name, | ||
}; | ||
ownerToRenderFrom.register(componentFullName, templateOrComponent); | ||
templateOrComponent = DYNAMIC_INVOKE_PROVIDED_COMPONENT; | ||
ownerToRenderFrom.register(templateFullName, templateOrComponent); | ||
} | ||
} else { | ||
templateId += 1; | ||
let templateFullName = `template:-undertest-${templateId}`; | ||
ownerToRenderFrom.register(templateFullName, templateOrComponent); | ||
templateOrComponent = lookupTemplate( | ||
ownerToRenderFrom, | ||
templateFullName | ||
); | ||
} | ||
} | ||
|
||
let outletState = { | ||
render: { | ||
|
@@ -139,7 +215,7 @@ export function render( | |
name: 'index', | ||
controller: context, | ||
ViewClass: undefined, | ||
template: lookupTemplate(ownerToRenderFrom, templateFullName), | ||
template: templateOrComponent, | ||
outlets: {}, | ||
}, | ||
outlets: {}, | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆 🤣 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, now that the Ember change has landed for realz let's update this to 4.5.0-beta.1.