describe('JSON', function () {
  var functionsHaveNames = (function foo() {}).name === 'foo';
  var ifFunctionsHaveNamesIt = functionsHaveNames ? it : xit;
  var hasSymbols = typeof Symbol === 'function' && typeof Symbol() === 'symbol';
  var ifSymbolsIt = hasSymbols ? it : xit;
  var ifSymbolsDescribe = hasSymbols ? describe : xit;

  describe('.stringify()', function () {
    ifFunctionsHaveNamesIt('has the right name', function () {
      expect(JSON.stringify.name).to.equal('stringify');
    });

    ifSymbolsIt('serializes a Symbol to undefined', function () {
      expect(JSON.stringify(Symbol())).to.equal(undefined);
    });

    ifSymbolsIt('serializes a Symbol object to {}', function () {
      expect(function stringifyObjectSymbol() { JSON.stringify(Object(Symbol())); }).not.to['throw']();
      expect(JSON.stringify(Object(Symbol()))).to.equal('{}');
    });

    ifSymbolsIt('serializes Symbols in an Array to null', function () {
      expect(JSON.stringify([Symbol('foo')])).to.equal('[null]');
    });

    ifSymbolsIt('serializes Symbol objects in an Array to {}', function () {
      expect(function stringifyObjectSymbolInArray() { JSON.stringify([Object(Symbol('foo'))]); }).not.to['throw']();
      expect(JSON.stringify([Object(Symbol('foo'))])).to.equal('[{}]');
    });

    ifSymbolsDescribe('skips symbol properties and values in an object', function () {
      var enumSym = Symbol('enumerable');
      var nonenum = Symbol('non-enumerable');
      var createObject = function () {
        var obj = { a: 1 };
        obj[enumSym] = true;
        obj.sym = enumSym;
        Object.defineProperty(obj, nonenum, { enumerable: false, value: true });
        expect(Object.getOwnPropertySymbols(obj)).to.eql([enumSym, nonenum]);
        return obj;
      };

      it('works with no replacer', function () {
        var obj = createObject();
        expect(JSON.stringify(obj)).to.equal('{"a":1}');
        expect(JSON.stringify(obj, null, '|')).to.equal('{\n|"a": 1\n}');
      });

      it('works with a replacer function', function () {
        var tuples = [];
        var replacer = function (key, value) {
          tuples.push([this, key, value]);
          return value;
        };
        var obj = createObject();
        expect(JSON.stringify(obj, replacer, '|')).to.equal('{\n|"a": 1\n}'); // populate `tuples` array
        expect(tuples).to.eql([
          [{ '': obj }, '', obj],
          [obj, 'a', 1],
          [obj, 'sym', enumSym]
        ]);
      });

      it('works with a replacer array', function () {
        var obj = createObject();
        obj.foo = 'bar';
        obj[Symbol.prototype.toString.call(enumSym)] = 'tricksy';
        expect(JSON.stringify(obj, ['a', enumSym])).to.equal('{"a":1}');
        expect(JSON.stringify(obj, ['a', enumSym], '|')).to.equal('{\n|"a": 1\n}');
      });

      it('ignores a non-array non-callable replacer object', function () {
        expect(JSON.stringify('z', { test: null })).to.equal('"z"');
      });
    });
  });
});