mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
feat: implement case-changing utility methods (fixes #15087)
Add the following case conversion methods to the Bun global object: - Bun.camelCase() - Convert to camelCase - Bun.pascalCase() - Convert to PascalCase - Bun.snakeCase() - Convert to snake_case - Bun.kebabCase() - Convert to kebab-case - Bun.constantCase() - Convert to CONSTANT_CASE - Bun.dotCase() - Convert to dot.case - Bun.capitalCase() - Convert to Capital Case - Bun.trainCase() - Convert to Train-Case These utility functions are implemented in Zig for performance and handle: - Multiple word delimiters (spaces, hyphens, underscores, etc.) - Case transitions (camelCase, PascalCase detection) - Numbers adjacent to letters - UTF-8 string encoding 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,16 @@ pub const BunObject = struct {
|
||||
pub const zstdDecompressSync = toJSCallback(JSZstd.decompressSync);
|
||||
pub const zstdCompress = toJSCallback(JSZstd.compress);
|
||||
pub const zstdDecompress = toJSCallback(JSZstd.decompress);
|
||||
|
||||
// Case conversion functions
|
||||
pub const camelCase = toJSCallback(CaseConvert.jsCamelCase);
|
||||
pub const pascalCase = toJSCallback(CaseConvert.jsPascalCase);
|
||||
pub const snakeCase = toJSCallback(CaseConvert.jsSnakeCase);
|
||||
pub const kebabCase = toJSCallback(CaseConvert.jsKebabCase);
|
||||
pub const constantCase = toJSCallback(CaseConvert.jsConstantCase);
|
||||
pub const dotCase = toJSCallback(CaseConvert.jsDotCase);
|
||||
pub const capitalCase = toJSCallback(CaseConvert.jsCapitalCase);
|
||||
pub const trainCase = toJSCallback(CaseConvert.jsTrainCase);
|
||||
|
||||
// --- Callbacks ---
|
||||
|
||||
@@ -180,6 +190,16 @@ pub const BunObject = struct {
|
||||
@export(&BunObject.zstdDecompressSync, .{ .name = callbackName("zstdDecompressSync") });
|
||||
@export(&BunObject.zstdCompress, .{ .name = callbackName("zstdCompress") });
|
||||
@export(&BunObject.zstdDecompress, .{ .name = callbackName("zstdDecompress") });
|
||||
|
||||
// Case conversion exports
|
||||
@export(&BunObject.camelCase, .{ .name = callbackName("camelCase") });
|
||||
@export(&BunObject.pascalCase, .{ .name = callbackName("pascalCase") });
|
||||
@export(&BunObject.snakeCase, .{ .name = callbackName("snakeCase") });
|
||||
@export(&BunObject.kebabCase, .{ .name = callbackName("kebabCase") });
|
||||
@export(&BunObject.constantCase, .{ .name = callbackName("constantCase") });
|
||||
@export(&BunObject.dotCase, .{ .name = callbackName("dotCase") });
|
||||
@export(&BunObject.capitalCase, .{ .name = callbackName("capitalCase") });
|
||||
@export(&BunObject.trainCase, .{ .name = callbackName("trainCase") });
|
||||
// --- Callbacks ---
|
||||
|
||||
// --- LazyProperty initializers ---
|
||||
@@ -2070,6 +2090,7 @@ pub fn createBunStdout(globalThis: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue
|
||||
}
|
||||
|
||||
const Braces = @import("../../shell/braces.zig");
|
||||
const CaseConvert = @import("./CaseConvert.zig");
|
||||
const Which = @import("../../which.zig");
|
||||
const options = @import("../../options.zig");
|
||||
const std = @import("std");
|
||||
|
||||
378
src/bun.js/api/CaseConvert.zig
Normal file
378
src/bun.js/api/CaseConvert.zig
Normal file
@@ -0,0 +1,378 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("bun");
|
||||
const jsc = bun.jsc;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
/// Check if a character is a word boundary (not alphanumeric)
|
||||
fn isWordBoundary(c: u8) bool {
|
||||
return !std.ascii.isAlphanumeric(c);
|
||||
}
|
||||
|
||||
/// Check if character is uppercase
|
||||
fn isUpper(c: u8) bool {
|
||||
return c >= 'A' and c <= 'Z';
|
||||
}
|
||||
|
||||
/// Check if character is lowercase
|
||||
fn isLower(c: u8) bool {
|
||||
return c >= 'a' and c <= 'z';
|
||||
}
|
||||
|
||||
/// Convert character to uppercase
|
||||
fn toUpper(c: u8) u8 {
|
||||
if (isLower(c)) {
|
||||
return c - 32;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Convert character to lowercase
|
||||
fn toLower(c: u8) u8 {
|
||||
if (isUpper(c)) {
|
||||
return c + 32;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Split a string into words based on various delimiters and case changes
|
||||
fn splitIntoWords(allocator: std.mem.Allocator, input: []const u8) !std.ArrayList([]const u8) {
|
||||
var words = std.ArrayList([]const u8).init(allocator);
|
||||
errdefer words.deinit();
|
||||
|
||||
if (input.len == 0) return words;
|
||||
|
||||
var start: usize = 0;
|
||||
var i: usize = 0;
|
||||
|
||||
while (i < input.len) : (i += 1) {
|
||||
const c = input[i];
|
||||
|
||||
// Skip non-alphanumeric characters
|
||||
if (!std.ascii.isAlphanumeric(c)) {
|
||||
if (i > start) {
|
||||
try words.append(input[start..i]);
|
||||
}
|
||||
start = i + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle transitions if we're not at the first character
|
||||
if (i > 0) {
|
||||
const prev = input[i - 1];
|
||||
|
||||
// Skip if previous was not alphanumeric (already handled above)
|
||||
if (std.ascii.isAlphanumeric(prev)) {
|
||||
const prevIsDigit = std.ascii.isDigit(prev);
|
||||
const currIsDigit = std.ascii.isDigit(c);
|
||||
|
||||
// Check for transitions that should cause splits
|
||||
if (!currIsDigit) {
|
||||
// Split on digit to uppercase letter transition (test123Case -> test123, Case)
|
||||
if (prevIsDigit and isUpper(c)) {
|
||||
if (i > start) {
|
||||
try words.append(input[start..i]);
|
||||
}
|
||||
start = i;
|
||||
}
|
||||
// Detect lowercase to uppercase transition (camelCase)
|
||||
else if (!prevIsDigit and isLower(prev) and isUpper(c)) {
|
||||
if (i > start) {
|
||||
try words.append(input[start..i]);
|
||||
}
|
||||
start = i;
|
||||
}
|
||||
// Detect uppercase sequence ending (XMLParser -> XML, Parser)
|
||||
else if (!prevIsDigit and i < input.len - 1) {
|
||||
const next = input[i + 1];
|
||||
if (isUpper(prev) and isUpper(c) and std.ascii.isAlphanumeric(next) and !std.ascii.isDigit(next) and isLower(next)) {
|
||||
if (i > start) {
|
||||
try words.append(input[start..i]);
|
||||
}
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last word if any
|
||||
if (start < input.len) {
|
||||
try words.append(input[start..]);
|
||||
}
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
/// Convert string to camelCase: "two words" -> "twoWords"
|
||||
pub fn camelCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx == 0) {
|
||||
// First word is all lowercase
|
||||
for (word) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
} else {
|
||||
// Subsequent words: capitalize first letter, lowercase rest
|
||||
try result.append(toUpper(word[0]));
|
||||
for (word[1..]) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to PascalCase: "two words" -> "TwoWords"
|
||||
pub fn pascalCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items) |word| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
// Capitalize first letter, lowercase rest
|
||||
try result.append(toUpper(word[0]));
|
||||
for (word[1..]) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to snake_case: "two words" -> "two_words"
|
||||
pub fn snakeCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('_');
|
||||
}
|
||||
|
||||
for (word) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to kebab-case: "two words" -> "two-words"
|
||||
pub fn kebabCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('-');
|
||||
}
|
||||
|
||||
for (word) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to CONSTANT_CASE: "two words" -> "TWO_WORDS"
|
||||
pub fn constantCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('_');
|
||||
}
|
||||
|
||||
for (word) |c| {
|
||||
try result.append(toUpper(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to dot.case: "two words" -> "two.words"
|
||||
pub fn dotCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('.');
|
||||
}
|
||||
|
||||
for (word) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to Capital Case: "two words" -> "Two Words"
|
||||
pub fn capitalCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append(' ');
|
||||
}
|
||||
|
||||
// Capitalize first letter, lowercase rest
|
||||
try result.append(toUpper(word[0]));
|
||||
for (word[1..]) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to Train-Case: "two words" -> "Two-Words"
|
||||
pub fn trainCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('-');
|
||||
}
|
||||
|
||||
// Capitalize first letter, lowercase rest
|
||||
try result.append(toUpper(word[0]));
|
||||
for (word[1..]) |c| {
|
||||
try result.append(toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Generic case conversion function that handles string extraction and conversion
|
||||
fn convertCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, comptime converter: fn (std.mem.Allocator, []const u8) anyerror![]u8) bun.JSError!JSValue {
|
||||
const arguments = callFrame.arguments_old(1);
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throw("expected 1 argument, got 0", .{});
|
||||
}
|
||||
|
||||
const input_value = arguments.ptr[0];
|
||||
|
||||
// Convert to string
|
||||
const bunstr = try input_value.toBunString(globalThis);
|
||||
if (globalThis.hasException()) return .zero;
|
||||
defer bunstr.deref();
|
||||
|
||||
// Get UTF8 bytes
|
||||
const allocator = bun.default_allocator;
|
||||
const utf8_slice = bunstr.toUTF8(allocator);
|
||||
defer utf8_slice.deinit();
|
||||
|
||||
// Apply the conversion
|
||||
const result_bytes = converter(allocator, utf8_slice.slice()) catch |err| {
|
||||
if (err == error.OutOfMemory) {
|
||||
return globalThis.throwOutOfMemory();
|
||||
}
|
||||
return globalThis.throw("case conversion failed", .{});
|
||||
};
|
||||
defer allocator.free(result_bytes);
|
||||
|
||||
// Create a new string from the result
|
||||
var result_str = bun.String.cloneUTF8(result_bytes);
|
||||
return result_str.transferToJS(globalThis);
|
||||
}
|
||||
|
||||
// JavaScript-exposed functions
|
||||
pub fn jsCamelCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, camelCase);
|
||||
}
|
||||
|
||||
pub fn jsPascalCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, pascalCase);
|
||||
}
|
||||
|
||||
pub fn jsSnakeCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, snakeCase);
|
||||
}
|
||||
|
||||
pub fn jsKebabCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, kebabCase);
|
||||
}
|
||||
|
||||
pub fn jsConstantCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, constantCase);
|
||||
}
|
||||
|
||||
pub fn jsDotCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, dotCase);
|
||||
}
|
||||
|
||||
pub fn jsCapitalCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, capitalCase);
|
||||
}
|
||||
|
||||
pub fn jsTrainCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, trainCase);
|
||||
}
|
||||
@@ -75,6 +75,14 @@
|
||||
macro(zstdDecompressSync) \
|
||||
macro(zstdCompress) \
|
||||
macro(zstdDecompress) \
|
||||
macro(camelCase) \
|
||||
macro(pascalCase) \
|
||||
macro(snakeCase) \
|
||||
macro(kebabCase) \
|
||||
macro(constantCase) \
|
||||
macro(dotCase) \
|
||||
macro(capitalCase) \
|
||||
macro(trainCase) \
|
||||
|
||||
#define DECLARE_ZIG_BUN_OBJECT_CALLBACK(name) BUN_DECLARE_HOST_FUNCTION(BunObject_callback_##name);
|
||||
FOR_EACH_CALLBACK(DECLARE_ZIG_BUN_OBJECT_CALLBACK);
|
||||
|
||||
@@ -807,6 +807,14 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
zstdDecompressSync BunObject_callback_zstdDecompressSync DontDelete|Function 1
|
||||
zstdCompress BunObject_callback_zstdCompress DontDelete|Function 1
|
||||
zstdDecompress BunObject_callback_zstdDecompress DontDelete|Function 1
|
||||
camelCase BunObject_callback_camelCase DontDelete|Function 1
|
||||
pascalCase BunObject_callback_pascalCase DontDelete|Function 1
|
||||
snakeCase BunObject_callback_snakeCase DontDelete|Function 1
|
||||
kebabCase BunObject_callback_kebabCase DontDelete|Function 1
|
||||
constantCase BunObject_callback_constantCase DontDelete|Function 1
|
||||
dotCase BunObject_callback_dotCase DontDelete|Function 1
|
||||
capitalCase BunObject_callback_capitalCase DontDelete|Function 1
|
||||
trainCase BunObject_callback_trainCase DontDelete|Function 1
|
||||
@end
|
||||
*/
|
||||
|
||||
|
||||
166
test/js/bun/case-convert.test.ts
Normal file
166
test/js/bun/case-convert.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("Bun.camelCase", () => {
|
||||
expect(Bun.camelCase("two words")).toBe("twoWords");
|
||||
expect(Bun.camelCase("hello world")).toBe("helloWorld");
|
||||
expect(Bun.camelCase("HELLO_WORLD")).toBe("helloWorld");
|
||||
expect(Bun.camelCase("kebab-case")).toBe("kebabCase");
|
||||
expect(Bun.camelCase("snake_case")).toBe("snakeCase");
|
||||
expect(Bun.camelCase("PascalCase")).toBe("pascalCase");
|
||||
expect(Bun.camelCase("multiple spaces")).toBe("multipleSpaces");
|
||||
expect(Bun.camelCase("123-numbers-456")).toBe("123Numbers456");
|
||||
expect(Bun.camelCase("")).toBe("");
|
||||
expect(Bun.camelCase("alreadyCamelCase")).toBe("alreadyCamelCase");
|
||||
expect(Bun.camelCase("XML-Parser")).toBe("xmlParser");
|
||||
expect(Bun.camelCase("XMLParser")).toBe("xmlParser");
|
||||
});
|
||||
|
||||
test("Bun.pascalCase", () => {
|
||||
expect(Bun.pascalCase("two words")).toBe("TwoWords");
|
||||
expect(Bun.pascalCase("hello world")).toBe("HelloWorld");
|
||||
expect(Bun.pascalCase("HELLO_WORLD")).toBe("HelloWorld");
|
||||
expect(Bun.pascalCase("kebab-case")).toBe("KebabCase");
|
||||
expect(Bun.pascalCase("snake_case")).toBe("SnakeCase");
|
||||
expect(Bun.pascalCase("camelCase")).toBe("CamelCase");
|
||||
expect(Bun.pascalCase("multiple spaces")).toBe("MultipleSpaces");
|
||||
expect(Bun.pascalCase("123-numbers-456")).toBe("123Numbers456");
|
||||
expect(Bun.pascalCase("")).toBe("");
|
||||
expect(Bun.pascalCase("AlreadyPascalCase")).toBe("AlreadyPascalCase");
|
||||
expect(Bun.pascalCase("xml-parser")).toBe("XmlParser");
|
||||
expect(Bun.pascalCase("XMLParser")).toBe("XmlParser");
|
||||
});
|
||||
|
||||
test("Bun.snakeCase", () => {
|
||||
expect(Bun.snakeCase("two words")).toBe("two_words");
|
||||
expect(Bun.snakeCase("hello world")).toBe("hello_world");
|
||||
expect(Bun.snakeCase("HELLO_WORLD")).toBe("hello_world");
|
||||
expect(Bun.snakeCase("kebab-case")).toBe("kebab_case");
|
||||
expect(Bun.snakeCase("camelCase")).toBe("camel_case");
|
||||
expect(Bun.snakeCase("PascalCase")).toBe("pascal_case");
|
||||
expect(Bun.snakeCase("multiple spaces")).toBe("multiple_spaces");
|
||||
expect(Bun.snakeCase("123-numbers-456")).toBe("123_numbers_456");
|
||||
expect(Bun.snakeCase("")).toBe("");
|
||||
expect(Bun.snakeCase("already_snake_case")).toBe("already_snake_case");
|
||||
expect(Bun.snakeCase("XMLParser")).toBe("xml_parser");
|
||||
});
|
||||
|
||||
test("Bun.kebabCase", () => {
|
||||
expect(Bun.kebabCase("two words")).toBe("two-words");
|
||||
expect(Bun.kebabCase("hello world")).toBe("hello-world");
|
||||
expect(Bun.kebabCase("HELLO_WORLD")).toBe("hello-world");
|
||||
expect(Bun.kebabCase("snake_case")).toBe("snake-case");
|
||||
expect(Bun.kebabCase("camelCase")).toBe("camel-case");
|
||||
expect(Bun.kebabCase("PascalCase")).toBe("pascal-case");
|
||||
expect(Bun.kebabCase("multiple spaces")).toBe("multiple-spaces");
|
||||
expect(Bun.kebabCase("123-numbers-456")).toBe("123-numbers-456");
|
||||
expect(Bun.kebabCase("")).toBe("");
|
||||
expect(Bun.kebabCase("already-kebab-case")).toBe("already-kebab-case");
|
||||
expect(Bun.kebabCase("XMLParser")).toBe("xml-parser");
|
||||
});
|
||||
|
||||
test("Bun.constantCase", () => {
|
||||
expect(Bun.constantCase("two words")).toBe("TWO_WORDS");
|
||||
expect(Bun.constantCase("hello world")).toBe("HELLO_WORLD");
|
||||
expect(Bun.constantCase("hello_world")).toBe("HELLO_WORLD");
|
||||
expect(Bun.constantCase("kebab-case")).toBe("KEBAB_CASE");
|
||||
expect(Bun.constantCase("camelCase")).toBe("CAMEL_CASE");
|
||||
expect(Bun.constantCase("PascalCase")).toBe("PASCAL_CASE");
|
||||
expect(Bun.constantCase("multiple spaces")).toBe("MULTIPLE_SPACES");
|
||||
expect(Bun.constantCase("123-numbers-456")).toBe("123_NUMBERS_456");
|
||||
expect(Bun.constantCase("")).toBe("");
|
||||
expect(Bun.constantCase("ALREADY_CONSTANT_CASE")).toBe("ALREADY_CONSTANT_CASE");
|
||||
expect(Bun.constantCase("XMLParser")).toBe("XML_PARSER");
|
||||
});
|
||||
|
||||
test("Bun.dotCase", () => {
|
||||
expect(Bun.dotCase("two words")).toBe("two.words");
|
||||
expect(Bun.dotCase("hello world")).toBe("hello.world");
|
||||
expect(Bun.dotCase("HELLO_WORLD")).toBe("hello.world");
|
||||
expect(Bun.dotCase("kebab-case")).toBe("kebab.case");
|
||||
expect(Bun.dotCase("camelCase")).toBe("camel.case");
|
||||
expect(Bun.dotCase("PascalCase")).toBe("pascal.case");
|
||||
expect(Bun.dotCase("multiple spaces")).toBe("multiple.spaces");
|
||||
expect(Bun.dotCase("123-numbers-456")).toBe("123.numbers.456");
|
||||
expect(Bun.dotCase("")).toBe("");
|
||||
expect(Bun.dotCase("already.dot.case")).toBe("already.dot.case");
|
||||
expect(Bun.dotCase("XMLParser")).toBe("xml.parser");
|
||||
});
|
||||
|
||||
test("Bun.capitalCase", () => {
|
||||
expect(Bun.capitalCase("two words")).toBe("Two Words");
|
||||
expect(Bun.capitalCase("hello world")).toBe("Hello World");
|
||||
expect(Bun.capitalCase("HELLO_WORLD")).toBe("Hello World");
|
||||
expect(Bun.capitalCase("kebab-case")).toBe("Kebab Case");
|
||||
expect(Bun.capitalCase("camelCase")).toBe("Camel Case");
|
||||
expect(Bun.capitalCase("PascalCase")).toBe("Pascal Case");
|
||||
expect(Bun.capitalCase("multiple spaces")).toBe("Multiple Spaces");
|
||||
expect(Bun.capitalCase("123-numbers-456")).toBe("123 Numbers 456");
|
||||
expect(Bun.capitalCase("")).toBe("");
|
||||
expect(Bun.capitalCase("already Capital Case")).toBe("Already Capital Case");
|
||||
expect(Bun.capitalCase("XMLParser")).toBe("Xml Parser");
|
||||
});
|
||||
|
||||
test("Bun.trainCase", () => {
|
||||
expect(Bun.trainCase("two words")).toBe("Two-Words");
|
||||
expect(Bun.trainCase("hello world")).toBe("Hello-World");
|
||||
expect(Bun.trainCase("HELLO_WORLD")).toBe("Hello-World");
|
||||
expect(Bun.trainCase("kebab-case")).toBe("Kebab-Case");
|
||||
expect(Bun.trainCase("camelCase")).toBe("Camel-Case");
|
||||
expect(Bun.trainCase("PascalCase")).toBe("Pascal-Case");
|
||||
expect(Bun.trainCase("multiple spaces")).toBe("Multiple-Spaces");
|
||||
expect(Bun.trainCase("123-numbers-456")).toBe("123-Numbers-456");
|
||||
expect(Bun.trainCase("")).toBe("");
|
||||
expect(Bun.trainCase("Already-Train-Case")).toBe("Already-Train-Case");
|
||||
expect(Bun.trainCase("XMLParser")).toBe("Xml-Parser");
|
||||
});
|
||||
|
||||
test("case conversion with special characters", () => {
|
||||
const input = "hello@world#test!";
|
||||
expect(Bun.camelCase(input)).toBe("helloWorldTest");
|
||||
expect(Bun.pascalCase(input)).toBe("HelloWorldTest");
|
||||
expect(Bun.snakeCase(input)).toBe("hello_world_test");
|
||||
expect(Bun.kebabCase(input)).toBe("hello-world-test");
|
||||
expect(Bun.constantCase(input)).toBe("HELLO_WORLD_TEST");
|
||||
expect(Bun.dotCase(input)).toBe("hello.world.test");
|
||||
expect(Bun.capitalCase(input)).toBe("Hello World Test");
|
||||
expect(Bun.trainCase(input)).toBe("Hello-World-Test");
|
||||
});
|
||||
|
||||
test("case conversion with numbers", () => {
|
||||
// Numbers stay with adjacent letters unless there's a case change
|
||||
const input = "test123case456";
|
||||
expect(Bun.camelCase(input)).toBe("test123case456");
|
||||
expect(Bun.pascalCase(input)).toBe("Test123case456");
|
||||
expect(Bun.snakeCase(input)).toBe("test123case456");
|
||||
expect(Bun.kebabCase(input)).toBe("test123case456");
|
||||
expect(Bun.constantCase(input)).toBe("TEST123CASE456");
|
||||
expect(Bun.dotCase(input)).toBe("test123case456");
|
||||
expect(Bun.capitalCase(input)).toBe("Test123case456");
|
||||
expect(Bun.trainCase(input)).toBe("Test123case456");
|
||||
|
||||
// When there's a case change after numbers, it splits
|
||||
const input2 = "test123Case456";
|
||||
expect(Bun.camelCase(input2)).toBe("test123Case456");
|
||||
expect(Bun.snakeCase(input2)).toBe("test123_case456");
|
||||
expect(Bun.kebabCase(input2)).toBe("test123-case456");
|
||||
});
|
||||
|
||||
test("case conversion with non-strings", () => {
|
||||
// Should convert to string first
|
||||
expect(Bun.camelCase(123)).toBe("123");
|
||||
expect(Bun.camelCase(true)).toBe("true");
|
||||
expect(Bun.camelCase(null)).toBe("null");
|
||||
expect(Bun.camelCase(undefined)).toBe("undefined");
|
||||
});
|
||||
|
||||
test("case conversion error handling", () => {
|
||||
// Should throw when no arguments provided
|
||||
expect(() => (Bun as any).camelCase()).toThrow();
|
||||
expect(() => (Bun as any).pascalCase()).toThrow();
|
||||
expect(() => (Bun as any).snakeCase()).toThrow();
|
||||
expect(() => (Bun as any).kebabCase()).toThrow();
|
||||
expect(() => (Bun as any).constantCase()).toThrow();
|
||||
expect(() => (Bun as any).dotCase()).toThrow();
|
||||
expect(() => (Bun as any).capitalCase()).toThrow();
|
||||
expect(() => (Bun as any).trainCase()).toThrow();
|
||||
});
|
||||
Reference in New Issue
Block a user