Home Reference Source Test

src/lib/core/installer.test.js

import test from 'ava';
import sinon from 'sinon';
import path from 'path';

import Subject from '../../__mocks__/rxjs-subject';
import fs, {
  alreadyExistsFilename,
  alreadyExistsPath,
  throwableMkdirPath,
} from '../../__mocks__/fs';
import memFs, { store } from '../../__mocks__/mem-fs';
import memFsEditor, {
  fs as memFsEditorFs,
} from '../../__mocks__/mem-fs-editor';

import { defaultUserGeneratorConfig, systemSeederTemplate } from '../constants';
import { mockImports, resetImports } from '../utils/test-helpers';

import InstallerError from './installer-error';

import Installer, { __RewireAPI__ as moduleRewireAPI } from './installer';

const helpData = {
  seedersFolder: 'seeders-folder',
  customSeederTemplate: 'some-template.js',
};

const defaultConfig = {
  projectRoot: '/project/root',
  userConfigExists: true,
  userConfigFilename: 'config-filename.js',
  userConfigFilepath: '/project/root/config-filename.js',
  configTemplate: '/template/folder/config-template.js',
};

test.beforeEach('mock imports', t => {
  const mocks = {
    Subject,
    fs,
    memFs,
    editor: memFsEditor,
    config: { ...defaultConfig },
    getObjectWithSelectedKeys: sinon.stub(),
    normalizeSeederName: sinon.stub().returnsArg(0),
  };

  t.context = { mocks };

  mockImports({ moduleRewireAPI, mocks });
});

test.afterEach.always('unmock imports', t => {
  const imports = Object.keys(t.context.mocks);

  resetImports({ moduleRewireAPI, imports });
});

test('Should create a installer instance', t => {
  sinon.stub(Installer.prototype, '_initConfig');
  sinon.stub(Installer.prototype, '_initMemFs');

  const installer = new Installer();

  t.truthy(installer._subject);
  t.is(typeof installer.install, 'function');
  t.true(
    installer._initConfig.calledWith({
      ...defaultUserGeneratorConfig,
      customSeederTemplate: undefined,
    })
  );
  t.true(installer._initMemFs.called);

  Installer.prototype._initConfig.restore();
  Installer.prototype._initMemFs.restore();
});

test('Should create a installer instance with args', t => {
  sinon.stub(Installer.prototype, '_initConfig');
  sinon.stub(Installer.prototype, '_initMemFs');

  const installer = new Installer({ ...helpData });

  t.truthy(installer._subject);
  t.is(typeof installer.install, 'function');
  t.true(installer._initConfig.calledWith({ ...helpData }));
  t.true(installer._initMemFs.called);

  Installer.prototype._initConfig.restore();
  Installer.prototype._initMemFs.restore();
});

test('Should _initConfig', t => {
  const context = {};
  const _initConfig = Installer.prototype._initConfig.bind(context);

  _initConfig({ ...helpData });

  t.snapshot(context);
});

test('Should _initConfig without customSeederTemplate', t => {
  const context = {};
  const _initConfig = Installer.prototype._initConfig.bind(context);

  const config = { ...helpData };
  delete config.customSeederTemplate;

  _initConfig(config);

  t.snapshot(context);
});

test('Should _initMemFs', t => {
  const { mocks } = t.context;

  const context = {};
  const _initMemFs = Installer.prototype._initMemFs.bind(context);

  _initMemFs();

  t.true(mocks.memFs.create.called);
  t.true(mocks.editor.create.calledWith(store));
  t.is(context._memFsEditor, memFsEditorFs);
});

test('Should install', t => {
  const context = {
    _install: sinon.stub().resolves(),
    _subject: { asObservable: () => 'observable' },
  };
  const install = Installer.prototype.install.bind(context);

  const results = install();

  t.is(results, 'observable');
  t.true(context._install.called);
});

