 Hello dear friends and welcome to EmberConf 2022. Before I introduce myself, I'm going to start off with an Ember new command. That's right, we're generating a brand new Ember app right from scratch. And that's the kind of talk this is going to be. We're going to be in the terminal and the browser a lot. So who am I and what am I doing here? My name is Ava Roten and I go by she her pronouns. I am a web engineer by trade and I have been building web apps in Ember for over nine years, dang near a decade. And it's been really awesome. I actually have been on the Ember accessibility strike team in the past and I just love everything about the community itself. This is a little bit out of date now, but actually for the last year I was focusing on building accessible react powered apps and components. Specifically in Storybook and a lot of things that we're going to be talking about today. What is it that we're going to be going over in today's topic? We're going to be thinking component first. My objective is to talk about some different technologies such as Storybook and Ember and how they can become really good friends by working together. We're going to be doing some live coding here around some CSFs. If you've never heard of that term, then don't worry. We're going to go into it and what it is. We're going to play around with it, get our fingers like right into that code and we're going to understand why it relates to Storybook. We're also going to just hint a little bit about how to do accessibility checks in Storybook as well as automation testing, which we'll do a lot of with some red, green testing. We'll also take a quick look at Tailwind and Tailwind UI just to get us off the ground a little bit more quickly, talk about some stories and ultimately, I want everybody to be able to come away with just a reimagined way of how to build components as a team. Okay, that's a lot of things and we don't have a whole lot of time. So once more though, I am currently, actually I just left the opportunity of being a UI engineer at ONQ where you can see me building a design system basically a component library in a lot of technologies that I'm going to be talking about today. It was just React instead of Ember, but you can see me scrolling through the page on mobile making sure that my components in Storybook were very mobile ready, which was really awesome. I have since moved on and now I am at AlloView where I am a senior software engineer. I actually used to work here and now after being, you know, two years able to come back and I'm super excited. We are hiring and we are all about anti-racism and equity and I get to work with other transgendered individuals like myself, which is great. I'm going to be building apps in Phoenix, which I'm really excited for, but we have an amazing classic app built in Ember that I'm really proud of and it pushed the boundaries of technology in a lot of really cool ways. So let's dive into it Storybook and what is a story? Well, a story is a lot like a user story. If you're from doing, you know, like agile land and you're used to doing JIRA stories where you have maybe a user story is saying what kind of thing a user expects in a feature, for example. This is sort of similar where it can capture the interesting states that a component can support. This will make sense as we look into it. These are usually written as a dot stories.js or stories.ts file format. And you can see here is that CSF thing that we saw a little bit about earlier. And if we look up what that even means, so if we do a quick search for component story format, we can see one of the top results is here on GitHub and it tries to explain it in the little about section saying it's an open standard for UI component stuff and ES6 modules. I like to dive down here to the code. Looking at the code, it makes it a little more obvious. So we have default exports. This is a part of the standard default export has an object that comes out of it that in this case has a title of Adams slash button. That's what they decided to call their story. And then we have named components. The first one being called text where we are outputting a template in the end that says it's a button with some text inside of it. Then there's also another example with emoji. We're calling it emoji and inside of it. It's got some of those. What makes this standard of formatting like component story format so exciting? Well, this whole page will go into it in detail, but I think it's going to feel better if we just kind of get our hands into it and really see the code. So no more jumping around in here. Let's actually dive into some code. So I'm going to go to the Ember framework in the dropdown and go to install. And I'm going to go into the folder of our brand new application and we're going to say NPX SB in it. NPX, if you don't know, is a way of using NPM where we say, hey, go find an executable that I can run without having to do a global install of that tool. So it'll go find SB and then run the init command against that executable. Pretty cool. We're also going to need this storybook adapter right here. So once storybook is finished installing, I will then install the adapter and this should all go pretty quickly. And before long, we're going to be jumping right in the storybook and we're going to see it like kind of working live. It's going to be really cool. I like to set up my Tmux in a particular way. I'm going to have this window be called runners and on the right hand side, I'm going to just start running Ember serve. And on the left hand side, I'm going to clear it out and this is going to be where I run my storybook. Now I'm going to wait until Ember has completed serving because there's sometimes on the first time that you start this process up a race condition because they build separately. And I know race conditions are not very exciting and you're wondering why would they build separately? And in fact, maybe you're wondering why on the left hand side, it says webpack and on the right hand side, it says broccoli roll up. They have their own build systems and it's actually more of an advantage than it is a disadvantage because storybook can build its own stuff. It can have its own opinions and it can be separate. You're not shipping all of this storybook code to your users. You can just use it as an engineering and QA and design tool, which is awesome. Okay. So we have storybook running and it has a welcome to storybook thing here. That's pretty cool. It comes with some text that describes a little bit more of how to do component-driven UIs, which I recommend checking out after this talk. It'll relate to a lot of things I'm talking about here. And it's got some suggestions for how to set up your favorite tools, such as TypeScript and SAS, configuring your webpack and Babble, CSS libraries, data mocking, all that kind of stuff. For this example, let's look at this tip around how to edit the markdown of this very page, which is in an introduction.stories.mdx file. Let's change welcome to storybook to something else. So I'm going to leave these runners alone and I'm going to make a new pane. I'm going to call it editor. And I'm going to go into that directory with our project, start up my NeoVim and I'm going to jump to introduction and search for welcome to storybook. Now this is markdown and it's extended markdown, so it can have code. It can have markdown. So let's put EmberConf 2022 in italics here. Come back to our storybook and we can see it live updates for us almost faster than I could switch back to the window. We also have an example over here on the left-hand side above introduction. It's called button. And this button has a couple of different stories. One is called text and one is called emoji. These emojis look very familiar, don't they? Well, that's because it's using a lot of the same code that we just looked at a moment ago for CSF, the standard, the component story format standard. So that tells us a little bit of what's going on and that's pretty cool. We'll look at the code for that in just a moment. But what we want to do next is build our own component. So we're going to do that using Tailwind CSS. And I'm not going to get into the details of what Tailwind really is. You just need to know that it is a utility first CSS framework. Love it or hate it. It'll allow you to have things like a class of flex and text center that handles all the styling for you. So again, we're not going to get into all the details behind it because it's kind of outside the scope of this talk and has been talked about many different times before, including while being used in Ember. Specifically, we're going to be using Tailwind UI because it's a paid-for service that actually gives us all kinds of different application UIs already built in a very mobile-friendly and accessible way and L of U has paid for an account that gives me access to be able to show this off. So one of them is called the contact card. And this is the component that we're going to be building, this contact card right here. So I'm going to close out my NeoVim and I'm going to generate a new component called contact card. And by default, it'll give us two files. One is our HBS, our template. It's going to skip the JavaScript creation. We're not going to do that today. And it'll give us a test file. One thing we're missing, however, is the story. And so what we're going to do is we're going to copy the existing button story example that they gave us. And we're going to make our own. We're going to put it in the app slash components directory, contactcardstories.js. Now we have a problem because Storybook only expects to look in the stories directory. And I would really like it to be in the app directory so that we can leverage Ember's ability to be able to do imports into our test later. We'll see why that's really useful when we get that far. But for now, just know that's something that we want to do. Now, unfortunately, we need to make a little customization to our Storybook in order to make that happen. So I'm going to close the Storybook runner. I'm going to open up this Storybook main file and we can see where it's only looking at these two directories. But we need to add another one. I'm actually going to pull a snippet that I wrote before this talk and I'm going to replace this code with it. So right here, we add a new line to look in the app slash components directory. And I also added one more bit of code to just throw the CDN for Tailwind up into the top of our Storybook. This is not a proper way to do a full installation of Tailwind, but it'll get us started today. If you ever want to know how to install Tailwind, there are other talks and there's some great documentation on that exact topic. So now we should have Tailwind and we are looking in that new directory for our Story. I have saved a file, the new configuration chains, and we are now building Storybook once more. It builds up very quickly and there's nothing different. Now, why is that? We have a new story file, don't we? Well, yes, but contact card stories. This is saying specifically that we have a title of button and we already have that. So let's change it to contact card instead. And we see immediately we have a brand new story called contact card. Let's do a little bit of cleanup because there's a lot of stuff in here that we just don't care about or need. The first thing we're going to do is we're going to just comment out this R types thing and you'll start to see that this looks familiar. This is our default export. This is exporting a title of contact card, which looks a lot like our CSF standard that we saw just earlier. Coming down here, we see this const object where we have a reusable template. And the example here was rendering out a button that had an ember action on it as well as some children. We're going to replace that with something that is just contact card. And I'm going to come down here and delete all the other stories and we're not going to pass in any args for now. And instead, we're just going to change this to be called standard instead of text. At the top, we're no longer using these imports. Saw safe. And we come back here and we go back into our contact card and it's empty. Is that okay? Is that what we're expecting? Well, what are we rendering? Let's go figure that out first. Contact card, HPS, it's rendering out a yield. So seems right to me. Let's actually put something in here and make sure that it's working. There's two ways we can test this. We could say hello yield or we can also come in here and actually yield something out. So we could say world inside of our contact card and we come over here. We can see it says hello world. So both of those are working as we expect it. Okay, let's undo those changes. We know that it works. What we want to do next right is take this code and drop it in. We could do that, but I want to encourage better practices. So we're at a good point now where we can do some test driven development, some red, green testing. I want us to get into habit of instead of looking visually at our changes, we want to have a test runner and I will hide the container and any passing test. Pardon me. And these passing tests are, you know, they're great. They're happy. But what we want to do is have a new test that is checking for something that we don't have yet. We want to see a red test. So something like maybe Jane Cooper, if we tested for that, for example, to be somewhere in the page. Sounds pretty useful to me. So open up contact card test. Let's do some quick cleanup. We don't need these comments. We also don't need any of this template block usage. Let's add in a has text test and we'll change it from has text to includes text. And we'll change it from it renders to it renders expected name. For example, we will come back to our test runner and it's a red failing test. But I was expecting that. Why would it pass? We haven't passed in the actual HTML yet. So that's what we're going to do next. We're going to make our test pass. If I come over here to the tailwind UI code, I can copy all this HTML. You'll notice there's a drop down here for react and view as well, but not for Ember. And that's partly because we don't need it. HTML will work just fine with no other changes required. So I'm going to drop in this huge wall of HTML and we're just going to hit save, not worry about it for a moment. And we're going to come over here to our test runner and it's going to pass already. We were failing and now we're passing because it does find Jane Cooper in the component somewhere. Furthermore, we can look at our storybook and we can see it's working as expected. It's actually styled and everything exactly how we wanted it to be. That is awesome. Now we're starting to make some real progress. Okay, let's make our linter a little bit happier. It has some opinions about things like no HTML comments, which I think is pretty valid because this would pass HTML comments to the end user. It would not be compiled away like only being shown for engineers. So are these comments anything that's useful for either an engineer or for a end user? No. So I'm going to comment, actually I'm going to just delete these comments entirely. So get rid of all these. There's another linter issue here where it's requiring valid alt text. So despite the avatar having an alt of nothing, it's actually expecting a role as well. So we can say a role of presentation and now that'll be happy as well. One thing that I think is a little bit weird semantically about this HTML besides being really bulky is that we have an unordered list and then a single li inside of it. I think for this component, it makes more sense for this to be a single div that doesn't have any of the call span styling and is not wrapped inside of an unordered list. So instead it's just a div on the outside. I'm going to do a little bit cleanup where I just make sure that I am closing my tags appropriately and we go back to our code and everything still looks fine and is happy. So I think we didn't mess anything up. Normally I want to see a red green test for that, but this is this HTML semantics and so it still looks visually appropriate in the browser and that's good enough for me this time. Okay, so we've made a little bit of progress, but we still have a lot of, first of all, redundancies in terms of we are rendering out contact card in our test and we are also doing the same thing in our template and we're not passing in any dynamic args here. We would love to pass in something like a name and actually have it verify that that dynamic name is what we expected. So let's start there. Let's assume that instead of Jane Cooper, we say that we expect the name of Tomster and it's to be a little bit more specific about our test selector. We're going to say data test ID is cc-name and we're going to come in here to our contact card. We're going to find that name. We're going to say data test ID cc-name for contact card name. If we go back to our test runner, we see that it is now saying that that element which it could find has text containing Tomster is not working instead of seeing Jane Cooper and that's exactly what we would expect it to do because we haven't done anything dynamic yet. So let's assume that we want to make this test pass by saying, well, you know, there's probably a at name that's being passed in. So kind of arg that way. And so where do we put that arg? Well, probably we want to put it into our story. So let's say that we have a name of Tomster and we're going to pass it in here in our template. So we're going to say at name equals this dot name. How does that work? Well, actually there's something a little bit interesting and kind of funky here. So just keep in mind that context is the same as args is the same as this. This is like a little bit of some weird naming conventions specifically around how the Ember CLI storybook add on kind of ties this all together. But for now, it's all you need to know and there are other changes coming in the future that I hope will make this even more clean and easier to understand. But for now, we can say at name is equal to this dot name. It'll work how we expect. However, I go to my test and it's, well, it's not working. But in my story, it is working. And that's because as we just established a moment ago, our story is rendering with the name being passed in, but our test is not. So I could also pass in at name equals, but that seems redundant. But I'd rather do is I'd rather take this standard export and pass it in as a render. So I can say standard as a wait render, but that won't quite work, but it's close. What we need to do is we need to take two more imports. We're going to import standard from our contact card stories and we're going to import a render story helper from the Ember CLI story book adapter. And so now we can say render story instead. And we no longer need these two imports of render or HBS, so we'll get rid of those. So we'll go back to our test runner and it's passing. It's happy, which means that it was able to render an entire story along with its args. That's pretty cool. Let's take this one step further because there's a little bit more redundancy that we can clean up. We see that there is an includes text of Tomster and we also put a string of Tomster right here. But what if that changes later? What if later we decide that we want it to be Zoe? Well, then our test is going to start failing, but it doesn't have to. Because instead we could say includes text standard.args.name. And now this will pass once more. So you see how we're going from red to green test back and forth verifying that things are working. Now I am completely confident when I go back to my story, it says Zoe right on the screen. OK, so that was a lot of different stuff that we covered. I think it'll make sense to now just do another round of this, but with our new assumptions in place in a cleaner way. So let's do this for the avatar. Let's start with our test. So we're going to say let's copy this first test. We're going to say it renders an avatar. And we can say cc.avatar has an attribute of avatar. We're going to say it's got a source of standard.args.avatar. OK, so we go back to our test runner and this will fail because it didn't find that element at all. So let's have it find the element now. So we'll say data test ID equals cc dash avatar. And of course it will say, well, we found it, but it doesn't have the value that we expected. So we will now change it to assume that we're passing in something like an at avatar. OK, so we're kind of getting somewhere, but standards.args.avatar isn't a thing. So let's make it a thing. So avatar and we want some kind of a URL of something that will not change. So I've already uploaded something here on the web, so I'm going to use that URL. And we are seeing that we are getting an error that is saying that the attribute of source has this value, but it does not have an attribute with this other value. So it looks like we are almost there, but not quite. So we're going to take a look at our test and do a little bit of live debugging here. So we are saying that we are expecting cc avatar to has an attribute of source that matches standard.args.avatar. And so what are we missing? It seems that we are missing actually passing in anything meaningful up here. So we can now do that. So we can say avatar equals this avatar. So when in doubt, just go back through every different step and make sure that you have passed everything through. Great. Now we have a passing test and we have Zoe's beautiful face on our contact card. Let's do this one more time, but with a different story because right now everybody's an admin all the time. And if we think about stories, we know that they are supposed to show different interesting states. So admin sounds like a pretty interesting state for this component to be in. So once again, we're going to copy a test and we're going to say it is not an admin by default. We're going to render out the standard story and we're going to say admin. How about does not exist? And if we come back over to our test runner, what do we expect? Oh, it's happy. That's interesting. And that's because it hasn't found this element of cc admin. So keep in mind that this is a good example of when you're expecting a failing test, but it's failing or it's passing rather for the wrong reasons. It's like a false positive. So we're going to look for admin. There it is. And we're going to say data test ID cc dash admin. And so now our test will find it and say actually that exists once. So that's what we expected. It's going to find it. And now what we need to do is make the test no longer pass. So we have this admin element here. And we're going to wrap it in a if and we'll pass in something like an is admin. We'll wrap it up in that if block and now our test is already passing. We no longer ever see admin, but we want to see admin sometimes. So let's make another test to assert that it does show up sometimes. So this test right here is going to say it can be an admin. And so we want to say exists, but this isn't going to work. How do we pass a different arc here? Well, ideally we don't ideally we test a different story like an admin story. So let's pretend that we're going to import an admin story from up here. One who doesn't exist yet. We'll go back to our test and it says, hey, I like your energy, but story is not defined. That's okay. Let's go to find it. So we'll copy our standard story and we'll say admin. And we'll replace these args with something different. We can actually spread the previous args in. So we can say standard args as a spread and then we can say is admin is true. We'll go back to our test. And in this case, it says that it's actually still not existing. And that's because we still haven't set up everything along the way. Keep in mind that we need to do these steps each and every time. So we have is admin. This stuff is admin. Let's try that again. And now we're passing. So we have a test for both a standard story and another admin story. To come over here to our storybook, we see that now we have two stories in the sidebar. We have one for standard and one for admin. And we can see how you could pass this off to another member of your team and they could see how this component could be used in different ways, which is like super awesome. Furthermore, I could open up with the key with the key of my keyboard of D in order to open up this side panel here. And I can say is admin is false is admin is true. I can change the different names around and all that different kind of stuff. Which is like pretty awesome to be able to play with this in real time. But one of the things that you might run into is in this case, if we go back to standard, there is no is admin flag. Now, why is it there on admin but not on standard? That's because these are being populated based on the knowledge that it has from the args it's provided. It has no way of knowing that is admin could be false. I could pass it in that way. That's one way I could do it. But there are many other ways to do it. I could do arg types up here in my default export. And I could say is admin is a control type of bullying, for example. And now it'll give me an option to set the bullying and it'll go to false by default. I can turn it on and off even in my standard story if I wanted to. Alternatively, there are lots of other ways to do this, including YUI docs, including just various different standards of mocking up your different args throughout your component interfaces and things like that. There's a lot of different ways and reasons why you would use the storybook ways of doing it versus any other standard. So for example, arg types actually has things beyond what TypeScript could offer, such as a color picker. That's like super awesome. But you might want to use TypeScript. You also might want to use some other interesting standards that are coming out. A lot of the boilerplate that I have written today might just go away. For example, if we start implementing things like Glint, we can start seeing exported interfaces for signatures for these components. You can imagine where you would sign the args and the yields for each of these components. That would be fantastic using Glint, for example. There's also ways that we would not have to possibly do all of this boilerplate, which you can imagine would get pretty exhausting of saying at name, at avatar, etc. If instead we started using something like the Ember template imports, which allows that to work in Ember. And it looks a little bit like this. You could imagine where you would have a template tag and then your component. Then you could pass around the component along with all of the different attributes and things that it is aware of in a more intelligent fashion. That would be fantastic. And there's other RSCs out there that combine all this stuff together in an interesting way where if we see all these things land and start getting proper attention, then hopefully we can see some really great improvements of having Glimmer able to understand what kind of args it has access to, playing nicely with TypeScript, and then playing nicely with Storybook. That's an exciting future I would love to live in. Something I don't have time to get into today is documentation because furthermore than just putting in arg types, but along with YUI docs and being able to describe your TypeScript props and things like that, you could also add in documentation. So you could go to the docs tab here and you could add documentation for what is a contact card. When should it be used? When should it not be used? You can also describe each of these different attributes right here. You see there's a description and defaults. You could add all the information here for a really well documented component. Furthermore, there's a fantastic add-on for accessibility that adds some automated accessibility checks into Storybook. Now, this is not an end-all be-all. This does not solve all your accessibility problems, but this is an option where you can take advantage of being able to get some free immediate feedback for whether or not the component that you are building in the browser is accessible in a very visual way. So this does not require any setup on your part besides doing the installation process, which is quite simple. And then it'll give you any tests that it can run against the HTML that it finds, including checking for color contrast. Now, there are other talks that are happening at EmberConf right now that go into way more detail on accessibility than I have time to talk about right now. And so you should go watch those. They are going to be fantastic. But this is a great little tool to throw in there for some quick checks. Why not? So documentation and accessibility are two things that would be fantastic to put into here. But I hope you still get a feel for how you could build components in a modern way. So some other things that I want to call out before I wrap up this talk. Some things I would love to see happen are, I would love to see more documentation from the community for how to do Ember and Storybook together, especially as these new RSEs and other tools land. The documentation on the Storybook site leaves a lot to be desired as it relates to Ember. So if you're looking for some open source opportunities, dive into the docs, make some PRs and do some updates. And do that while you're playing around with maybe the repo that I will share with this presentation. Additionally, one of the steps I was not a fan of while doing this talk was doing the manual copy. You might recall that I did a copy command of Stories and I took the example story and then I put it into the right spot. Ideally, we would have a blueprint, so an Ember generator. And blueprints allow you to basically make your own generators. So there could be a generator out there that would create a story. Imagine an Ember generate story that would be super useful and would be far less confusing and would be great for your team. So that's an opportunity for an open source project as well. Well, that is all I have for you today. We have done a lot of test driven development, red-green testing. We have built a component in Storybook with a component story format CSF standard. We have also taken a quick glance at accessibility and how you could just have this be a process for your team where you are excited to build mobile ready, accessible, like just modern tooled components. I love building components in this way. I hope you and your team do as well. Thank you so much for listening to me talk about this. My name is Ava Rodin and I am signing off. Cheers.