@ember/test-helpers render() style integration tests currently not possible
izelnakri opened this issue · 2 comments
Hi there,
I've been experimenting with implementing emberx/router
and emberx/test-helper
library on top of glimmerx
:
https://github.com/izelnakri/emberx
Currently there is an interesting roadblock for implementing readable and maintanable ember.js like import { render } from 'emberx/test-helpers'
, it could be a limitation of the current API. I'll explain the situation below, feel free to let me know if there are ways to implement this behavior, currently the documentation links are down.
In case if it doesn't exist, I would appreciate if we could implement it. Because otherwise it is currently not possible for one to write maintainable integration tests for glimmerx
when they npm their way to ember from glimmer
:
Intention:
import { module, test } from 'qunit';
import { render } from 'emberx/test-helpers';
import { setupRenderingTest } from 'emberx/test-helpers/setup';
import LinkTo from '../../src/components/LinkTo'; // NOTE: imported component to test
module('Integration | <LinkTo />', function (hooks) {
setupRenderingTest(hooks);
test('it works for a basic route without params', async function (assert) {
await render('<LinkTo @route="public.index">Go to homepage</LinkTo>', LinkTo); // NOTE: currently 2nd argument needed unfortunately, explained below
assert.dom('.ember-testing a').hasText('Go to homepage');
});
});
Implementation and the roadblock(s):
// in package: emberx/src/test-helpers/index.js:
import GlimmerComponent, { hbs } from '@glimmerx/component';
import { renderComponent } from '@glimmerx/core';
import { getContext } from './context';
export function render(
template: string,
component: GlimmerComponent, // NOTE: normally we shouldn't need it if we could build an existing Component registry
services: object | undefined
): Promise<any> {
const context = getContext(); // NOTE: this just returns 'this' context of the test where its used
const targetServices = context.services || services; // NOTE: this render API should be usable without implicit resolvers, thus needs an explicit way to set services context
// testing something out:
window.hbs = hbs`<LinkTo @route="something">Another</LinkTo>`; // NOTE: hbs is just a token currently, a precompile transpiler token, a babel plugin/addon
// thus no way to get an analysis of the dynamically provided template string in the `render(template)` argument.
// we need that/or another function to return an AST with passed arguments,
// yield references as JS object when an hbs string is provided.
// Imported components inside the strings should be tokenized/included in the AST as well, somehow...
console.log('template is', template); // this only gets transpiled when provided as hbs`` in the source file,
// so as a string it loses the ComponentDefinition reference when its passed from another file,
// also imported Components inside this string miss their references, arguments, yielded state.
// TODO: in other words we need to figure out a way to serialize template strings with passed in internal components, arguments and yielded context.
return renderComponent(component, {
element: document.getElementById('ember-testing'),
args: Object.assign({}, context), // NOTE: I cant read the provided arguments from the template argument/string at the moment: I should be able to read { route: 'public.index', yield: ... }
services: Object.assign({}, context, targetServices), // NOTE: interesting approach to make it ember.js compatible but also problematic
});
// NOTE: I can't read the yielded content in this API: 'Go to homepage' yielded string in this case.
// It could also be imported components with arguments/yields
}
Issues:
-
How can I parse an hbs file/string dynamically to get passed in static and dynamic arguments, static html attributes, yielded test or components(that can be imported where they are defined, in another file)?
-
renderComponent
should accept strings or TemplateOnlyComponent with state(properties, yieldedData). -
renderComponent
should accept yielded data to a component, behaving more like an outlet, thus this should also work:
interface ComponentRegistry {
[componentName: string]: GlimmerComponent;
}
return renderComponent('<LinkTo @route={{this.targetRoute}}><Icon @name="arrow-right" /> Go to homepage</LinkTo>',
element: document.getElementById('ember-testing'),
properties: { targetRoute: 'public.index' },
yieldContext: {}, // ComponentRegistry{'Icon': IconComponentDefinition, 'AnotherComponent': GlimmerComponentWithYieldedStateAndAttributesAndArguments }
services: Object.assign({}, servicesInThisContext),
});
// or maybe some other function that has the same API, accepts similar arguments as renderComponent,
// with additional properties, yieldContext option & keys provided above.
Thinking again we should probably have an in-browser compiler and and pre-compilation babel transpiler plugin for hbs
template function so new glimmerx templates could be read by node.js and deno js runtimes/environments directly. Example:
in-browser compiler: https://riot.js.org/compiler/#in-browser-compilation-with-inline-templates
precompiler: https://riot.js.org/compiler/#pre-compilation
As it turns on I missed to put the hbs
template function to every integration test 🤦. So everything works with template imports etc which is awesome! : izelnakri/emberx@7174bad#diff-694dacb1c70161f6edd8614dff46205525d5f04bb3464ea2105d369bd5a447a4
Only one caveat: renderComponent()
doest include properties/context so instead of:
await render(hbs`<LinkTo @route="public.index">{{this.linkText}}</LinkTo>`);
I had to do:
await render(hbs`<LinkTo @route="public.index">{{@linkText}}</LinkTo>`);
Closing this issue now, sorry for the noise!