Five bugs our tests caught before they shipped
I maintain yizhiweb.top, a browser-only tool site with 108 conversions, counters and calculators. There is no backend: every engine is pure TypeScript, every page is static, and the only thing between a wrong answer and
I maintain yizhiweb.top, a browser-only tool site with 108
conversions, counters and calculators. There is no backend: every engine is pure
TypeScript, every page is static, and the only thing between a wrong answer and a
user is the test suite.
That suite has caught some failures that were not about a wrong output at all. They
were about memory, clipboard permissions, grapheme clusters, browser locale and
template-literal escaping. These are the five that changed how I test:
1. The diff that ate 10 GB of memory
The first text diff used a full longest-common-subsequence table. It was correct on
small inputs and catastrophic on large ones. A 20,000-line comparison took about
seven seconds and 1.5 GB; a 50,000-line comparison would have allocated roughly
10 GB and crashed the tab. The page's FAQ claimed there was an input cap. There was
not โ the comment describing the cap was the only thing that existed.
The rewrite trims the common prefix and suffix first, then falls back to a linear
line-by-line diff when the edited region is too large for the quadratic table. The
same 20,000-line case went from 7,023 ms to about 10 ms. The result now carries a
linearised flag, so the page can say when the alignment is approximate.
The lesson was not "write a better diff". It was "test the resources a tool
consumes". A correctness test on a ten-line input will never find the allocation
that kills the tab.
2. The copy button that said "Copied!" when it had not
The copy feedback component showed success as soon as the click handler ran. It
never checked whether navigator.clipboard.writeText resolved, and it had no
fallback when the Clipboard API was missing. In a browser that denied the
permission, the UI claimed success while the clipboard still held the old value.
The wrapper now awaits the write, falls back to a hidden <textarea> and
execCommand('copy') when the API is absent, and reports failure when both paths
fail. The tests exercise three states: normal, API missing and permission denied.
A success message is a claim about an operation, not a reaction to a click. Tie it
to the result.
3. An emoji is not seven characters
The text counter counted JavaScript string length, so the family emoji
๐จโ๐ฉโ๐งโ๐ฆ counted as seven. That is the number of code points, but it is not
what a reader means by "characters", and it is not what a field with a character
limit counts either.
The page now reports grapheme clusters through Intl.Segmenter alongside code
points and UTF-8 bytes, and labels each one. The point is not that one number is
correct and the others are wrong. The point is that "character" is ambiguous, and a
counter that hides the ambiguity is easy to misread.
4. The date picker that spoke Chinese on an English page
The pages are in English and declare lang="en", but a browser's native date
control follows the browser's UI language, not the document language. In a
Chinese-language Chrome the date field showed Chinese year/month/day labels and the
file input showed a Chinese "choose file" label. The format was ambiguous and the
page looked half-translated.
Nine date fields became plain YYYY-MM-DD text fields with a small English
calendar. Sixteen file inputs were wrapped by one helper that renders its own
English labels while keeping the original input and drag-and-drop behaviour.
A native control is not automatically the accessible choice when its language is
out of your hands. If the format or the wording matters, own the control.
5. The guide that shipped a NUL byte
A guide about invisible characters included shell examples such as
grep -nP '[^\x00-\x7F]' file.txt. The examples lived inside a JavaScript template
literal, where \x00 is not four printable characters โ it is an escape that
produces a NUL byte. The published HTML contained a literal NUL and several
mojibake sequences, and the shell examples were wrong on the page.
Doubling the backslashes fixed the page. The release check now scans every rendered
HTML file for NUL bytes. Content is code: an escape sequence in a code sample needs
the same review as one in an engine, and the rendered output needs a byte-level
check.
What I changed in the test strategy
- Assert resources, not only outputs. Time and memory are part of correctness for a browser tool.
- Tie UI claims to operation results. A button that says "Copied" must await the write and handle the failure path.
- Name the unit. Code points, grapheme clusters and UTF-8 bytes are different numbers; show them and label them.
- Test the platform. Native controls, clipboard permissions and locale are part of the interface, even when you did not write them.
- Scan the rendered bytes. A template literal can turn a code sample into a control character.
The longer write-ups, with the dates and the exact fixes, are in the site's
build log. The test layers and their limits are
on the methodology page.
I maintain the site; this is not a paid promotion. If you find a case that is still
wrong, the feedback form reaches the same person who wrote the engine.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.