test('Should getGeneratorConfig', t => {
  const context = {
    config: {
      userSeedersFolderName: 'foldername',
    },
  };
  const getGeneratorConfig = Installer.prototype.getGeneratorConfig.bind(
    context
  );

  const expectedResults = {
    seedersFolder: context.config.userSeedersFolderName,
  };
  const results = getGeneratorConfig();

  t.deepEqual(results, expectedResults);
});

test('Should getGeneratorConfig with customSeederTemplate', t => {
  const context = {
    config: {
      userSeedersFolderName: 'foldername',
      customSeederTemplateFilename: 'template-filename.js',
    },
  };
  const getGeneratorConfig = Installer.prototype.getGeneratorConfig.bind(
    context
  );

  const expectedResults = {
    seedersFolder: context.config.userSeedersFolderName,
    customSeederTemplate: context.config.customSeederTemplateFilename,
  };
  const results = getGeneratorConfig();

  t.deepEqual(results, expectedResults);
});

test('Should _install and success', async t => {
  const context = {
    _createCustomSeederTemplate: sinon.stub().resolves(),
    _writeUserGeneratorConfigToPackageJson: sinon.stub().resolves(),
    _createSeedersFolder: sinon.stub().resolves(),
    _writeUserConfig: sinon.stub().resolves(),
    _subject: {
      next: sinon.stub(),
      complete: sinon.stub(),
      error: sinon.stub(),
    },
  };
  const _install = Installer.prototype._install.bind(context);

  await _install();

  t.true(context._createCustomSeederTemplate.called);
  t.true(context._writeUserGeneratorConfigToPackageJson.called);
  t.true(context._createSeedersFolder.called);
  t.true(context._writeUserConfig.called);
  t.true(context._subject.next.calledWith({ type: 'START' }));
  t.true(context._subject.next.calledWith({ type: 'SUCCESS' }));
  t.true(context._subject.complete.called);
  t.false(context._subject.error.called);
});

test('Should _install and fail', async t => {
  const error = new Error('some-error');
  const context = {
    _createCustomSeederTemplate: sinon.stub().resolves(),
    _writeUserGeneratorConfigToPackageJson: sinon.stub().resolves(),
    _createSeedersFolder: sinon.stub().rejects(error),
    _writeUserConfig: sinon.stub().resolves(),
    _subject: {
      next: sinon.stub(),
      complete: sinon.stub(),
      error: sinon.stub(),
    },
  };
  const _install = Installer.prototype._install.bind(context);

  await t.notThrowsAsync(() => _install());

  t.true(context._createCustomSeederTemplate.called);
  t.true(context._writeUserGeneratorConfigToPackageJson.called);
  t.true(context._createSeedersFolder.called);
  t.false(context._writeUserConfig.called);
  t.true(context._subject.next.calledWith({ type: 'START' }));
  t.false(context._subject.next.calledWith({ type: 'SUCCESS' }));
  t.false(context._subject.complete.called);
  t.true(
    context._subject.error.calledWith({ type: 'ERROR', payload: { error } })
  );
});

test('Should _install and fail with InstallerError', async t => {
  const type = 'CREATE_SEEDERS_FOLDER_ERROR';
  const payload = { some: 'data' };
  const baseError = new Error('some-base-error');

  const error = new InstallerError({ type, payload, error: baseError });
  const context = {
    _createCustomSeederTemplate: sinon.stub().resolves(),
    _writeUserGeneratorConfigToPackageJson: sinon.stub().resolves(),
    _createSeedersFolder: sinon.stub().rejects(error),
    _writeUserConfig: sinon.stub().resolves(),
    _subject: {
      next: sinon.stub(),
      complete: sinon.stub(),
      error: sinon.stub(),
    },
  };
  const _install = Installer.prototype._install.bind(context);

  await _install();

  t.true(context._createCustomSeederTemplate.called);
  t.true(context._writeUserGeneratorConfigToPackageJson.called);
  t.true(context._createSeedersFolder.called);
  t.false(context._writeUserConfig.called);
  t.true(context._subject.next.calledWith({ type: 'START' }));
  t.false(context._subject.next.calledWith({ type: 'SUCCESS' }));
  t.false(context._subject.complete.called);
  t.true(
    context._subject.error.calledWith({
      type,
      payload: { ...payload, error: baseError },
    })
  );
});

