Jasmine: Use `throwUnless` for testing-library's `waitFor`
testing-library are widely used testing utilities libraries for javascript dependent frontend testing. The main utilities provided are query methods, user interactions, dom expectations and interacting with components of several frontend frameworks, which allows us to worry less about the details happening in the browser and focus more on user centric tests instead!
Some of the time you will find a necessity to use methods like [waitFor
](https://testing-library.com/docs/dom-testing-library/api-async/...
Efficiently add an event listener to many elements
When you need to add a event listener to hundreds of elements, this might slow down the browser.
An alternative is to register an event listener at the root of the DOM tree (document
). Then wait for events to bubble up and check whether the triggering element (event.target
) matches the selector before you run your callback.
This technique is called event delegation.
Performance considerations
Because you only register a single listener, registering is ...
Rails: Flagging all cookies as secure-only to pass a security audit
Why secure-only cookies used to be necessary
Cookies have an optional secure
flag. It tells the browser to not send the cookie for a non-https request.
It used to be important to activate the secure
flag even on sites that automatically redirect users from http://
to https://
. The reason was that most users will only enter a scheme-less domain like makandra.de
into their location bar, which will default to `http://m...
Use CSS "text-overflow" to truncate long texts
When using Rails to truncate strings, you may end up with strings that are still too long for their container or are not as long as they could be. You can get a prettier result using stylesheets.
The CSS property text-overflow: ellipsis
has been around for quite a long time now but since Firefox did not support it for ages, you did not use it. Since Firefox 7 you can!
Note that this only works for single-line texts. If you want to truncate tests across multiple lines, use a JavaScript solution like...
A gotcha of Ruby variable scoping
I recently stumbled over a quirk in the way Ruby handles local variables that I find somewhat dangerous.
Consider:
def salutation(first_name, last_name = nil)
if last_name
full_name = "#{first_name} #{last_name}"
end
"Hi #{full_name}"
end
This is obviously wrong, full_name
is unset when last_name
is nil.
However, Ruby will not raise an exception. Instead, full_name
will simply be nil
, and salutation('Bob')
returns 'Hi '
.
The same would happen in an else
branch:
def salutation(fi...
In Chrome 121+ the now supported spec-compliant scrollbar properties override the non-standard `-webkit-scrollbar-*` styles
Up until Chrome 120, scrollbars could only be styled using the various -webkit-scrollbar-*
pseudo elements, e.g. to make the scrollbars have no arrows, be rounded, or with additional margin towards their container.
Starting with version 121, Chrome now also supports the spec-compliant properties scrollbar-width
and scrollbar-color
.
These allow less styling. You may only specify the track and thumb colors, and a non-specific width like auto
, thin
, or none
.
Implementing social media "like" buttons: Everything you never wanted to know
So you client has asked you to implement a row of buttons to like the URL on Facebook, Twitter and Google+. Here are some things you should know about this.
0. Security considerations
Each "like" button is implemented by including a Javascript on your site. This means you are running fucking remote code on your page. You are giving Facebook, Twitter and Google+ full permission to e. g. copy user cookies. Check with your client if she is cool with that. Also note that if you're site is suggesting security by operating under HTTPS ...
Open UI: Future development in web components and controls
tl;dr When browsers start to adapt proposals from Open UI, it might not be necessary to use any 3rd party libraries to have nice components and controls in web applications e.g. selects. It would require only a minimum of CSS and Javascript to get them working and looking good.
The purpose of the Open UI, a W3C Community Group, is to allow web developers to style and extend built-in web UI components and controls, such as
<select>
dropdowns, checkboxes, radio buttons, and date/color pickers.To do that, we’ll need to fully speci...
Migrating from CoffeeScript to ES6
It is quite easy to migrate from CoffeeScript to ES6. You can use decaffeinate to convert your CoffeeScript source to modern JavaScript.
Install decaffeinate globally:
npm install -g decaffeinate
Call decaffeinate
on each .coffee
file, relaxing some options to get the most modern (and concise) JS:
decaffeinate file.coffee --use-cs2 --loose --optional-chaining --logical-assignment
Tip
If you use Babel and see errors while decaffeinati...
A collection of graph and diagram tools
- Plot graphs in Ruby
- WebGraphviz renders in your browser via JavaScript (to store the rendered graph, extract the SVG using your browser's DOM inspector)
- GraphViz with DOT: Online http://graphs.grevian.org/graph https://graphviz.christine.website/ or offline https://makandracards.com/makandra/1589-auto-generate-state_machine-graphs-as-png-images
- Balsamiq
- Draw.io
- [Excalidraw](https:...
Minify Font Awesome fonts with webpack
Font Awesome 5 is a comprehensive solution for vector icons on your website.
Originally, Font Awesome came as an icon font (plus stylesheets), but recently it can also be used as a pure JavaScript solution (which will render icons as inline <svg>
tags), or even as SVG sprites.
All solutions have their pros and cons:
Icon font:
- little CPU load (no JavaScript)
- fonts are relatively large
- 1 extra HTTP request
Javascript + inline SVG:
- higher CPU load (needs to watch the DOM via mutation observers to ad...
How to display an unsaved changes alert
All browsers implement an event named beforeunload. It is fired when the active window is closed and can be used to display an alert to warn the user about unsaved changes.
To trigger the alert, you have to call preventDefault()
on the event.
Note
The
beforeunload
event is only dispatched when the user navigation makes a full page load, or if it closes the tab entirely. It will not be dispatched when navigating via JavaScript. In this case you need to ...
Virtual scrolling: A solution for scrolling wide content on desktops
I recently built a screen with a very high and wide table in the center. This posed some challenges:
- Giving the table a horizontal scroll bar is very unergonomic, since the scrollbar might be far off screen.
- Making the whole page scrollable looks bad, since I don't want the rest of the UI to scroll.
- Giving the table its own vertical scrollbar and a limited height would have solved it, but felt weird, since the table was 90% of the page.
What I ended up doing is reusing the horizontal page scrollbar (which is naturally fixed at t...
Five years of "Today I Learned" from Josh Branchaud
The linked GitHub repository is a bit like our "dev" cards deck, but groomed from a single person (Josh Branchaud). It includes an extensive list of over 900 TILs on many topics that might be interesting for most of us. (e.g. Ruby, Rails, Git, Unix..)
Ruby
Here is an excerpt of all the Ruby TILs that were new to me. I encourage you to take your time to skim over the original list as well!
-
Assoc For Hashes
- `Hash#ass...
Introduction to Google Tag Manager (for web developers who know Google Analytics)
As a web developer, you know Google Analytics (GA). Probably you've dropped the GA snippet into more than one website, maybe you've even used its Javascript API to implement tracking at the event level.
Google Tag Manager (GTM) is a related tool, but on a higher level and thus with much more power. GTM is not a replacement for GA. Rather, it can make GA configurable without changing anything in the application's code base (and much more beyond, see below).
Only prefer GTM if the customer requests it, or if he is updating his tracking r...
Jasmine: Testing complex types for equality
Jasmine comes with two matchers that test for equality. The first is toBe
:
expect(first).toBe(second)
toBe
passes when first === second
. Unfortunately this is useless for non-primitive values because JavaScript is a horrible language.
However, Jasmine comes with another matcher toEqual
:
expect(first).toEqual(second)
This matcher behaves as a human would expect for types like the following:
- Arrays
- Objects
- Nested array/object constructs
- Regular expressions...
Selector for finding the currently selected option in a <select> tag
Use option:checked
to find the currently selected option:
select.querySelector('option:checked')
Yes, :checked
, not :selected
.
This is the same as finding an option with the { selected: true }
property in JavaScript:
select.querySelectorAll('option').find((option) => option.selected)
What about the selected
attribute?
Note that option[selected]
would only find an <option selected>
tag. This may be the selected option right after loading the page, but not once the user switched to a different value. ...
Bookmarklet to generate a commit message for an issue in Linear.app
Your commit messages should include the ID of the issue your code belongs to.
Our preferred syntax prefixes the issue title with its ID in brackets, e.g. [FOO-123] Avatars for users
.
Here is how to generate that from an issue in Linear.
Add a new link to your browser's bookmarks bar with the following URL.
javascript:(() => {
if (document.querySelector('[data-view-id="issue-view"]')) {
const [id, ...words] = document.title.split(' ') ;
prompt('Commit message:', `[${id}] ${words.join(' ')}`)
} else {
alert('Open issue...
How to transition the height of elements with unknown/auto height
If you want to collapse/expand elements with dynamic content (and thus unknown height), you can not transition between height: 0
and height: auto
.
Doing it properly, with modern CSS features
In the past, you might have resorted to bulky JavaScript solutions or CSS hacks like transitioning between max-height: 0
and max-height: 9999px
. All of them were awkward and/or have several edge cases.
With modern CSS, there is actually a way to do it properly:
Just use a display: grid
container which transitions its grid row height betwe...
Capybara: Pretending to interact with the document
Browsers blocks abusable JavaScript API calls until the user has interacted with the document. Examples would be opening new tab or start playing video or audio.
E.g. if you attempt to call video.play()
in a test, the call will reject with a message like this:
NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
Workaround
To pretend document interaction in a test you can create an element, click on it, and remove the element again. This unblocks the entire JavaSc...
How to work around selenium chrome missing clicks to elements which are just barely visible
Chromedriver (or selenium-webdriver?) will not reliably scroll elements into view before clicking them, and actually not click the element because of that.
We've seen this happen for elements which are just barely in the viewport (e.g. the upper 2px of a 40px button). Our assumption is that the element is considered visible (i.e. Capybara::Selenium::ChromeNode#visible?
returns true
for such elements) but the Selenium driver wants to actually click the center of the element which is outside of the viewport.
We don't know who exactly i...
The HTML5 video element
# Basic HTML example
<video poster="preview_image.png" controls>
<source src="or_here.webm" type="video/webm" />
<source src="alternative_if_browser_cant_pay_first_source.mp4" type="video/mp4" />
<track src="optional_subtitles.vtt" kind="subtitles" srclang="de" label="Deutsch" default>
</video>
# Javascript API (notable methods and properties)
video = document.querySelector('video')
video.play()
video.pause()
video.load() // Reset to the beginning and select the best available source
video.currentSrc // The selected source
video.c...
Capybara: Most okayest helper to download and inspect files
Testing file download links in an end-to-end test can be painful, especially with Selenium.
The attached download_helpers.rb
provides a download_link
method for your Capybara tests. It returns a hash describing the download's response:
details = download_link('Download report')
details[:disposition] # => 'attachment' or 'inline'
details[:filename] # => 'report.txt'
details[:text] # => file content as string
details[:content_type] # => 'text/plain'
Features
Compared to [other approaches](...
How to migrate CoffeeScript files from Sprockets to Webpack(er)
If you migrate a Rails application from Sprockets to Webpack(er), you can either transpile your CoffeeScript files to JavaScript or integrate a CoffeeScript compiler to your new process. This checklist can be used to achieve the latter.
- If you need to continue exposing your CoffeeScript classes to the global namespace, define them on
window
directly:
-class @User
+class window.User
- Replace Sprocket's
require
statement with Webpacker's...