From 8efa01bd62f51b9c3969dfd74c79163c032ca563 Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Thu, 18 Jan 2024 20:13:57 -0800 Subject: [PATCH] fix(bun:test): `toContain` and `toContainEqual` string fix (#8270) * update toContain, toContainEqual, and tests * fix build, use cursor * `strings.indexOf` and `jsType` once --- src/bun.js/bindings/bindings.zig | 33 ++++++++++++++++++++++++++ src/bun.js/test/expect.zig | 40 ++++++++++++++++++++++---------- test/js/bun/test/expect.test.js | 17 ++++++++++---- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 4146ce024f..e35bf03cbe 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3360,6 +3360,18 @@ pub const JSValue = enum(JSValueReprInt) { return this == .String; } + pub inline fn isStringObject(this: JSType) bool { + return this == .StringObject; + } + + pub inline fn isDerivedStringObject(this: JSType) bool { + return this == .DerivedStringObject; + } + + pub inline fn isStringObjectLike(this: JSType) bool { + return this == .StringObject or this == .DerivedStringObject; + } + pub inline fn isStringLike(this: JSType) bool { return switch (this) { .String, .StringObject, .DerivedStringObject => true, @@ -4183,6 +4195,27 @@ pub const JSValue = enum(JSValueReprInt) { return jsType(this).isStringLike(); } + /// Returns true only for string literals + /// - `" string literal"` + pub inline fn isStringLiteral(this: JSValue) bool { + if (!this.isCell()) { + return false; + } + + return jsType(this).isString(); + } + + /// Returns true if + /// - `new String("123")` + /// - `class DerivedString extends String; new DerivedString("123")` + pub inline fn isStringObjectLike(this: JSValue) bool { + if (!this.isCell()) { + return false; + } + + return jsType(this).isStringObjectLike(); + } + pub fn isBigInt(this: JSValue) bool { return cppFn("isBigInt", .{this}); } diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 4f982b1e46..225ff6582f 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -670,13 +670,15 @@ pub const Expect = struct { break; } } - } else if (value.isString() and expected.isString()) { - const value_string = value.toString(globalObject).toSlice(globalObject, default_allocator).slice(); - const expected_string = expected.toString(globalObject).toSlice(globalObject, default_allocator).slice(); + } else if (value.isStringLiteral() and expected.isStringLiteral()) { + const value_string = value.toString(globalObject).toSlice(globalObject, default_allocator); + defer value_string.deinit(); + const expected_string = expected.toString(globalObject).toSlice(globalObject, default_allocator); + defer expected_string.deinit(); if (expected_string.len == 0) { // edge case empty string is always contained pass = true; - } else if (strings.contains(value_string, expected_string)) { + } else if (strings.contains(value_string.slice(), expected_string.slice())) { pass = true; } else if (value_string.len == 0 and expected_string.len == 0) { // edge case two empty strings are true pass = true; @@ -952,7 +954,10 @@ pub const Expect = struct { pass: *bool, }; - if (value.jsTypeLoose().isArrayLike()) { + const value_type = value.jsType(); + const expected_type = expected.jsType(); + + if (value_type.isArrayLike()) { var itr = value.arrayIterator(globalObject); while (itr.next()) |item| { if (item.jestDeepEquals(expected, globalObject)) { @@ -960,13 +965,24 @@ pub const Expect = struct { break; } } - } else if (value.isString() and expected.isString()) { - const value_string = value.toString(globalObject).toSlice(globalObject, default_allocator).slice(); - const expected_string = expected.toString(globalObject).toSlice(globalObject, default_allocator).slice(); - if (strings.contains(value_string, expected_string)) { - pass = true; - } else if (value_string.len == 0 and expected_string.len == 0) { // edge case two empty strings are true - pass = true; + } else if (value_type.isStringLike() and expected_type.isStringLike()) { + if (expected_type.isStringObjectLike() and value_type.isString()) pass = false else { + const value_string = value.toString(globalObject).toSlice(globalObject, default_allocator); + defer value_string.deinit(); + const expected_string = expected.toString(globalObject).toSlice(globalObject, default_allocator); + defer expected_string.deinit(); + + // jest does not have a `typeof === "string"` check for `toContainEqual`. + // it immediately spreads the value into an array. + + var expected_codepoint_cursor = strings.CodepointIterator.Cursor{}; + var expected_iter = strings.CodepointIterator.init(expected_string.slice()); + _ = expected_iter.next(&expected_codepoint_cursor); + + pass = if (expected_iter.next(&expected_codepoint_cursor)) + false + else + strings.indexOf(value_string.slice(), expected_string.slice()) != null; } } else if (value.isIterable(globalObject)) { var expected_entry = ExpectedEntry{ diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index 5e6fae5347..a18498f72b 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -2086,9 +2086,9 @@ describe("expect()", () => { test.each([ ["hello", "h"], + ["hello", ""], ["hello", "hello"], [new String("hello"), "h"], - [new String("hello"), "hello"], ["emoji: 😃", "😃"], ["😄", "😄"], ["", ""], @@ -2118,9 +2118,10 @@ describe("expect()", () => { test.each([ ["hello", "a"], ["hello", "hello?"], - ["hello", ""], [new String("hello"), "a"], [new String("hello"), "hello?"], + [new String("hello"), "hello"], + [new String("hello"), "llo"], [new String("hello"), ""], ["emoji: 😃", "😄"], [[1, 2, 3], -1], @@ -2147,12 +2148,10 @@ describe("expect()", () => { test.each([ ["hello", "h"], - ["hello", "hello"], [new String("hello"), "h"], - [new String("hello"), "hello"], ["emoji: 😃", "😃"], ["😄", "😄"], - ["", ""], + [new String("😄"), "😄"], [[1, 2, 3], 1], [[{ a: 1 }, { b: 2 }, { c: 3 }], { c: 3 }], [[{}], {}], @@ -2197,10 +2196,18 @@ describe("expect()", () => { test.each([ ["hello", "a"], ["hello", "hello?"], + ["hello", "hello"], + ["hello", "llo"], ["hello", ""], + ["", ""], + [new String(""), new String("")], [new String("hello"), "a"], [new String("hello"), "hello?"], + [new String("hello"), "hello"], + [new String("hello"), "llo"], [new String("hello"), ""], + ["😄", new String("😄")], + ["1", new String("1")], ["emoji: 😃", "😄"], [[1, 2, 3], -1], [[{ a: 1 }, { b: 2 }, { c: 3 }], { c: 1 }],