@dittowords/cli 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +14 -7
  2. package/babel.config.js +2 -8
  3. package/bin/add-project.js +38 -0
  4. package/bin/add-project.js.map +1 -0
  5. package/bin/api.js +20 -0
  6. package/bin/api.js.map +1 -0
  7. package/bin/config.js +174 -0
  8. package/bin/config.js.map +1 -0
  9. package/bin/consts.js +21 -0
  10. package/bin/consts.js.map +1 -0
  11. package/bin/ditto.js +71 -74
  12. package/bin/ditto.js.map +1 -0
  13. package/bin/init/init.js +39 -0
  14. package/bin/init/init.js.map +1 -0
  15. package/bin/init/project.js +115 -0
  16. package/bin/init/project.js.map +1 -0
  17. package/bin/init/token.js +89 -0
  18. package/bin/init/token.js.map +1 -0
  19. package/bin/output.js +34 -0
  20. package/bin/output.js.map +1 -0
  21. package/bin/pull.js +288 -0
  22. package/bin/pull.js.map +1 -0
  23. package/bin/remove-project.js +40 -0
  24. package/bin/remove-project.js.map +1 -0
  25. package/bin/types.js +3 -0
  26. package/bin/types.js.map +1 -0
  27. package/bin/utils/getSelectedProjects.js +76 -0
  28. package/bin/utils/getSelectedProjects.js.map +1 -0
  29. package/bin/utils/processMetaOption.js +15 -0
  30. package/bin/utils/processMetaOption.js.map +1 -0
  31. package/bin/utils/projectsToText.js +16 -0
  32. package/bin/utils/projectsToText.js.map +1 -0
  33. package/bin/utils/promptForProject.js +44 -0
  34. package/bin/utils/promptForProject.js.map +1 -0
  35. package/bin/utils/sourcesToText.js +25 -0
  36. package/bin/utils/sourcesToText.js.map +1 -0
  37. package/jest.config.js +1 -1
  38. package/lib/add-project.ts +51 -0
  39. package/lib/api.ts +15 -0
  40. package/lib/{config.test.js → config.test.ts} +14 -25
  41. package/lib/config.ts +214 -0
  42. package/lib/consts.ts +20 -0
  43. package/lib/ditto.ts +97 -0
  44. package/lib/init/{init.js → init.ts} +11 -12
  45. package/lib/init/project.test.ts +49 -0
  46. package/lib/init/{project.js → project.ts} +58 -32
  47. package/lib/init/{token.test.js → token.test.ts} +3 -3
  48. package/lib/init/{token.js → token.ts} +23 -19
  49. package/lib/output.ts +21 -0
  50. package/lib/pull.test.ts +142 -0
  51. package/lib/{pull.js → pull.ts} +119 -92
  52. package/lib/remove-project.ts +50 -0
  53. package/lib/types.ts +23 -0
  54. package/lib/utils/getSelectedProjects.ts +55 -0
  55. package/lib/utils/processMetaOption.test.ts +18 -0
  56. package/lib/utils/processMetaOption.ts +16 -0
  57. package/lib/utils/{projectsToText.js → projectsToText.ts} +5 -4
  58. package/lib/utils/{promptForProject.js → promptForProject.ts} +18 -9
  59. package/lib/utils/{sourcesToText.js → sourcesToText.ts} +6 -5
  60. package/package.json +20 -15
  61. package/testing/fixtures/project-config-pull.yml +0 -0
  62. package/tsconfig.json +12 -0
  63. package/lib/add-project.js +0 -30
  64. package/lib/api.js +0 -18
  65. package/lib/config.js +0 -169
  66. package/lib/consts.js +0 -14
  67. package/lib/init/project.test.js +0 -99
  68. package/lib/output.js +0 -21
  69. package/lib/pull.test.js +0 -173
  70. package/lib/remove-project.js +0 -39
  71. package/lib/utils/getSelectedProjects.js +0 -35
  72. package/lib/utils/processMetaOption.js +0 -16
  73. package/lib/utils/processMetaOption.test.js +0 -16
