v0.1.1 284 cases, 11 generators, zero dependencies

edgegoat: edge case test inputs for JavaScript

The inputs that break code, ready to drop into your tests.

Curated, documented edge cases for time zones, daylight saving time, dates before 1970, unicode test strings, -0 and NaN, URLs, paths and JSON. Each one says why it breaks code. Works with Vitest, Jest, node:test and Mocha, in TypeScript or JavaScript.

edgegoat npm version edgegoat npm downloads per month MIT license

Ledge, the edgegoat mascot: a mountain goat standing on the edge of a cliff
Terminal
npm install --save-dev edgegoat

Quick start

Edge cases in a real test suite

Every generator returns an array of { label, value, why, tags } objects, so it plugs straight into test.each. Filter with { tags: [...] } or { exclude: [...] }, pick zones with dates({ zone: 'Europe/Berlin' }).

dates.test.ts
import { describe, expect, test } from 'vitest';
import { dates, numbers, strings, withTimeZone } from 'edgegoat';
import { addDays, formatPrice, truncate } from './my-code';

describe('addDays keeps the wall clock across DST', () => {
  test.each(dates({ tags: ['dst'] }))('$label', ({ value, zone }) => {
    withTimeZone(zone, () => {
      expect(addDays(value, 1).getHours()).toBe(value.getHours());
    });
  });
});

test.each(strings({ tags: ['surrogate'] }))('truncate keeps $label whole', ({ value }) => {
  expect(truncate(value, 5).isWellFormed()).toBe(true);
});

test.each(numbers({ tags: ['sign'] }))('formatPrice($label)', ({ value }) => {
  expect(formatPrice(value)).not.toMatch(/^-0/);
});

How it works

Three steps, no setup

  1. Install

    Add edgegoat as a dev dependency. No configuration, no runtime dependencies.

  2. Pick the inputs

    Call a generator and filter it with { tags } or { exclude }: DST gaps, surrogate pairs, -0, path traversal.

  3. Run them with test.each

    Each case has a label for the test title and a why for the failure, so a red test tells you what broke.

What is in the box

284 cases across 11 generators

  • Calendar icon for date and daylight saving time edge cases

    Dates 46 cases

    DST gaps and overlaps, New Year in 7 zones, leap days, before 1970, 2038, year 10000

  • Globe icon for time zone test cases

    Time zones 18 cases

    Curated IANA zones: +05:45, +14:00, -11:00, 30 minute DST, midnight DST

  • Text icon for unicode test strings

    Strings 49 cases

    Emoji, surrogate pairs, NFC vs NFD, bidi, invisible spaces, Turkish i, newlines

  • Hash icon for JavaScript number edge cases like negative zero and NaN

    Numbers 51 cases

    -0, NaN, Infinity, safe integer limits, 0.1 + 0.2, 1e21, numeric strings and BigInt

  • Link icon for URL and email edge cases

    URLs and emails 54 cases

    WHATWG parser surprises, SSRF and open redirect shapes, RFC 5321 limits

  • Folder icon for Windows and POSIX file path edge cases

    Paths 30 cases

    Windows vs POSIX, UNC, reserved names, traversal, NFD file names

  • Curly braces icon for JSON parsing edge cases

    JSON 22 cases

    10000 levels deep, duplicate keys, 2^53 + 1, __proto__, BOM

  • Brackets icon for array edge cases with holes and huge lengths

    Arrays 14 cases

    Holes, length 2^32 - 1, NaN membership, default sort, too big to spread

  • Stopwatch icon for the time zone and frozen time test helpers

    Helpers 4 functions

    withTimeZone, withFrozenTime, check, assertNoThrow

Proof

Bugs these inputs found

Real bugs of this kind, fixed upstream by the author of edgegoat in widely used projects. The library packages the inputs that exposed them.

Catalog

Search the catalog

All 284 cases, generated from the published library. Also available as catalog.json. Emoji and invisible characters are shown as escaped code points. Pick a generator or a tag to narrow the list, open Details on a case, and copy a ready test.each for the current filter.

Tags, any of:

Showing 284 of 284 cases.