test('Should _commitMemFsChanges', async t => {
  const context = {
    _memFsEditor: { commit: sinon.stub().callsArg(0) },
  };
  const _commitMemFsChanges = Installer.prototype._commitMemFsChanges.bind(
    context
  );

  await _commitMemFsChanges();

  t.true(context._memFsEditor.commit.called);
});

test('Should _createCustomSeederTemplate and success', async t => {
  const customSeederTemplateFilename = 'seeder-template.js';
  const customSeederTemplatePath = `/some/${customSeederTemplateFilename}`;
  const config = { customSeederTemplateFilename, customSeederTemplatePath };
  const payload = { customSeederTemplateFilename, customSeederTemplatePath };

  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _createCustomSeederTemplate = Installer.prototype._createCustomSeederTemplate.bind(
    context
  );

  await t.notThrowsAsync(() => _createCustomSeederTemplate());

  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS',
      payload,
    })
  );
  t.true(
    context._memFsEditor.copy.calledWith(
      systemSeederTemplate,
      customSeederTemplatePath
    )
  );
  t.true(context._commitMemFsChanges.called);
});

test('Should _createCustomSeederTemplate and skip because no custom seeder choosed', async t => {
  const customSeederTemplateFilename = undefined;
  const customSeederTemplatePath = undefined;
  const config = { customSeederTemplateFilename, customSeederTemplatePath };
  const payload = { customSeederTemplateFilename, customSeederTemplatePath };

  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _createCustomSeederTemplate = Installer.prototype._createCustomSeederTemplate.bind(
    context
  );

  await t.notThrows(() => _createCustomSeederTemplate());

  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS',
      payload,
    })
  );
  t.false(context._memFsEditor.copy.called);
  t.false(context._commitMemFsChanges.called);
});

test('Should _createCustomSeederTemplate and skip because no the seeder template already exists', async t => {
  const customSeederTemplateFilename = alreadyExistsFilename;
  const customSeederTemplatePath = alreadyExistsPath;
  const config = { customSeederTemplateFilename, customSeederTemplatePath };
  const payload = { customSeederTemplateFilename, customSeederTemplatePath };

  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _createCustomSeederTemplate = Installer.prototype._createCustomSeederTemplate.bind(
    context
  );

  await t.notThrows(() => _createCustomSeederTemplate());

  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS',
      payload,
    })
  );
  t.false(context._memFsEditor.copy.called);
  t.false(context._commitMemFsChanges.called);
});

test('Should _createCustomSeederTemplate and fail', async t => {
  const customSeederTemplateFilename = 'seeder-template.js';
  const customSeederTemplatePath = `/some/${customSeederTemplateFilename}`;
  const config = { customSeederTemplateFilename, customSeederTemplatePath };
  const payload = { customSeederTemplateFilename, customSeederTemplatePath };

  const subject = new Subject();
  const error = new Error('some-error');
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().rejects(error),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _createCustomSeederTemplate = Installer.prototype._createCustomSeederTemplate.bind(
    context
  );

  const rejectionError = await t.throwsAsync(() =>
    _createCustomSeederTemplate()
  );

  t.is(rejectionError.type, 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR');
  t.deepEqual(rejectionError.payload, { ...payload, error });
  t.true(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS',
      payload,
    })
  );
  t.true(context._memFsEditor.copy.called);
  t.true(context._commitMemFsChanges.called);
});

