Reading List
The most recent articles from a list of feeds I subscribe to.
Back to hugo
This blog has gone through a few technical iterations. Most recently: from a Laravel app with Sheets, to a static site with Hugo, to a CMS-driven site with Statamic. And now, back to Hugo!
I love Hugo. It’s purposefully built with the right abstractions, and incredibly fast. The kind of fast that makes you wonder why other software can be that fast. Fast software, the best software. But the templating language… it might be an acquired taste, but I never acquired it. Updating my theme or adding a custom page was always a chore. I got tired of it. Coincidentally, I wanted to experiment with more dynamic workflows on here (posting to social media, more control over scheduling…) so I migrated to Statamic (a lovely CMS!)
Fast forward three years and a blogging hiatus, and I started to crave the simplicity of the good old static site. Having a dynamic system was fun, but it requires maintenance and having it made me realize I didn’t need it. Add to that we’ve commoditized robots building things for us, and switching back to Hugo was the right move. A single prompt and 15 minutes later, my entire blog was translated from Statamic to Hugo. The best part: I don’t need to care about the templates anymore. Sure, I’ll dive in occasionally for some surgical styling tweaks. But generally, changes are handled with a quick prompt.
GitHub pings my webserver to auto-deploy on push. Scheduling is solved by running the deploy script every hour. As a static site, everything’s fast by default and there are almost no points of failure. Life is simple. All that remains is to write!
Ramble on
I have a blank canvas problem. I’m way better at staying in flow when I have something to work through, so these days, the first thing I do is ramble.
I open Codex or Spiral, turn on Monologue (my preferred text-to-speech tool), and dump everything I can think of on the topic. Before Monologue, I never considered typing speed to be a bottleneck. My fingers move faster than the speed I shape ideas. But when I want to vomit a bunch of unstructured thoughts onto a canvas, they suddenly feel slow and error-prone.
Instead, I can start a task by talking through it to generate a transcript. Then I ask an agent to structure my ramblings (the other day, the agent responded with “Your brain dump is rich."—probably the nicest compliment I’ve ever gotten from a robot!). This gives me a great starting point to really dig into the problem. Before I know it, I will have rewritten all that rambling into coherent a string of thought.
I still can’t shake the dystopian feeling of talking to my computer. But text-to-speech has made some tasks feel so natural, I almost can’t imagine working without it anymore.
The robots are replacing the packages
I wrote a piece on the Spatie blog about the place packages/library have in the ecosystem when first-party code is cheaper than ever with AI.
Scrolling to the end:
The question before every
composer requireis no longer “is there a package for this?” Almost certainly there is. The new question is: “do I want to own this problem?”
Links to CSS colour palettes
A while back I decided to stop using Tailwind for new projects and to just write vanilla CSS instead.
But one thing I missed about Tailwind was the colour palette (here as CSS).
If I wanted a light blue I could just use blue-100 and if I didn’t like it
maybe try blue-200 or blue-50. I’m not very good with colours so it makes
a big difference to me to have a reasonable colour palette that somebody who is
better at colour than me has thought about.
But I’m also a little tired of those Tailwind colours, so I asked on Mastodon today what other colour palettes were out there. And then a friend said they wanted links to those colour palettes, so here’s a blog post so my friend can see them, and all the rest of you too :)
my favourites
The ones I liked the most were:
- uchū (css file, FAQ)
- flexoki (css file)
- reasonable colours, which seems to have a focus on accessibility (css file)
more colour palettes
colourscheme generators
Folks also linked to a bunch of colour palette generators
I’ve always found these types of generators too hard to use but maybe one day I will get better enough at colour that I’m able to use a colour palette generator successfully so I’ll leave those links there anyway.
and more colour tools:
- colorhexa has some info about colorblindness
oklch
Generative colors with CSS gives an example of
how to use the oklch CSS function to dynamically generate colors.
Testing Vue components in the browser
Hello! One of my long term projects on here is figuring out how to write frontend Javascript without using Node or any other server JS runtime.
One issue I run into a lot in my frontend JS projects is that I don’t know how to write tests for them. I’ve tried to use Playwright in the past, but it felt slow and unwieldy to be starting these new browser processes all the time, and it involved some Node code to orchestrate the tests.
The result is that I just don’t test my frontend code which doesn’t feel great. Usually I don’t update my projects much either so it doesn’t come up that much, but it would be nice to be able to make changes with more confidence! So a way to do frontend testing that I like has been on my wishlist for a long time.
idea: just run the tests in the browser tab
Alex Chan wrote a great post a while back called Testing JavaScript without a (third-party) framework in response to one of my previous posts in this series that explained how to write a tiny unit-testing framework that runs in a page in browser.
I loved this post at the time, but it only talked about unit testing and I wanted to write end-to-end integration tests for my Vue components, and I didn’t know how to do that.
So when I was talking to Marco the other day and he said something like “you know, you can just run tests for your Vue components in the browser”, I thought “hey, I should try that again!!!”
I just did all of this yesterday so certainly there’s a lot to improve but I wanted to write down a few things I noticed about the process before I forget.
This was a bit tricky for me because the Vue site usually assumes that you’re
using Node as part of your build process in some way (there’s a lot of “step 1:
npm install THING), and I didn’t want to use Node/Deno/etc. But it turned
out to not be too complicated.
The project I’m going to talk about testing is this zine feedback site I wrote in 2023.
the test framework: QUnit
I used QUnit. It worked great but I don’t have anything interesting to say about how it works so I’ll leave it at that. I think that Alex’s “write your own test framework” approach would have worked too. I followed these directions.
I did appreciate that QUnit has a “rerun test” button that will only rerun 1 test. Because there are so many network requests in my tests, having a way to run just 1 test makes it a lot less confusing to debug the test.
step 1: set up the component for testing
The first thing I needed to do was get my Vue components set up in the test environment.
I changed my main app to put all my components in window._components,
kind of like this:
const components = {
'Feedback': FeedbackComponent,
...
}
window._components = components;
Then I was able to write a mountComponent function which
does basically exactly the same thing my normal main app does
(render a tiny template with the component I want to use).
The only differences are:
- I can optionally pass some some extra data to use as its props.
- It mounts the component to a temporary invisible div which will get removed
from the DOM after the test is done. The div is positioned off the page
(
position: absolute; top: -10000, ...) so you can’t see it.
Here’s what using the mountComponent function looks like:
const {div} = mountComponent(
'<Page :feedbacks="feedbacks" id=2 />',
{feedbacks: [testFeedback]},
);
and here’s the code for it:
function mountComponent(template, data) {
const app = Vue.createApp({
template: template,
data: () => data,
})
for (const [c, v] of Object.entries(window._components)) {
app.component(c, v);
}
const div = document.getElementById('qunit-fixture')
.appendChild(document.createElement('div'));
return div;
}
The result is a div where I can programmatically click, fill in form data, check that the right content appears, etc.
step 2: add some fixture data
Because I was writing end-to-end integration tests to make sure my client JS worked properly with my server, I needed to have some test data in my database. So I wrote ~25 lines of SQL to set up some test data in my database, and added an endpoint to my dev server to run the SQL to reset the test data to a known state.
async function reset() {
return fetch('/api/reset_test_data', {method: "POST"})
}
Then I just run await reset() at the beginning of any test that needs the
test data.
My reset() function actually doesn’t always totally reset everything which is
kind of bad, but it was workable to start with and can always be improved.
step 3: a basic test
Here’s what a basic test looks like! Basically we’re rendering the div and make sure it contains some approximately correct data.
QUnit.test('renders feedback content', async function (assert) {
const {div} = mountComponent(
'<Page :feedbacks="feedbacks" id=2 image=2 page_hash=2 />',
{feedbacks: [testFeedback]},
);
assert.ok(div.textContent.includes('loved this section'));
})
Those are all the basic pieces! Now here are a few issues I ran into along the way
waiting for parts of the page to render
I have a lot of network requests in my tests, and it takes time for them to finish and for the Vue code to do what it has to do with the results and update the DOM.
I think we all learned a long time ago that putting random sleep() calls in
your tests and hoping that the timings are right is slow and flaky and extremely
frustrating, so I needed a different way.
As far as I can tell the normal way to deal with this is to figure out a way to tell from the DOM whether it’s okay to proceed or not. Like “if this button is visible, we can “.
So I wrote a little waitFor() function that polls every 20ms to see if a
condition has finished yet. It times out after 2 seconds.
Here’s what using it looks like:
QUnit.test("click item", async function (assert) {
const {div} = mountComponent(
'<Feedback zine_id="test123" image_width="800px" />',
{});
const item = await waitFor(() => div.querySelector('.feedback-item'));
item.click();
// rest of test goes here...
})
It looks like there are a lot of implementations of this concept out there and they’re all better thought-through than mine. (from a quick Google: qunit-wait-for, playwright expect.poll)
figuring out the right thing to wait for is not straightforward
In some cases I thought I’d identified the right thing to wait for in the DOM (“just wait for this textarea to appear!’) but it turned out that because of some internal details of how my program works, actually I needed to wait for something else later on which was hard to pin down.
I ended up changing one of my components to add some random value to the DOM
when it was finished an important action (like data-this-thing-is-ready=true)
which didn’t feel great.
My best guess is that the right way to fix this kind of test issue is a refactor that also makes the app more reliable for the users: if there’s an element in the DOM that isn’t actually ready for the user to interact with, maybe I shouldn’t be displaying it yet!
adding some CSS classes to identify things (but is that right?)
I ended up adding a few classes to HTML elements that I needed to find in the tests, either because I needed to click on them or wait for them to appear in the DOM.
I might want to change this approach later - frontend testing frameworks seem to suggest avoiding using CSS classes and instead using something like getByRole or as a last resort something like a data-testid. Feels like there’s a way to make the app more accessible and easier to test at the same time.
filling out forms is tricky
To fill out a form, I can’t just set the value, I also need to dispatch an
event to tell Vue that the element has changed. For example, checkbox and
textarea need different kinds of events.
textarea.value = 'banana banana banana';
textarea.dispatchEvent(new Event('input'));
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change'));
This is kind of annoying and it made me realize why I might want to use some kind of UI testing library, for example:
- Testing Library’s example of filling out a form looks extremely different from what I’m doing
- Vue Test Utils: their section on form handling looks like it simplifies this a lot.
test coverage
I want to have an idea of what my test coverage was, and it turns out that Chrome actually has a built-in code coverage feature for JS and CSS!
My JS is bundled into a file called bundle.js with esbuild, so I could just
look at bundle.js and see which lines weren’t covered.
The process was a little finicky: I had to turn off sourcemaps in the Chrome devtools to get this to work, and there’s a specific not super obvious series of actions I have to do in order to see the coverage data.
this was so fun!
As usual with these posts I’ve never really worked as a frontend or backend developer (other than for myself!) and I feel like I’m constantly learning how to do super basic tasks.
I really had a blast doing this. My frontend projects always feel so fragile because they’re untested, and maybe one day I’ll have a test suite I’m confident in!
Some things I’m still thinking about:
- While writing this post I found this frontend testing library called
Testing Library that has a lot of
guidelines for how to write tests that are very different from my initial ideas.
I experimented with rewriting everything to use Testing Library and it felt
pretty good, so we’ll see how that goes. They distribute a
.umd.jsfile that works without Node. - I’m not sure how I feel about not having a way to run these tests on the command line at all. Maybe there’s a simple way to work primarily in the browser but have an way to run them in CI too if I want?