2
0
Эх сурвалжийг харах

Merge pull request #63902 from dalexeev/string-cases

Rémi Verschelde 3 жил өмнө
parent
commit
0bf3f79157

+ 47 - 38
core/string/ustring.cpp

@@ -970,62 +970,71 @@ const char32_t *String::get_data() const {
 	return size() ? &operator[](0) : &zero;
 }
 
-String String::capitalize() const {
-	String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges();
-	String cap;
-	for (int i = 0; i < aux.get_slice_count(" "); i++) {
-		String slice = aux.get_slicec(' ', i);
-		if (slice.length() > 0) {
-			slice[0] = _find_upper(slice[0]);
-			if (i > 0) {
-				cap += " ";
-			}
-			cap += slice;
-		}
-	}
-
-	return cap;
-}
-
-String String::camelcase_to_underscore(bool lowercase) const {
+String String::_camelcase_to_underscore() const {
 	const char32_t *cstr = get_data();
 	String new_string;
 	int start_index = 0;
 
 	for (int i = 1; i < this->size(); i++) {
-		bool is_upper = is_ascii_upper_case(cstr[i]);
-		bool is_number = is_digit(cstr[i]);
+		bool is_prev_upper = is_ascii_upper_case(cstr[i - 1]);
+		bool is_prev_lower = is_ascii_lower_case(cstr[i - 1]);
+		bool is_prev_digit = is_digit(cstr[i - 1]);
 
-		bool are_next_2_lower = false;
-		bool is_next_lower = false;
-		bool is_next_number = false;
-		bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]);
-		bool was_precedent_number = is_digit(cstr[i - 1]);
-
-		if (i + 2 < this->size()) {
-			are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
-		}
+		bool is_curr_upper = is_ascii_upper_case(cstr[i]);
+		bool is_curr_lower = is_ascii_lower_case(cstr[i]);
+		bool is_curr_digit = is_digit(cstr[i]);
 
+		bool is_next_lower = false;
 		if (i + 1 < this->size()) {
 			is_next_lower = is_ascii_lower_case(cstr[i + 1]);
-			is_next_number = is_digit(cstr[i + 1]);
 		}
 
-		const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number;
-		const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower;
-		const bool cond_c = is_number && !was_precedent_number;
-		const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower;
-		const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number);
+		const bool cond_a = is_prev_lower && is_curr_upper; // aA
+		const bool cond_b = (is_prev_upper || is_prev_digit) && is_curr_upper && is_next_lower; // AAa, 2Aa
+		const bool cond_c = is_prev_digit && is_curr_lower && is_next_lower; // 2aa
+		const bool cond_d = (is_prev_upper || is_prev_lower) && is_curr_digit; // A2, a2
 
-		bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number;
-		if (should_split) {
+		if (cond_a || cond_b || cond_c || cond_d) {
 			new_string += this->substr(start_index, i - start_index) + "_";
 			start_index = i;
 		}
 	}
 
 	new_string += this->substr(start_index, this->size() - start_index);