@@ -1,17 +1,16 @@
1
1
  // Related to initializing a user/environment to ditto.
2
2
  // expected to be run once per project.
3
- const boxen = require("boxen");
4
- const chalk = require("chalk");
5
- const projectsToText = require("../utils/projectsToText");
3
+ import boxen from "boxen";
4
+ import chalk from "chalk";
5
+ import projectsToText from "../utils/projectsToText";
6
6
 
7
- const { needsSource, collectAndSaveProject } = require("./project");
8
- const { needsToken, collectAndSaveToken } = require("./token");
7
+ import { needsSource, collectAndSaveProject } from "./project";
8
+ import { needsToken, collectAndSaveToken } from "./token";
9
9
 
10
- const config = require("../config");
11
- const output = require("../output");
12
- const sourcesToText = require("../utils/sourcesToText");
10
+ import config from "../config";
11
+ import sourcesToText from "../utils/sourcesToText";
13
12
 
14
- const needsInit = () => needsToken() || needsSource();
13
+ export const needsInit = () => needsToken() || needsSource();
15
14
 
16
15
  function welcome() {
17
16
  const msg = chalk.white(`${chalk.bold(
@@ -23,7 +22,7 @@ We're glad to have you here.`);
23
22
  console.log(boxen(msg, { padding: 1 }));
24
23
  }
25
24
 
