About this talk
Learn how to test a Glimmer component in an Ember application.
As I said before, so maybe a week, a week and a bit ago, they started merging in the ability to write, I guess not merging in the ability to write tests for Glimmer components, but started kind of crafting the story for how you would test a Glimmer component, right. So we've had a lot of you know, this is how you would make an app that uses Glimmer in a rough way, this is how you would create a web component that uses Glimmer, but something that hasn't really been hit on until now is, how would you go about testing a Glimmer component. And I think this will become increasingly more important as Glimmer becomes more integral, well, the ability to integrate Glimmer into Ember applications or even Rails applications, or PHP applications, or really, any applications, as they become web components will become quite vital. And so given that this is fairly new, I'd like to enter this talk with a bit of an open mind. It's still not a fully fledged, what we might be used to with something like Ember, where we can visit pages and click on buttons and find like, those things are still possible to some extent. But it is quite a, like, fledgling thing that's happening. But the things that are happening in the Glimmer testing world, if you will, they are coming into Ember in different ways, as well, so I think it's quite interesting to see where some of these new ideas are coming in and how they're feeding into Ember through Glimmer. So yeah, this talk is gonna focus on testing Glimmer components. But I kind of wanted to touch on a little bit about testing Glimmer itself, and how they are going about actually testing, like how is Glimmer the rendering engine actually working, and is it working, and how those tests run. And to do this they follow a kind of four steps to make sure everything works fine. So the first one is initial render. So an initial render means basically what it says. Does this component or does this element, whether it's an image tag or an A tag or a title, or an H1, like does that render? Given a variable, does it render? And I'll show you some examples of this test in a bit. The first one is a no-op render. Okay, so if you re-render the component with the same arguments, what we want to make sure is that that component doesn't re-render, because that's an expensive operation, right? So we want to test if I have an image tag and I update the source it re-renders correctly. But if I have an image tag and I don't update the source, for some reason, the argument that I've passed in, as the source doesn't change, then you want that render to not happen at all. The third one is update via mutation. So this is the opposite of a no-op render. So if I change the argument, so if I mutate whatever argument that is, in our example, an image tag. So given a source, does it actually update, and does that new value get rendered into the DOM? And the last one is reset via replacement. So this is going back to the beginning, right? The initial one that we rendered, the image tag with the source. If I render it back to the original does it, in fact, update on the DOM? And this is important because it helps catch caching bugs. Because if I'm changing the elements and I'm changing the arguments that I'm passing in, these are cached at some level. And then returning it to normal, in some cases, you would shortcut some part of that process, and then it might not update because of that cached value. And they go into like detail into this pull request that I'm about to show, so I would recommend having a look at that. But generally, those are the four things. So they say N-I-U-R, initial render, no-op re-render, update via mutation, and reset via replacement. And this is an example of how they would do that. Using this image source, they have a simple test. Unquoted attribute string values, They start here with the render. This is the initial render. They render that image, they pass in the source, you can see they give the source as an argument, which is just image.png, and then they assert that the HTML that was rendered is what was expected. So it's like quite a very simple test, right? And then they assert a stable re-render. Then they do the same thing, except they pass in a new source. And this is checking, okay, does it update if I change the value, okay? So here they're saying re-render, pass in the new source, and then assert that the component re-rendered with that new argument. Doing the same one with a blank. So just making sure that you can use, I'm assuming this is that's related to the string values, and then resetting it back to kind of catch any of those caching issues that might happen. Yeah, this is like a pretty simple test. Here's the whole thing. Again, just like testing does Glimmer work? Does it render in the way that we expect it to, and do arguments get re-rendered, and those types of things, so quite simple. The reason I mention this, and kind of briefly go over it, is because if you are looking to get more into Glimmer JS and kind of like dig into the source code a little bit and get your feet wet in the project itself, they're actually looking for people to rewrite these tests using this new approach that they've abstracted some of the helpers. So they're looking for people, and it's glimmer-vm, issue 561. So yeah, if you are interested, if you have some free time, or if you're interested in like getting into the Glimmer project a little bit more, this would be the one to look at. It also goes over like, okay, what are the reasons for testing, how do we test, and why do we test in this way. And they go into a little bit more detail in the INUR, or whatever it was. Okay, so Glimmer components. So this is the actual components that we're making, as developers using Glimmer and building applications using Glimmer. So if you imagine a simple template or a simple component that renders one template that looks like this. It's just a div with a H1, and then it says, welcome to Glimmer. This is actually the default Glimmer application setup. I think, a week and a half ago, or two weeks ago, if you had created a Glimmer application with a blueprint, you wouldn't actually have been generated any tests. But now that this PR was merged, if you go home and you upgrade Ember and you make sure you're on the next one, and you create a new Glimmer app, with the Glimmer blueprint, what you're gonna get is a component test. And I don't actually have the file structure here, but I will go to it at the end. Someone remind me, because it is very unique. 'Cause it follows the new module unification system that they're rolling out for Ember soon, as well. So inside of it you'll get your component, so it might be like Glimmer, or in this case, it's Todo-app, even though it didn't end up being a Todo app at all. Then you'll have like component template and component-test, and they'll all be in the same folder. So if you were at Ember camp, you would have seen this structure, but I'll be sure to show it at the end. And then, this is the test that you'll get. This will be InComponent-test. You're importing setupRenderingTest, and this imports some helpers, some initial setup, it assigns some things for you, so you get easy access to what you're rendering. If you're familiar with this inline-precompile, doesn't anyone use that in their Ember apps at all? So you can import HBS, and then in a component you can render in HBS, I think they call it literal template within your component, which is quite handy if you're doing like small components, you can just keep it all in one file. And then we're pulling in some things from QUnit. Mostly it looks pretty much the same. We have a module there, we pass in a string for that module, we do the function, and we get hooks, and then we passed it in to setupRendering, and I can show you that in a bit. But it basically does it in some initial set up for the ability to test this component. One of the key differences here is that, in an Ember test, you would have your module, and then underneath you would have your tests, living outside of that module. Whereas here, it's not the case, it's nested within the module and within that module function. So here's we're saying, okay, test, it renders. You'll also note that we're using Async Await functions, as well, so this means that you can, you don't have to be, like, and then, and then, and then, if that's something that you've been doing quite a lot. So you can use Async, and the we know that this.render is an Async operation in itself, so we can Await for that and then move on to the next line. What's happening here? We have Await, we're calling this.render, and we're passing in that template literal, or literal template. I think that's it's called. And we're passing in that initial Glimmer component. So is everyone familiar with a Glimmer component itself? Enough that you would be able to recognise that this is one? Basically, a glimmer component is a component, we use angle brackets and it has a name. So in this case it's todo-app. In this case, it also has no arguments. So it's literally just a template. Okay, so if you look back at this original file, we had, Hi, welcome to Glimmer, sorry, we had H1 welcome to Glimmer. This was a test, we're just testing, does it render, does it have welcome to Glimmer? So if you're wondering what this.container element is, it's literally just the DOM element itself. So it's the rendered template of that component. So in this case, I console logged it, and I got div label, ach, this is the wrong one, but... Imagine this slide in like two slides. I'll come back to this, in fact. So if you imagine this template, where I have welcome to Glimmer, so the thing we saw before, and then name-label, if I was to do the same thing in this test, so you can see down here, I have console.log, this.containerElement. What that is, is this. So we get the div, we get the label inside it, with that class, with my name in it, and then the end label. So this is just a, it's like a literal, it's like just a node, it's a DOM node. So there is no JQuery. You can't like call JQuery elements on it. You can do those types of things, for better or for worse. Okay, so here we have a new Glimmer component that I've made called name-label. All it does is takes an argument of name and then it puts that name within a label. Exactly what we saw back here. If I was to do this and pass it in will as the argument, it would return a label with a class name label, and then will within that label. And for those who don't know, this is a Glimmer component, and this is how you would use it in another template. And glimmer will find it and realise that it's a component and then render it correctly. Okay, so this is the test for that. So I'm trying to keep it very simple. Mainly due to my own ignorance of how these things might actually work, so as not to embarrass myself too much. But essentially all the same setup. So I have the module, I do the setup rendering tests, and then I test that it renders again, Async function there. And then I print out my, or I render that component. And you can see, I'm just passing in name=will directly. Because I just want to test, if I give it the right arguments, does it actually render and then give me the right thing back? So here, you can see I've also logged the container element. So you can do things like console log and those types of things. And then I've asserted equal. So here I've just gone, this.containerElement.textContent is will. And I think this actually might be wrong. Actually, maybe not, because it did pass, though I don't remember it actually working. But I can have a look afterwards. But yeah, essentially, those two tests, passing, the elements or the arguments that we passed in are coming back correctly, based off this test. So that's kind of loosely how you would go around testing a Glimmer component, based off what is there today, and what exists within the blueprint. Any questions about this so far? Yeah. - [Participant] Would you need to test the re-rendering thing, or is that something that's just a framework thing? - You can do, if you, if something changed, depending on your arguments, then you might. I don't think you would need to test specifically, whether it re-renders and then gets updated correctly. Only because that's like almost a Glimmer concern would be my only thought. Any other questions on this so far? - [Participant] What was the @test that you had at the beginning? Was that Ember decorators or something? - Yeah, so this is something that they're doing, internally, within Glimmer. This bit here? So the @test? Yeah, so, at the end here I'll bring up the PR and show where that's coming from. I think they're importing it in some special ways. Some of their own internal Glimmer helpers. Because you'll see they also don't do the, await this render, or anything like that. So I think there's something internal there that they're doing that is different to the API that they're providing developers to test with. I think that is like a little bit of an internal thing. What does this mean for the future? I think this is like one of those instances where you can see parts of how they're building out Glimmer, kind of coming in to Ember, and trying to reuse some of the things there. What we'll find is, I think Robert has opened up a new RFC to kind of bring about some changes to how we use QUnit. So if you're interested, this is the RFC, 2-3-2 on emberjs/rfcs, and it will essentially take this, what your tests today may look like. In this case, I think, we're testing a component called x-foo. And you can see, even in this setup, now these things are Async functions, or Async Await functions, and we're doing this.render. And then they're able to use JQuery and stuff in these tests because we're back in Ember land. But I think it is a really good example of how some of the benefits that we're seeing in Glimmer can now kind of be pulled back into Ember a little bit more, and worked on by both parties. And in this RFC there are quite a lot of instances of where code that was written previously is better situated in this. And also I think the connection between Ember and Ember QUnit, obviously, I like QUnit, is like quite brittle and by changing this and opening it up and allowing for this type of API, we'll be able to use a lot of the things that QUnit gives us that would otherwise have to be manually coded into this connector add-on. Just for fun, I have the same slide twice. So yeah, this is in final comment period. So essentially, we can see like in order to embrace newer features being added by QUnit, which we all know and love as the default, there is this brittle coupling, as I was saying, between Ember QUnit and and QUnit itself, which requires quite a lot of manual intervention. Yeah, they run through some basic examples here. They have their module, integration. This is what it previously would have been but now the API is not that much different. We get to use things like Async Await, so we don't have to do things like, and then, or we don't have to mess around with any of this type of thing. It adds in some different hooks and different ways to actually run those tests. It's quite interesting. I would recommend having a look. They show off a few more. What would this look like before and after shots, component unit tests, service route and controller tests, before and after, and then yet, updating blueprints and things like that. And then an eventual deprecation of the older APIs. I think the other thing that I mentioned was this module unification. So another RWJ blur... I'm sorry, this is the Grand Testing Unification. But then if we go to Ember module unification... Are people familiar with this? This new folder structure. So in this instance if we had a component test in Glimmer, it would actually be in there with the folder. So we would have paginator control in this instance, and then we would have component-test. And then that would be a test similar to what we saw before. Basically, everything is coming into that folder in Glimmer. And then the other thing was the Glimmer-vm. I think it's here. Oh, no, sorry. It's Glimmer test helpers, yeah, here we go. If we go into Source. This is that setup rendering test, so you can see this is the function itself, it gets passed in those hooks. We get the app, it does render, settle, those types of things. After it's done, it cleans up the element, the container element. So you can see here, when you're calling render, you're getting that, you're passing in that HBS template. And then it's assigning the container element to that repetitive that's there. Then we don't have to do that every time. Then it's rendering, I assume rendering that component into the container element and then giving us access to that container element within. And I guess now, yeah, here we go, so it's got promise.resolve. So perhaps they're allowing themselves, perhaps there's something to do with scheduling this re-render, allowing themselves to do these kind of asynchronous actions within it. I guess it's property did change and then scheduling this re-render would be my guess as to why, maybe. Anyway, I wouldn't want to say. I have no idea. But, yeah, this is this, and then the other Rfs, or the other one that I mentioned was the great re-testing, which is Glimmer-vm, what number? 561. Okay, cool, yeah. I guess the reasons they write here is, the original test was inherited from HTML Buzz. And now they want to change it away from that and into a more friendlier API, I suppose, that fits better with Glimmer. They've, as they say, turning on the bat signal, sending out, they go into detail of why, the way there are the kind of patents that they've set up. So we have the NIUR, uh, INUR cycle. They explain why. They also touch in on that caching issue that I mentioned before, and list out all the tests. As you can see there's plenty of space there to get in and kind of understand Glimmer a little bit more, if you're interested. But, yeah, that was it. Thanks very much. - [Participant] How easy is it to start integrating Glimmer into existing large Ember apps? - I think they covered it a little bit at Ember camp, and I think it was Matthew and Robert who were working on it. And they had to do, if I remember, they had to do a lot of manual hacking to get it to work. They did get it to work, but I don't think it was quite ready to show. I think that was right, if I'm remembering correctly. But I think soon, right, it's in the future.