Use this filter in a test
edge-cases.test.js
  1. dates()

    unix epoch 1970-01-01T00:00:00.000Z

    1970-01-01T00:00:00.000Z

    Timestamp 0 is falsy, so `if (!time)` treats a valid date as missing, and west of UTC it is still 1969 locally.

  2. dates()

    1 ms before the epoch

    1969-12-31T23:59:59.999Z

    Negative timestamps: `ms % 1000` is -1, not 999, so splitting seconds and milliseconds with % and Math.floor disagree.

  3. dates()

    before 1970 with fractional seconds (1969-12-31T23:59:58.500Z)

    1969-12-31T23:59:58.500Z

    Truncating -1.5 s toward zero gives 23:59:59 instead of 23:59:58; Math.trunc vs Math.floor and integer division bugs live here.

  4. dates()

    moon landing 1969-07-20T20:17:40.123Z

    1969-07-20T20:17:40.123Z

    Negative timestamp with milliseconds: serializers that build "seconds.millis" from a negative value print a wrong or doubly negative fraction.

  5. dates()

    local mean time era 1850-01-01T00:00:00Z

    1850-01-01T00:00:00.000Z

    Before standard time, zones used local mean time with second level offsets (Sao Paulo was -03:06:28); getTimezoneOffset() reports whole minutes, so local arithmetic drifts by seconds.

  6. dates()

    year 0 (0000-01-01T00:00:00Z)

    0000-01-01T00:00:00.000Z

    Date.UTC(0, 0, 1) and new Date(0, 0, 1) map years 0 to 99 to 1900 to 1999, and toISOString() yields "0000", which "1 BC" formatting must handle.

  7. dates()

    negative year (-000001-01-01T00:00:00Z)

    -000001-01-01T00:00:00.000Z

    toISOString() produces an expanded, signed year "-000001"; regexes that expect four digits reject it.

  8. dates()

    year 2038 limit 2038-01-19T03:14:07Z

    2038-01-19T03:14:07.000Z

    Last second representable as a signed 32 bit Unix time; anything that stores seconds in an int32 (or uses `| 0`) is fine here.

  9. dates()

    year 2038 overflow 2038-01-19T03:14:08Z

    2038-01-19T03:14:08.000Z

    One second past the int32 limit: `(ms / 1000) | 0` wraps to 1901-12-13, and so do 32 bit database columns.

  10. dates()

    last instant of year 9999

    9999-12-31T23:59:59.999Z

    Largest instant with a four digit year; one millisecond later toISOString() switches to "+010000".

  11. dates()

    year 10000 (+010000-01-01T00:00:00.000Z)

    +010000-01-01T00:00:00.000Z

    toISOString() returns a six digit signed year; parsers, databases and YYYY format strings break.

  12. dates()

    maximum Date (+275760-09-13T00:00:00.000Z)

    +275760-09-13T00:00:00.000Z

    Largest valid Date. Adding 1 ms produces Invalid Date, so "end of time" sentinels overflow.

  13. dates()

    minimum Date (-271821-04-20T00:00:00.000Z)

    -271821-04-20T00:00:00.000Z

    Smallest valid Date. Subtracting anything produces Invalid Date, and local offsets west of UTC push the local wall clock out of range.

  14. dates()

    invalid date

    Invalid Date

    getTime() is NaN, toISOString() throws RangeError, JSON.stringify gives null and every comparison is false.

  15. dates()

    date-only string parsed as UTC (new Date("2026-03-01"))

    2026-03-01T00:00:00.000Z

    Date-only ISO strings are UTC midnight, while date-time strings without Z are local; west of UTC this date displays as February 28.

  16. dates()

    leap day 2024-02-29

    2024-02-29T12:00:00.000Z

    Adding a year with setFullYear(y + 1) lands on 2025-03-01 because Feb 29 2025 does not exist; birthdays and anniversaries drift.

  17. dates()

    leap day 2000-02-29 (400 year rule)

    2000-02-29T12:00:00.000Z

    2000 is a leap year because it is divisible by 400, unlike 1900 and 2100; a "divisible by 100 means not leap" rule rejects it.

  18. dates()

    not a leap year 2100-02-28

    2100-02-28T12:00:00.000Z

    2100 is not a leap year; `year % 4 === 0` claims Feb 29 2100 exists and the day after this is computed wrong.

  19. dates()

    month end 2026-01-31

    2026-01-31T12:00:00.000Z

    setMonth(month + 1) on January 31 overflows to March 3, because February 31 does not exist.

  20. dates()

    month end 2026-03-31

    2026-03-31T12:00:00.000Z

    Adding one month to March 31 gives May 1, since April has 30 days; "same day next month" needs clamping.

  21. dates()

    last millisecond of 2026 (UTC)

    2026-12-31T23:59:59.999Z

    Range queries ending at 23:59:59 miss this millisecond; month index 11 plus one must roll over into the next year.

  22. dates()

    2027-01-01 is in ISO week 53 of 2026

    2027-01-01T12:00:00.000Z

    ISO week years differ from calendar years: this Friday belongs to week 53 of 2026, so `getFullYear()` plus week number is wrong.

  23. dates()

    2024-12-30 is in ISO week 1 of 2025

    2024-12-30T12:00:00.000Z

    A Monday in December that belongs to week 1 of the next ISO year; "yyyy-ww" built from the calendar year shows 2024-01.

  24. dates()

    2023-01-01 is a Sunday in ISO week 52 of 2022

    2023-01-01T12:00:00.000Z

    ISO weeks start on Monday, US weeks on Sunday: this day is week 52 of 2022 in ISO but week 1 of 2023 in US numbering.

  25. dates()

    America/New_York 2026: 1 ms before spring forward (2026-03-08 01:59 local)

    2026-03-08T06:59:59.999Z

    Last instant before clocks jump from 02:00 to 03:00 (UTC-05:00 to UTC-04:00); adding 60 minutes of wall time here is not 60 minutes of real time.

  26. dates()

    America/New_York 2026: spring forward (2026-03-08 03:00 local)

    2026-03-08T07:00:00.000Z

    Clocks jump from 02:00 to 03:00; local times in between do not exist, so building a Date from local fields shifts it and that local day is 23 hours long.

  27. dates()

    America/New_York 2026: fall back, first 01:30 (2026-11-01)

    2026-11-01T05:30:00.000Z

    Clocks fall back from 02:00 to 01:00, so 01:30 happens twice; this is the first one (UTC-04:00). Parsing the local time picks one of the two.

  28. dates()

    America/New_York 2026: fall back, second 01:30 (2026-11-01)

    2026-11-01T06:30:00.000Z

    Same wall clock reading as the first 01:30, 60 minutes later (UTC-05:00); sorting or deduplicating by local time breaks and that local day is 25 hours long.

  29. dates()

    America/New_York: last instant of 2025 local time

    2026-01-01T04:59:59.999Z

    Local time is 2025-12-31 23:59:59.999 (UTC-05:00) but the UTC year is already 2026; a year check with getUTCFullYear() is off by one. real bug

  30. dates()

    America/Sao_Paulo 2018: fall back, first 23:30 (2018-02-17)

    2018-02-18T01:30:00.000Z

    Clocks fall back from 00:00 to 23:00, so 23:30 happens twice; this is the first one (UTC-02:00). Parsing the local time picks one of the two.

  31. dates()

    America/Sao_Paulo 2018: fall back, second 23:30 (2018-02-17)

    2018-02-18T02:30:00.000Z

    Same wall clock reading as the first 23:30, 60 minutes later (UTC-03:00); sorting or deduplicating by local time breaks and that local day is 25 hours long.

  32. dates()

    America/Sao_Paulo 2018: 1 ms before spring forward (2018-11-03 23:59 local)

    2018-11-04T02:59:59.999Z

    Last instant before clocks jump from 00:00 to 01:00 (UTC-03:00 to UTC-02:00); adding 60 minutes of wall time here is not 60 minutes of real time.

  33. dates()

    America/Sao_Paulo 2018: spring forward (2018-11-04 01:00 local)

    2018-11-04T03:00:00.000Z

    Clocks jump from 00:00 to 01:00; local times in between do not exist, so building a Date from local fields shifts it and that local day is 23 hours long. Midnight does not exist that day, so new Date(2018, 10, 4) returns 01:00.

  34. dates()

    America/Sao_Paulo: last instant of 2017 local time

    2018-01-01T01:59:59.999Z

    Local time is 2017-12-31 23:59:59.999 (UTC-02:00) but the UTC year is already 2018; a year check with getUTCFullYear() is off by one. real bug

  35. dates()

    Europe/London 2026: 1 ms before spring forward (2026-03-29 00:59 local)

    2026-03-29T00:59:59.999Z

    Last instant before clocks jump from 01:00 to 02:00 (UTC+00:00 to UTC+01:00); adding 60 minutes of wall time here is not 60 minutes of real time.

  36. dates()

    Europe/London 2026: spring forward (2026-03-29 02:00 local)

    2026-03-29T01:00:00.000Z

    Clocks jump from 01:00 to 02:00; local times in between do not exist, so building a Date from local fields shifts it and that local day is 23 hours long.

  37. dates()

    Europe/London 2026: fall back, first 01:30 (2026-10-25)

    2026-10-25T00:30:00.000Z

    Clocks fall back from 02:00 to 01:00, so 01:30 happens twice; this is the first one (UTC+01:00). Parsing the local time picks one of the two.

  38. dates()

    Europe/London 2026: fall back, second 01:30 (2026-10-25)

    2026-10-25T01:30:00.000Z

    Same wall clock reading as the first 01:30, 60 minutes later (UTC+00:00); sorting or deduplicating by local time breaks and that local day is 25 hours long.

  39. dates()

    Australia/Lord_Howe 2026: fall back, first 01:45 (2026-04-05)

    2026-04-04T14:45:00.000Z

    Clocks fall back from 02:00 to 01:30, so 01:45 happens twice; this is the first one (UTC+11:00). Parsing the local time picks one of the two.

  40. dates()

    Australia/Lord_Howe 2026: fall back, second 01:45 (2026-04-05)

    2026-04-04T15:15:00.000Z

    Same wall clock reading as the first 01:45, 30 minutes later (UTC+10:30); sorting or deduplicating by local time breaks and that local day is 24.5 hours long.

  41. dates()

    Australia/Lord_Howe 2026: 1 ms before spring forward (2026-10-04 01:59 local)

    2026-10-03T15:29:59.999Z

    Last instant before clocks jump from 02:00 to 02:30 (UTC+10:30 to UTC+11:00); adding 30 minutes of wall time here is not 30 minutes of real time.

  42. dates()

    Australia/Lord_Howe 2026: spring forward (2026-10-04 02:30 local)

    2026-10-03T15:30:00.000Z

    Clocks jump from 02:00 to 02:30; local times in between do not exist, so building a Date from local fields shifts it and that local day is 23.5 hours long.

  43. dates()

    Australia/Lord_Howe: first instant of 2026 local time

    2025-12-31T13:00:00.000Z

    Local time is 2026-01-01 00:00 (UTC+11:00) but the UTC year is still 2025; a year check with getUTCFullYear() is off by one. real bug

  44. dates()

    Asia/Kathmandu: first instant of 2026 local time

    2025-12-31T18:15:00.000Z

    Local time is 2026-01-01 00:00 (UTC+05:45) but the UTC year is still 2025; a year check with getUTCFullYear() is off by one. real bug

  45. dates()

    Pacific/Kiritimati: first instant of 2026 local time

    2025-12-31T10:00:00.000Z

    Local time is 2026-01-01 00:00 (UTC+14:00) but the UTC year is still 2025; a year check with getUTCFullYear() is off by one. This zone is among the first places on Earth to enter the new year. real bug

  46. dates()

    Pacific/Pago_Pago: last instant of 2025 local time

    2026-01-01T10:59:59.999Z

    Local time is 2025-12-31 23:59:59.999 (UTC-11:00) but the UTC year is already 2026; a year check with getUTCFullYear() is off by one. This zone is among the last places on Earth to leave the old year. real bug

  47. timeZones()

    UTC

    "UTC"

    The control zone: offset 0 all year. Bugs that only show up away from UTC hide here, which is why CI should not run only in UTC.

  48. timeZones()

    America/New_York

    "America/New_York"

    UTC-05:00 with US DST rules: the classic negative offset where UTC midnight is the previous local day.

  49. timeZones()

    America/Los_Angeles

    "America/Los_Angeles"

    UTC-08:00: late enough that evening events land on the next UTC day.

  50. timeZones()

    America/Sao_Paulo

    "America/Sao_Paulo"

    UTC-03:00; used DST with midnight transitions until 2019, so historical dates have days without 00:00.

  51. timeZones()

    America/St_Johns

    "America/St_Johns"

    UTC-03:30: a half hour offset with DST in the Americas; code that assumes whole hours breaks.

  52. timeZones()

    Europe/London

    "Europe/London"

    UTC+00:00 in winter but +01:00 in summer: tests that pass in January fail in July.

  53. timeZones()

    Europe/Dublin

    "Europe/Dublin"

    The tz database models Irish time with a negative DST save (standard time in summer), which trips code that reads the isDST flag of tz libraries.

  54. timeZones()

    Africa/Casablanca

    "Africa/Casablanca"

    UTC+01:00 that drops to +00:00 during Ramadan each year, so transitions move with the lunar calendar.

  55. timeZones()

    Asia/Kolkata

    "Asia/Kolkata"

    UTC+05:30, no DST: the most populous half hour offset.

  56. timeZones()

    Asia/Kathmandu

    "Asia/Kathmandu"

    UTC+05:45: a quarter hour offset that breaks `offset / 60` rounding and hour only offset pickers.

  57. timeZones()

    Asia/Tehran

    "Asia/Tehran"

    UTC+03:30; abolished DST in 2022, so historical and current rules differ.

  58. timeZones()

    Australia/Adelaide

    "Australia/Adelaide"

    UTC+09:30 / +10:30: southern hemisphere DST plus a half hour offset.

  59. timeZones()

    Australia/Lord_Howe

    "Australia/Lord_Howe"

    UTC+10:30 / +11:00: DST shifts by only 30 minutes, so "DST adds one hour" is false.

  60. timeZones()

    Pacific/Chatham

    "Pacific/Chatham"

    UTC+12:45 / +13:45: quarter hour offset with DST, far east of the date line.

  61. timeZones()

    Pacific/Kiritimati

    "Pacific/Kiritimati"

    UTC+14:00, the largest offset: local dates are a full day ahead of most of the Americas and the first to reach New Year.

  62. timeZones()

    Pacific/Apia

    "Pacific/Apia"

    Samoa skipped 2011-12-30 entirely when it moved across the date line; day counting across that date is off by one.

  63. timeZones()

    Pacific/Pago_Pago

    "Pacific/Pago_Pago"

    UTC-11:00, among the last places to leave a year; local dates lag a day behind most of Asia.

  64. timeZones()

    Antarctica/Troll

    "Antarctica/Troll"

    UTC+00:00 in winter and +02:00 in summer: a two hour DST jump.

  65. strings()

    empty string

    ""

    Falsy, length 0: `if (!name)` and `str[0].toUpperCase()` both misbehave.

  66. strings()

    single space

    " "

    Truthy but blank: passes `if (str)` checks and fails after trim().

  67. strings()

    mixed ASCII whitespace

    " \t\n\r\u000B\u000C "

    Every ASCII whitespace character; trim() returns an empty string but `str.length > 0` validators accept it.

  68. strings()

    no-break space (U+00A0)

    "a\u00A0b"

    Arrives from HTML   and copy paste; \s and trim() treat it as whitespace but split(" ") and many tokenizers do not.

  69. strings()

    ideographic space (U+3000)

    "\u3000\u65E5\u672C\u3000"

    Full width space used in CJK text; trim() removes it, byte based or ASCII only trimming does not.

  70. strings()

    zero width space (U+200B)

    "foo\u200Bbar"

    Invisible and not whitespace for trim() or \s, so it looks identical to "foobar" but is not equal to it.

  71. strings()

    byte order mark prefix (U+FEFF)

    "\uFEFFid"

    Left by Windows editors at the start of files: CSV headers become "\uFEFFid" and JSON.parse of BOM prefixed text throws.

  72. strings()

    soft hyphen (U+00AD)

    "co\u00ADoperate"

    Invisible unless a line breaks there, so search and equality fail on text that looks the same.

  73. strings()

    very long string (100000 chars)

    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..." (100000 chars)

    Exceeds column and header limits, overflows layouts and exposes quadratic algorithms and backtracking regexes.

  74. strings()

    emoji outside the BMP (U+1F600)

    "\u{1F600}"

    One visible character but length 2 (a surrogate pair); charAt(0) and str[0] return half of it.

  75. strings()

    ZWJ family emoji (man, woman, girl, boy)

    "\u{1F468}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F466}"

    Seven code points joined by zero width joiners render as one glyph with length 11; splitting by code point shows four people.

  76. strings()

    flag emoji (regional indicators B and R)

    "\u{1F1E7}\u{1F1F7}"

    A flag is two regional indicator letters; cutting between them leaves letters and reversing them names another country or none.

  77. strings()

    skin tone modifier (thumbs up, medium)

    "\u{1F44D}\u{1F3FD}"

    Base emoji plus a modifier: one grapheme, two code points, four code units; truncating after the base drops the tone.

  78. strings()

    emoji with variation selector (red heart)

    "\u2764\uFE0F"

    U+2764 plus U+FE0F: removing "invisible" characters changes emoji presentation, and the base alone is in the BMP.

  79. strings()

    keycap sequence (digit one)

    "1\uFE0F\u20E3"

    Starts with an ASCII digit, so /^\d/ matches what renders as a single keycap symbol.

  80. strings()

    surrogate pair cut by slice(0, 10)

    "abcdefghi\u{1F600}"

    The emoji starts at index 9, so slice(0, 10) keeps only its high surrogate and produces malformed UTF-16. real bug

  81. strings()

    surrogate pair at the end for trimEnd style truncation

    "Done \u{1F389}"

    Truncating "to N code units" from the end lands inside the pair; output ends in a lone surrogate that renders as U+FFFD. real bug

  82. strings()

    lone high surrogate

    "a\uD83Db"

    Malformed UTF-16: encodeURIComponent throws URIError, TextEncoder writes U+FFFD and isWellFormed() is false.

  83. strings()

    lone low surrogate

    "\uDE00"

    A trailing surrogate with no lead: string comparison works but encoding, hashing and JSON round trips through UTF-8 change it.

  84. strings()

    precomposed e acute (NFC)

    "caf\u00E9"

    Length 4. Compare with the decomposed form: same rendering, different code points.

  85. strings()

    decomposed e acute (NFD)

    "cafe\u0301"

    Length 5 and !== the NFC form; macOS file names and some keyboards produce this form. Use normalize() before comparing.

  86. strings()

    stacked combining marks

    "Z\u0351\u036B\u0343\u036A\u0302\u036B\u033D\u034F\u0334\u0319"

    One base letter with ten combining marks: one grapheme, eleven code units, and rendering that escapes its line box.

  87. strings()

    leading combining mark

    "\u0301abc"

    A combining mark with nothing to attach to; it combines with whatever precedes the string after concatenation.

  88. strings()

    Hangul syllables (precomposed)

    "\uD55C\uAD6D\uC5B4"

    Korean syllable blocks. NFD splits each into two or three jamo, so lengths change after normalization.

  89. strings()

    Hangul jamo (decomposed)

    "\u1112\u1161\u11AB"

    Three jamo that render as one syllable and equal the first syllable of the previous case only after NFC normalization.

  90. strings()

    right to left text mixed with Latin and digits

    "abc \u05E9\u05DC\u05D5\u05DD 123"

    Hebrew mixed with Latin and numbers: visual order differs from memory order, which breaks cursor math and naive truncation with ellipsis.

  91. strings()

    Arabic text

    "\u0645\u0631\u062D\u0628\u0627"

    Right to left script with contextual letter shapes; per code point styling or splitting breaks the joining.

  92. strings()

    right to left override (U+202E)

    "invoice\u202Etxt.exe"

    Displays as "invoiceexe.txt": the Trojan Source trick (CVE-2021-42574) for file names and code review.

  93. strings()

    bidi isolate controls (U+2067, U+2069)

    "user\u2067admin\u2069"

    Invisible directional isolates change display order; strip or escape them in identifiers and logs.

  94. strings()

    Turkish dotted and dotless i

    "I\u0130\u0131i"

    Capital dotted I lowercases to i plus U+0307 (length grows), and toLocaleLowerCase("tr") maps I to dotless i; case insensitive keys break.

  95. strings()

    German sharp s

    "stra\u00DFe"

    toUpperCase() gives "STRASSE" (length 6 to 7), so upper and lower case comparisons disagree and length based layouts shift.

  96. strings()

    Greek final sigma

    "\u039F\u0394\u039F\u03A3"

    Lowercasing the whole word yields a final sigma at the end; lowercasing letter by letter does not, so results differ.

  97. strings()

    null character

    "a\u0000b"

    Terminates C strings, is rejected by PostgreSQL text columns and disappears in some loggers.

  98. strings()

    CRLF newline

    "line1\r\nline2"

    split("\n") leaves a trailing "\r" on each line; Windows files and HTTP headers use this form.

  99. strings()

    CR only newline

    "line1\rline2"

    Classic Mac line ending: code that only looks for \n sees one line.

  100. strings()

    line separator (U+2028)

    "a\u2028b"

    A JavaScript line terminator: `.` in a regex does not match it, and it broke inline scripts and JSONP before ES2019.

  101. strings()

    paragraph separator (U+2029)

    "a\u2029b"

    Another JavaScript line terminator that most text editors render as nothing; line counting tools disagree about it.

  102. strings()

    vertical tab and form feed

    "a\u000Bb\u000Cc"

    Rarely seen whitespace that XML 1.0 forbids and some JSON encoders escape inconsistently.

  103. strings()

    HTML markup as text

    "<script>alert(\"x\")</script>"

    Must render as literal text; any innerHTML or template without escaping executes it.

  104. strings()

    already escaped HTML

    "&lt;b&gt; &amp;amp;"

    Escaping twice shows "&amp;lt;" to users; unescaping on input then escaping on output corrupts it.

  105. strings()

    SQL quote and comment

    "Robert'); DROP TABLE Students;--"

    A quote ends a string literal in hand built SQL; also tests that names with apostrophes survive a round trip.

  106. strings()

    regex metacharacters

    ".*+?^${}()|[]\\"

    Passing user text to new RegExp() throws on "(" or matches everything with ".*"; escape it first.

  107. strings()

    template placeholders

    "${7*7} {{7*7}} %s %d"

    Text that looks like template or printf syntax; any second pass through a formatter changes it.

  108. strings()

    __proto__ as a key

    "__proto__"

    Used as an object key it sets the prototype instead of a property; use Map or Object.create(null) for user keys.

  109. strings()

    the word null

    "null"

    A real surname and a common placeholder: systems that treat "null" as missing lose data.

  110. strings()

    Arabic-Indic digits

    "\u0663\u0664"

    Unicode digits: Number() and parseInt return NaN and \d only matches ASCII 0 to 9, yet users type these.

  111. strings()

    full width digits

    "\uFF11\uFF12\uFF13"

    Typed by CJK input methods; look like 123 but fail /^\d+$/ and Number().

  112. strings()

    Thai without spaces

    "\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22"

    Thai writes words without spaces and uses combining vowels; word wrapping and counting need Intl.Segmenter.

  113. strings()

    wide CJK characters

    "\u4F60\u597D\uFF01"

    Each character takes two terminal columns, so padEnd based tables and fixed width output misalign.

  114. numbers()

    zero

    0

    Falsy: `if (!count)` and `value || defaultValue` replace a legitimate 0.

  115. numbers()

    negative zero

    -0

    -0 === 0 is true, but Object.is(-0, 0) is false, 1 / -0 is -Infinity and String(-0) is "0", so the sign silently disappears.

  116. numbers()

    NaN

    NaN

    NaN !== NaN, indexOf cannot find it, JSON.stringify writes null and every comparison is false, so sorting with it is unstable.

  117. numbers()

    Infinity

    Infinity

    JSON.stringify writes null, toFixed returns "Infinity" and a max-age or timeout of Infinity can mean "never" or overflow a timer. real bug

  118. numbers()

    -Infinity

    -Infinity

    Math.max() of an empty list; a common reducer seed that leaks into output as "-Infinity".

  119. numbers()

    MAX_SAFE_INTEGER

    9007199254740991

    2^53 - 1, the largest integer where n + 1 is exact; IDs and counters past it stop being unique.

  120. numbers()

    MAX_SAFE_INTEGER + 1

    9007199254740992

    2^53: here n + 1 === n, so loops never advance and 64 bit IDs from JSON collide.

  121. numbers()

    MAX_SAFE_INTEGER + 2

    9007199254740992

    Is not representable: the literal 9007199254740993 rounds to 9007199254740992 before your code ever sees it.

  122. numbers()

    MIN_SAFE_INTEGER

    -9007199254740991

    -(2^53 - 1), the negative safe integer limit; mirrors the positive overflow bugs.

  123. numbers()

    MIN_SAFE_INTEGER - 1

    -9007199254740992

    First unsafe negative integer: Number.isSafeInteger is false and decrementing it is not exact.

  124. numbers()

    MAX_VALUE

    1.7976931348623157e+308

    Largest finite double; doubling it or adding a large value gives Infinity.

  125. numbers()

    MIN_VALUE

    5e-324

    Smallest positive double (5e-324), not the most negative number; code that uses it as "minus infinity" is wrong.

  126. numbers()

    EPSILON

    2.220446049250313e-16

    Gap between 1 and the next double; only meaningful as a tolerance near 1, not for large or tiny values.

  127. numbers()

    2^32

    4294967296

    Breaks `x | 0` and `x >>> 0` truncation, array index limits and 32 bit counters.

  128. numbers()

    2^31

    2147483648

    Overflows signed 32 bit math: `(2 ** 31) | 0` is -2147483648.

  129. numbers()

    -0.5

    -0.5

    Between -1 and 0: Math.trunc gives -0, Math.round gives -0, and code that formats the integer part then the fraction drops the minus sign. real bug

  130. numbers()

    -0.0001

    -0.0001

    A small negative between -1 and 0: formatting with toFixed(2) gives "-0.00", and splitting into integer and fraction parts loses the sign. real bug

  131. numbers()

    -1e-7

    -1e-7

    Very small negative: String() gives "-1e-7", so string based number formatting and regexes for digits fail.

  132. numbers()

    1e-7

    1e-7

    Smallest magnitude where String() switches to exponent notation ("1e-7"), while 0.000001 still prints in full.

  133. numbers()

    0.1 + 0.2

    0.30000000000000004

    0.30000000000000004, not 0.3: exact equality on money and percentages fails; compare with a tolerance or use integers.

  134. numbers()

    1.005

    1.005

    Stored as 1.00499999999999989..., so (1.005).toFixed(2) is "1.00" and Math.round(1.005 * 100) / 100 is 1.

  135. numbers()

    0.5 (round half)

    0.5

    Math.round(0.5) is 1 but Math.round(-0.5) is -0 and toFixed uses a different rule: half rounding is not symmetric.

  136. numbers()

    1e21

    1e+21

    First integer where String() uses exponent notation ("1e+21"); toFixed also falls back to exponent form, breaking digit only checks.

  137. numbers()

    999999999999999900000

    999999999999999900000

    Largest round number below 1e21 that still prints all its digits; one step up the format changes.

  138. numbers()

    negative integer

    -42

    Modulo keeps the sign (-42 % 5 is -2), so "positive modulo" wraparound for indexes and calendars is wrong.

  139. numbers()

    non-integer array index

    1.5

    arr[1.5] is undefined and creates a string property on write; Number.isInteger guards are often missing.

  140. numericStrings()

    empty string

    ""

    Number("") is 0, not NaN, so empty form fields become zero.

  141. numericStrings()

    whitespace only

    " \n\t "

    Number(" ") is 0 as well; parseFloat returns NaN, so the two parsers disagree.

  142. numericStrings()

    padded number

    " 42 "

    Number trims whitespace and accepts it; strict validators and parseInt with a regex may not.

  143. numericStrings()

    negative zero string

    "-0"

    Number("-0") is -0, which then prints as "0"; round trips lose the sign.

  144. numericStrings()

    leading zeros

    "007"

    Decimal 7 for Number and parseInt, but octal in some legacy parsers and a string ID that must keep its zeros.

  145. numericStrings()

    hex string

    "0x1A"

    Number("0x1A") is 26 and parseInt("0x1A") is 26, but parseFloat("0x1A") is 0.

  146. numericStrings()

    binary and octal prefixes

    "0b101"

    Number("0b101") is 5 while parseInt("0b101") is 0; prefixes are accepted by one parser only.

  147. numericStrings()

    exponent notation

    "1e3"

    Number gives 1000 but parseInt gives 1, so "digits only" assumptions break.

  148. numericStrings()

    trailing garbage

    "12px"

    parseInt and parseFloat return 12, Number returns NaN; lenient parsing hides bad input.

  149. numericStrings()

    comma decimal separator

    "1,5"

    Decimal comma used in most of Europe and Brazil: parseFloat gives 1 and Number gives NaN.

  150. numericStrings()

    thousands separators

    "1,234,567.89"

    Grouped digits from formatted output; parseFloat stops at the first comma and returns 1.

  151. numericStrings()

    numeric separator literal

    "1_000"

    Valid in JavaScript source but Number("1_000") is NaN.

  152. numericStrings()

    Infinity string

    "Infinity"

    Number("Infinity") is Infinity, so numeric validation accepts a word; JSON cannot represent it.

  153. numericStrings()

    NaN string

    "NaN"

    Number("NaN") is NaN, which passes typeof === "number" checks after conversion.

  154. numericStrings()

    unsafe integer string

    "9007199254740993"

    Parses to 9007199254740992: IDs from JSON or databases silently change; parse as BigInt or keep as string.

  155. numericStrings()

    plus sign

    "+5"

    Accepted by Number but rejected by many /^\d+$/ validators, and vice versa for UI masks.

  156. numericStrings()

    Arabic-Indic digits

    "\u0664\u0662"

    Real digits for many users, but Number returns NaN; normalize with Intl or a digit map first.

  157. bigints()

    0n

    0n

    Falsy like 0, and -0n is just 0n (BigInt has no negative zero), so sign preserving code paths diverge from Number.

  158. bigints()

    -1n

    -1n

    BigInt division truncates toward zero (-1n / 2n is 0n) and % keeps the sign.

  159. bigints()

    2^53 + 1 (first integer Number cannot hold)

    9007199254740993n

    Number(2n ** 53n + 1n) rounds to 2^53; converting back to Number for math or JSON loses it.

  160. bigints()

    int64 max

    9223372036854775807n

    Largest signed 64 bit integer (PostgreSQL bigint, Java long); one more overflows the column.

  161. bigints()

    int64 min

    -9223372036854775808n

    Smallest signed 64 bit integer; its absolute value does not fit in int64.

  162. bigints()

    uint64 max

    18446744073709551615n

    Largest unsigned 64 bit integer; BigInt.asIntN(64, n) turns it into -1n.

  163. bigints()

    2^64 (overflows uint64)

    18446744073709551616n

    Wraps to 0n with BigInt.asUintN(64, n); database drivers reject it.

  164. bigints()

    1e30 as BigInt

    1000000000000000000000000000000n

    Far beyond any Number integer: JSON.stringify throws TypeError for any BigInt, and mixing with Number in + throws.

  165. urls()

    localhost with port

    "http://localhost:3000/"

    No dot in the host: "must contain a TLD" validators reject development URLs.

  166. urls()

    credentials in URL

    "https://user:p%40ss@example.com/"

    Userinfo with an encoded @; naive parsers split on the first @ and leak or mangle the password.

  167. urls()

    IPv6 host with port

    "http://[::1]:8080/"

    hostname is "[::1]" with brackets; splitting host and port on ":" breaks.

  168. urls()

    shorthand IPv4 (127.1)

    "http://127.1/"

    The parser normalizes it to 127.0.0.1, so SSRF block lists that compare strings miss it.

  169. urls()

    hex IPv4 (0x7f.0.0.1)

    "http://0x7f.0.0.1/"

    Also loopback after normalization; another SSRF filter bypass.

  170. urls()

    internationalized domain

    "https://\u4F8B\u3048.jp/"

    hostname becomes punycode ("xn--r8jz45g.jp"), so comparing the input string with url.hostname fails.

  171. urls()

    trailing dot in host

    "https://example.com./"

    A fully qualified name that is a different origin from example.com for cookies, CORS and allow lists.

  172. urls()

    uppercase scheme and host

    "HTTPS://EXAMPLE.COM/Path"

    Scheme and host are lowercased but the path keeps its case; string compares of URLs disagree with the parser.

  173. urls()

    explicit default port

    "https://example.com:443/"

    url.port is "" because the default port is dropped; code expecting "443" breaks.

  174. urls()

    port out of range

    "http://example.com:65536/"

    Ports above 65535 make the URL invalid; regex validators accept any digits.

  175. urls()

    encoded slash in path

    "https://example.com/a%2Fb"

    Servers and routers disagree on whether %2F is a path separator; decode once at the right layer.

  176. urls()

    plus and space in query

    "https://example.com/?q=a+b&q=%20c"

    URLSearchParams decodes "+" as a space but decodeURIComponent does not; the key also repeats.

  177. urls()

    double encoded space

    "https://example.com/file%2520name"

    Decoding twice turns %2520 into a space; decoding once gives "%20" literally.

  178. urls()

    empty fragment

    "https://example.com/#"

    url.hash is "" even though href ends with "#", so hash based checks see no fragment.

  179. urls()

    dot segments in path

    "http://example.com/public/../secret"

    Normalized to /secret by the parser; checks done on the raw string allow traversal.

  180. urls()

    non ASCII path and query

    "https://example.com/\u{1F600}?q=caf\u00E9"

    The parser percent encodes both as UTF-8, so href no longer equals the input.

  181. urls()

    very long URL (8000 char path)

    "https://example.com/aaaaaaaaaaaaaaaaaaaa..." (8020 chars)

    Exceeds common 2048 character limits and 8 KB request line limits in proxies and servers.

  182. urls()

    javascript: scheme

    "javascript:alert(1)"

    A valid URL that runs code when used as an href; allow list schemes instead of block listing.

  183. urls()

    javascript: with a newline inside

    "java\nscript:alert(1)"

    The parser removes tabs and newlines, so this is javascript:alert(1); block lists that match the raw string miss it.

  184. urls()

    data: URL with HTML

    "data:text/html,<script>alert(1)</script>"

    Valid and executable in some contexts; never forward user supplied data URLs to href or src.

  185. urls()

    file URL with Windows drive

    "file:///C:/Windows/win.ini"

    host is empty and pathname is "/C:/Windows/win.ini" with a leading slash before the drive letter.

  186. urls()

    mailto URL

    "mailto:user@example.com"

    No host at all: url.host is "" and url.origin is "null".

  187. urls()

    surrounding spaces

    " https://example.com/ "

    The parser strips leading and trailing spaces, so it is valid, but the stored string is not.

  188. urls()

    protocol relative

    "//example.com/path"

    Invalid without a base, but resolves to another host with one: "starts with /" open redirect checks pass it.

  189. urls()

    backslash relative

    "/\\example.com"

    Browsers treat "\" as "/" in http URLs, so this resolves to //example.com: a classic open redirect.

  190. urls()

    bare domain without scheme

    "example.com"

    What users type, but not a URL: new URL throws and links become relative paths.

  191. urls()

    space in host

    "http://exa mple.com/"

    Forbidden host code point; new URL throws.

  192. urls()

    empty string

    ""

    new URL("") throws; optional URL fields must be checked before parsing.

  193. emails()

    plus addressing

    "user+tag@example.com"

    Gmail and others deliver it to "user"; validators that reject "+" lose real users.

  194. emails()

    dots in local part

    "first.last@example.com"

    Valid and common; some providers ignore the dots, so deduplication by string misses duplicates.

  195. emails()

    apostrophe in local part

    "o'brien@example.com"

    Valid; breaks SQL built by concatenation and regexes limited to [a-z0-9._].

  196. emails()

    quoted local part with space

    "\"john doe\"@example.com"

    A quoted string may contain spaces; nearly every regex rejects it and naive splitting on spaces breaks.

  197. emails()

    quoted local part with @

    "\"a@b\"@example.com"

    Splitting on the first "@" yields the wrong domain; use the last "@".

  198. emails()

    IP address literal domain

    "user@[192.168.0.1]"

    Domain literal in brackets; DNS based checks and domain allow lists do not expect it.

  199. emails()

    dotless domain

    "user@localhost"

    Valid syntax used on intranets and in tests, rejected by "needs a TLD" rules.

  200. emails()

    uppercase address

    "USER@EXAMPLE.COM"

    Domains are case insensitive, local parts technically are not; lowercasing everything can merge different mailboxes, not lowercasing creates duplicates.

  201. emails()

    subdomains and long TLD

    "user@mail.sub.example.co.uk"

    Multi label domains and two part public suffixes; "last two labels" logic picks co.uk.

  202. emails()

    new generic TLD

    "user@example.photography"

    TLDs longer than 4 letters exist; {2,4} quantifiers reject them.

  203. emails()

    shortest realistic address

    "a@b.co"

    Single character local part and short domain; minimum length rules are often too strict.

  204. emails()

    64 character local part (maximum)

    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@example.com"

    The longest local part RFC 5321 allows; storage columns sized at 50 truncate it.

  205. emails()

    internationalized local part

    "jos\u00E9@example.com"

    Valid with SMTPUTF8 (RFC 6531) but rejected by ASCII only servers; decide explicitly what you accept.

  206. emails()

    internationalized domain

    "user@\u4F8B\u3048.jp"

    The domain must be converted to punycode for DNS and SMTP; comparing raw strings fails.

  207. emails()

    65 character local part (too long)

    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@example.com"

    One over the RFC 5321 limit; lenient validators accept it and the SMTP server rejects it later.

  208. emails()

    missing @

    "plainaddress"

    No separator at all; code that splits on "@" gets one part and reads undefined.

  209. emails()

    missing local part

    "@example.com"

    Empty local part; split("@")[0] is "".

  210. emails()

    missing domain

    "user@"

    Empty domain; split("@")[1] is "".

  211. emails()

    two @ signs

    "user@@example.com"

    Unquoted second "@" is invalid; splitting gives three parts.

  212. emails()

    leading dot

    ".user@example.com"

    A dot may not start the local part.

  213. emails()

    trailing dot in local part

    "user.@example.com"

    A dot may not end the local part (some providers accept it anyway).

  214. emails()

    consecutive dots

    "us..er@example.com"

    Consecutive dots are not allowed unquoted.

  215. emails()

    consecutive dots in domain

    "user@example..com"

    Empty domain label; DNS lookups fail.

  216. emails()

    domain label starting with hyphen

    "user@-example.com"

    Host labels may not start with a hyphen.

  217. emails()

    unquoted space

    "john doe@example.com"

    Spaces need quoting; forms that do not trim also produce trailing spaces.

  218. emails()

    display name format

    "John Doe <john@example.com>"

    A valid mailbox header, not a bare address; pasted from mail clients into address fields.

  219. paths()

    Windows absolute path

    "C:\\Users\\Ana\\file.txt"

    Backslash separators: splitting on "/" returns one segment, and on POSIX this is a relative file name containing backslashes.

  220. paths()

    Windows path with forward slashes

    "C:/Users/Ana/file.txt"

    Windows accepts it, path.win32 understands it, path.posix treats it as relative.

  221. paths()

    drive relative path

    "C:file.txt"

    Relative to the current directory of drive C, not the root; path.win32.isAbsolute returns false.

  222. paths()

    root relative on Windows

    "\\temp\\file.txt"

    Absolute on the current drive only; the result depends on process.cwd() drive.

  223. paths()

    UNC share path

    "\\\\server\\share\\folder\\file.txt"

    Starts with two backslashes; the "root" is \\server\share\, so dirname and parent walking stop there.

  224. paths()

    long path prefix

    "\\\\?\\C:\\very\\long\\path"

    Disables normalization: "/" is not a separator and ".." is literal; needed beyond 260 characters.

  225. paths()

    path longer than 260 characters

    "C:\\segment\\segment\\segment\\segment\\segme..." (291 chars)

    Exceeds the classic MAX_PATH limit; many Windows tools fail without long path support.

  226. paths()

    reserved device name

    "CON"

    CON, PRN, AUX, NUL, COM1 and LPT1 cannot be file names on Windows, with any extension.

  227. paths()

    reserved name with extension

    "aux.txt"

    Still the AUX device on Windows; repositories with this file cannot be checked out there.

  228. paths()

    trailing dot

    "file."

    Windows strips trailing dots, so "file." and "file" are the same file there but different on POSIX.

  229. paths()

    trailing space

    "file "

    Windows strips trailing spaces as well; invisible in most UIs.

  230. paths()

    case only difference

    "README.md"

    Case insensitive on default Windows and macOS file systems, so readme.md and README.md collide.

  231. paths()

    POSIX absolute path

    "/usr/local/bin"

    Absolute on POSIX; on Windows path.win32 treats it as root relative on the current drive.

  232. paths()

    trailing slash

    "dir/sub/"

    basename("dir/sub/") is "sub", but split("/").pop() is ""; joining adds a double slash.

  233. paths()

    repeated slashes

    "/a//b///c"

    Equivalent to /a/b/c after normalization; string comparisons and cache keys disagree.

  234. paths()

    root only

    "/"

    dirname("/") is "/" and basename is ""; loops that walk up until dirname changes must stop here.

  235. paths()

    empty path

    ""

    path.join("", "") is "." and path.resolve("") is the cwd; empty is not "no path".

  236. paths()

    current directory

    "."

    A path that refers to the cwd; extname(".") is "" and basename is ".".

  237. paths()

    parent directory traversal

    "../../etc/passwd"

    Escapes the base directory after join; validate with path.relative after resolving.

  238. paths()

    traversal hidden inside a path

    "uploads/./a/../../../secret"

    Looks rooted in uploads/ but normalizes outside it.

  239. paths()

    Windows traversal with backslashes

    "..\\..\\Windows\\win.ini"

    Checks that only look for "../" miss backslash traversal on Windows.

  240. paths()

    dotfile

    ".env"

    extname(".env") is "", not ".env"; hidden on POSIX and easy to leak in static servers.

  241. paths()

    double extension

    "archive.tar.gz"

    extname gives ".gz"; type detection and "strip the extension" give "archive.tar".

  242. paths()

    spaces in names

    "My Documents/file name.txt"

    Breaks unquoted shell commands and URL building without encoding.

  243. paths()

    backslash in a POSIX file name

    "a\\b.txt"

    A legal single file name on Linux that becomes two segments on Windows.

  244. paths()

    NFC unicode name

    "caf\u00E9.txt"

    Precomposed form, as typed on Windows and Linux.

  245. paths()

    NFD unicode name

    "cafe\u0301.txt"

    Decomposed form produced by older macOS file systems; looks the same as the NFC name but does not match it.

  246. paths()

    emoji in file name

    "notes \u{1F4DD}.md"

    Surrogate pairs in a path: byte limits, truncation and some archive tools mishandle them.

  247. paths()

    leading dash

    "-rf"

    Interpreted as an option by command line tools unless passed after "--".

  248. paths()

    newline in file name

    "a\nb.txt"

    Legal on Linux; breaks line based tools such as `find | xargs` and log parsing.

  249. json()

    deeply nested arrays (10000 levels)

    "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[..." (20000 chars)

    Valid JSON that overflows the stack of recursive walkers, deep equality checks and schema validators.

  250. json()

    deeply nested objects (10000 levels)

    "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":..." (60001 chars)

    Same as nested arrays for code that recurses into object values, such as deep merge and clone helpers.

  251. json()

    duplicate keys

    "{\"a\":1,\"a\":2}"

    Allowed by JSON.parse, which keeps the last value; other parsers keep the first or reject it, so validators and consumers disagree.

  252. json()

    integer above 2^53

    "{\"id\":9007199254740993}"

    Parses to 9007199254740992: IDs silently change. Send large IDs as strings.

  253. json()

    huge exponent

    "1e400"

    Valid JSON that parses to Infinity, which JSON.stringify then writes as null.

  254. json()

    negative zero

    "-0"

    Parses to -0, but JSON.stringify(-0) is "0", so round trips lose the sign.

  255. json()

    number with trailing zeros

    "1.0"

    Parses to 1 and stringifies as "1": signatures and hashes over JSON text change after a round trip.

  256. json()

    __proto__ key

    "{\"__proto__\":{\"isAdmin\":true}}"

    JSON.parse creates an own property, but merging the result into another object with assignment pollutes its prototype.

  257. json()

    empty key

    "{\"\":1}"

    Valid; path based libraries (a.b.c syntax) cannot address it.

  258. json()

    escaped lone surrogate

    "\"\\ud800\""

    Valid JSON that decodes to malformed UTF-16; re-encoding to UTF-8 or a database fails.

  259. json()

    line and paragraph separators

    "\"a\u2028b\u2029c\""

    Valid JSON that was invalid in JavaScript string literals before ES2019, which broke JSONP and inline script tags.

  260. json()

    top level string

    "\"just a string\""

    Valid JSON since RFC 7159; code that assumes an object reads properties of a string.

  261. json()

    top level null

    "null"

    Valid JSON that parses to null; `const { a } = JSON.parse(text)` throws.

  262. json()

    whitespace around value

    " \n\t {\"a\":1} \r\n"

    Valid; byte or regex based detectors that check the first character for "{" fail.

  263. json()

    byte order mark prefix

    "\uFEFF{\"a\":1}"

    Windows tools write a BOM; JSON.parse throws on it, so strip it when reading files.

  264. json()

    trailing comma

    "[1,2,]"

    Accepted by JavaScript and JSON5, rejected by JSON.parse.

  265. json()

    comments

    "{\"a\":1 /* note */}"

    Common in config files (JSONC) but invalid JSON.

  266. json()

    single quotes

    "{'a':1}"

    JavaScript object syntax, not JSON.

  267. json()

    NaN literal

    "{\"a\":NaN}"

    Python json.dumps writes NaN by default, and JSON.parse rejects it.

  268. json()

    unquoted key

    "{a:1}"

    JavaScript object literal syntax that JSON does not allow.

  269. json()

    empty text

    ""

    An empty response body is not JSON; JSON.parse("") throws "Unexpected end of JSON input".

  270. json()

    raw control character in string

    "\"a\tb\""

    Unescaped tab inside a string is invalid JSON even though it looks harmless.

  271. arrays()

    empty array

    []

    Truthy even though empty; reduce without a seed throws and Math.max(...[]) is -Infinity.

  272. arrays()

    array with a hole

    [1, <hole>, 3]

    forEach, map and filter skip the hole but for...of and spread yield undefined, so the two styles disagree.

  273. arrays()

    explicit undefined

    [1, undefined, 3]

    Looks like the hole case when printed, but every iteration method visits it and `1 in arr` is true.

  274. arrays()

    length 5 with no elements

    [<hole>, <hole>, <hole>, <hole>, <hole>]

    new Array(5) has five holes: map returns holes, not values, and JSON.stringify writes nulls.

  275. arrays()

    huge sparse length (2^32 - 1)

    ["first", <hole>, <hole>, <hole>, <hole>, <hole>, ...] (length 4294967295)

    The maximum array length: cheap to create, but any loop to length or Array.from runs four billion times.

  276. arrays()

    NaN element

    [NaN]

    indexOf(NaN) is -1 while includes(NaN) is true; Set and Map treat NaN as equal to itself.

  277. arrays()

    -0 and 0

    [-0, 0]

    Set collapses them into one value and includes(-0) finds 0; Object.is tells them apart.

  278. arrays()

    numbers that sort as strings

    [10, 9, 1, 100]

    Default sort() compares strings, giving [1, 10, 100, 9]; always pass a comparator.

  279. arrays()

    undefined during sort

    [3, undefined, 1]

    sort() moves undefined to the end without calling the comparator, so comparators never see it.

  280. arrays()

    mixed number and numeric string

    [1, "1"]

    includes("1") and Set treat them as different, while == and loose lookups do not.

  281. arrays()

    nested empty arrays

    [[[]]]

    flat() and recursive flatten helpers must handle empty levels; String([[[]]]) is "".

  282. arrays()

    array with extra property

    [1, 2]

    for...in visits "extra" and Object.keys includes it, but length and JSON.stringify ignore it.

  283. arrays()

    frozen array

    [1, 2, 3]

    push and in place sort throw TypeError in strict mode; libraries that mutate input break.

  284. arrays()

    500000 elements (too many to spread)

    [0, 1, 2, 3, 4, 5, ...] (length 500000)

    Math.max(...arr) and fn.apply(null, arr) exceed the argument limit and throw RangeError.

Guides

Guides

FAQ

Frequently asked questions

A small, curated library of 284 inputs that are known to break JavaScript code: DST transitions, New Year in far away zones, dates before 1970, surrogate pairs, invisible characters, -0, NaN, Windows paths, deep JSON and sparse arrays. Every case has a label, a value, a one line reason and tags.

Any of them. Generators return plain arrays of { label, value, why, tags }, so they plug into test.each in Vitest and Jest, a loop in node:test or Mocha, or a property based testing library such as fast-check.

No. Every call returns the same cases, so failures are reproducible. DST instants are computed from the runtime tz database, so they are deterministic for a given Node.js version.

Wrap it in withTimeZone(zone, fn). In Node.js it sets process.env.TZ, which applies to Date and Intl immediately, and restores the previous zone afterwards, even when the function throws.

So you can see exactly what is in each string. Invisible characters, combining marks and surrogate pairs look identical or empty when rendered, so the catalog prints code points such as \u{1F600} instead.

It is a dev dependency with zero runtime dependencies, shipped as ESM and CommonJS with TypeScript types. It only runs in your tests.