'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.deepMerge = exports.saveSnapshotFile = exports.ensureDirectoryExists = exports.escapeBacktickString = exports.unescape = exports.serialize = exports.getSnapshotData = exports.getSnapshotPath = exports.keyToTestName = exports.testNameToKey = exports.SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_GUIDE_LINK = exports.SNAPSHOT_VERSION = exports.SNAPSHOT_EXTENSION = undefined;

var _plugins = require('./plugins');

var _chalk = require('chalk');

var _chalk2 = _interopRequireDefault(_chalk);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _mkdirp = require('mkdirp');

var _mkdirp2 = _interopRequireDefault(_mkdirp);

var _naturalCompare = require('natural-compare');

var _naturalCompare2 = _interopRequireDefault(_naturalCompare);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _prettyFormat = require('pretty-format');

var _prettyFormat2 = _interopRequireDefault(_prettyFormat);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : {default: obj};
}

/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 */

const SNAPSHOT_EXTENSION = (exports.SNAPSHOT_EXTENSION = 'snap');
const SNAPSHOT_VERSION = (exports.SNAPSHOT_VERSION = '1');
const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
const SNAPSHOT_GUIDE_LINK = (exports.SNAPSHOT_GUIDE_LINK =
  'https://goo.gl/fbAQLP');
const SNAPSHOT_VERSION_WARNING = (exports.SNAPSHOT_VERSION_WARNING = _chalk2.default.yellow(
  `${_chalk2.default.bold('Warning')}: Before you upgrade snapshots, ` +
    `we recommend that you revert any local changes to tests or other code, ` +
    `to ensure that you do not store invalid state.`
));

const writeSnapshotVersion = () =>
  `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;

const validateSnapshotVersion = snapshotContents => {
  const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
  const version = versionTest && versionTest[1];

  if (!version) {
    return new Error(
      _chalk2.default.red(
        `${_chalk2.default.bold(
          'Outdated snapshot'
        )}: No snapshot header found. ` +
          `Jest 19 introduced versioned snapshots to ensure all developers ` +
          `on a project are using the same version of Jest. ` +
          `Please update all snapshots during this upgrade of Jest.\n\n`
      ) + SNAPSHOT_VERSION_WARNING
    );
  }

  if (version < SNAPSHOT_VERSION) {
    return new Error(
      _chalk2.default.red(
        `${_chalk2.default.red.bold(
          'Outdated snapshot'
        )}: The version of the snapshot ` +
          `file associated with this test is outdated. The snapshot file ` +
          `version ensures that all developers on a project are using ` +
          `the same version of Jest. ` +
          `Please update all snapshots during this upgrade of Jest.\n\n`
      ) +
        `Expected: v${SNAPSHOT_VERSION}\n` +
        `Received: v${version}\n\n` +
        SNAPSHOT_VERSION_WARNING
    );
  }

  if (version > SNAPSHOT_VERSION) {
    return new Error(
      _chalk2.default.red(
        `${_chalk2.default.red.bold(
          'Outdated Jest version'
        )}: The version of this ` +
          `snapshot file indicates that this project is meant to be used ` +
          `with a newer version of Jest. The snapshot file version ensures ` +
          `that all developers on a project are using the same version of ` +
          `Jest. Please update your version of Jest and re-run the tests.\n\n`
      ) +
        `Expected: v${SNAPSHOT_VERSION}\n` +
        `Received: v${version}`
    );
  }

  return null;
};

function isObject(item) {
  return item && typeof item === 'object' && !Array.isArray(item);
}

const testNameToKey = (exports.testNameToKey = (testName, count) =>
  testName + ' ' + count);

const keyToTestName = (exports.keyToTestName = key => {
  if (!/ \d+$/.test(key)) {
    throw new Error('Snapshot keys must end with a number.');
  }

  return key.replace(/ \d+$/, '');
});

const getSnapshotPath = (exports.getSnapshotPath = testPath =>
  _path2.default.join(
    _path2.default.join(_path2.default.dirname(testPath), '__snapshots__'),
    _path2.default.basename(testPath) + '.' + SNAPSHOT_EXTENSION
  ));

const getSnapshotData = (exports.getSnapshotData = (snapshotPath, update) => {
  const data = Object.create(null);
  let snapshotContents = '';
  let dirty = false;

  if (_fs2.default.existsSync(snapshotPath)) {
    try {
      snapshotContents = _fs2.default.readFileSync(snapshotPath, 'utf8');
      // eslint-disable-next-line no-new-func
      const populate = new Function('exports', snapshotContents);
      // $FlowFixMe
      populate(data);
    } catch (e) {}
  }

  const validationResult = validateSnapshotVersion(snapshotContents);
  const isInvalid = snapshotContents && validationResult;

  if (update === 'none' && isInvalid) {
    throw validationResult;
  }

  if ((update === 'all' || update === 'new') && isInvalid) {
    dirty = true;
  }

  return {data: data, dirty: dirty};
});

// Extra line breaks at the beginning and at the end of the snapshot are useful
// to make the content of the snapshot easier to read
const addExtraLineBreaks = string =>
  string.includes('\n') ? `\n${string}\n` : string;

const serialize = (exports.serialize = data =>
  addExtraLineBreaks(
    normalizeNewlines(
      (0, _prettyFormat2.default)(data, {
        escapeRegex: true,
        plugins: (0, _plugins.getSerializers)(),
        printFunctionName: false
      })
    )
  ));

// unescape double quotes
const unescape = (exports.unescape = data => data.replace(/\\(")/g, '$1'));

const escapeBacktickString = (exports.escapeBacktickString = str =>
  str.replace(/`|\\|\${/g, '\\$&'));

const printBacktickString = str => '`' + escapeBacktickString(str) + '`';

const ensureDirectoryExists = (exports.ensureDirectoryExists = filePath => {
  try {
    _mkdirp2.default.sync(
      _path2.default.join(_path2.default.dirname(filePath)),
      '777'
    );
  } catch (e) {}
});

const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n');

const saveSnapshotFile = (exports.saveSnapshotFile = (
  snapshotData,
  snapshotPath
) => {
  const snapshots = Object.keys(snapshotData)
    .sort(_naturalCompare2.default)
    .map(
      key =>
        'exports[' +
        printBacktickString(key) +
        '] = ' +
        printBacktickString(normalizeNewlines(snapshotData[key])) +
        ';'
    );

  ensureDirectoryExists(snapshotPath);
  _fs2.default.writeFileSync(
    snapshotPath,
    writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n'
  );
});

const deepMerge = (exports.deepMerge = (target, source) => {
  const mergedOutput = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key]) && !source[key].$$typeof) {
        if (!(key in target)) Object.assign(mergedOutput, {[key]: source[key]});
        else mergedOutput[key] = deepMerge(target[key], source[key]);
      } else {
        Object.assign(mergedOutput, {[key]: source[key]});
      }
    });
  }
  return mergedOutput;
});
