
Sign up to save your podcasts
Or


Dillon Kearns turns the pages of his journey with Elm, from applying meta-learning techniques as a classical piano player & agile coach to building a full-stack Elm framework (elm-pages).
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.05.04
GuestWolfgang Schuster shares his journey with Elm, describes writing & deleting Elm code at Vendr, and explains the productivity gains in an ecosystem that values building things for the end user.
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.05.02
GuestKatja Mordaunt talks about Elm vibes versus other programming communities and gives us the low-down on Elm Camp.
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.04.14
GuestThanks, everyone, for coming to Elm Town! If you're enjoying the show, please share it with friends and like/rate it on your podcast platform.
Tessa Kelly shares her experience unblocking users while building quality software, explains how to avoid the "accessibility dongle" using the Elm philosophy, and considers some tesk9/accessible-html design changes.
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.04.04
Guest"Don’t assume that you’re the default. Make space for there to be other ways of being.” - runner Lauren Fleshman on the Daily Stoic podcast
[00:18:32] The business motivation for accessibilityAaron Strick shares what it was like learning Elm at NoRedInk, and explains some of the "zany" (delightful) ways Elm is used at Brilliant.
Thanks to our sponsor, Logistically. Email: [email protected].
Intro music by Jesse Moore.
Outro music (The Elm Song) by Matt Farley. (Commissioned by Michael Glass for elm-conf 2019.)
Recording date: 2023.03.10
GuestThanks, everyone, for coming to Elm Town! If you're enjoying the show, please share it with friends and like/rate it on your podcast platform.
Théophile Kalumbu comes by to talk about how Elm makes developing a frontend fun, building a design system explorer, iterating quickly, and getting Elm to production.
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.03.06
GuestThanks, everyone, for coming to Elm Town! If you're enjoying the show, please share it with friends and like/rate it on your podcast platform.
Dwayne Crooks stops in to share his appreciation of algorithms, experience learning Elm, challenges building Elm 2048, and how he uses Elm at work to build things like the decentralized finance app, Qoda.
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.03.07
GuestJared makes generalizations here that work well for many cases but do not apply for every situation. For example, interop with UI tools such as a map would make more sense with custom elements rather than ports.
Check out Dwayne's post on Discourse explaining how ports are used in Qoda.
If you're stuck, please ask the community on Elm Slack or Discourse.
[00:59:10] elm-naturalDisclaimer: Comments about how Evan works are based on our outside observations.
[01:04:50] PicksDwayne's picksKevin Yank shares the challenges Culture Amp faced when scaling Elm & React with a Design System team in the middle, and how an acquisition tipped the ultimate decision to move Elm from “adopt” to “contain” at the company.
Oh yeah, and there’s a new host, Jared M. Smith...stay tuned for more episodes!
Thanks to our sponsor, Logistically. Email: [email protected].
Music by Jesse Moore.
Recording date: 2023.04.06 (07 on Kevin’s side of the world.)
Guest00:01:03 Some of Kevin’s contributions to Elm
00:01:45 History of Elm at Culture Amp
00:04:49 "Why didn’t you just…"
00:05:54 The exciting challenge: styling Elm
00:10:50 Sharing a design system
00:13:48 Design System team challenges
00:16:30 Acquiring a large React codebase
00:17:24 Upgrading to Elm 0.19
00:19:24 Momentum before the acquisition
00:24:12 Communicating the decision to “contain” Elm
00:32:43 An alternative reality with Elm for Culture Amp
00:42:26 How Elm changed the way that the teams at Culture Amp work
00:44:34 Elm as a secret weapon for recruitment
00:48:00 “Why didn’t you just use web components?”
00:49:25 Kevin's blog post
00:55:56 Success, or how never adopting Elm at Culture Amp would be worse
00:59:34 Take responsibility for user experiences
01:00:23 Reminiscing on past episodes
01:06:15 Elm Radio rocks!
01:08:10 Thanks
Kolja Lampe takes us on a deep dive into the internals of the Elm Language Server that powers a growing number of editors' Elm support, and explains what motivates him to continue to spend more time building tooling for his favourite front end language than he does using the language itself!
Thank you to our sponsor, Culture Amp.
Special thanks to Xavier Ho (@Xavier_Ho) for editing and production of this episode!
Recording date: 4 April 2020
00:00:00 Intro
00:02:11 Kolja introduction
00:03:17 Side projects at work in Elm
00:05:26 Kolja's history with web development
00:11:13 Elm Language Server origins
00:15:26 Language servers and Language Server Protocol
00:20:56 Elm tree sitter
00:28:09 Elm Language Server internals
00:30:13 The Elm community in Germany
00:33:47 Type inference and other future features
00:40:13 Outside contributions
00:45:36 Increasing robustness by removing dependencies
00:50:41 Depending on feedback from the community
00:53:01 Mutual appreciation society
00:54:06 Sign-off and outro
[00:00:00] Kevin Yank: Hello and welcome back to the Elm Town podcast. It's Kevin coming to you with another episode. I'm joined today by Kolja Lampe. Hi Kolja.
[00:00:09] Kolja Lampe: Hi.
[00:00:10] Kevin Yank: Kolja goes by @Razzee on the Elm Slack. You may have seen him there especially in the #elm-language-server channel. that is a bit of a spoiler about what we're going to be talking about today. Kolja is the author of the Elm Language Server and the Elm Visual Studio Code Plugin that leverages it to provide a really nice and increasingly nice editor experience for that editor, and we'll be going into all the nitty gritty details there as well as hearing, you know, what brought Kolja to Elm. But before we dive into all of that, as usual, I would just like to shout out to Culture Amp for supporting this podcast. Aside from my time, I often credit them for letting me record these podcasts during working hours, but just so happens it's a Saturday evening, so it's not working hours today, but they pay for the very cool web-based recording studio that we use to make this show. Just in general, they don't mind the slightly excessive amount of time that I put towards this show sometimes. So it is great to have an employer like that, that doesn't mind giving back to the open source community that puts together the language that we love using to build our front ends there.
[00:01:26] As I've said before, Culture Amp builds a web application that companies all over the world use to hear from their employees about what they're thinking and feeling about their jobs and their workplace. That's something I really love going to work every day to contribute to. And also a shout out to Xavier Ho.
[00:01:45] Xavier, I have been overwhelming you with the number of new episodes I've been recording ever since this, COVID0-19 virus came in and, and made us all stay at home. It suddenly seems like a really good time to stock up on podcast recordings, but as I record this, there are four episodes in Xavier's inbox waiting to be edited.
[00:02:03] So, thank you so much, my friend for continuing to help me put these out. Kolja welcome to the show.
[00:02:10] Kolja Lampe: Hi, nice to be here.
[00:02:12] Kevin Yank: For the benefit of our listeners and for myself, who this is the first time I'm getting to meet you properly, why don't you introduce yourself?
[00:02:20] Kolja Lampe: Well hi, I'm Kolja and I'm mostly working on open source stuff right now, well I'm actually going to work normally - as normal as you can go to work right now. Like I'm working from home, obviously.
[00:02:33] Kevin Yank: There's not a lot that's very normal at the moment.
[00:02:36] Kolja Lampe: Yeah, agreed. I'm actually enjoying working from home very much. Programmers are in a good position to do that.
[00:02:44] Kevin Yank: Do you work professionally in Elm or is Elm a pastime for you at this point?
[00:02:49] Kolja Lampe: It's a pastime for me. I do have, like, we have something that we call 10% time at work, and I do have a project there that's using Elm. But other than that, it's not used at work.
[00:03:05] Kevin Yank: Okay. Yeah, I've seen a lot of that happen, of Elm being used as kind of a tool for building admin UIs and internal tools and things like that.
[00:03:16] Kolja Lampe: So if you're interested in what I built, it's actually something where we as a company, like everybody can log in and say, I have these skills, like I can program, and I have spent this many hours in doing it, and maybe this many hours in doing Elixir, and I really love it, or I really hate it. And so everybody in the company gets, an idea who knows what.
[00:03:45] And, I actually do some graphs based on that and stuff like this.
[00:03:50] Kevin Yank: That's cool. okay. And that's something that you've just built, internally for the use of your company.
[00:03:55] Kolja Lampe: Yeah, at the moment right now. but we've been thinking to open source it, but it needs more work to not be entangled with the company network, I guess.
[00:04:07] Kevin Yank: I have the exact same problem with a side project that I'm working on at work in Elm. One of the things that I'm doing at work is an app called the Face Wall. And, what it does is it connects to our Slack account and pulls down all of the employee profiles, and it's an app that runs on the meeting room TV screens when they're not in use and it
[00:04:28] just kind of goes through a slideshow of showing the employees and information from their Slack profiles. And that is written all in Elm, but just like yours, there is a significant amount of code in it that is very specific to our company and the specific fields we have in our Slack profiles.
[00:04:46] And so disentangling that, making it configurable and usable by another company is something I wouldn't mind doing one day, but I'm not sure if I'll get around to it.
[00:04:53] Kolja Lampe: Yeah, I totally understand that. We actually have been thinking if it would be a good thing to open source it right now.
[00:05:01] Kevin Yank: Yeah. Oh, we have a lot in common, it sounds like. I don't know what your company's main product is, if it's even a product company, but I am fascinated by these internal tools that connect people within an organization, because that's kind of my day job, is to work on a software platform that does things like that.
[00:05:20] Kolja Lampe: Yeah. I realize that; that's actually why I bring it up.
[00:05:23] Kevin Yank: Well, thank you! So how long, apart from Elm, how long have you been doing, you know, web application development of one kind or another? And, eventually I'd like to hear how you discovered Elm.
[00:05:37] Kolja Lampe: web application development only started at my current workplace, and I've been here for three or four years. I don't
[00:05:45] remember exactly. Before that I was doing a lot of .NET, like C#, mostly a desktop application for a warehouse, basically. So you put some data into masks and those get transferred to a web shop and the web shop will do stuff with it.
[00:06:03] And logistics. We'll do stuff with it and stuff like that, like get packages and, get them to customers.
[00:06:11] Kevin Yank: And you know, more and more of that sort of software is moving to the web these days. So, I suppose, did you consider it a luxury to be working on a desktop app and not have to worry about the web and JavaScript and all of the, the cruft that comes with that?
[00:06:29] Kolja Lampe: It was pretty nice in some regards, but we did do very many things in SQL Server. And that's kind of a two sided blade. It's very, very powerful, but it's also very easy to do some stuff wrong, I guess. It's, as with any organization, there are… there might be some people that are not exactly helping, and have some point of views that are very different from yours.
[00:07:01] So it's interesting, but at some point you have to move on, I guess.
[00:07:06] Kevin Yank: Yeah. You take the good with the bad in any ecosystem until at some point you get to try something new, I guess.
[00:07:13] Kolja Lampe: Yeah. At work, I'm now doing Angular for better or for worse, I guess. And yeah, that's quite a big change.
[00:07:20] But, it's not like we're only doing the front end, like I'm doing pretty much full stack. And to stack is quite, quite deep in the project. Like we got RabbitMQ, we got TimescaleDB, Postgres and MongoDB, and some machine-specific protocols. And so it's very, very big place to do all kinds of stuff.
[00:07:47] Kevin Yank: There would be quite a contrast between your, your work during the day, on angular and some of your, side project work in Elm. I often talk to people who are familiar with React and who have moved to Elm or are exploring Elm. And there the difference is, mostly syntax. A lot of the concepts are the same and there's a lot more strictness and guarantees that you get from Elm.
[00:08:09] But ultimately the programming model is quite similar, where you are, you know, controlling a state that gets rendered to a virtual DOM in like a unidirectional data flow. And it seems to me that Angular kind of has gone the other way and accepted the other side of all of those tradeoffs. And it gets good things from that and it's gets bad— bad things from that.
[00:08:33] But, it would be more of a jump back and forth between the world of Angular in the world of Elm. Is that something you feel or is that, am I overstating that?
[00:08:42] Kolja Lampe: So we actually started to use NGXS, which is state management for Angular. So I'm using some learnings from functional programming in general.
[00:08:55] Kevin Yank: Right. So I'm not familiar with that package, but it sounds like it sort of adapts the Angular environment to that sort of functional stateless approach.
[00:09:04] Kolja Lampe: You just get a state and you get to update it and it's not as nice as it's in Elm, if you ask me. It's actually kind of weird in some regards. Like you have multiple stores, and stores kind of exist when they shouldn't exist. The debugger tells you. But yeah, it works for the most part.
[00:09:29] Kevin Yank: So how did Elm end up in the mix for you? Are you just the kind of person who's like really into open source and so you end up exploring a lot of things as they come up, and Elm just crossed your radar for that reason? Or were you out looking for something like Elm.
[00:09:42] Kolja Lampe: I don't actually remember how it happened. Like, I've been testing many languages. I had the phase where I was jumping from language to language, I guess, just to learn stuff. Maybe because I was bored at my last job. But I'm pretty much the language guy in the new company.
[00:09:59] Kevin Yank: I've spoken about, that guy at Culture Amp before, his name is Marcos and he is the reason we are using Elm, but he is the engineer who when we hired him, he was like, “Oh, I've used this and this and this and this before. And I like these ones and these ones I like less. And Elm is something that I would really love to use here.
[00:10:19] “What do you think?” So do you collect languages for fun?
[00:10:23] Kolja Lampe: It's probably not that bad, I guess, but I might be denying it. Like, I did like a small thing I published on GitHub at some time, just to test out of the waters, I guess. But I actually met Evan at Google in 2015 I think.
[00:10:41] Kevin Yank: Were you working at Google at the time?
[00:10:43] Kolja Lampe: No, we were invited to Google Summer of Code Mentor Summit.
[00:10:48] That was the one year where, I saw him, and, it was kind of weird, like, I somehow knew who he was, and he obviously did not know who I was, so I tried to speak to him and it was actually fun. But yeah, that might have something to do with be getting involved.
[00:11:08] I really liked how he thinks about stuff.
[00:11:11] Kevin Yank: You are the author of the Elm language server and, a constellation of related software products around it, from the Visual Studio Code extension for Elm, the new current Elm language support package for that editor. I understand in order to write the language server, you had to write this other thing, which is, the tree sitter.
[00:11:34] Where did that effort start? Because it's not everyone who is a hobbyist in a language, who decides the thing I want to contribute is, the tooling for editors.
[00:11:47] Kolja Lampe: Yeah, I kind of wanted to always do tooling. I'm not sure if it's always, but coming from the other project I'm involved with, we did start to do some tooling, and I really enjoyed it. And so when people were looking at how to do stuff in Elm and get better integration, there were some people that actually started out, but it didn't go as fast as I wanted it to go, basically. They took the compiler and forked it so they could use the AST and, the problem about Elm's AST is that it's pretty strict. So what we want in an IDE is you need to be able to figure out stuff, even if your code is broken.
[00:12:39] Kevin Yank: You've got to half-written function. You still need to be able to provide syntax highlighting and auto-completion on, what is code that the compiler would say, this is nonsense.
[00:12:51] Kolja Lampe: Exactly. And while Evan said it would totally be possible to figure that out, I'm not sure when he would get around to that, like, and it's probably something the compiler might not even-- should not even be doing, I guess. There was a Haskell implementation, but I don't understand Haskell. So I was like waiting for the repo to update and it did not update. And I think the people that were working on it just got lost in other things, so I started to start it again doing a TypeScript implementation, I think December two years ago.
[00:13:31] Kevin Yank: The idea of a language server in the first place, I'm interested in. I had heard the term “language server” a couple of times in relation to TypeScript itself.
[00:13:42] Ruby has a language server that, when I'm working on Ruby files, it mentions that it is talking to something called the Ruby language server. Can you talk about a language server in the abstract and why that seemed like a good thing for Elm to have at the time?
[00:13:58] Kolja Lampe: Well basically, you get multiple editors for free. We can run implementation for the server and just get, multiple clients working for free. Like Vim, now, does completions; Emacs should be able to get completions; Sublime is working; VSCode is obviously working. That's the nice part about it.
[00:14:21] Kevin Yank: So you have relatively thin editor-specific clients for this persistent process that sits running and provides kind of the brain of an editor experience.
[00:14:33] Kolja Lampe: Most clients are just going to connect to the server. They have some settings that you can do and the other stuff is all stock. There are some other nice things that are not in the language server protocol yet that you could do-- which could do on top in the client, but, it's already too much for me to maintain the tree siter, the language server and the VS Code client.
[00:15:02] So I'm not going to implement any more and I'm actually trying to focus on the server. So everybody gets most features for free and not write as many client-specific code.
[00:15:16] Kevin Yank: So you mentioned the language server protocol, there. What I've seen in the code leads me to this understanding and tell me if this is right, is that, editors that provide, standard language server support mean that you can really just-- To write something like the Elm extension for VS Code,
[00:15:36] you basically say, “Hey VS Code. This is a language supported by this language server and here's how you connect to it, and you know what to do with the language server so just, you know, consume its features.” Is this, you know, growing family of editors well supported by, like, this built-in support for language servers?
[00:15:57] Kolja Lampe: Well, it's mostly true for VS Code cause it's maintained by Microsoft. And, like if you were doing changes to the language of a protocol itself, they are saying that you need an example implementation. And most of those example implementations are done in the Node connector to a language server, basically.
[00:16:22] So that's how most stuff ends up in that one.
[00:16:27] We want to attract new developers to use Elm and there's basically no tooling, it's not gonna work, I think.
[00:16:36] Kevin Yank: What was your initial ambition for this thing? Because Visual Studio Code already had an Elm language extension that as far as it went, it was working okay. And you were starting over, I imagine there was, a certain feature or a certain point you wanted to get to where you were like: That.
[00:16:57] That is the thing that was not possible in the old, language extension. And we have now succeeded in our mission for this project.
[00:17:07] Can you remember what that was?
[00:17:09] Kolja Lampe: I don't think it was like that, for me. I feel like it is like the old implementation, which was pretty good at one point, pretty much fell into disrepair when I think when 0.19 came out, like some stuff was working, but it felt not as nice to me. Like most stuff did-- like, completions were pretty much nowhere to be found as far as I can remember.
[00:17:36] But some people who were still reporting that completions are working. I think some people would ask, they're saying that Elm Oracle is working for them, which is weird to me, I guess. But the latest author of the old implementation, moved on to do open source for, like, as a day job, and he's now working on the F# implementation of the language server,
[00:18:05] which is Ionide and which is pretty, pretty good. If you have to look at F# code, I highly recommend Ionide.
[00:18:14] Kevin Yank: And so if the old maintainer of the old extension had moved on to building language servers, that was a pretty good sign that that was a reasonable direction to take for Elm as well.
[00:18:26] Kolja Lampe: Yeah. But I did not know that at the time.
[00:18:29] Kevin Yank: So this is what fascinates me is like how much of an unknown was this project for you when you started on it? When I wanted to, to contribute to it, I was kind of like, “Well, today is the day I'm going to have to find out what a language server is.” And I went to the Microsoft language server specification website and started reading, and it was much bigger than I expected.
[00:18:52] were you in a similar boat with it?
[00:18:54] Kolja Lampe: Yeah, absolutely. Like, it's a big spec. But to be honest, you only need the parts, there that you are going to implement right now. Getting the server to run is the main thing at the start, but I think that's a good starting-- like a template for it to get started,
[00:19:11] where you can get a basic server running, or generated basically, and get it running and see that they can talk to each other and then you can just start to implement stuff.
[00:19:21] Kevin Yank: Was that the reason you ended up building it in TypeScript, because there was a strong template to start from?
[00:19:29] Kolja Lampe: Not the template, but that I could be sure that the-- like we're using a package to-- like a Node package that I mentioned before, we're using to talk to the language server basically. But the thing for me really was that I could be sure that they are going to maintain it and keep it up to date. Like I have been thinking about learning Haskell, and doing it that way, but I would have needed to have the same library, I guess, and I don't think I would have been able to pull that off in Haskell quite as good.
[00:20:13] Kevin Yank: Yeah. I think back to like the elmjutsu package for Atom, having been written in Elm itself, that always seemed to me like an amazing magic trick, but if that wasn't what you were setting out to prove was possible, it might not be the easiest path to success. How long were you working at it before you had something that you felt like was a release that people could use?
[00:20:37] Kolja Lampe: That's a good question. Like, I think I needed about six months to get Tree Sitter to work. As good as what's working at that point, I guess.
[00:20:46] Kevin Yank: So let's talk about Tree Sitter. What problem does it solve? What led you to that problem and what led you to think, okay, this is a separate enough problem that I'm going to implement this as a separate package first.
[00:20:59] Kolja Lampe: Tree Sitter itself is the library from a guy that's working for GitHub. It's, I think it's even owned by GitHub right now. So it aims to be general enough to, parse different programming languages. So you write a definition file, basically, and it will figure it out, where your nodes are, create an AST and be fast about it.
[00:21:24] Be robust so that it can even parse stuff where errors are in your file. And it's doing some different tricks to achieve that under the hood. But the next thing is like, it's completely dependency free. It-- When we started it was implemented in C, now it's done completely in Rust and it's just growing and growing.
[00:21:48] Like they started to add, like coloring some time ago and just yesterday they merged a pull request where you basically get something like ctags where you can, make the language server aware of-- no, not the language server, but tree sitter aware of how references are working, so they're actually using it on GitHub.
[00:22:13] Right now for the data science teams. And if you've ever used the JavaScript or TypeScript, functions in GitHub where you jump to a definition or find references, that's all implemented on the back of Tree Sitter.
[00:22:27] Kevin Yank: Yeah. Wow. The tree it's sitting on, I think I made this mistake when I first heard about Tree Sitter of assuming that the tree it's sitting on is the directory structure of your project. And it's, it's something that is spotting changes to files and letting you know when you need to refresh your model of the contents of that file.
[00:22:46] But it's actually the, tree of the syntax. It's the abstract syntax tree inside of a given file that it is sitting and observing and notifying you of changes to. Is that right?
[00:22:58] Kolja Lampe: Well, it's not really notifying you about anything. Like it's just, you show it input, basically-- you give it a file and you-- no, you're not giving it a file, but you're giving it the content of your
[00:23:13] file and it will parse those into an AST. You need to say “this is an Elm file”, obviously, like use the Elm parser for it.
[00:23:23] It can also have nested programming languages. But in our case we say, "Hey, this is Elm, and please parse this as Elm”, and it tries to figure out what the AST is, and the next time that we want to parse it, Tree Sitter is optimized to parse stuff on typing basically, so it should be fast enough to, reparse whenever you add something to the buffer or remove something from the buffer.
[00:23:54] So, we can actually do an update of a tree. It's really, really so fast. We're parsing really every Elm file I can basically get my hands on with Tree Sitter.
[00:24:06] So I'm downloading the package index for Elm, and I also handpicked projects. And I think it's 10 thousand files and just, okay, 45 seconds or something like that. Most of the time is spent on downloading stuff from GitHub.
[00:24:24] Kevin Yank: The actual work happens in the blink of an eye.
[00:24:26] Kolja Lampe: And that's quite amazing, actually. Like, one of the problems I had was like when I went downloading every package that has ever been there for 0.19, some people like to have broken, broken files, and they're like-- Either they are not updating their tests,
[00:24:46] I'm not sure why that is, or maybe some old 0.18 examples. We fail to parse those obviously, but like we're parsing 98% of the files I'm giving it. And parsing, is this case it just means that some nodes don't work, like most of the files will still work.
[00:25:07] Kevin Yank: So how did, how did you come to Tree Sitter? Like is that a, is that a tool that is in common use by other language servers now and that's how you get high-performance parsing, or was it a case of you knew you were going to need some parsing at the heart of this language server and
[00:25:24] Tree Sitter happened to fit the bill really well?
[00:25:28] Kolja Lampe: Yeah, the latter. I knew I needed to do something with the syntax of the files to be able to understand it and to walk the trees, basically, but I have never done an AST implementation. And so I knew we would need it in some way, and it being robust, like, it needs to absolutely parse files that are not complete, and figuring all this stuff out would have taken me probably a year I guess.
[00:26:00] Kevin Yank: It's already available as a NPM package that you can pull into your language server and, you know, it installs the native version of it for your platform automatically. It must've seemed like a miracle when you found that and realized how many problems it solved for you.
[00:26:17] Kolja Lampe: We started out with including it normally, but now we're using the, WebAssembly version, so that we can basically sidestep having your PC to download a binary
[00:26:28] for each platform individually-- like a Mac needs something different than Windows will need and stuff,
[00:26:34] so with WebAssembly we can just include the WebAssembly and every platform should be happy.
[00:26:40] Kevin Yank: Yeah. Wow. I'm trying to put myself in your shoes, here, working through this of like, “Okay, I'm, I'm going to build a language server. I guess I'm going to have to start building a parser. Oh wait, there is this thing, this Tree Sitter that is a framework for parsers and I can just contribute the Elm support to that.”
[00:26:59] You were several layers deep at this point. Imagining myself in your shoes and I'm wondering if I would ever get back to the top of the stack and actually deliver Elm language support for an editor. Or if I would end up, you know, stuck at contributing to one of these inner layers of the thing.
[00:27:16] Kolja Lampe: I think I just enjoyed writing the parser because I've never done any AST parsing, and it was kind of fun, like I was inspired by the trees that the IntelliJ plugin is using. I started to, read the Kotlin files they use to parse and tried to understand that too. And so the trees were pretty similar in the beginning, as far as I can tell.
[00:27:44] But yeah, it was nice task and something I could do on the side while watching TV, I guess.
[00:27:53] Kevin Yank: I guess at a certain point you found yourself with a working Elm parser. You were then able to start work on supporting one by one the features of the language server protocol.
[00:28:04] Kolja Lampe: Yeah, it's exactly like, I was aware that there was also a Ruby implementation that was using Tree Sitter.
[00:28:11] Kevin Yank: So it was another language server that had Tree Sitter in its core.
[00:28:15] Kolja Lampe: Yeah. And I'm not even sure if that's still alive or going, but it's the only one I've seen so far. If you start your editor, we start, like getting all your Elm files and parsing them.
[00:28:29] And like, we have a forest full of trees, which are not connected, right? So we need to figure out which tree is referencing which other tree, and basically build another layer on top of that. Otherwise we won't be able to, like, go to files or find references. As I mentioned earlier, this part might die whenever we implement the new features in Tree Sitter, so, we could move it deeper into the stack to Tree Sitter,
[00:29:02] which we absolutely should do because--
[00:29:04] Kevin Yank: It's so much faster.
[00:29:05] Kolja Lampe: Good point. It's going to be faster. Not that I'm not sure if that has been a problem, but we can probably get, GitHub to include the Tree Sitter on their website for Elm.
[00:29:17] Kevin Yank: So the embedded editor for Elm in GitHub could then be reference aware, for example.
[00:29:23] Kolja Lampe: Yes, absolutely, like, just like for JavaScript. And we could have done that, but they were saying like-- The project is named Semantic, I think, from GitHub. And you could, like, do a pull request and add Elm. But, they wanted to have like so many tests and stuff, but they're trying to make it easier to do this.
[00:29:47] And I think there's effort going into it and they really want it to happen. Like, it's only going to be a good thing for them right.
[00:29:57] Kevin Yank: I'd like to, go off on a tangent for a second. Something that you mentioned a little while ago was that you were involved in this Google Summer of Code project and, and, you know, got to meet Evan in the process. I'm curious to hear, kind of, what has been your experience of the Elm community?
[00:30:17] Like what part of the world are you in, for example? Do you go to any meetups? Have you been to any conferences? How have you joined the Elm community apart from being the maintainer of this software package.
[00:30:29] Kolja Lampe: So I'm in Germany. I have been like, I've been with a friend in Berlin, some like last year, I think, and I just popped into the german Slack room, I think, and ask if there's anybody in Berlin that wants to meet up. And so I met up with @unsoundscapes.
[00:30:49] Kevin Yank: He is on my list of people I really want to get on this show.
[00:30:52] Kolja Lampe: Yeah, it's-- Awesome dude. And some other guys like, was it Erkal?
[00:30:56] Yeah. We just hung out a bunch. And we were like, normally we go to America, for Google Summer of Code Summit,
[00:31:06] but last year it was the first time in Germany and Munich, and as it might happen, like, there was an Elm meet up the first night
[00:31:17] I was in town, so I grabbed my friend from the States. He came along and we heard a talk from Christophe, I think. We don't have any meetup in my city, but I'm just going to the, like, all the functional guys here around seem to go to this F# meetup that's done by a good friend and,
[00:31:44] well, we just enjoyed to talk about functional stuff, I think.
[00:31:47] Kevin Yank: That reminds me of here in Melbourne, the Ruby meetup, for example, has very much morphed into a social group for people who like programming languages, and were led to their current programming language via Ruby at some point in the past. I would say that on a given night, only about half the people there,
[00:32:10] are actively working in Ruby to this day.
[00:32:13] Kolja Lampe: I actually think that the thing that's bound to happen to most communities, I think.
[00:32:19] Kevin Yank: Well, I suppose that's how you know it's a good community when it outlives or grows beyond the specific language that started it.
[00:32:28] Kolja Lampe: Yeah, absolutely. Another thing I was thinking before this pandemic happened was like, I've done an Elm talk at my company, like, where we do meetups sometimes, and I did a meetup to talk about Elm to people that never heard about it, basically. But so I basically have a talk lying around, which I've only done once.
[00:32:54] So I was thinking to just fly to other communities all over Germany and say, “Hey, can I talk about Elm in your meetup?” and just drive there, like, have a good time.
[00:33:07] But yeah, that's not going to happen very soon, I guess.
[00:33:13] Kevin Yank: Yeah. Well, one day all of our doors will open again and we can get on the road.
[00:33:18] Kolja Lampe: Well, I guess we should just get started doing online meetups, like we've, like I said, F# community started to do this kind of cool online meetups and hangouts, and that's been pretty cool so far.
[00:33:32] Kevin Yank: So coming back to the language server, you put out an initial release at some point, and how were you feeling about the project at that time? What were you proud of? What were you still hoping, or still very much eager to add?
[00:33:46] Kolja Lampe: Oh, there's so many things that could still be done. Like, as soon as you add something that's awesome, you still think like we can maybe do it better this way, or maybe add this. Like one thing that's always been on the wishlist is like, type inference,
[00:34:06] which I actually started to work on, but I'm not going to give any ETA because that's just going to hurt everybody.
[00:34:14] Kevin Yank: Type inference, meaning, on any given expression in the editor, you can ask the language server, “What is the type of this?”
[00:34:23] Kolja Lampe: Yes, which will be most interesting for functions, I guess, but it's going to be good to learn stuff and figure out what the language server thinks this is. And hopefully reflect what Elm thinks it is. Like, the times I skipped type annotations always came back to bite me, in my experience.
[00:34:47] Like, if you put a type annotation above it and you figure out that something you thought was something completely different is actually not
[00:34:56] the type you get at this point.
[00:34:58] Kevin Yank: Well, yeah, even just writing my type annotations for me. That would probably be my, my biggest wishlist item. And it's something that we used to be able to do by harvesting the warning messages from the Elm compiler, but Elm 0.19 doesn't have those warning messages anymore, so we have to come up with those types ourselves these days.
[00:35:20] Kolja Lampe: Tree Sitter is nice and working pretty much, as far as I'm concerned, but we can still improve it. Like, I've mostly only done testing on happy paths for now, and we can probably improve how it fails. Like if you have incomplete code, is it really taking the optimal failure state, I guess?
[00:35:45] Some nodes might get lost, even if it figures out just to ignore those that leaf that you have, maybe it's changed some other leaf to accommodate for that.
[00:35:58] Kevin Yank: So I can imagine you would want to build up at some point a library of, like, broken Elm files and test that they break in the most desirable way.
[00:36:08] Kolja Lampe: Which is, also pretty easy to set up, but I haven't taken that time. Like, building up that library and the tests is pretty easy. But getting the Tree Sitter to actually break like that, if that makes sense, is maybe not as easy for me.
[00:36:27] Kevin Yank: And is that expertise something that you, you want to develop? Is like that the way you want to spend your time on this project, given all the other things you could be doing, I suppose?
[00:36:35] Kolja Lampe: Well, not at the moment.
[00:36:37] I really think that, type inference would be the most helpful thing right now, not just for annotating your functions, but it also will help the compiler-- not the compiler, but it will help the server to figure out, the types, basically, and help you maybe in an anonymous function to know what this type is.
[00:37:05] Kevin Yank: Yeah. So yeah, you're writing an anonymous function. It takes an argument and then you want to provide completion for the fields of that argument, if it is a record, for example. That sort of stuff. So that's your, that's your white whale at the moment. That's the thing. That's the big feature you would like to head towards. You're not prepared to give an estimate on it yet…
[00:37:27] Kolja Lampe: The other thing that I'm not sure how and when we will figure it out is, at the moment, we are only indexing stuff on startup, basically. Like, all your files get indexed and you can then edit.
[00:37:43] But normally if you install, you go-- you work in your Elm files and you add a dependency and it gets installed into your, cache. Basically, like there's a folder on your hard disk somewhere that's not in your project, which is kind of where my problem starts. And there are files that are getting added.
[00:38:07] And so do I set up a watcher for that, location that's not anywhere near your project? Should I just do that, or-- I really don't want to, jump into locations where the user doesn't want me to look. Like, is that going to be spooky for people?
[00:38:28] Kevin Yank: Yeah. Wait, how did you know about that file?
[00:38:31] Kolja Lampe: Yeah. I can see some people not liking that. That's the thing, like with NPM or, yeah, with Node you always get a node_modules for in your project. And it's pretty obvious what to do with that. But in Elm, it's all centralized, basically, which has its pros obviously. But I'm struggling a bit with this decision and how to nicely do that.
[00:38:59] Like the best thing would even be to have, in installer, have a callback and just say, “Hey, we installed, somehow, and you need to rescan.”
[00:39:10] Kevin Yank: Yeah, I mean I could imagine you providing the feature in the editor extension to install a new package and because your extension initiated that install, you could then, you could then parse the files once it was complete, but that would, that would still leave the problem of
[00:39:26] what if the user install the package a different way?
[00:39:29] Kolja Lampe: It's going to be recommended everywhere to do it via the terminal. So there's no easy way of getting out of this, I think.
[00:39:39] Kevin Yank: Oh, well, that's what makes it a fun challenge, right?
[00:39:42] Kolja Lampe: Yeah. That's that part is correct, yeah.
[00:39:46] Kevin Yank: I know as we record this, you just recently did a pretty major feature release for, the Elm language server and the VS Code, extension along with it. And you mentioned in the release that this was largely a collection of contributions from someone else on the project. Can you talk about some of the collaborations, some of the contributions you've had?
[00:40:10] If you, if you want to sing my praises as a onetime contributor, you would be very welcome to…
[00:40:16] Kolja Lampe: Yeah. I actually love to have contributions, like it's a way too big place to be owning mself, basically. Like, there are so many awesome people that have been involved, and are involved, like @jmbockhorst, I think he's named. He's been doing that pretty much, so much over the last weeks, which is mostly most of those feature releases
[00:40:44] you're seeing right now. Ahere was, Andy, which is doing anything with Vim basically. So he's maintaining the vim client as-- Well, there's not really a Vim client, but he's maintaining how to configure stuff and he's helping with documentations and he's just a great guy. He actually sent me a package to my company unannounced, and I got stickers for Elm-- for my language server, basically, with the logo.
[00:41:17] And my-- our front office was like, do you know anybody by the name of… and I'm like, no. And then I saw the nickname on it and then it clicked. But, so those kinds of things are really awesome. And your contribution was also very much appreciated because it was not the easiest thing to get fixed, let me say that much.
[00:41:46] Kevin Yank: I was very happy of how willing you were for me to kind of turn the code structure of the language server inside out in a couple of places. I think coming in, I-- you know, like any other sort of cocky new contributor I thought, “Oh, this is going to be an easy bug to fix; it'll be like a five line change.”
[00:42:11] And, and I opened the issue and I went, “Do you mind if I have a crack at tackling this?” And you said, “This is something I have tried to fix before, and it turned into a much bigger job than I thought, but if you think you can fix it, go ahead.” And I was like, “Great! I'm going to impress you with just how easy a fix it was.”
[00:42:29] And then like three weeks later, I was refactoring half of the files in the language server, and I knew exactly what you had meant. But I appreciated your willingness, to go along with me, to review my changes, to tell me what I had missed when I had an initial version ready for testing.
[00:42:45] Yeah, I enjoyed that partnership a lot.
[00:42:48] Kolja Lampe: Yeah, I think it's always fun to do stuff like this, and I think as far as I can tell, it worked out pretty good.
[00:42:56] Kevin Yank: For those listening, wondering what this was, it was, because, at Culture Amp, we have a large number of Elm apps in a single Git repository that we opened in our editor. And, in many cases, the same library modules are being used by more than one of those, Elm apps.
[00:43:14] So I think at the time, the Elm language server was built on the assumption that you would point it at a single elm.json file and it would support the editor working across that context. But in our case, we wanted a single instance of the editor working across something like 17 elm.json files.
[00:43:35] And if you open any old Elm file, it should be able to know which elm.json file is responsible for it and what its dependencies are. And, you know, clearly we were pushing the boundaries and that is-- I was not at all surprised that that was not a supported scenario in a brand new language server,
[00:43:55] but yeah now that it's working, it's working surprisingly well.
[00:43:59] Kolja Lampe: That's good to hear. Like, it's interesting that we get-- like, we have multiple levels of workspaces. VS Code can have workspaces, Elm can have something like workspaces, I guess, but then again, you can nest them, I think? It's getting pretty, pretty confusing at some point.
[00:44:21] Kevin Yank: So an elm.json file and all of the Elm source files that go with it is referred to as an Elm workspace in the code base. But like you said, Visual Studio Code has the idea of a workspace and you can have more than one of them open. And do you have one language server per VS Code workspace, or do you have a single language server for all of the workspaces?
[00:44:45] And if you have multiple workspaces with multiple elm.json files inside of them, just what workspace are you talking about when you say “workspace”? It's very hard to keep straight.
[00:44:56] Kolja Lampe: On the other hand, if you've solved that, it's pretty much going to be solved for everything.
[00:45:02] Kevin Yank: Yeah. Well, fingers crossed.
[00:45:04] Kolja Lampe: Fingers crossed.
[00:45:06] Kevin Yank: From an observer's perspective, it still feels like the language server is somewhat young in that, you know, on any given week, if you're looking at the Slack channel, there's often someone coming in and going, “Hey, it's not working for my scenario and it's crashing in a weird way. Can you help me?” And usually they get the help they need and, it ends up working. But it seems like there is still like a number of edge cases out there or strange configurations, or in my case, like really weird directory structures that weren't anticipated. It feels like the project is still somewhat working its way through those and shaking those, kinks out of it.
[00:45:49] Does it feel like that to you?
[00:45:51] Kolja Lampe: It might sound worse than it actually is. Like, I'm really, really glad if people come and report stuff, because I can only test on the codebases I have, and it might really depend on your code base of it's going to crash or your setup and there's so much stuff involved that
[00:46:11] can affect it, I guess. And I can't really test everything, like. But now that you mention it that there's like, if I had all the time in the universe, I would start writing more tests, obviously. Another big thing that we could do is not depend on elm-analyse and not depend on the compiler.
[00:46:32] Basically write our own thing based on Tree Sitter and figure out, like, not use elm-analyse, but figure out the stuff that elm-analyse gives you, via Tree Sitter.
[00:46:48] Kevin Yank: Are you saying that's where any fragility seems to, occur is at that, interface between, the language server and the external tools that it is calling, elm-make, elm-analyse and elm-format, that at that moment of going, “Hey, I'm going to invoke another process and interpret the shell output that comes out of it,” that, that tends to be the place where unexpected things happen?
[00:47:17] Kolja Lampe: Yeah. Like with a Elm 0.19 we had cases where the --report=json function from Elm did not report valid JSON, and so we couldn't parse it, and just crashed. Stuff like that you can probably handle it with integrations, but it seems like somebody has found another edge case where this might be happening like yesterday.
[00:47:43] And I'm not sure how to get out of that without, when we rely also on those tools and for some cases, I'm not sure how much time is spent to, like on the Elm compiler, there's probably not going to be a problem in the end, but like, I'm not sure how much time is spent to maintain stuff like elm-analyse, and how long it's going to be in a state that we can work with it. We actually have to maintain a fork of elm-analyse right now because we couldn't upstream stuff we have at the moment.
[00:48:19] So yeah, it's probably going to happen at one point, but, I think there's actually an open merge request, but it hasn't really moved. And as far as I could tell, it's not the fastest tool we've used.
[00:48:34] Like, I've done some performance stuff some weeks ago where we were loading the Tailwind Elm File, which is just like 1,500 functions, something like that, and it took like forever. And one of the reasons it took forever, it was just that, elm-analyse did not
[00:48:55] like that file really. We had our own problems, which increased the time, but after I fixed that and disabled elm-analyse, it worked really good. So it was a combination of both. elm-format is at the moment you get a whole new file every time.
[00:49:14] Like, what's the language is service doing is it's asking, “Hey, is this file has to be formatted? Here's the content.” elm-format would parse the file and format it for us. Then we get the formatted file back and we'd actually diff the old file against the new one, and create a diff and apply just the diff to your buffer.
[00:49:39] So that has… that causes… well that leads to your editor not jumping up and down. Like if we replace the whole file for you, where is your cursor?
[00:49:53] Kevin Yank: So it's worth doing, but it's a fairly inefficient set of layers of parsers sending, communicating with other parsers through whole file snapshots.
[00:50:03] Kolja Lampe: Yeah. It would be much nicer to just ask the formatter to get us a diff.
[00:50:10] Kevin Yank: Yeah. Right.
[00:50:11] So I think I speak for the entire Elm community or at least everyone who is using an editor that, that has an integration with the language server, that I, I want to thank you for all the work you are putting into this. Is it fair to say you are spending more time working on the Elm language server, than you get to spend actually writing Elm?
[00:50:30] Kolja Lampe: Yeah, absolutely.
[00:50:34] Kevin Yank: That is something of an injustice, I have to say, that the person who is working hardest on the tooling gets to benefit from it the least.
[00:50:42] Kolja Lampe: But it might've actually highlight how much I'm depending on feedback from the community. Like, open up issues. Even if I'm not be able to help you this week or next week, but it's really, really important that we keep stuff reported and maybe there's some people from the community that want to help out. Like, I pretty much have three projects that I need to maintain alone just for this.
[00:51:08] Kevin Yank: So bug reports, welcome. Contributors, welcome.
[00:51:11] Thank you again for all of that work you're putting into it. I am a big fan of the language server. I have to say every time I log into the Elm Slack, the Elm language server channel is the first one I click on to go and see what's new and whether there's a new release and, and honestly,
[00:51:27] there have been days where I have chosen to spend some time on an Elm side project because there is a new feature in the language server that I want to play with,
[00:51:36] and I can think of no higher compliment for you than that: that yours is a tool that makes me want to use Elm more.
[00:51:43] Kolja Lampe: Yeah, that's, that really makes me have a big smile on my face right now. Like, that's a big compliment. But I really want to thank you too, for doing Elm Town.
[00:51:52] Kevin Yank: Really happy to hear that you're a happy listener. I agree that it is a really good back catalog and that's not only my doing, that's Murphy's doing before me as well. I am only hosting this show because I was such a big fan of it beforehand.
[00:52:04] Kolja Lampe: But that's a good way to work it, right? Maybe someday you will have to move on, but maybe we can find another one that's seeing it just like this.
[00:52:15] Kevin Yank: That sounds good to me.
[00:52:17] And I think that's a great place to leave it. Thank you so much for the chat Kolja. I really enjoyed it.
[00:52:22] Kolja Lampe: Thanks for inviting me.
[00:52:24] Kevin Yank: And thank you, listener for joining us once again in Elm Town. like I said, we've got a lot of episodes in the hopper, and you can look forward to more Elm Town coming to you soon.
[00:52:33] Bye for now.
Aaron VonderHaar returns to the show for a deep dive on automated tests, test-driven development, and elm-program-test, a new high-level test framework for Elm.
Thank you to our sponsor, Culture Amp.
Special thanks to Xavier Ho (@Xavier_Ho) for editing and production of this episode!
Recording date: 2 April 2020
00:00:00 Intro
00:01:42 TDD and automated tests at NoRedInk
00:02:19 elm-program-test
00:04:36 Why write automated tests
00:06:44 Test-driven development
00:08:55 Tests vs types
00:11:33 Test-driven development (continued)
00:13:25 Red, green, refactor
00:16:18 Test-driven development (continued)
00:20:23 Testing at the right level
00:24:53 Testing culture in a team
00:26:43 The need for elm-program-test
00:30:22 The elm-program-test API
00:32:12 Standing in for the Elm runtime
00:35:34 Testing Elm commands
00:37:49 Standing in for the Elm runtime (continued)
00:39:46 Resolving and asserting on HTTP requests
00:43:08 Other supported effects
00:45:12 Modelling the user interface in your test suite
00:47:18 Smart DOM matchers
00:49:05 Keyboard focus tracking
00:49:48 elm-program-test vs non-Elm alternatives
00:51:53 Stability and feature-completeness
00:53:00 elm-program-test at NoRedInk
00:54:42 Testing the interface with the back end
00:55:58 Related talks by NoRedInk colleagues
00:56:53 Sign-off and outro
[00:00:00] Kevin Yank: Hello and welcome back to Elm town. It's your old friend Kevin, and I am rejoined by Aaron VonderHaar back for his second episode. Welcome back, Aaron.
[00:00:09] Aaron VonderHaar: Hey, Kevin, it's great to be back.
[00:00:10] Kevin Yank: Aaron, you were with us a few weeks back now to talk about your work on elm-format, among other things, including a new refactoring tool that, we'll be releasing very soon, I imagine, by the time our listeners hear this. If you haven't caught that episode,
[00:00:28] go back two episodes and have a listen to that first chat we had with Aaron, because we will be picking up on the threads of that conversation here today with a focus on elm-program-test, and testing in general for Elm. Richard Feldman is not the only person that, NoRedInk who cares about testing.
[00:00:48] Am I to understand that right, Aaron?
[00:00:50] Aaron VonderHaar: That's right. Test driven, and having a lot of automated tests has been pretty core and NoRedInk, for everybody there. So, yeah, we all get into it now.
[00:00:58] Kevin Yank: I'm actually interested in hearing about the background of that, cause I would say that's maybe not true of every company that's using Elm today. But I'm aware that NoRedInk started as a Ruby on Rails application, where in that ecosystem, in the Ruby ecosystem, test driven development and automated testing in general is very strongly embedded in the culture of that programming community.
[00:01:23] Is there a sense that NoRedInk's investment in testing and belief in testing flowed out of that background in Rails or, not?
[00:01:30] Aaron VonderHaar: Yeah, I'd say that is definitely fair to say. RSpec is the big testing framework there, and that was definitely encouraged by Rails, the framework as well.
[00:01:40] Kevin Yank: And so I guess a lot of developers who are used to having a strong testing framework on the back end, they are invested in the idea of having that on the front end with Elm. And that leads to people like Evan (sic.) working on elm-test and yourself working on this new package, elm-program-test.
[00:01:59] Aaron VonderHaar: I mean, web apps in general these days, testing is highly thought of across a lot of JavaScript platforms as well. So it's not unique to Elm, but Elm kind of being a totally separate language, even though it works in the JavaScript ecosystem, kind of needs its own tools and its own way of doing things that supports the language itself.
[00:02:19] In a nutshell, elm-program-test is an Elm package that you can use alongside elm-test, and it lets you write tests that are maybe at a higher level of testing than what you can do with elm-test out of the box. So the types of tests you can write in elm-program-test are similar to maybe what you might write in tools like Capybara in Rails or Selenium where you write tests saying, okay, the user is loading this page,
[00:02:48] the user is going to click this button, fill in text in the input with this label, navigate to this other page, submit the form, and then check that certain things appear on the page afterwards. So writing tests at that high level of the user perspective, is what program-test is designed for. And I like to think I've made a pretty nice and easy-to-use API to let you do that in a powerful way.
[00:03:10] Kevin Yank: So if elm-test by itself is for unit testing, elm-program-test is for the other kinds of tests you're going to write.
[00:03:20] Aaron VonderHaar: Yeah. So certainly if you have a function or a module with pure functions in it, using the built in stuff in elm-test is the place to start. After a while, Noah Hall developed the elm-html-test, which is now incorporated into the elm-test library itself, that lets you check things on the HTML values that get produced by your views. elm-program-test goes a step further and actually lets you incorporate your init function, your update function and your view function and all the messages and everything into a single unit that you can just run user steps on and inspect the output of the view and kind of interact with your program and check what the user's going to see afterwards.
[00:04:03] Kevin Yank: So tests at the program level. Thus the name.
[00:04:05] Aaron VonderHaar: It took a while actually to get to that name, but I think it reflects the purpose now.
[00:04:10] Kevin Yank: I can imagine, cause as you were describing the type of tests it's for, those tests I've heard called acceptance tests and feature tests and end-to-end tests. And I imagine all of those terms, were on the table to name this framework. And in the end, you landed on, “Well, actually, what do we call the thing we're testing here in Elm?
[00:04:29] “We call it a program.” So yeah, it makes sense once I understand what it does.
[00:04:34] Aaron VonderHaar: Yup. Exactly.
[00:04:36] Kevin Yank: Hmm. Well, let's take a step back, here. We're going to get to the details of elm-program-test by the end, here, but I'd like to come at this, from kind of a first principles, “What are you trying to achieve and why?”
[00:04:48] question here, cause I dare say there are a lot of people who use Elm, who are listening to this, who use it as a hobbyist or, as a first language of this type, are exploring the space and are maybe, maybe not necessarily feeling the need for a test framework as the first thing they want to invest their energy in as they're exploring Elm.
[00:05:11] So tell me a little bit about your philosophy around testing. When should you start testing a piece of Elm code what should that look like?
[00:05:20] Aaron VonderHaar: If you're working on a production system where you have a job that depends on code that you're writing working with other code, and many people are touching it, and it's a system that's used by real people in that environment, there's certainly value in testing in that it protects you from bugs, it protects you from future regressions, that sort of thing.
[00:05:41] But there's another aspect to testing, which, I think last time we talked about some of my background, previously working at Pivotal Labs doing agile consulting, and the way I learned to use testing in development then, and specifically the test driven development practice, is an approach to kind of integrating writing tests and thinking about tests
[00:06:05] into the way that you actually write the code. And you can do that through test driven development in a way that helps you think about the design process for designing the code, both the architecture of your code and the implementation. So I'd say if you're working on a hobby project that maybe doesn't have the correctness requirements of a paid job, it's certainly up to the individual.
[00:06:30] But personally, I find a lot of benefit in the process of using tests to think about and educate and even help me find correct or even better solutions faster. So I kind of use it as a thinking tool as well.
[00:06:44] Kevin Yank: Right. So for someone who hasn't practiced TDD, or maybe is hearing the term for the first time, how would you describe that process and the experience of using it as a thinking tool, as you put it?
[00:06:56] Aaron VonderHaar: When you get into kind of the details of test driven development, one of the big things is to think about the context of the caller, the person calling your code if you're writing an API, or the user that's using your site if you are building an application. So the way it tends to play out for me is I'll have some feature that I want to implement.
[00:07:19] Rather than just looking at it as a specific list of things to implement and make work, I want to look at that feature and think, okay, who is going to be using this feature? In the case of NoRedInk, we have a lot of teachers and students, so we might have. Like a student leader board for a class. We think about who is a teacher, who's going to be using this feature, what's going through their mind when they come to interact with it?
[00:07:44] And then from that you kind of walk through a scenario of a real teacher in your mind, actually using the feature and try to write tests from that. Kind of the process of TDD is you think about, okay, if you tried to go through this scenario as a user, what's the first thing that the site currently doesn't do that you would run into in trying to go through the workflow and that's your first test.
[00:08:10] You write a test saying, okay, as a teacher want to see the leaderboards for my students. That's your first test, and you'd say, okay, what page is that going to be on? The body of the test would be load the page where it's supposed to be, stub out some data about the class and maybe the grades in the class, and then check that certain things appear on the leaderboard, which are the things that the teacher would be looking for,
[00:08:35] like maybe the name of the first student and the number of points that student has.
[00:08:39] Kevin Yank: Assuming that page doesn't exist yet, that test will immediately fail.
[00:08:43] Aaron VonderHaar: Yup, exactly. And in the case of a strictly type language like Elm, it may not even compile yet. So, you can often think of compiler errors as a sort of test failure in a way.
[00:08:55] Kevin Yank: That does get at something, which I've heard said and said myself a number of times over the years, which is having a strongly typed language, like Elm means you have to write fewer tests. Is that something you believe?
[00:09:09] Aaron VonderHaar: I would say that is sort of true. Yeah, I definitely know that argument of types versus tests. there was an interesting way I saw of thinking about this, where in a functional language functions are types of values in a sense, like you can have a variable that contains a function. So when you're writing tests, you can think of that as reducing the number of possible functions that exist that could be implementing the type annotation that you've defined for that function.
[00:09:42] So in that sense, there's some similarities between tests and types in that by changing the type annotation, you could restrict what kind of inputs the function can take or what kind of outputs it can produce, that reduces the number of possible implementations you could have for that function, which hopefully reduces the number of possible incorrect implementations you could have.
[00:10:04] And the same is true for tests. By adding a test, you are in a different way and using different tools, reducing the number of possible valid implementations, for whatever the function is.
[00:10:15] Kevin Yank: All right. And so if your goal is to, first narrow the possibility space of what this code might do, if you're using a type to do that, maybe that's one less test that you have to write. But speaking for myself, those would be the, probably the lowest value tests I would be writing in another language.
[00:10:34] And the higher value tests are still worth writing.
[00:10:36] Aaron VonderHaar: Yes, exactly. So like an example is in JavaScript, let's say, you might want to write a test saying that when given these inputs, the particular function does not return null. It always returns some default value or something like that, in case of an error. In Elm, that's an example of something where the type can handle that.
[00:10:58] You can enforce in the types that whatever the function will never return null. But also on the other side of the coin, in JavaScript, you aren't always writing a test that your function does not return null. For every function, certain functions, it makes sense because you know it's a risk depending on the inputs, but other functions, you know, okay, it's never going to come up,
[00:11:19] or if it does come up, it's obvious what's wrong, in which case you don't necessarily need to write those tests. But certainly in Elm, there is a lot of safety that you can get basically for free or just for thinking about the types. Whereas in JavaScript, you don't have that protection.
[00:11:33] Kevin Yank: So back to this TDD scenario, we've created a test to where the teacher wants to go to the page and see the scoreboard and, the compiler fails, or if you happen to luck out and the compiler passes, the test fails because the page doesn't exist. What next?
[00:11:50] Aaron VonderHaar: I tend to think about different levels. So the test we just talked about is a fairly high level test. Maybe you heard the words high level when I talked about elm-program-test.
[00:12:00] Kevin Yank: Yeah. I was going to say, this sounds like the kind of test you would write without elm-program-test.
[00:12:04] Aaron VonderHaar: Right, exactly. And that's in fact kind of why I wrote elm-program-test to fill that gap. But even if you aren't writing that test, you can think about that as a test case anyway and say, okay, I'm going to start my local development server. I'm going to login.
[00:12:19] I'm going to try to go to that page. You see it fail. In a sense, you can think of that as a manual test case. In either case, the next step would be, okay, why doesn't it fail? We've already diagnosed that is because the page doesn't exist. Okay, we'll go create the page. If there's any logic related to the creation of the page that's non-trivial,
[00:12:39] we might want to write a test for a lower level detail. For instance, maybe we need to decode some page flags, for loading the page or something like that. This is getting a little bit contrived as a first page for a new test, but I think about it in layers where I write this kind of high level test describing the user behavior,
[00:12:59] then I see what's failing. I think about why it's failing, and then I figure out, okay, what code needs to exist for that first step to be able to move on. And sometimes at that point I go down and write a lower level or a more unit test on some specific module maybe to implement some function that I'm going to need to wire up the test at the higher level.
[00:13:24] Kevin Yank: Often when talking about TDD, we talk about the red, green, refactor cycle. Is that something that is still a real part of your workflow today or is that more like something you learn on your first day of TDD, but you kinda let go of the details of that process over time?
[00:13:40] Aaron VonderHaar: It's definitely something I keep in mind, and I feel like I follow that pretty consistently, but also it's not like I have a stoplight on my desk, like, to remind me—
[00:13:51] Kevin Yank: What mode am I in? Yeah.
[00:13:53] Aaron VonderHaar: Right. Yeah, I think it's something I tend to maybe reflect back on if I am in a position where I'm getting stuck and I can think, okay,
[00:14:04] let me run through kind of my checklist of what I need to do. Do I understand what behavior I'm trying to implement right now? Am I trying to take too big of a bite right now? Am I getting lost in the weeds? Is there some simpler version of the problem that I could focus on making work right now?
[00:14:23] An example that comes up for me a lot is, okay, maybe I'll have a failing test and I know that to make that test pass, it's going to require both maybe writing some information into the database and then reading it out later. I guess this would be in the case of a Ruby test where we're on the back end. But often you'll have a test failure where to make it work, you have to do something to both change what's getting stored internally and something that's being used, reading that internal state to produce some outcome. And often what I'll do in that case is I will make the test pass by just hard-coding the output value somewhere in the code, which the way that helps is that it helps me find the exact location in the code where I'm going to need to have some logic to decide what to output.
[00:15:14] And then often that'll make it obvious to me what specific value I need stored in the internal state that I need to be able to use to make that determination. So it kind of breaks the overall problem into. The output and the input step and you can tackle those separately.
[00:15:30] Kevin Yank: For those who may not have heard of red, green refactor, the idea is you first write a failing test and your tests are then red because they fail. You then write the necessary implementation to make that test pass, and the tests turn green, and then having that passing test suite, you are then allowed to refactor your code to try to make the structure of it a better expression or a more maintainable version of the set of features that your tests guarantee will exist.
[00:16:01] And as long as your test say green, you know, you haven't broken anything, and when you're happy with the shape of your code, it is time to write another failing test before you make your code do anything new. And so you go through that red, green refactor cycle all the way to success. Now, in your example of the scoreboard for the teacher, you mentioned hard-coding
[00:16:23] a value. And the thing that came to mind was, okay, as we are writing this scoreboard screen, we might start with the empty state. So there are no students have taken this test. So there are no scores yet. And so the first test I might write is I go to the scoreboard screen and it says “no scores”.
[00:16:44] And the easiest way to make that test that is currently failing pass is to hard-code a view that outputs the HTML “There are no scores” – at all times – and your test turns green. And the idea of red, green, refactor is before you then go on to add more features to this page, you need to add another test that says, oh, and if there happens to be some data in the database, then I get a different result on the page.
[00:17:13] And you add that feature while ensuring that the old empty state continues to work.
[00:17:19] Aaron VonderHaar: Yup. Exactly. And you were asking about how strictly I follow that. An interesting optimization for the example you just laid out is something that actually I learned to do from a product manager. But the idea is that, okay, the empty state seems like a natural starting case, but if you try to think about, okay, can I make my first test be a test
[00:17:44] that is the most common test that's going to happen in real life. So in this case, okay, most of the time teachers are going to have students. So if you make your first test be having actual data, it can kind of be a shortcut in a sense because it forces you to drive out more of the implementation upfront,
[00:18:02] and also gives you some flexibility from the product side to maybe descope the fine tuning of the empty state later, if you want to get a feature released sooner.
[00:18:11] Kevin Yank: One of my pet peeves is opening a test file and having to scroll past two pages of really artificial, edge-casey tests before I get to the meat of what does this thing actually do,
[00:18:23] so yeah, I like that approach. When I find myself leveraging the rigorous version of TDD is when I'm writing a piece of code that I don't necessarily fully understand yet, or I can't quite hold it in my head.
[00:18:42] Richard Feldman has done a number of talks about how, you want to get your data representation right: he's given a talk about, you know, focus on designing data structures; he's given another talk on making impossible states impossible. And all of those things come back to getting the data structure right and everything flows out of there.
[00:19:03] But every now and then I am solving a problem where I go, wow, I honestly don't know what the right data structure is going to be for this. There's just one too many features to support with a single Elm type definition, and how am I going to implement those constraints? How many of them will be provided by the type system?
[00:19:23] How many of them will be enforced by the functions that act on that opaque type? And if I don't know, that's when I break out TDD and I start going, all right. One requirement at a time, here. Start with what is the most important thing this has to do. Get a green test that guarantees it will always do that, and then start thinking through edge cases,
[00:19:43] start adding constraints one at a time. And invariably my implementation code starts to get ugly at that point, and forcing myself to wait until my test suite is green before I start messing with alternative approaches is really valuable. It kind of forces me to slow down, to prove to myself that an idea I'm experimenting with, actually satisfies all of the constraints that I can't hold in my head all at once yet.
[00:20:12] Aaron VonderHaar: Yeah, that's a great example. And a situation that is easy to run into when you aren't quite sure what data structure or what algorithm you should be using, internally, a lot of times it's easy to do what I was describing earlier and jumped down to testing at the lower level too soon.
[00:20:30] And you might start writing unit tests that are very specific to the data structure that you chose, in which case, if you later decide you want to totally change that data structure, you now have a whole bunch of tests that were tied to that old data structure that you now have to throw out or rewrite in terms of the new one.
[00:20:50] So that's an example where if you have that uncertainty, it's good to be able to identify that and have your testing at a little bit higher of a level. Think about kind of the high level API that you're trying to implement and what behaviors and outcomes are being observed on the system as a whole.
[00:21:08] So if you write your tests at that level, it gives you the flexibility to entirely change how it's all implemented, but still have those tests providing coverage without any risks the tests will be invalid.
[00:21:20] Kevin Yank: So what I'm hearing is want to work from the outside in. Before you start defining the criteria for a tiny module in the middle of your program, you want to start by justifying its existence at the outer levels. And so everything starts by an elm-program-test test that says, this feature should do this thing for this user under these conditions.
[00:21:43] You work your way in to the point where you are writing a single module in support of that.
[00:21:49] Aaron VonderHaar: Yeah. Be able to think about and identify the different levels that you could test at, and make a choice that's the correct one for whatever situation you're in, about how many tests at which level you want to do. We actually have some variants at NoRedInk. We have at the moment, two different teams that are focused on building user facing features, and do most of the Elm work at the moment.
[00:22:14] I'm on one of those, and my team tends to, on the one hand, have less involves features. Like we do a lot of user interface work for the teachers and changing the overall not very deep features, but features across the site. Let me give an example. So like, our team is responsible for like the dashboard that shows all the assignments or the grade book that shows a summary of all the grades, which can have a lot of Elm code, but it's not a particularly algorithm-heavy code.
[00:22:48] Whereas the other feature team has been working on, like our new self-reviews feature, which is this entire interactive assignment with multiple steps. And you kind of worked through an essay section by section where there's a little quiz and you can highlight stuff. So there's kind of a state machine involved on the back end.
[00:23:07] We're using a rich text editor that we had to integrate with Elm, be able to merge updates if there's conflicting changes in multiple browser tabs, things like that. So that's kind of like a deeper feature, and that team ends up doing a lot more of the more focused unit tests compared to my team where we have a lot more features that are—
[00:23:27] We only need to go as deep as kind of thinking about the user experience because the code to wire it up isn't that complex from an algorithmic sense.
[00:23:36] Kevin Yank: You were saying that informs the testing practice of your respective teams.
[00:23:41] Aaron VonderHaar: Yeah I like to always start by thinking about what's the highest level test, but there are tradeoffs on the spectrum too. Like if you test too high, one thing is your test can be slow if, for instance, you're using Capybara or Selenium or something like that.
[00:23:56] Luckily on the Elm side, even elm-program-test is very fast even for high level tests. But the other downside of high level tests is it can be harder to debug what is exactly wrong when a test fails. You might get a failure like, “Oh, the, the button with the submit text doesn't exist on the page.”
[00:24:15] But it doesn't tell you why it doesn't exist. Whereas having some lower level unit tests gives you some brittleness in that a lower level interface is now protected by tests, and if you need to change the API of that module, your tests will break or have to be rewritten, but it gives you the benefit that if one of those lower level tests fails, it's going to be a much clearer idea of like where exactly the problem is in your code.
[00:24:43] So there's kind of a balance you need to, you need to make between how many of each kind of test are appropriate for whatever your situation is and the experience level of the people on your team, and so forth.
[00:24:53] Kevin Yank: How do you and your team think of and talk about that balance? Are there established norms? If you're reviewing a code change that deviates from those norms, it will raise your eyebrows and cause you to question it. Or is there any tooling around things like code coverage and things like that?
[00:25:11] Aaron VonderHaar: Yeah, that's an interesting question. I believe there's a one or two Elm code coverage tools, but I haven't personally played with them, and we don't use them consistently at NoRedInk
[00:25:22] Kevin Yank: The thinking behind the question is, for me, this is one area where front end tests and back in tests differ significantly. Maybe it comes back again to the difference between a dynamic language like Ruby or a statically type language
[00:25:37] like Elm. But on the back end, it is way more common in my experience to have the, that tooling to go the code you are contributing must have a minimum level of test coverage before it is considered acceptable. Whereas on the front end. At least at Culture Amp, we kind of trust our engineers to think about and make good decisions about that and we trust them to hold each other accountable to that in code reviews.
[00:26:05] But it is a much more informal process that is difficult to set clear expectations around in a growing team.
[00:26:11] Aaron VonderHaar: Yes. Yeah. NoRedInk is definitely on the informal side there. Personally, my recommendation is just that aligning a team on that is pretty much like any other team process, it's going to require good communication, as much communication as possible, typically more than you think is going to be necessary.
[00:26:33] And ideally pair programming is a great way to really get people to talk about differences of opinion that they didn't realize they had until they started working together.
[00:26:43] Kevin Yank: Let's start to talk about elm-program-test a bit. I said at the beginning that this was new to me, but obviously this, this framework has been around a little while to, to get to a third major version, unless, you know, that is just semantic versioning at play and you had to put a couple of breaking changes in there, early in its life.
[00:27:02] But, was there a time when NoRedInk was shipping Elm code without the ability to write program level tests in Elm?
[00:27:12] Aaron VonderHaar: Even today, we still have a fair amount of our front end code does not have tests at that level. html-test itself was kind of an early attempt at answering the question of what kind of tests are appropriate and necessary to write for front end code in Elm. That turned out, in my opinion, to not be particularly useful for applications because you ended up unit testing your view function, which
[00:27:40] by itself, the view function is very declarative. You tend to not have a lot of complicated logic in your view function. So where Elm programs get interesting is the interaction between the messages that can get produced by the view or through other means and how the update function processes those, and in particular when you start having a sequence of interactions, how the state of the program evolves over time, as a sequence of messages gets played back.
[00:28:10] So being able to kind of test the view function and the update function together as a unit, and have a single test. So in our case, the origins of elm-program-test came when we were redesigning the assignment form at NoRedInk where teachers can create assignments and we have seven different kinds of assignments, all with different kinds of contents.
[00:28:32] So the page itself was pretty complicated. And having only unit tests kind of available in elm-test at the time, you tend to get very, a very confusing set of tests when you have a test that reads like, okay, given that the teacher has already selected these 10 options in this way, then we're rendering a view that has a button that when clicked, produces this message, and then you have a separate test saying that, okay, given that you have this initial state, when this message is processed, here's the next state.
[00:29:06] And it's just very hard to read and hard to understand and just grows very, very fast to write tests that way when you have a complex page.
[00:29:14] Kevin Yank: You end up with a bunch of isolated tests about a single transition from one state to the next, and the big picture of what is the system supposed to do gets lost. And bugs can fall into those cracks.
[00:29:26] Aaron VonderHaar: Yup. Exactly.
[00:29:28] There was kind of a choice of like, okay, do we just reduce and slow down on the amount of tests we're writing and kind of step away from having as much coverage of this page? Or do we continue to write tests that way? And to me there was the other solution of how can we write a testing API that makes tests that we want to write and that are easy to write and read easier to produce? And actually my background at Pivotal Labs, I've worked on a bunch of test framework API in the past. There's the Robolectric framework for Android, and we did an experimental one while I was there for iOS apps. That was kind of a similar type of high level API where you could specify interactions with the user interface and see what kind of effects it produced.
[00:30:15] So having that background, it was natural to me of like, oh, we just need to start writing a new API to, for this.
[00:30:22] Kevin Yank: So let's talk about that API. How do elm-program-test test suites look different from the test suites that people might already be writing with elm-test.
[00:30:31] Aaron VonderHaar: Typically in elm-program-test, you are going to typically define a top level definition called start, I like to call it, which basically sets up your program. It needs to specify the init function, your update function, your view function – and we'll talk about, commands and how to test commands and subscriptions in a minute –
[00:30:52] but you set that all up in the first place and that produces a value of the type called ProgramTest, which basically represents your program and also its current state in the test world. And it also tracks other things like, any pending HTTP requests or other effects that are being simulated, by elm-program-test. So your typical test case is gonna say start, and then the next line you'll pipe it to some test program command, typically clickButton or fillIn and you'll say like, oh, clickButton "next", or fillIn and you'll give the label of the input field to fill in and the value to put in. And then there's another set of functions, that all start with the word expect, or there's an ensure version if you need to do multiples.
[00:31:42] So you can say, ensure page has, and then give some html-test selectors, like ensure that the page has a certain piece of text on it or ensure that there's a heading with certain text in it or ensure that there's a button with this class on it, and so forth. So you basically just string a bunch of those together,
[00:32:00] and if you want, you can alternate – click some things, check things on the page, click other things – and kind of go through an entire workflow as much as you feel is appropriate for whatever particular test you're trying to set up.
[00:32:11] Kevin Yank: It sounds to me like there would have been a lot of work in modeling the outer layer of that system that you are testing there. like most Elm code that we write, we get to assume there is an Elm runtime there doing all of the things that we ask it to do by returning it commands, and we also assume there is a browser there that is, you know, maintaining the DOM and, sending us messages when the user interacts with it.
[00:32:40] And in a way to write the kind of tests you write, your testing framework kind of needs to step outside of that system and poke at it from the outside. So does that mean you have had to basically emulate the Elm runtime itself and, at least a simplified version of a web browser?
[00:33:00] Aaron VonderHaar: Yes, to some degree. And a big answer to that is something I learned from working on the Android test framework I mentioned, Robolectric, is you can kind of be as a TDD approach here to where your test framework only needs to implement the features that people actually need for their tests that exist so far.
[00:33:20] So there's a lot of, for instance, web APIs that are available in Elm that elm-program-test doesn't support yet, but it's kind of set up in a way that that can be added, hopefully if people like this and have unusual, effects that they need to simulate and so forth. Hopefully people will start contributing some PRs to add and flush out the API.
[00:33:42] But it's definitely been an incremental approach in developing it. And I think towards the end of last year, I decided, okay, I'm going to make an effort to add good documentation to that, clean up the API, and it was actually version three that was the first that I started publicizing it.
[00:33:57] Kevin Yank: I'm trying to get my head around some aspect of it that I think you're going to clarify when you explain how you test commands and update functions and subscriptions. But the thing that's going through my head is, if my program inside of an elm-program-test suite makes an HTTP request, does that HTTP request actually occur by default? Are those things, mocked or stubbed by default or by exception?
[00:34:23] Aaron VonderHaar: Oh yeah, that's an interesting question. It comes down to a subtlety in Elm where when you use the HTTP library in Elm and you call get or post or whatever, you get back a command. What's interesting to note is that the commands in Elm, when you call a function that returns a command, it hasn't actually done anything yet.
[00:34:48] The command basically represents a piece of data that when the Elm runtime receives that command from your update function, it interprets that and does the actual effect. So, stemming from that, when you run elm-test, there's basically no mechanism for commands to get to the Elm runtime. Kind of, the test runner is hiding all of that.
[00:35:09] So to answer that question, any commands you produce, HTTP requests, when you run them in tests, there's in fact no way for them to actually get executed and actually performed as real HTTP requests.
[00:35:23] Kevin Yank: Okay, so elm-program-test isn't breaking any new rules. There are no runtime environment exceptions specifically for this test framework is what I'm hearing.
[00:35:34] Aaron VonderHaar: So being able to test commands, program-test provides a way to do it, but it is certainly not the ideal. So the way it works now, and actually, on the elm-program-test documentation, I kind of wrote a guidebook of walking through step by step how to do what I'm about to describe.
[00:35:51] But basically at the moment, because Elm has command as an opaque type and there's really no way in Elm code to be able to take a command and look at it and figure out what it represents – only the Elm runtime can do that. So out of necessity at the moment, the way elm-program-test works is that if you're going to use it to simulate commands or subscriptions, you basically have to refactor your program to define a new data type that you define, that lists out all the possible, commands that your program can produce.
[00:36:31] I tend to call that type Effect and it's just a union type that you would define. And then you have a separate function that takes values of that new type and turns them into the real commands. And then on the testing side, you have to provide a separate function that basically looks the same, that takes your new type and turns it into the simulated commands that elm-program-test can read.
[00:36:57] So that's the way it works now, but I believe Richard actually talked about this in the episode he was in, about looking at ways for elm-test itself to provide some JavaScript code that would be able to eliminate that step and allow elm-test to be able to inspect commands,
[00:37:15] which would support features of elm-program-test and be able to do everything you can do now just with a bit less boilerplate and without having to refactor your current program before you can test commands and so forth.
[00:37:27] Kevin Yank: For listeners who want to go back and hear the last time Richard was on talking about elm-test, that's Elm Town episode 46, in September 2019. He was on to talk about the release of Elm 0.19.1, and we talked a little bit about what was coming in elm-test and it definitely is forming a big picture now, seeing elm-program-test landing.
[00:37:49] Aaron VonderHaar: You asked earlier about the complexity of simulating the Elm runtime. With the current solution, it's relatively straightforward. Basically, I have, a union type defined internally in elm-program-test that defines all the possible commands that can be simulated at the moment, which is a growing number, but basically HTTP, the sleep timer and a couple of others. I guess you can deal with tasks and things like that.
[00:38:17] But actually previously, another precursor to elm-program-test, I was separately looking into how to test commands and have this thing in called elm-testable, where I was actually trying to write native code that could inspect the internals of commands and figure out what they were and destructure them.
[00:38:36] And in that version of testing, I actually went through the work of writing Elm code that simulated the effect managers that Elm has internally and, like, queuing up all of the events that would happen in the event manager queue, and processing those. So that was actually a lot of complexity, but ended up not really being needed
[00:38:59] with this new approach, as long as you provide a way to tell elm-program-test what commands you're trying to produce. It's relatively straight forward to implement that internally now.
[00:39:10] Kevin Yank: I like that pattern of kind of replacing all of your commands with your own custom inspectable commands that get converted to real Elm runtime commands kind of at the outer layer of your program. I'm understanding that you are able to make these kind of transparent-to-you versions of Elm commands, and that leads you then to a process of stubbing out the responses to HTTP requests, for example. What does that end up looking like in practice? What are the kind of features that you need to provide test versions of to a typical Elm program?
[00:39:46] Aaron VonderHaar: So inside of the Elm program, state or like the internal state of a running program, there is a couple of things that are tracked now. So one is kind of a list of, or a dictionary of all of the outstanding HTTP requests. So whenever your update function produces an effect that maps to an HTTP command, all elm-program-test does at that moment is track it as a new pending request.
[00:40:15] What that means is then after you say, click a button that results in your update function, posting an HTTP to an HTTP endpoint, then after that, at any point – and you don't have to resolve it right away – but at any point after that, in your test, you can either assert that a request was made to a particular endpoint. If you want to, you can assert on things in that request. If you want to assert on the body of the request or the headers, something like that. And there's also a mechanism for stubbing a response. So you can either say that the request errored with some particular HTTP error, you can provide an okay value, with the status code, with the response body,
[00:40:58] and basically at any moment after that. So if you have multiple requests in flight, you could have a test that resolves them in different orders, if that's the case, if you want to test for and so on.
[00:41:07] Kevin Yank: Do you need to explicitly handle requests after they have been queued or is there the ability to, for example, set up some request handlers that say, if I get a request that goes to this URL, it will receive that response. I don't care in any given test if that has actually occurred or not,
[00:41:27] I care about the effect of that response having come back that the user can observe. That's a way I've seen these kinds of tests written in the past is you set up the system first and then you knock it down by playing at the user's interactions and expectations.
[00:41:41] Aaron VonderHaar: Yeah, that's a good question. That was actually the alternate API that was considered when I was first looking at what kind of API to design to expose this. In looking into it, I actually determined that that type of API, where you define your handlers upfront, can actually be built on top of the API that currently exists, where you let things get queued up and then you can resolve them later.
[00:42:08] So the API you're describing doesn't exist right now. It could. There's some interest in it. I think I have probably a GitHub issue, tracking that. So that's something I'd like to build in the future. But, it does not currently exist, but totally could and could be built in a particular test helper, for an individual project if you needed.
[00:42:26] Kevin Yank: Yeah. If I really wanted that, I could model a set of request handlers and then at a point in my test suite, say resolve all requests or something like that.
[00:42:36] Aaron VonderHaar: The tricky part in making an API for it tends to be that it needs to be flexible enough to allow, for instance, dynamic responses depending on content in the request, but not everyone needs that level of complexity. So how do you make an API that's simple for regular usage, but also is flexible enough?
[00:42:56] So that's kind of the API design issue there in making a general solution. But yeah, certainly I'd encourage anyone that wants that, it could be done, as you just described, using helper functions within your project.
[00:43:08] Kevin Yank: I'm sort of mentally going down my internal list of effects that I know that the Elm runtime can handle. And is an HTTP request an example of a particularly complex one? Are there harder ones that you've had to support? I'm thinking, okay. There's random numbers. There's ports. There's viewport queries now in Elm 19. Are you having to work through those one at a time, or is the same pattern applicable to supporting all of those?
[00:43:39] Aaron VonderHaar: Well at a high level, the same pattern's gonna work in that there's some internal state tracked with the running of the program. And then there's some new simulated commands that manipulate that internal state. And then there's a couple of new public functions on elm-program-test that lets you write assertions
[00:43:58] that inspect that internal state or produce new messages based on the internal state. HTTP is the most complicated one that's been implemented so far. The only other big one I have at the moment is, Process.sleep or delay, I forget, which it's called an Elm. And I've started implementing a couple of the browser navigation ones like back and forward and having a back stack and integrating that with the Program.Application
[00:44:28] like onRouteChange-type stuff. So all that works. So that, and the HTTP have been the most complicated so far. So, I do have a list of possible APIs, like for instance, even for HTTP, doing stuff like tracking the progress of ongoing requests and canceling in-flight request is something that isn't implemented yet. But it's on the to-do list. Random number generation, I think would be relatively easy.
[00:44:54] The internal, runtime state for that is just that there's a seed value somewhere that would be used and updated. So yeah, I think probably the depth of complication has been tested out. I think it's been proved as a concept, but certainly there's a lot more APIs that still could be added to support more things.
[00:45:12] Kevin Yank: I have not used elm-program-test myself, but I have written extensive test suites in something equivalent like Capybara in Ruby, and something that has happened in a couple of projects is that as soon as you start building up a nontrivial number of these tests, you start to want for a more abstract representation of your application's user interface, from the user's point of view. I've gone through the process of modeling a couple of times in my career with varying levels of satisfaction as to the result.
[00:45:49] Is that something that you have had to do at NoRedInk in your test suites, and how does that go with Elm?
[00:45:55] Aaron VonderHaar: NoRedInk is still largely a bunch of individual Elm apps. So pretty much every page on our site for the most part is still a separate Elm program, which means there's a limited number of interactions you can want to go through.
[00:46:10] You kind of start at the loading point of that page and only do actions within the page in any given elm-program-test. We do have a couple of things like within a container, like we have these little, raised rounded corner containers that can have things in them,
[00:46:27] so we have a helper that like checks and you can say, okay, look within the container that has this title, and then click the submit button within the container that says "class1" or whatever. So we have a couple of those, but I guess at NoRedInk, we're still relatively lean in our like building new features kind of fast rather than have we, we haven't shifted yet to focusing on having a comprehensive design system across the site that's hard to work with.
[00:46:55] Kevin Yank: What's got me smiling is I'm thinking back on those libraries of abstract descriptions of pages and their features and I'm realizing they were very object-oriented code bases. The pattern that I've heard this called is the Page Objects pattern. And just the fact that it has object in the name makes me wonder… I suddenly want to explore what that API could and should look like. Were there any other unique challenges in getting a elm-program-test to do what it needed to do to be useful?
[00:47:25] Aaron VonderHaar: Well, on the lines of UI components, elm-program-test actually does a lot of stuff behind the scenes, to work with whatever DOM structure you have. I've tried to make the API as simple to use and in the user terms as much as possible. So for instance, the clickButton function that we keep mentioning, it actually can check for a lot of different situations.
[00:47:48] It can find an input of type button. It can find a button with text in it. It can find a button with an image in it where the alt text is the text that you want. It can find a div that has an ARIA label and ARIA role button. There's some functions to help you write your own custom DOM matchers if you want to,
[00:48:09] but the built-in ones actually do a lot of extra work and also help provide nicer error messages in a lot of cases.
[00:48:16] Kevin Yank: User interfaces get refactored as well. It's not uncommon to start with something as a text link and then decide it should have been a button, for example
[00:48:26] and having to rewrite your tests when you make that change would be tedious.
[00:48:30] Aaron VonderHaar: Yup. Exactly. So I've tried to also help encourage good accessibility practices as well, when possible in the default helpers. But implementing that stuff has been kind of a pain. Like the way elm-html-test is implemented at the moment, it doesn't give you a lot of flexibility, for instance to find an element and then find another element nearby, or that has the same ID, or that is the parent.
[00:48:58] So I've kind of had to do a bunch of hacks internally to make the selectors work, to do some of those more sophisticated matchings.
[00:49:05] Kevin Yank: I imagine things like keyboard interactions are really hard to model and reproduce as well, especially since they can vary from browser to browser as well, what user input does in a particular widget.
[00:49:19] Aaron VonderHaar: Yeah. Well, that reminds me that doing focus tracking, is something that's not implemented. That's going to be a pain, someday when somebody gets interested in it.
[00:49 it's for,] Kevin Yank: So right no, do you sort of fudge it by saying, “and this thing gets focus?”
[00:49:35] Aaron VonderHaar: Huh? I believe at the moment. If you care about focus, you'll just have to do that manually. So you'll have to simulate a focus event. Then do the fill in to do the change event, and then do the blur event if you want to.
[00:49:47] Kevin Yank: What would you sayto someone who is already testing their Elm at this level, using another language like a Capybara or something like that, and they're just going, all right, spin up my actual Elm app in an actual headless browser and interact with it. What is the value of moving these kinds of tests into Elm itself?
[00:50:09] Aaron VonderHaar: Yeah. So the reason would be speed. And, fast compile time and getting the type safety within your test as well. But speed's the main reason. I guess the reason to still use Capybara would be for certain cases where you're using ports to interoperate with JavaScript and, you want to actually test the interaction between Elm and JavaScript.
[00:50:33] Or with a real server backing, your code instead of just a simulated server. But yeah, I would say at NoRedInk, our CI builds are now around 24 minutes, but most of that is taken up by Capybara tests. So if we could convert all those to elm-tests, or most of them, that would cut a huge percentage off of our test time.
[00:50:51] Kevin Yank: That's about the same amount of time it takes the builds that Culture Amp. I would be very interested in seeing that bell curve, for companies of a certain maturity, what is the, tolerated slowness of CI, past which engineers revolt and make the necessary changes,
[00:51:11] but below which they kind of live with it?
[00:51:13] Aaro,n VonderHaar: Yeah. Which I think. In my mind That's why it's good to get started with, program tests kind of right off the bat so that you get used to learning, okay, when do you actually need a higher level test that's slower and flakier? But I think given that Elm is a very stable language and that the Elm architecture is very well designed, and easy to understand,
[00:51:38] Most things you're doing in Elm, you don't need Capybara tests because you can trust that Elm is going to compile to working code. So if you have tests and you feel confident that if your tests pass, your page is going to work, then that's the ideal state.
[00:51:53] Kevin Yank: Last time you were on we talked briefly about the version number of elm-format and how there were a couple of, kind of, must have features that were still on the roadmap before you would call it a 1.0. elm-program-test is at version 3.2. Do those version numbers, line up with each other?
[00:52:11] Is that number an expression of the maturity of elm-program-test? And I guess, would you advise people to go out there and pick it up today?
[00:52:19] Aaron VonderHaar: Yeah, I would say that the 3.0 release was— A lot of effort went into that to improve the documentation, to publish some guide books about how to pick it up, how to test commands, how to test ports if you need to. And I got a couple of my coworkers to help on reviewing that.
[00:52:36] Kevin Yank: As a fan of good documentation, seeing a section called Guidebooks with multiple links to fully worked examples, that makes me smile when I'm encountering a new package.
[00:52:47] Aaron VonderHaar: So 3.0 definitely stable, but there's definitely some missing APIs, certain commands you might be using that aren't testable, but that shouldn't get in your way of testing the things that are currently supported at the moment.
[00:53:00] Kevin Yank: This is a tool that is, part of the everyday at NoRedInk? It's not unusual to be adding some program tests to the feature you're shipping?
[00:53:10] Aaron VonderHaar: Yeah. I mentioned we have several different teams now. My team uses it pretty consistently for every new feature that has front end code. The other teams use it to some degree, and I'm starting to roll out some internal documentation about what tests look like at different levels and how to think about choosing what level is appropriate for your particular feature, to kind of have more consistent approach to that across the company.
[00:53:35] Kevin Yank: If you can share that, even if parts of it only apply or are the right tradeoff for NoRedInk, I think just seeing how a company of your size thinks through those tradeoffs would be super valuable to the community.
[00:53:49] Aaron VonderHaar: Yeah, I'm sure I can get some version of that available publicly.
[00:53:54] Kevin Yank: Your point on the speed of the way this is implemented is especially interesting to me here because we long ago got to the point where we said, okay, we can't afford to write a Capybara test for every single thing these screens do because the test suite would run forever. And so we need to test the critical features that we want to make sure never break.
[00:54:19] And so that is often the happy path and any especially sensitive error states that we want to make sure remain error states. But, it means that, you know, in practice we have less than 50% of the actual features of our product covered by these or described by these tests. And it feels like you could get much closer to 100% with this.
[00:54:42] Aaron VonderHaar: Yeah. I mean, the concern you will have switching to elm-program-test is that the interface between your front end and back end is not implicitly covered. So for instance, at NoRedInk, we do this by having some Rails controller tests that actually output the JSON that they would be producing and save that in a file.
[00:55:04] And then our elm-tests— Well actually we have a script that like converts those JSON test fixtures into Elm files that just contain that JSON as a string and we load those in the elm-test.
[00:55:17] Kevin Yank: I'm smiling because I've seen that wheel reinvented so many times now. There's the behemoth that is Pact, which is this collection of frameworks for different languages that say, “Okay, we are the framework for testing that, that contract in two directions between your front end and backend.”
[00:55:36] But in my experience, it is so heavy and so featureful that it ends up being no fun to use. And at the other end of the spectrum, there are these, like homegrown, quick and dirty solutions of like, let's script together some JSON. And that tends to make people happier. But, the programmer in me hates to see that wheel reinvented over and over again.
[00:55:58] Aaron VonderHaar: Well, I did want to give a shout out to, uh, two of my coworkers who both did some strange loop talks in the past about testing. Tessa Kelly has one specifically about kind of designing your Elm code to, be more testable. And she actually works on the other feature team I mentioned here at NoRedInk that tends to do more unit testing in their Elm code, compared to my team.
[00:56:22] And the other is Brooke Angel, who gave a talk about accessibility in Elm and kind of using some testing approaches. And she actually worked with me on the assignment form project that I mentioned earlier where we started the initial prototype of building what became an elm-program-test, into NoRedInk's code.
[00:56:41] Kevin Yank: You're doing my scouting for future guests for me. I've got to sign them both up to do an episode in the future. But, in the meantime, those talks will be linked in the show notes of this episode. So go have a watch. I'm definitely going to be doing so.
[00:56:53] Well, all right. Thank you, Aaron. It was great to have you on a second time in so short a period. I feel like we have wrung the sponge that is your brain of everything that you've got going on right now. And I'm looking forward to seeing what it refills with next. And in the meantime, keep an eye out for elm-refactor that, if it's not out by the time you hear this, it will be out soon. And as Aaron said last time, he's eager to hear from people who might want to test drive it in the meantime.
[00:57:24] Aaron VonderHaar: Absolutely. I've been doing some work in the past couple of weeks, so it's getting closer.
[00:57:28] Kevin Yank: All right. Well, thanks again, Aaron, and thank you, listener for joining us once again in Elm Town. It's always great to have you here. Especially in these isolating times, it's nice to send a message in a bottle out into the ether and know that, it's finding your ears. Please hit me up
[00:57:45] on @elmtown on Twitter or on the #elm-town channel on the Elm Slack. Let me know what you're thinking of this new batch of episodes, and if there's anything going on in the universe of Elm that you would love to hear from the person who created it,
[00:58:02] please let me know. I'm filling my calendar with new recordings as we speak, so, eager to hear what you want to hear about. Until then, I'm Kevin Yank and this is Aaron VonderHarr. Bye for now.
From the publisher's feed