test('Should _writeUserGeneratorConfigToPackageJson and success', async t => {
  const config = {
    userPackageJsonPath: path.join(__dirname, './__mocks__/package.json'),
  };
  const generatorConfig = {
    seedersFolder: '/some/folder',
    customSeederTemplateFilename: 'some-filename.js',
  };
  const payload = {
    packageJsonPath: config.userPackageJsonPath,
  };
  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { writeJSON: sinon.stub() },
    getGeneratorConfig: () => generatorConfig,
  };
  const _writeUserGeneratorConfigToPackageJson = Installer.prototype._writeUserGeneratorConfigToPackageJson.bind(
    context
  );

  await t.notThrowsAsync(() => _writeUserGeneratorConfigToPackageJson());

  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_GENERETOR_CONFIG_START',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_GENERETOR_CONFIG_SUCCESS',
      payload,
    })
  );
  t.true(
    context._memFsEditor.writeJSON.calledWith(payload.packageJsonPath, {
      mdSeed: generatorConfig,
    })
  );
  t.true(context._commitMemFsChanges.called);
});

test('Should _writeUserGeneratorConfigToPackageJson and fail', async t => {
  const config = {
    userPackageJsonPath: path.join(__dirname, './__mocks__/package.json'),
  };
  const generatorConfig = {
    seedersFolder: '/some/folder',
    customSeederTemplateFilename: 'some-filename.js',
  };
  const payload = {
    packageJsonPath: config.userPackageJsonPath,
  };
  const subject = new Subject();
  const error = new Error('some-error');
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().rejects(error),
    _memFsEditor: { writeJSON: sinon.stub() },
    getGeneratorConfig: () => generatorConfig,
  };
  const _writeUserGeneratorConfigToPackageJson = Installer.prototype._writeUserGeneratorConfigToPackageJson.bind(
    context
  );

  const rejectionError = await t.throwsAsync(
    _writeUserGeneratorConfigToPackageJson()
  );

  t.is(rejectionError.type, 'WRITE_USER_GENERETOR_CONFIG_ERROR');
  t.deepEqual(rejectionError.payload, { ...payload, error });
  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_GENERETOR_CONFIG_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'WRITE_USER_GENERETOR_CONFIG_SUCCESS',
      payload,
    })
  );
  t.true(context._memFsEditor.writeJSON.called);
  t.true(context._commitMemFsChanges.called);
});

test('Should _createSeedersFolder and success', async t => {
  const { mocks } = t.context;
  const folderpath = '/some/folder/path/folder';
  const foldername = folderpath.split('/').pop();
  const payload = { folderpath, foldername };
  const config = {
    userSeedersFolderPath: folderpath,
    userSeedersFolderName: foldername,
  };
  const subject = new Subject();
  const context = { _subject: subject, config };
  const _createSeedersFolder = Installer.prototype._createSeedersFolder.bind(
    context
  );

  await t.notThrows(() => _createSeedersFolder());

  t.true(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SUCCESS',
      payload,
    })
  );
  t.true(mocks.fs.existsSync.calledWith(folderpath));
  t.true(mocks.fs.mkdirSync.calledWith(folderpath));
});

test('Should _createSeedersFolder and skip', async t => {
  const { mocks } = t.context;
  const folderpath = alreadyExistsPath;
  const foldername = alreadyExistsPath.split('/').pop();
  const payload = { folderpath, foldername };
  const config = {
    userSeedersFolderPath: folderpath,
    userSeedersFolderName: foldername,
  };
  const subject = new Subject();
  const context = { _subject: subject, config };
  const _createSeedersFolder = Installer.prototype._createSeedersFolder.bind(
    context
  );

  await t.notThrows(() => _createSeedersFolder());

  t.true(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_START',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SUCCESS',
      payload,
    })
  );
  t.true(mocks.fs.existsSync.calledWith(folderpath));
  t.false(mocks.fs.mkdirSync.calledWith(folderpath));
});

