JSFiddle - React, Tailwind, and code Playground

HTML

<style>body { white-space : pre }</style>

<script>
	function out(text) {
    	document.body.appendChild(document.createTextNode((text || '') + '\n'));
    }
    var text;
    
    text = 'hello there';
    out(text.replace(/e/g, 'E') + ' ' + 'hello there'.replace(/e/g, 'E'));
    out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
    
    text = new String('oh my');
    out(text.toUpperCase() + ' ' + 'oh my'.toUpperCase());
    out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
    
    text = 368;
    out((text + ' is a big number') + ' ' + ('368' + ' is a big number'));
    out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
    
    text = ['\uD83D', '\uDE07'];
    out(text[1].charCodeAt(0) + ' ' + '😇'[1].charCodeAt(0));
    out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
    
    function Text() { this.math = 7; }; Text.prototype = {toString:function() { return this.math + 3 + ''; }}
    text = new Text();
    out(String.prototype.match.call(text, '0') + ' ' + text.toString().match('0'));
    out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
    
    
    out('Edge-case testing')
    function test(text, options) {
    	var result = duckyString(text, false, options);
        text = duckyString(text, true, options);
    	out(result + ' ' + ((result) ? '"' + text + '"' : text));
    }
    test('');
    test(null);
    test(undefined);
    test(0);
    test({length:0});
    test({'0':'!', length:'1'});
    test({});
    test(window);
    test(false);
    test(['hi']);
    test(['\uD83D\uDE07']);
    test([['1'], 2, new String(3)]);
    test([['1'], 2, new String(3)], {chars:true});
</script>

JavaScript

function duckyString(string, normalise, unacceptable) {
        var type = null;
        if (!unacceptable)
            unacceptable = {};
        if (string && !unacceptable.chars && unacceptable.to == null)
            unacceptable.to = string.toString == Array.prototype.toString;

        if (string == null)
            ;

        //tests if `string` just is a string
        else if (
            !unacceptable.is &&
            (typeof string == 'string' || string instanceof String)
        )
            type = 'is';

        //tests if `string + ''` or `/./.test(string)` is valid
        else if (
            !unacceptable.to &&
            string.toString && typeof string.toString == 'function' && string.toString != Object.prototype.toString
        )
            type = 'to';

        //tests if `[...string]` is valid
        else if (
            !unacceptable.chars &&
            (string.length > 0 || string.length == 0)
        ) {
            type = 'chars';
            //for each char
            for (var index = 0; type && index < string.length; ++index) {
                var char = string[index];

                //efficiently get its length
                var length = ((duckyString(char, false, {to:true})) ?
                    char :
                    duckyString(char, true) || {}
                ).length;

                if (length == 1)
                    continue;

                //unicode surrogate-pair support
                char = duckyString(char, true);
                length = String.prototype[Symbol && Symbol.iterator];
                if (!(length = length && length.call(char)) || length.next().done || !length.next().done)
                    type = null;
            }
        }

        //return true or false if they dont want to auto-convert to real string
        if (!(type && normalise))
            //return truthy or falsy with <type>/null if they want why it's true
            return (normalise == null) ? type != null :...