-	return lowercase ? new_string.to_lower() : new_string;
+	return new_string.to_lower();
+}
+
+String String::capitalize() const {
+	String aux = this->_camelcase_to_underscore().replace("_", " ").strip_edges();
+	String cap;
+	for (int i = 0; i < aux.get_slice_count(" "); i++) {
+		String slice = aux.get_slicec(' ', i);
+		if (slice.length() > 0) {
+			slice[0] = _find_upper(slice[0]);
+			if (i > 0) {
+				cap += " ";
+			}
+			cap += slice;
+		}
+	}
+
+	return cap;
+}
+
+String String::to_camel_case() const {
+	String s = this->to_pascal_case();
+	if (!s.is_empty()) {
+		s[0] = _find_lower(s[0]);
+	}
+	return s;
+}
+
+String String::to_pascal_case() const {
+	return this->capitalize().replace(" ", "");
+}
+
+String String::to_snake_case() const {
+	return this->_camelcase_to_underscore().replace(" ", "_").strip_edges();
 }
 
 String String::get_with_code_lines() const {

+ 4 - 1
core/string/ustring.h

@@ -196,6 +196,7 @@ class String {
 
 	bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;
 	int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const;
+	String _camelcase_to_underscore() const;
 
 public:
 	enum {
@@ -335,7 +336,9 @@ public:
 	static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
 
 	String capitalize() const;
-	String camelcase_to_underscore(bool lowercase = true) const;
+	String to_camel_case() const;
+	String to_pascal_case() const;
+	String to_snake_case() const;
 
 	String get_with_code_lines() const;
 	int get_slice_count(String p_splitter) const;

+ 3 - 0
core/variant/variant_call.cpp

@@ -1506,6 +1506,9 @@ static void _register_variant_builtin_methods() {
 	bind_method(String, repeat, sarray("count"), varray());
 	bind_method(String, insert, sarray("position", "what"), varray());
 	bind_method(String, capitalize, sarray(), varray());
+	bind_method(String, to_camel_case, sarray(), varray());
+	bind_method(String, to_pascal_case, sarray(), varray());
+	bind_method(String, to_snake_case, sarray(), varray());
 	bind_method(String, split, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));
 	bind_method(String, rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));
 	bind_method(String, split_floats, sarray("delimiter", "allow_empty"), varray(true));

+ 18 - 0
doc/classes/String.xml

@@ -775,6 +775,12 @@
 				Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces.
 			</description>
 		</method>
+		<method name="to_camel_case" qualifiers="const">
+			<return type="String" />
+			<description>
+				Returns the string converted to [code]camelCase[/code].
+			</description>
+		</method>
 		<method name="to_float" qualifiers="const">
 			<return type="float" />
 			<description>
@@ -804,6 +810,18 @@
 				Returns the string converted to lowercase.
 			</description>
 		</method>
+		<method name="to_pascal_case" qualifiers="const">
+			<return type="String" />
+			<description>
+				Returns the string converted to [code]PascalCase[/code].
+			</description>
+		</method>
+		<method name="to_snake_case" qualifiers="const">
+			<return type="String" />
+			<description>
+				Returns the string converted to [code]snake_case[/code].
+			</description>
+		</method>
 		<method name="to_upper" qualifiers="const">
 			<return type="String" />
 			<description>

+ 6 - 16
editor/connections_dialog.cpp

@@ -753,22 +753,12 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
 	}
 
 	Dictionary subst;
-
-	String s = node_name.capitalize().replace(" ", "");
-	subst["NodeName"] = s;
-	if (!s.is_empty()) {
-		s[0] = s.to_lower()[0];
-	}
-	subst["nodeName"] = s;
-	subst["node_name"] = node_name.capitalize().replace(" ", "_").to_lower();
-
-	s = signal_name.capitalize().replace(" ", "");
-	subst["SignalName"] = s;
-	if (!s.is_empty()) {
-		s[0] = s.to_lower()[0];
-	}
-	subst["signalName"] = s;
-	subst["signal_name"] = signal_name.capitalize().replace(" ", "_").to_lower();
+	subst["NodeName"] = node_name.to_pascal_case();
+	subst["nodeName"] = node_name.to_camel_case();
+	subst["node_name"] = node_name.to_snake_case();
+	subst["SignalName"] = signal_name.to_pascal_case();
+	subst["signalName"] = signal_name.to_camel_case();
+	subst["signal_name"] = signal_name.to_snake_case();
 
 	String dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_name")).format(subst);
 

+ 3 - 3
editor/editor_autoload_settings.cpp

@@ -164,7 +164,7 @@ void EditorAutoloadSettings::_autoload_add() {
 		if (!fpath.ends_with("/")) {
 			fpath = fpath.get_base_dir();
 		}
-		dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().camelcase_to_underscore())), false, false);
+		dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().to_snake_case())), false, false);
 		dialog->popup_centered();
 	} else {
 		if (autoload_add(autoload_add_name->get_text(), autoload_add_path->get_text())) {
@@ -371,7 +371,7 @@ void EditorAutoloadSettings::_autoload_open(const String &fpath) {
 
 void EditorAutoloadSettings::_autoload_file_callback(const String &p_path) {
 	// Convert the file name to PascalCase, which is the convention for classes in GDScript.
-	const String class_name = p_path.get_file().get_basename().capitalize().replace(" ", "");
+	const String class_name = p_path.get_file().get_basename().to_pascal_case();
 
 	// If the name collides with a built-in class, prefix the name to make it possible to add without having to edit the name.
 	// The prefix is subjective, but it provides better UX than leaving the Add button disabled :)
@@ -580,7 +580,7 @@ void EditorAutoloadSettings::_script_created(Ref<Script> p_script) {
 	FileSystemDock::get_singleton()->get_script_create_dialog()->hide();
 	path = p_script->get_path().get_base_dir();
 	autoload_add_path->set_text(p_script->get_path());
-	autoload_add_name->set_text(p_script->get_path().get_file().get_basename().capitalize().replace(" ", ""));
+	autoload_add_name->set_text(p_script->get_path().get_file().get_basename().to_pascal_case());
 	_autoload_add();
 }
 

+ 4 - 4
editor/editor_node.cpp

@@ -1314,7 +1314,7 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String
 			file->set_current_file(p_resource->get_path().get_file());
 		} else {
 			if (extensions.size()) {
-				String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore();
+				String resource_name_snake_case = p_resource->get_class().to_snake_case();
 				file->set_current_file("new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower());
 			} else {
 				file->set_current_file(String());
@@ -1331,7 +1331,7 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String
 	} else if (preferred.size()) {
 		String existing;
 		if (extensions.size()) {
-			String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore();
+			String resource_name_snake_case = p_resource->get_class().to_snake_case();
 			existing = "new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower();
 		}
 		file->set_current_path(existing);
@@ -2730,10 +2730,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 						// Use casing of the root node.
 						break;
 					case SCENE_NAME_CASING_PASCAL_CASE: {
-						root_name = root_name.capitalize().replace(" ", "");
+						root_name = root_name.to_pascal_case();
 					} break;
 					case SCENE_NAME_CASING_SNAKE_CASE:
-						root_name = root_name.capitalize().replace(" ", "").replace("-", "_").camelcase_to_underscore();
+						root_name = root_name.replace("-", "_").to_snake_case();
 						break;
 				}
 				file->set_current_path(root_name + "." + extensions.front()->get().to_lower());

+ 1 - 1
editor/plugins/bone_map_editor_plugin.cpp

@@ -609,7 +609,7 @@ int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_pic
 }
 
 BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) {
-	String fixed_bn = p_bone_name.camelcase_to_underscore().to_lower();
+	String fixed_bn = p_bone_name.to_snake_case();
 
 	LocalVector<String> left_words;
 	left_words.push_back("(?<![a-zA-Z])left");

+ 1 - 1
editor/plugins/script_text_editor.cpp

@@ -1590,7 +1590,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
 					}
 				}
 
-				String variable_name = String(node->get_name()).camelcase_to_underscore(true).validate_identifier();
+				String variable_name = String(node->get_name()).to_snake_case().validate_identifier();
 				if (use_type) {
 					text_to_drop += vformat("@onready var %s: %s = %s%s\n", variable_name, node->get_class_name(), is_unique ? "%" : "$", path);
 				} else {

+ 2 - 2
editor/rename_dialog.cpp

@@ -500,7 +500,7 @@ String RenameDialog::_postprocess(const String &subject) {
 	if (style_id == 1) {
 		// PascalCase to snake_case
 
-		result = result.camelcase_to_underscore(true);
+		result = result.to_snake_case();
 		result = _regex("_+", result, "_");
 
 	} else if (style_id == 2) {
@@ -521,7 +521,7 @@ String RenameDialog::_postprocess(const String &subject) {
 				end = start + 1;
 			}
 			buffer += result.substr(end, result.size() - (end + 1));
-			result = buffer.replace("_", "").capitalize();
+			result = buffer.to_pascal_case();
 		}
 	}
 

+ 1 - 1
editor/script_create_dialog.cpp

@@ -898,7 +898,7 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL
 
 	// Get name from file name if no name in meta information
 	if (script_template.name == String()) {
-		script_template.name = p_filename.get_basename().replace("_", " ").capitalize();
+		script_template.name = p_filename.get_basename().capitalize();
 	}
 
 	return script_template;

+ 1 - 1
editor/shader_create_dialog.cpp

@@ -161,7 +161,7 @@ void ShaderCreateDialog::_create_new() {
 			shader = text_shader;
 
 			StringBuilder code;
-			code += vformat("shader_type %s;\n", mode_menu->get_text().replace(" ", "").camelcase_to_underscore());
+			code += vformat("shader_type %s;\n", mode_menu->get_text().to_snake_case());
 
 			if (current_template == 0) { // Default template.
 				code += "\n";

+ 1 - 1
editor/translations/extract.py

@@ -139,7 +139,7 @@ theme_property_patterns = {
 }
 
 
-# See String::camelcase_to_underscore().
+# See String::_camelcase_to_underscore().
 capitalize_re = re.compile(r"(?<=\D)(?=\d)|(?<=\d)(?=\D([a-z]|\d))")
 
 

+ 9 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

@@ -436,6 +436,15 @@ namespace Godot.NativeInterop
         public static partial void godotsharp_string_simplify_path(in godot_string p_self,
             out godot_string r_simplified_path);
 
+        public static partial void godotsharp_string_to_camel_case(in godot_string p_self,
+            out godot_string r_camel_case);
+
+        public static partial void godotsharp_string_to_pascal_case(in godot_string p_self,
+            out godot_string r_pascal_case);
+
+        public static partial void godotsharp_string_to_snake_case(in godot_string p_self,
+            out godot_string r_snake_case);
+
         // NodePath
 
         public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self,

+ 39 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs

@@ -287,6 +287,45 @@ namespace Godot
             return cap;
         }
 
+        /// <summary>
+        /// Returns the string converted to <c>camelCase</c>.
+        /// </summary>
+        /// <param name="instance">The string to convert.</param>
+        /// <returns>The converted string.</returns>
+        public static string ToCamelCase(this string instance)
+        {
+            using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
+            NativeFuncs.godotsharp_string_to_camel_case(instanceStr, out godot_string camelCase);
+            using (camelCase)
+                return Marshaling.ConvertStringToManaged(camelCase);
+        }
+
+        /// <summary>
+        /// Returns the string converted to <c>PascalCase</c>.
+        /// </summary>
+        /// <param name="instance">The string to convert.</param>
+        /// <returns>The converted string.</returns>
+        public static string ToPascalCase(this string instance)
+        {
+            using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
+            NativeFuncs.godotsharp_string_to_pascal_case(instanceStr, out godot_string pascalCase);
+            using (pascalCase)
+                return Marshaling.ConvertStringToManaged(pascalCase);
+        }
+
+        /// <summary>
+        /// Returns the string converted to <c>snake_case</c>.
+        /// </summary>
+        /// <param name="instance">The string to convert.</param>
+        /// <returns>The converted string.</returns>
+        public static string ToSnakeCase(this string instance)
+        {
+            using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
+            NativeFuncs.godotsharp_string_to_snake_case(instanceStr, out godot_string snakeCase);
+            using (snakeCase)
+                return Marshaling.ConvertStringToManaged(snakeCase);
+        }
+
         private static string CamelcaseToUnderscore(this string instance, bool lowerCase)
         {
             string newString = string.Empty;

+ 15 - 0
modules/mono/glue/runtime_interop.cpp

@@ -1096,6 +1096,18 @@ void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_
 	memnew_placement(r_simplified_path, String(p_self->simplify_path()));
 }
 
+void godotsharp_string_to_camel_case(const String *p_self, String *r_camel_case) {
+	memnew_placement(r_camel_case, String(p_self->to_camel_case()));
+}
+
+void godotsharp_string_to_pascal_case(const String *p_self, String *r_pascal_case) {
+	memnew_placement(r_pascal_case, String(p_self->to_pascal_case()));
+}
+
+void godotsharp_string_to_snake_case(const String *p_self, String *r_snake_case) {
+	memnew_placement(r_snake_case, String(p_self->to_snake_case()));
+}
+
 void godotsharp_node_path_get_as_property_path(const NodePath *p_ptr, NodePath *r_dest) {
 	memnew_placement(r_dest, NodePath(p_ptr->get_as_property_path()));
 }
@@ -1471,6 +1483,9 @@ static const void *unmanaged_callbacks[]{
 	(void *)godotsharp_string_sha256_buffer,
 	(void *)godotsharp_string_sha256_text,
 	(void *)godotsharp_string_simplify_path,
+	(void *)godotsharp_string_to_camel_case,
+	(void *)godotsharp_string_to_pascal_case,
+	(void *)godotsharp_string_to_snake_case,
 	(void *)godotsharp_node_path_get_as_property_path,
 	(void *)godotsharp_node_path_get_concatenated_names,
 	(void *)godotsharp_node_path_get_concatenated_subnames,

+ 4 - 7
scene/main/node.cpp

@@ -951,14 +951,11 @@ String Node::validate_child_name(Node *p_child) {
 String Node::adjust_name_casing(const String &p_name) {
 	switch (GLOBAL_GET("editor/node_naming/name_casing").operator int()) {
 		case NAME_CASING_PASCAL_CASE:
-			return p_name.capitalize().replace(" ", "");
-		case NAME_CASING_CAMEL_CASE: {
-			String name = p_name.capitalize().replace(" ", "");
-			name[0] = name.to_lower()[0];
-			return name;
-		}
+			return p_name.to_pascal_case();
+		case NAME_CASING_CAMEL_CASE:
+			return p_name.to_camel_case();
 		case NAME_CASING_SNAKE_CASE:
-			return p_name.capitalize().replace(" ", "_").to_lower();
+			return p_name.to_snake_case();
 	}
 	return p_name;
 }

+ 75 - 12
tests/core/string/test_string.h

@@ -466,11 +466,6 @@ TEST_CASE("[String] String to float") {
 	}
 }
 
-TEST_CASE("[String] CamelCase to underscore") {
-	CHECK(String("TestTestStringGD").camelcase_to_underscore(false) == String("Test_Test_String_GD"));
-	CHECK(String("TestTestStringGD").camelcase_to_underscore(true) == String("test_test_string_gd"));
-}
-
 TEST_CASE("[String] Slicing") {
 	String s = "Mars,Jupiter,Saturn,Uranus";
 
@@ -1096,8 +1091,36 @@ TEST_CASE("[String] IPVX address to string") {
 }
 
 TEST_CASE("[String] Capitalize against many strings") {
-	String input = "bytes2var";
-	String output = "Bytes 2 Var";
+	String input = "2D";
+	String output = "2d";
+	CHECK(input.capitalize() == output);
+
+	input = "2d";
+	output = "2d";
+	CHECK(input.capitalize() == output);
+
+	input = "2db";
+	output = "2 Db";
+	CHECK(input.capitalize() == output);
+
+	input = "HTML5 Html5 html5 html_5";
+	output = "Html 5 Html 5 Html 5 Html 5";
+	CHECK(input.capitalize() == output);
+
+	input = "Node2D Node2d NODE2D NODE_2D node_2d";
+	output = "Node 2d Node 2d Node 2d Node 2d Node 2d";
+	CHECK(input.capitalize() == output);
+
+	input = "Node2DPosition";
+	output = "Node 2d Position";
+	CHECK(input.capitalize() == output);
+
+	input = "Number2Digits";
+	output = "Number 2 Digits";
+	CHECK(input.capitalize() == output);
+
+	input = "bytes2var";
+	output = "Bytes 2 Var";
 	CHECK(input.capitalize() == output);
 
 	input = "linear2db";
@@ -1112,10 +1135,6 @@ TEST_CASE("[String] Capitalize against many strings") {
 	output = "Sha 256";
 	CHECK(input.capitalize() == output);
 
-	input = "2db";
-	output = "2 Db";
-	CHECK(input.capitalize() == output);
-
 	input = "PascalCase";
 	output = "Pascal Case";
 	CHECK(input.capitalize() == output);
@@ -1153,6 +1172,50 @@ TEST_CASE("[String] Capitalize against many strings") {
 	CHECK(input.capitalize() == output);
 }
 
+struct StringCasesTestCase {
+	const char *input;
+	const char *camel_case;
+	const char *pascal_case;
+	const char *snake_case;
+};
+
+TEST_CASE("[String] Checking case conversion methods") {
+	StringCasesTestCase test_cases[] = {
+		/* clang-format off */
+		{ "2D",                "2d",              "2d",              "2d"                },
+		{ "2d",                "2d",              "2d",              "2d"                },
+		{ "2db",               "2Db",             "2Db",             "2_db"              },
+		{ "Vector3",           "vector3",         "Vector3",         "vector_3"          },
+		{ "sha256",            "sha256",          "Sha256",          "sha_256"           },
+		{ "Node2D",            "node2d",          "Node2d",          "node_2d"           },
+		{ "RichTextLabel",     "richTextLabel",   "RichTextLabel",   "rich_text_label"   },
+		{ "HTML5",             "html5",           "Html5",           "html_5"            },
+		{ "Node2DPosition",    "node2dPosition",  "Node2dPosition",  "node_2d_position"  },
+		{ "Number2Digits",     "number2Digits",   "Number2Digits",   "number_2_digits"   },
+		{ "get_property_list", "getPropertyList", "GetPropertyList", "get_property_list" },
+		{ "get_camera_2d",     "getCamera2d",     "GetCamera2d",     "get_camera_2d"     },
+		{ "_physics_process",  "physicsProcess",  "PhysicsProcess",  "_physics_process"  },
+		{ "bytes2var",         "bytes2Var",       "Bytes2Var",       "bytes_2_var"       },
+		{ "linear2db",         "linear2Db",       "Linear2Db",       "linear_2_db"       },
+		{ "sha256sum",         "sha256Sum",       "Sha256Sum",       "sha_256_sum"       },
+		{ "camelCase",         "camelCase",       "CamelCase",       "camel_case"        },
+		{ "PascalCase",        "pascalCase",      "PascalCase",      "pascal_case"       },
+		{ "snake_case",        "snakeCase",       "SnakeCase",       "snake_case"        },
+		{ "Test TEST test",    "testTestTest",    "TestTestTest",    "test_test_test"    },
+		{ nullptr,             nullptr,           nullptr,           nullptr             },
+		/* clang-format on */
+	};
+
+	int idx = 0;
+	while (test_cases[idx].input != nullptr) {
+		String input = test_cases[idx].input;
+		CHECK(input.to_camel_case() == test_cases[idx].camel_case);
+		CHECK(input.to_pascal_case() == test_cases[idx].pascal_case);
+		CHECK(input.to_snake_case() == test_cases[idx].snake_case);
+		idx++;
+	}
+}
+
 TEST_CASE("[String] Checking string is empty when it should be") {
 	bool state = true;
 	bool success;
@@ -1663,7 +1726,7 @@ TEST_CASE("[String] Variant ptr indexed set") {
 TEST_CASE("[Stress][String] Empty via ' == String()'") {
 	for (int i = 0; i < 100000; ++i) {
 		String str = "Hello World!";
-		if (str.is_empty()) {
+		if (str == String()) {
 			continue;
 		}
 	}