26
- async function init() {
25
+ export const init = async () => {
27
26
  welcome();
28
27
 
29
28
  if (needsToken()) {
@@ -43,6 +42,6 @@ async function init() {
43
42
  sourcesToText(validProjects, shouldFetchComponentLibrary);
44
43
 
45
44
  console.log(message);
46
- }
45
+ };
47
46
 
48
- module.exports = { needsInit, init };
47
+ export default { needsInit, init };
@@ -0,0 +1,49 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const yaml = require("js-yaml");
5
+ const { createFileIfMissing } = require("../config");
6
+
7
+ import { _testing } from "./project";
8
+
9
+ const fakeProjectDir = path.join(__dirname, "../../testing/tmp");
10
+ const badYaml = path.join(__dirname, "../../testing/fixtures/bad-yaml.yml");
11
+ const configEmptyProjects = path.join(
12
+ __dirname,
13
+ "../../testing/fixtures/bad-yaml.yml"
14
+ );
15
+ const configMissingName = path.join(
16
+ __dirname,
17
+ "../../testing/fixtures/project-config-no-name.yml"
18
+ );
19
+ const configMissingId = path.join(
20
+ __dirname,
21
+ "../../testing/fixtures/project-config-no-id.yml"
22
+ );
23
+ const configLegit = path.join(
24
+ __dirname,
25
+ "../../testing/fixtures/project-config-working.yml"
26
+ );
27
+
28
+ describe("saveProject", () => {
29
+ const configFile = path.join(fakeProjectDir, "ditto/config.yml");
30
+ const projectName = "My Amazing Project";
31
+ const projectId = "5f284259ce1d451b2eb2e23c";
32
+
33
+ beforeEach(() => {
34
+ if (!fs.existsSync(fakeProjectDir)) fs.mkdirSync(fakeProjectDir);
35
+ _testing.saveProject(configFile, projectName, projectId);
36
+ });
37
+
38
+ afterEach(() => {
39
+ fs.rmdirSync(fakeProjectDir, { recursive: true });
40
+ });
41
+
42
+ it("creates a config file with config data", () => {
43
+ const fileContents = fs.readFileSync(configFile, "utf8");
44
+ const data = yaml.load(fileContents);
45
+ expect(data.projects).toBeDefined();
46
+ expect(data.projects[0].name).toEqual(projectName);
47
+ expect(data.projects[0].id).toEqual(projectId);
48
+ });
49
+ });
@@ -1,12 +1,17 @@
1
- const ora = require("ora");
2
-
3
- const api = require("../api").default;
4
- const config = require("../config");
5
- const consts = require("../consts");
6
- const output = require("../output");
7
- const { collectAndSaveToken } = require("../init/token");
8
- const getSelectedProjects = require("../utils/getSelectedProjects");
9
- const promptForProject = require("../utils/promptForProject");
1
+ import ora from "ora";
2
+
3
+ import api from "../api";
4
+ import config from "../config";
5
+ import consts from "../consts";
6
+ import output from "../output";
7
+ import { collectAndSaveToken } from "./token";
8
+ import {
9
+ getSelectedProjects,
10
+ getIsUsingComponents,
11
+ } from "../utils/getSelectedProjects";
12
+ import promptForProject from "../utils/promptForProject";
13
+ import { AxiosResponse } from "axios";
14
+ import { Project, Token } from "../types";
10
15
 
11
16
  function quit(exitCode = 2) {
12
17
  console.log("\nExiting Ditto CLI...\n");
@@ -14,23 +19,23 @@ function quit(exitCode = 2) {
14
19
  process.exit();
15
20
  }
16
21
 
17
- function saveProject(file, name, id) {
22
+ function saveProject(file: string, name: string, id: string) {
18
23
  // old functionality included "ditto_component_library" in the `projects`
19
24
  // array, but we want to always treat the component library as a separate
20
25
  // entity and use the new notation of a top-level `components` key
21
- if (id === "ditto_component_library") {
22
- config.writeData(file, { components: true });
26
+ if (id === "components") {
27
+ config.writeProjectConfigData(file, { components: true });
23
28
  return;
24
29
  }
25
30
 
26
31
  const projects = [...getSelectedProjects(), { name, id }];
27
32
 
28
- config.writeData(file, { projects });
33
+ config.writeProjectConfigData(file, { projects });
29
34
  }
30
35
 
31
- function needsSource() {
36
+ export const needsSource = () => {
32
37
  return !config.parseSourceInformation().hasSourceData;
33
- }
38
+ };
34
39
 
35
40
  async function askForAnotherToken() {
36
41
  config.deleteToken(consts.CONFIG_FILE, consts.API_HOST);
@@ -39,14 +44,17 @@ async function askForAnotherToken() {
39
44
  await collectAndSaveToken(message);
40
45
  }
41
46
 
42
- async function listProjects(token, projectsAlreadySelected) {
47
+ async function listProjects(
48
+ token: Token,
49
+ projectsAlreadySelected: Project[],
50
+ componentsSelected: boolean
51
+ ) {
43
52
  const spinner = ora("Fetching projects in your workspace...");
44
53
  spinner.start();
45
54
 
46
- let projects = [];
47
-
55
+ let response: AxiosResponse<{ id: string; name: string }[]>;
48
56
  try {
49
- projects = await api.get("/project-names", {
57
+ response = await api.get("/project-names", {
50
58
  headers: {
51
59
  Authorization: `token ${token}`,
52
60
  },
@@ -57,13 +65,16 @@ async function listProjects(token, projectsAlreadySelected) {
57
65
  }
58
66
 
59
67
  spinner.stop();
60
-
61
- return projects.data.filter(
62
- ({ id }) => !projectsAlreadySelected.some((project) => project.id === id)
63
- );
68
+ return response.data.filter(({ id }: Project) => {
69
+ if (id === "ditto_component_library") {
70
+ return !componentsSelected;
71
+ } else {
72
+ return !projectsAlreadySelected.some((project) => project.id === id);
73
+ }
74
+ });
64
75
  }
65
76
 
66
- async function collectProject(token, initialize) {
77
+ async function collectProject(token: Token, initialize: boolean) {
67
78
  const path = process.cwd();
68
79
  if (initialize) {
69
80
  console.log(
@@ -74,23 +85,36 @@ async function collectProject(token, initialize) {
74
85
  }
75
86
 
76
87
  const projectsAlreadySelected = getSelectedProjects();
77
- const projects = await listProjects(token, projectsAlreadySelected);
88
+ const usingComponents = getIsUsingComponents();
89
+ const projects = await listProjects(
90
+ token,
91
+ projectsAlreadySelected,
92
+ usingComponents
93
+ );
78
94
 
79
95
  if (!(projects && projects.length)) {
80
96
  console.log("You're currently syncing all projects in your workspace.");
81
- console.log(output.warnText("Not seeing a project that you were expecting? Verify that developer mode is enabled on that project. More info: https://www.dittowords.com/docs/ditto-developer-mode"));
97
+ console.log(
98
+ output.warnText(
99
+ "Not seeing a project that you were expecting? Verify that developer mode is enabled on that project. More info: https://www.dittowords.com/docs/ditto-developer-mode"
100
+ )
101
+ );
82
102
  return null;
83
103
  }
84
104
 
105
+ const nonInitPrompt = usingComponents
106
+ ? "Add a project"
107
+ : "Add a project or library";
108
+
85
109
  return promptForProject({
86
110
  projects,
87
111
  message: initialize
88
- ? "Choose the project you'd like to sync text from"
89
- : "Add a project",
112
+ ? "Choose the project or library you'd like to sync text from"
113
+ : nonInitPrompt,
90
114
  });
91
115
  }
92
116
 
93
- async function collectAndSaveProject(initialize = false) {
117
+ export const collectAndSaveProject = async (initialize = false) => {
94
118
  try {
95
119
  const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
96
120
  const project = await collectProject(token, initialize);
@@ -110,7 +134,7 @@ async function collectAndSaveProject(initialize = false) {
110
134
  );
111
135
 
112
136
  saveProject(consts.PROJECT_CONFIG_FILE, project.name, project.id);
113
- } catch (e) {
137
+ } catch (e: any) {
114
138
  console.log(e);
115
139
  if (e.response && e.response.status === 404) {
116
140
  await askForAnotherToken();
@@ -119,6 +143,8 @@ async function collectAndSaveProject(initialize = false) {
119
143
  quit();
120
144
  }
121
145
  }
122
- }
146
+ };
147
+
148
+ export const _testing = { saveProject, needsSource };
123
149
 
124
- module.exports = { needsSource, collectAndSaveProject };
150
+ export default { needsSource, collectAndSaveProject };
@@ -1,7 +1,7 @@
1
- const tempy = require("tempy");
1
+ import tempy from "tempy";
2
2
 
3
- const config = require("../config");
4
- const { needsToken } = require("./token");
3
+ import config from "../config";
4
+ import { needsToken } from "./token";
5
5
 
6
6
  describe("needsToken()", () => {
7
7
  it("is true if there is no config file", () => {
@@ -1,38 +1,38 @@
1
- const fs = require("fs");
1
+ import fs from "fs";
2
2
 
3
- const chalk = require("chalk");
3
+ import chalk from "chalk";
4
4
 
5
- const { prompt } = require("enquirer");
5
+ import { prompt } from "enquirer";
6
6
 
7
- const api = require("../api");
8
- const consts = require("../consts");
9
- const output = require("../output");
10
- const config = require("../config");
7
+ import { create } from "../api";
8
+ import consts from "../consts";
9
+ import output from "../output";
10
+ import config from "../config";
11
11
 
12
- function needsToken(configFile, host = consts.API_HOST) {
12
+ export const needsToken = (configFile?: string, host = consts.API_HOST) => {
13
13
  if (config.getTokenFromEnv()) {
14
14
  return false;
15
15
  }
16
16
 
17
17
  const file = configFile || consts.CONFIG_FILE;
18
18
  if (!fs.existsSync(file)) return true;
19
- const configData = config.readData(file);
19
+ const configData = config.readGlobalConfigData(file);
20
20
  if (
21
21
  !configData[config.justTheHost(host)] ||
22
22
  configData[config.justTheHost(host)][0].token === ""
23
23
  )
24
24
  return true;
25
25
  return false;
26
- }
26
+ };
27
27
 
28
28
  // Returns true if valid, otherwise an error message.
29
- async function checkToken(token) {
30
- const axios = api.create(token);
29
+ async function checkToken(token: string): Promise<any> {
30
+ const axios = create(token);
31
31
  const endpoint = "/token-check";
32
32
 
33
33
  const resOrError = await axios
34
34
  .get(endpoint)
35
- .catch((error) => {
35
+ .catch((error: any) => {
36
36
  if (error.code === "ENOTFOUND") {
37
37
  return output.errorText(
38
38
  `Can't connect to API: ${output.url(error.hostname)}`
@@ -48,7 +48,6 @@ async function checkToken(token) {
48
48
  .catch(() =>
49
49
  output.errorText("Sorry! We're having trouble reaching the Ditto API.")
50
50
  );
51
-
52
51
  if (typeof resOrError === "string") return resOrError;
53
52
 
54
53
  if (resOrError.status === 200) return true;
@@ -56,7 +55,7 @@ async function checkToken(token) {
56
55
  return output.errorText("This API key isn't valid. Please try another.");
57
56
  }
58
57
 
59
- async function collectToken(message) {
58
+ async function collectToken(message: string | null) {
60
59
  const blue = output.info;
61
60
  const apiUrl = output.url("https://app.dittowords.com/account/user");
62
61
  const breadcrumbs = `${blue("User")}`;
@@ -67,7 +66,7 @@ async function collectToken(message) {
67
66
  )}".`;
68
67
  console.log(tokenDescription);
69
68
 
70
- const response = await prompt({
69
+ const response = await prompt<{ token: string }>({
71
70
  type: "input",
72
71
  name: "token",
73
72
  message: "What is your API key?",
@@ -82,7 +81,12 @@ function quit(exitCode = 2) {
82
81
  process.exit();
83
82
  }
84
83
 
85
- async function collectAndSaveToken(message = null) {
84
+ /**
85
+ *
86
+ * @param {string | null} message
87
+ * @returns
88
+ */
89
+ export const collectAndSaveToken = async (message: string | null = null) => {
86
90
  try {
87
91
  const token = await collectToken(message);
88
92
  console.log(
@@ -97,6 +101,6 @@ async function collectAndSaveToken(message = null) {
97
101
  } catch (error) {
98
102
  quit();
99
103
  }
100
- }
104
+ };
101
105
 
102
- module.exports = { needsToken, collectAndSaveToken };
106
+ export default { needsToken, collectAndSaveToken };
package/lib/output.ts ADDED
@@ -0,0 +1,21 @@
1
+ import chalk from "chalk";
2
+
3
+ export const errorText = (msg: string) => chalk.magenta(msg);
4
+ export const warnText = (msg: string) => chalk.yellow(msg);
5
+ export const info = (msg: string) => chalk.blueBright(msg);
6
+ export const success = (msg: string) => chalk.green(msg);
7
+ export const url = (msg: string) => chalk.blueBright.underline(msg);
8
+ export const subtle = (msg: string) => chalk.grey(msg);
9
+ export const write = (msg: string) => chalk.white(msg);
10
+ export const nl = () => console.log("\n");
11
+
12
+ export default {
13
+ errorText,
14
+ warnText,
15
+ info,
16
+ success,
17
+ url,
18
+ subtle,
19
+ write,
20
+ nl,
21
+ };
@@ -0,0 +1,142 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import api from "./api";
4
+ import config from "./config";
5
+
6
+ const testProjects = [
7
+ {
8
+ id: "1",
9
+ name: "Project 1",
10
+ fileName: "Project 1",
11
+ },
12
+ { id: "2", name: "Project 2", fileName: "Project 2" },
13
+ ];
14
+
15
+ jest.mock("./api");
16
+ api.get = jest.fn().mockResolvedValue({ data: [] });
17
+
18
+ jest.mock("./consts", () => ({
19
+ TEXT_DIR: ".testing",
20
+ API_HOST: "https://api.dittowords.com",
21
+ CONFIG_FILE: ".testing/ditto",
22
+ PROJECT_CONFIG_FILE: ".testing/config.yml",
23
+ TEXT_FILE: ".testing/text.json",
24
+ }));
25
+
26
+ import consts from "./consts";
27
+ import allPull from "./pull";
28
+
29
+ const {
30
+ _testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
31
+ } = allPull;
32
+ const variant = "english";
33
+
34
+ const cleanOutputDir = () => {
35
+ if (fs.existsSync(consts.TEXT_DIR))
36
+ fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
37
+
38
+ fs.mkdirSync(consts.TEXT_DIR);
39
+ };
40
+
41
+ afterAll(() => {
42
+ fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
43
+ });
44
+
45
+ describe("cleanOutputFiles", () => {
46
+ it("removes .js and .json files", () => {
47
+ cleanOutputDir();
48
+
49
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.json"), "test");
50
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.js"), "test");
51
+ // this file shouldn't be deleted
52
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.txt"), "test");
53
+
54
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(3);
55
+
56
+ cleanOutputFiles();
57
+
58
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
59
+ });
60
+ });
61
+
62
+ // describe("downloadAndSaveVariant", () => {
63
+ // beforeAll(() => {
64
+ // if (!fs.existsSync(consts.TEXT_DIR)) {
65
+ // fs.mkdirSync(consts.TEXT_DIR);
66
+ // }
67
+ // });
68
+
69
+ // it("writes a single file for default format", async () => {
70
+ // cleanOutputDir();
71
+
72
+ // const output = await downloadAndSaveVariant(variant, testProjects, "");
73
+ // expect(/saved to.*english\.json/.test(output)).toEqual(true);
74
+ // expect(output.match(/saved to/g)?.length).toEqual(1);
75
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
76
+ // });
77
+
78
+ // it("writes multiple files for flat format", async () => {
79
+ // cleanOutputDir();
80
+
81
+ // const output = await downloadAndSaveVariant(variant, testProjects, "flat");
82
+
83
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
84
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
85
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
86
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
87
+ // });
88
+
89
+ // it("writes multiple files for structured format", async () => {
90
+ // cleanOutputDir();
91
+
92
+ // const output = await downloadAndSaveVariant(
93
+ // variant,
94
+ // testProjects,
95
+ // "structured"
96
+ // );
97
+
98
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
99
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
100
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
101
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
102
+ // });
103
+ // });
104
+
105
+ describe("downloadAndSaveBase", () => {
106
+ beforeAll(() => {
107
+ if (!fs.existsSync(consts.TEXT_DIR)) {
108
+ fs.mkdirSync(consts.TEXT_DIR);
109
+ }
110
+ });
111
+
112
+ it("writes to text.json for default format", async () => {
113
+ cleanOutputDir();
114
+
115
+ const output = await downloadAndSaveBase(testProjects, "");
116
+
117
+ expect(/saved to.*text\.json/.test(output)).toEqual(true);
118
+ expect(output.match(/saved to/g)?.length).toEqual(1);
119
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
120
+ });
121
+
122
+ it("writes multiple files for flat format", async () => {
123
+ cleanOutputDir();
124
+
125
+ const output = await downloadAndSaveBase(testProjects, "flat");
126
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
127
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
128
+ expect(output.match(/saved to/g)?.length).toEqual(2);
129
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
130
+ });
131
+
132
+ it("writes multiple files for structured format", async () => {
133
+ cleanOutputDir();
134
+
135
+ const output = await downloadAndSaveBase(testProjects, "structured");
136
+
137
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
138
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
139
+ expect(output.match(/saved to/g)?.length).toEqual(2);
140
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
141
+ });
142
+ });