2
\$\begingroup\$

Actually I needed a function which converts underscore to camel-case. But then I've had the idea: Why not extend the function so, that is works the other way around?

Finally I recalled that there is a third case which programmers use: Pascal-Case. So I included that too.

The function doesn't work perfect but as long as the different cases are used as expected it works alright.

Any hints and suggestions concerning improvements are welcomed.

/**
* Converts a string to either underscore-case or camel-case or
*   to pascal-case. 
* @param {string} toChange
* @param {string} [toCase='toUnderscoreCase'] - Options: 
*   'toUnderscoreCase' | 'toCamelCase' | 'toPascalCase'  
* @returns {string} according toCase-param.
*   Returns an empty string in case of error.
*/

function changeCase(toChange, toCase) {
  var needle = null;
  var funct = null;
  var needleChangingUpperLower = /[a-z0-9]_[a-z0-9]/g;
  var needleToUnderscore = /(([a-zA-Z0-9]*?)(([a-z0-9][A-Z]))|([a-zA-Z0-9]*)$)/g;  

  toCase = toCase || 'toUnderscoreCase'

  if ( !toChange || 
      typeof toChange !== 'string' || 
      toChange.length < 2 ) {
    return '';
  }

  if (toChange.search(/[^\w]/g) > -1) {
    return '';
  }

  function toChangingUpperLower(match) {
    var chars = match.split('_');

    return chars[0] + chars[1].toUpperCase();
  }

  function toUnderscore(match, wordComplete, wordTruncated, wordChange) {
    var ret = '';

    if (wordChange) {

      var chars = wordChange.split('');

      ret += wordTruncated.toLowerCase() + chars[0] + '_' + chars[1].toLowerCase();
    } else {
      ret = wordComplete.toLowerCase();
    }

    return ret;
  }

  switch (toCase) {
    case 'toCamelCase':
      needle = needleChangingUpperLower;

      toChange = toChange.slice(0, 1).toLowerCase() + toChange.slice(1);

      funct = toChangingUpperLower;

      break;
    case 'toPascalCase':
      needle = needleChangingUpperLower;

      toChange = toChange.slice(0, 1).toUpperCase() + toChange.slice(1);

      funct = toChangingUpperLower;

      break;
    case 'toUnderscoreCase':
      needle = needleToUnderscore;

      funct = toUnderscore;

      break;
    default:
      return '';
  }

  return toChange.replace(needle, funct);
}

// --- Usage-examples and test-cases -------- 
var tests = [ {
  toChange: 'get_amount_of_taxes',
  toCase: ''
},
             
{
  toChange: 'ThisIsTheSoCalledPascalCase',
  toCase: 'toCamelCase'
},
{
  toChange: 'thisIsTheSoCalledCamelCase',
  toCase: 'toPascalCase'
},
{
  toChange: 'set_sum_of_articles',
  toCase: 'toCamelCase'
},
{
  toChange: 'getAmountOfTaxes',
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'XMLHttpRequest',
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'theXMLconnector1ForABC',
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'aB',
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'c',
  toCase: 'toUnderscoreCase'
},
{
  toChange: [2, 3],
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'abc#123Spec',
  toCase: 'toUnderscoreCase'
},
{
  toChange: 'abc_890-spec',
  toCase: 'toCamelCase'
},
{
  toChange: 'get_amount_of_taxes',
  toCase: 'abc123'
} ];

tests.forEach(function(test) {
  var result = changeCase(test.toChange, test.toCase);
  var log = console.log;
  var err = console.error;

  !result
  ? err( '%s\n%s', 
        'Something has gone wrong.', 
        JSON.stringify(test, null, 2)) 
  : log( '%s - %s', 
        result,
        JSON.stringify(test, null, 2));
});

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Tests

Your test code isn't the clearest. Assuming the rest of the code works as it should, then you should look into making your tests clearer and more reliable.

As it stands, you don't give any expected value for the test results. Having that would enable you to throw an error if the result is wrong as well as if the method throws. Consider something like this:

var tests = [
    {
        'toChange': 'ThisIsPascalCase',
        'toCase': 'toUnderscoreCase',
        'expected': 'this_is_pascal_case'
    }
];

tests.forEach(function(test) {
    var result = changeCase(test.toChange, test.toCase);
    if(!result) {
        console.error("Something has gone wrong!");
        // log extra error details if you want them
    }
    else if(result !== test.expected) {
        console.error("Result doesn't match expected result!");
        // log extra error and environment/stack details
    }
    else {
        console.log("Test passed successfully.");
    }
});

That makes your test code clearer, and provides you with accuracy checking.

\$\endgroup\$
1
  • \$\begingroup\$ Lovely technique. :) Simple to use and you don't have to integrate an additional test-library. Thanks a lot. \$\endgroup\$ Apr 20, 2016 at 9:22
1
\$\begingroup\$

Instead of writing one changeCase function that takes a parameter for what type of transformation to do, you may want consider writing a module that has three separate functions:

var Case = {
    toCamelCase : function (string) {

    },
    toPascalCase : function (string) {

    },
    toUnderscoreCase : function (string) {

    }
}

This would make your code more cohesive, since each part will focus on one part of the code.

For example, this would make it so the code for underscore case is in the toUnderscoreCase function, and the code for camel case is in the toCamelCase function. That way if you have a bug where and underscore case test isn't working right, you can edit the toUnderscoreCase without the risk of inadvertently breaking your other function's test cases. Code for a different case just acts as clutter in these situations.

I particularly bring this up since you assign which function to use with the funct variable during the execution of the changeCase in a switch statement. Why not just call separate the separate functions directly?

One problem with breaking up the code is that you will end up with some duplicate code, like when you validate the input string. (by the way, single character strings are perfectly valid: 'A' is pascal case and 'a' is in both camel and underscore case) You can overcome this by also breaking the duplicate code out it's own function and then calling those functions inside your case conversion functions.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.