test('Should _createSeedersFolder and fail', async t => {
  const { mocks } = t.context;
  const folderpath = throwableMkdirPath;
  const foldername = throwableMkdirPath.split('/').pop();
  const payload = { folderpath, foldername };
  const config = {
    userSeedersFolderPath: folderpath,
    userSeedersFolderName: foldername,
  };
  const subject = new Subject();
  const context = { _subject: subject, config };
  const _createSeedersFolder = Installer.prototype._createSeedersFolder.bind(
    context
  );

  const rejectionError = await t.throwsAsync(() => _createSeedersFolder());

  t.is(rejectionError.type, 'CREATE_SEEDERS_FOLDER_ERROR');
  t.deepEqual(rejectionError.payload, {
    ...payload,
    error: new Error('some-error'),
  });
  t.true(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'CREATE_SEEDERS_FOLDER_SUCCESS',
      payload,
    })
  );
  t.true(mocks.fs.existsSync.calledWith(folderpath));
  t.true(mocks.fs.mkdirSync.calledWith(folderpath));
});

test('Should _writeUserConfig and success', async t => {
  const config = {
    userConfigExists: false,
    userConfigFilename: 'filename.js',
    userConfigFilepath: '/some/path/filename.js',
    configTemplatePath: '/some/template.js',
  };
  const payload = {
    fileExists: config.userConfigExists,
    filename: config.userConfigFilename,
    filepath: config.userConfigFilepath,
  };
  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _writeUserConfig = Installer.prototype._writeUserConfig.bind(context);

  await t.notThrowsAsync(() => _writeUserConfig());

  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SUCCESS',
      payload,
    })
  );
  t.true(
    context._memFsEditor.copy.calledWith(
      config.configTemplatePath,
      config.userConfigFilepath
    )
  );
  t.true(context._commitMemFsChanges.called);
});

test('Should _writeUserConfig and skip', async t => {
  const config = {
    userConfigExists: true,
    userConfigFilename: 'filename.js',
    userConfigFilepath: '/some/path/filename.js',
    configTemplatePath: '/some/template.js',
  };
  const payload = {
    fileExists: config.userConfigExists,
    filename: config.userConfigFilename,
    filepath: config.userConfigFilepath,
  };
  const subject = new Subject();
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().resolves(),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _writeUserConfig = Installer.prototype._writeUserConfig.bind(context);

  await t.notThrows(() => _writeUserConfig());

  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_START',
      payload,
    })
  );
  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SUCCESS',
      payload,
    })
  );
  t.false(context._memFsEditor.copy.called);
  t.false(context._commitMemFsChanges.called);
});

test('Should _writeUserConfig and fail', async t => {
  const config = {
    userConfigExists: false,
    userConfigFilename: 'filename.js',
    userConfigFilepath: '/some/path/filename.js',
    configTemplatePath: '/some/template.js',
  };
  const payload = {
    fileExists: config.userConfigExists,
    filename: config.userConfigFilename,
    filepath: config.userConfigFilepath,
  };
  const subject = new Subject();
  const error = new Error('some-error');
  const context = {
    _subject: subject,
    config,
    _commitMemFsChanges: sinon.stub().rejects(error),
    _memFsEditor: { copy: sinon.stub() },
  };
  const _writeUserConfig = Installer.prototype._writeUserConfig.bind(context);

  const rejectionError = await t.throwsAsync(() => _writeUserConfig());

  t.is(rejectionError.type, 'WRITE_USER_CONFIG_ERROR');
  t.deepEqual(rejectionError.payload, { ...payload, error });
  t.true(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_START',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SKIP_FILE_EXISTS',
      payload,
    })
  );
  t.false(
    subject.next.calledWith({
      type: 'WRITE_USER_CONFIG_SUCCESS',
      payload,
    })
  );
  t.true(
    context._memFsEditor.copy.calledWith(
      config.configTemplatePath,
      config.userConfigFilepath
    )
  );
  t.true(context._commitMemFsChanges.called);
});