diff --git a/src/bun.js/test/diff/printDiff.zig b/src/bun.js/test/diff/printDiff.zig index 51ba8eb7b2..5726334f94 100644 --- a/src/bun.js/test/diff/printDiff.zig +++ b/src/bun.js/test/diff/printDiff.zig @@ -395,20 +395,6 @@ fn shouldHighlightChar(char: u8) bool { return false; } -fn areOnlyHighlightableDifferences(char_diff: []const DMP.Diff) bool { - // Check if all differences (inserts/deletes) are characters that should be highlighted - for (char_diff) |*item| { - if (item.operation != .equal) { - for (item.text) |char| { - if (!shouldHighlightChar(char)) { - return false; - } - } - } - } - return true; -} - const ModifiedStyle = struct { single_line: bool, }; @@ -462,14 +448,20 @@ fn printModifiedSegment( return printModifiedSegmentWithoutDiffdiff(writer, config, segment, modified_style); } - // Check if differences are only whitespace/control characters that should be highlighted - const only_highlightable = areOnlyHighlightableDifferences(char_diff.items); - try printLinePrefix(writer, config, removed_prefix); for (char_diff.items) |*item| { switch (item.operation) { .delete => { + const only_highlightable = brk: { + for (item.text) |char| { + if (!shouldHighlightChar(char)) { + break :brk false; + } + } + break :brk true; + }; + if (only_highlightable) { // Use background color for whitespace/control character differences try printSegment(item.text, writer, config, base_styles.green_bg_removed); @@ -488,6 +480,15 @@ fn printModifiedSegment( switch (item.operation) { .delete => {}, .insert => { + const only_highlightable = brk: { + for (item.text) |char| { + if (!shouldHighlightChar(char)) { + break :brk false; + } + } + break :brk true; + }; + if (only_highlightable) { // Use background color for whitespace/control character differences try printSegment(item.text, writer, config, base_styles.red_bg_inserted); diff --git a/test/js/bun/test/printing/diffexample.fixture.ts b/test/js/bun/test/printing/diffexample.fixture.ts index 6d1d847143..a240371e8e 100644 --- a/test/js/bun/test/printing/diffexample.fixture.ts +++ b/test/js/bun/test/printing/diffexample.fixture.ts @@ -1,5 +1,16 @@ import { test, expect } from "bun:test"; +function normalizeInspectError(e: any) { + let str = Bun.inspect(e, { colors: true }); + + str = str.slice(str.indexOf("error")); + return str + .replaceAll(import.meta.dirname, "") + .replaceAll("\r\n", "\n") + .replaceAll("\\", "/") + .replaceAll(process.cwd(), ""); +} + test("example 1", () => { expect("a\nb\nc\n d\ne").toEqual("a\nd\nc\nd\ne"); }); @@ -296,3 +307,51 @@ test("completely different long value does not truncate", () => { } expect(received).toEqual(expected); }); + +test("whitespace-only difference", () => { + expect("hello\nworld ").toEqual("hello\nworld"); +}); + +test.skipIf(!Bun.enableANSIColors)("whitespace-only difference (ANSI)", () => { + try { + expect("hello\nworld ").toEqual("hello\nworld"); + } catch (e) { + expect(normalizeInspectError(e)).toMatchInlineSnapshot(` + "error\x1B[0m\x1B[2m:\x1B[0m \x1B[1m\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m + + \x1B[0m\x1B[2m"hello\x1B[0m + \x1B[32m- \x1B[0m\x1B[32mworld\x1B[0m\x1B[32m"\x1B[0m + \x1B[31m+ \x1B[0m\x1B[31mworld\x1B[0m\x1B[31m\x1B[7m \x1B[0m\x1B[31m"\x1B[0m + + \x1B[32m- Expected - 1\x1B[0m + \x1B[31m+ Received + 1\x1B[0m + \x1B[0m + \x1B[0m \x1B[2mat \x1B[0m\x1B[0m\x1B[2m\x1B[0m\x1B[2m (\x1B[0m\x1B[0m\x1B[36m/diffexample.fixture.ts\x1B[0m\x1B[2m:\x1B[0m\x1B[33m317\x1B[0m\x1B[2m:\x1B[33m29\x1B[0m\x1B[2m)\x1B[0m + " + `); + } +}); + +test("mix of whitespace-only and non-whitespace-only differences", () => { + expect("hello\nworld ").toEqual("Hello\nworld "); +}); + +test.skipIf(!Bun.enableANSIColors)("mix of whitespace-only and non-whitespace-only differences (ANSI)", () => { + try { + expect("hello\nworld ").toEqual("Hello\nworld "); + } catch (e) { + expect(normalizeInspectError(e)).toMatchInlineSnapshot(` + "error\x1B[0m\x1B[2m:\x1B[0m \x1B[1m\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m + + \x1B[32m- \x1B[0m\x1B[32m"\x1B[0m\x1B[32mH\x1B[0m\x1B[32mello\x1B[0m + \x1B[31m+ \x1B[0m\x1B[31m"\x1B[0m\x1B[31mh\x1B[0m\x1B[31mello\x1B[0m + \x1B[0m\x1B[2mworld "\x1B[0m + + \x1B[32m- Expected - 1\x1B[0m + \x1B[31m+ Received + 1\x1B[0m + \x1B[0m + \x1B[0m \x1B[2mat \x1B[0m\x1B[0m\x1B[2m\x1B[0m\x1B[2m (\x1B[0m\x1B[0m\x1B[36m/diffexample.fixture.ts\x1B[0m\x1B[2m:\x1B[0m\x1B[33m341\x1B[0m\x1B[2m:\x1B[33m29\x1B[0m\x1B[2m)\x1B[0m + " + `); + } +}); diff --git a/test/js/bun/test/printing/diffexample.test.ts b/test/js/bun/test/printing/diffexample.test.ts index 110ed23fa4..5f4c0368b4 100644 --- a/test/js/bun/test/printing/diffexample.test.ts +++ b/test/js/bun/test/printing/diffexample.test.ts @@ -29,11 +29,13 @@ test("no color", async () => { expect(noColorStderr).toMatchInlineSnapshot(` " test/js/bun/test/printing/diffexample.fixture.ts: - 1 | import { test, expect } from "bun:test"; - 2 | - 3 | test("example 1", () => { - 4 | expect("a\\nb\\nc\\n d\\ne").toEqual("a\\nd\\nc\\nd\\ne"); - ^ + 10 | .replaceAll("\\\\", "/") + 11 | .replaceAll(process.cwd(), ""); + 12 | } + 13 | + 14 | test("example 1", () => { + 15 | expect("a\\nb\\nc\\n d\\ne").toEqual("a\\nd\\nc\\nd\\ne"); + ^ error: expect(received).toEqual(expected) "a @@ -49,12 +51,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 1 - 6 | test("example 2", () => { - 7 | expect({ - 8 | object1: "a", - 9 | object2: "b", - 10 | object3: "c\\nd\\ne", - 11 | }).toEqual({ + 17 | test("example 2", () => { + 18 | expect({ + 19 | object1: "a", + 20 | object2: "b", + 21 | object3: "c\\nd\\ne", + 22 | }).toEqual({ ^ error: expect(received).toEqual(expected) @@ -75,12 +77,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 2 - 26 | expectedLines[750] = "line 751 - MODIFIED"; // Change line 751 - 27 | expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901 - 28 | expectedLines.splice(100, 0, "line 101 - INSERTED"); - 29 | const expectedString = expectedLines.join("\\n"); - 30 | - 31 | expect(originalString).toEqual(expectedString); + 37 | expectedLines[750] = "line 751 - MODIFIED"; // Change line 751 + 38 | expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901 + 39 | expectedLines.splice(100, 0, "line 101 - INSERTED"); + 40 | const expectedString = expectedLines.join("\\n"); + 41 | + 42 | expect(originalString).toEqual(expectedString); ^ error: expect(received).toEqual(expected) @@ -142,12 +144,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 3 - very long string with few changes (todo) example 4 - ansi colors don't get printed to console - 111 | line 35 - 112 | line 36 - 113 | line 37 - 114 | line 38 - 115 | line 39\`; - 116 | expect(received).toEqual(expected); + 122 | line 35 + 123 | line 36 + 124 | line 37 + 125 | line 38 + 126 | line 39\`; + 127 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -199,12 +201,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 12 - large multiline diff - 194 | line six - 195 | line seven - 196 | - 197 | === has newline at end vs doesn't === - 198 | \`; - 199 | expect(received).toEqual(expected); + 205 | line six + 206 | line seven + 207 | + 208 | === has newline at end vs doesn't === + 209 | \`; + 210 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -265,12 +267,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 13 - simple multiline diff with sections - 200 | }); - 201 | - 202 | test("example 14 - single line diff", () => { - 203 | const received = \`"Β‘hello, world"\`; - 204 | const expected = \`"hello, world!"\`; - 205 | expect(received).toEqual(expected); + 211 | }); + 212 | + 213 | test("example 14 - single line diff", () => { + 214 | const received = \`"Β‘hello, world"\`; + 215 | const expected = \`"hello, world!"\`; + 216 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -279,12 +281,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 14 - single line diff - 206 | }); - 207 | - 208 | test("example 15 - unicode char diff", () => { - 209 | const received = \`Hello πŸ‘‹ δΈ–η•Œ 🌎!\`; - 210 | const expected = \`Hello πŸ‘‹ δΈ–η•Œ 🌍!\`; - 211 | expect(received).toEqual(expected); + 217 | }); + 218 | + 219 | test("example 15 - unicode char diff", () => { + 220 | const received = \`Hello πŸ‘‹ δΈ–η•Œ 🌎!\`; + 221 | const expected = \`Hello πŸ‘‹ δΈ–η•Œ 🌍!\`; + 222 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -293,12 +295,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 15 - unicode char diff - 220 | }\`; - 221 | const expected = \`function main() { - 222 | print("Hello, world!"); - 223 | print("Goodbye, world!"); - 224 | }\`; - 225 | expect(received).toEqual(expected); + 231 | }\`; + 232 | const expected = \`function main() { + 233 | print("Hello, world!"); + 234 | print("Goodbye, world!"); + 235 | }\`; + 236 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -316,12 +318,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 16 - indentation change diff - 256 | } - 257 | - 258 | // The Zig code adds a trailing newline to each string. - 259 | const receivedString = receivedLines.join("\\n") + "\\n"; - 260 | const expectedString = expectedLines.join("\\n") + "\\n"; - 261 | expect(receivedString).toEqual(expectedString); + 267 | } + 268 | + 269 | // The Zig code adds a trailing newline to each string. + 270 | const receivedString = receivedLines.join("\\n") + "\\n"; + 271 | const expectedString = expectedLines.join("\\n") + "\\n"; + 272 | expect(receivedString).toEqual(expectedString); ^ error: expect(received).toEqual(expected) @@ -382,12 +384,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 17 - very long string - 262 | }); - 263 | - 264 | test("example 18 - very long single line string", () => { - 265 | const expected = "a".repeat(1000000); - 266 | const received = "a".repeat(1000001); - 267 | expect(received).toEqual(expected); + 273 | }); + 274 | + 275 | test("example 18 - very long single line string", () => { + 276 | const expected = "a".repeat(1000000); + 277 | const received = "a".repeat(1000001); + 278 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -396,12 +398,12 @@ test("no color", async () => { at (FILE:LINE) (fail) example 18 - very long single line string - 266 | const received = "a".repeat(1000001); - 267 | expect(received).toEqual(expected); - 268 | }); - 269 | - 270 | test("not", () => { - 271 | expect("Hello, World!").not.toEqual("Hello, World!"); + 277 | const received = "a".repeat(1000001); + 278 | expect(received).toEqual(expected); + 279 | }); + 280 | + 281 | test("not", () => { + 282 | expect("Hello, World!").not.toEqual("Hello, World!"); ^ error: expect(received).not.toEqual(expected) @@ -409,12 +411,12 @@ test("no color", async () => { at (FILE:LINE) (fail) not - 270 | test("not", () => { - 271 | expect("Hello, World!").not.toEqual("Hello, World!"); - 272 | }); - 273 | - 274 | test("has end newline vs doesn't", () => { - 275 | expect("Hello, World!\\n").toEqual("Hello, World!"); + 281 | test("not", () => { + 282 | expect("Hello, World!").not.toEqual("Hello, World!"); + 283 | }); + 284 | + 285 | test("has end newline vs doesn't", () => { + 286 | expect("Hello, World!\\n").toEqual("Hello, World!"); ^ error: expect(received).toEqual(expected) @@ -427,12 +429,12 @@ test("no color", async () => { at (FILE:LINE) (fail) has end newline vs doesn't - 281 | const received = new Float64Array(length); - 282 | for (let i = 0; i < length; i++) { - 283 | expected[i] = i; - 284 | received[i] = i + 1; - 285 | } - 286 | expect(received).toEqual(expected); + 292 | const received = new Float64Array(length); + 293 | for (let i = 0; i < length; i++) { + 294 | expected[i] = i; + 295 | received[i] = i + 1; + 296 | } + 297 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -458,12 +460,12 @@ test("no color", async () => { at (FILE:LINE) (fail) extremely float64array - 292 | const received = new Int32Array(length); - 293 | for (let i = 0; i < length; i++) { - 294 | expected[i] = i; - 295 | received[i] = length - i - 1; - 296 | } - 297 | expect(received).toEqual(expected); + 303 | const received = new Int32Array(length); + 304 | for (let i = 0; i < length; i++) { + 305 | expected[i] = i; + 306 | received[i] = length - i - 1; + 307 | } + 308 | expect(received).toEqual(expected); ^ error: expect(received).toEqual(expected) @@ -674,12 +676,51 @@ test("no color", async () => { at (FILE:LINE) (fail) completely different long value does not truncate + 307 | } + 308 | expect(received).toEqual(expected); + 309 | }); + 310 | + 311 | test("whitespace-only difference", () => { + 312 | expect("hello\\nworld ").toEqual("hello\\nworld"); + ^ + error: expect(received).toEqual(expected) + + "hello + - world" + + world " + + - Expected - 1 + + Received + 1 + + at (FILE:LINE) + (fail) whitespace-only difference + (skip) whitespace-only difference (ANSI) + 331 | \`); + 332 | } + 333 | }); + 334 | + 335 | test("mix of whitespace-only and non-whitespace-only differences", () => { + 336 | expect("hello\\nworld ").toEqual("Hello\\nworld "); + ^ + error: expect(received).toEqual(expected) + + - "Hello + + "hello + world " + + - Expected - 1 + + Received + 1 + + at (FILE:LINE) + (fail) mix of whitespace-only and non-whitespace-only differences + (skip) mix of whitespace-only and non-whitespace-only differences (ANSI) 0 pass + 2 skip 1 todo - 14 fail - 14 expect() calls - Ran 15 tests across 1 file. + 16 fail + 16 expect() calls + Ran 19 tests across 1 file. " `); expect(noColorSpawn.exitCode).toBe(1);