@jam-comments/server-utilities 4.1.0 → 4.3.0

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.
@@ -0,0 +1,61 @@
1
+ import { expect, it } from "vitest";
2
+ import { injectSchema } from "./injectSchema";
3
+ it("injects schema into markup", () => {
4
+ const markup = `
5
+ <div id="jcComments">
6
+ <div jc-data="jcSchema" data-schema="[{&quot;@context&quot;:&quot;https:\/\/schema.org&quot;,&quot;@type&quot;:&quot;Comment&quot;,&quot;name&quot;:&quot;Alexis&quot;,&quot;dateCreated&quot;:&quot;2022-10-28T10:41:49+00:00&quot;,&quot;url&quot;:&quot;https:\/\/macarthur.me\/posts\/dynamic-routing#comment-133&quot;}]"></div>
7
+
8
+ <!-- JC:SCHEMA -->
9
+ </div>`;
10
+ const blogPostSchema = {
11
+ "@context": "http://schema.org",
12
+ "@type": "BlogPosting",
13
+ };
14
+ const result = injectSchema(markup, blogPostSchema);
15
+ expect(result).toEqual(`
16
+ <div id="jcComments">
17
+ <script type="application/ld+json">{"@context":"http://schema.org","@type":"BlogPosting","comment":[{"@context":"https://schema.org","@type":"Comment","name":"Alexis","dateCreated":"2022-10-28T10:41:49+00:00","url":"https://macarthur.me/posts/dynamic-routing#comment-133"}]}</script>
18
+ </div>`);
19
+ });
20
+ it("returns markup if schema is not found", () => {
21
+ const markup = `
22
+ <div id="jcComments">
23
+ <!-- JC:SCHEMA -->
24
+ </div>`;
25
+ const blogPostSchema = {
26
+ "@context": "http://schema.org",
27
+ "@type": "BlogPosting",
28
+ };
29
+ const result = injectSchema(markup, blogPostSchema);
30
+ expect(result).toEqual(markup);
31
+ });
32
+ it("returns markup if schema is not valid JSON", () => {
33
+ const markup = `
34
+ <div id="jcComments">
35
+ <div jc-data="jcSchema" data-schema="not-valid-json"></div>
36
+ <!-- JC:SCHEMA -->
37
+ </div>`;
38
+ const blogPostSchema = {
39
+ "@context": "http://schema.org",
40
+ "@type": "BlogPosting",
41
+ };
42
+ const result = injectSchema(markup, blogPostSchema);
43
+ expect(result).toEqual(markup);
44
+ });
45
+ it("injects even when JSON is not HTML-encoded", () => {
46
+ const markup = `
47
+ <div id="jcComments">
48
+ <div jc-data="jcSchema" data-schema="[{"@context":"https://schema.org","@type":"Comment","name":"Alexis","dateCreated":"2022-10-28T10:41:49+00:00","url":"https://macarthur.me/posts/dynamic-routing#comment-133"}]"></div>
49
+
50
+ <!-- JC:SCHEMA -->
51
+ </div>`;
52
+ const blogPostSchema = {
53
+ "@context": "http://schema.org",
54
+ "@type": "BlogPosting",
55
+ };
56
+ const result = injectSchema(markup, blogPostSchema);
57
+ expect(result).toEqual(`
58
+ <div id="jcComments">
59
+ <script type="application/ld+json">{"@context":"http://schema.org","@type":"BlogPosting","comment":[{"@context":"https://schema.org","@type":"Comment","name":"Alexis","dateCreated":"2022-10-28T10:41:49+00:00","url":"https://macarthur.me/posts/dynamic-routing#comment-133"}]}</script>
60
+ </div>`);
61
+ });
@@ -1,39 +1,101 @@
1
- import { getEnvironment, isValidTimezone } from "./utils";
2
- export const markupFetcher = (platform, fetchImplementation = fetch) => {
3
- return async ({ tz = undefined, path, domain, apiKey, schema, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), }) => {
4
- const trimmedTimezone = tz?.trim();
5
- if (trimmedTimezone && !isValidTimezone(trimmedTimezone)) {
6
- throw new Error(`The timezone passed to JamComments is invalid: ${trimmedTimezone}`);
1
+ import { injectSchema } from "./injectSchema";
2
+ import { createTempDirectory, deleteTempDirectory, getEnvironment, isValidTimezone, parseJson, readFile, saveFile, } from "./utils";
3
+ export async function fetchAll({ tz = undefined, domain, apiKey, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), }, platform, fetchImplementation, batchMarkupFetcherImpl = batchMarkupFetcher) {
4
+ const fetchBatchMarkup = batchMarkupFetcherImpl(platform, fetchImplementation);
5
+ createTempDirectory();
6
+ let shouldKeepFetching = true;
7
+ let page = 1;
8
+ console.log("Fetching comments from JamComments! This might take a sec.");
9
+ try {
10
+ while (shouldKeepFetching) {
11
+ const { data: items, meta: { current_page, last_page }, } = await fetchBatchMarkup({
12
+ domain,
13
+ apiKey,
14
+ baseUrl,
15
+ environment,
16
+ page,
17
+ tz,
18
+ });
19
+ console.log(`Checking for comment data. Batch: ${current_page}/${last_page}`);
20
+ const saveMarkupPromises = items.map((item) => {
21
+ return saveFile(item.path, item.markup);
22
+ });
23
+ await Promise.all(saveMarkupPromises);
24
+ if (current_page === last_page) {
25
+ shouldKeepFetching = false;
26
+ }
27
+ else {
28
+ page++;
29
+ }
7
30
  }
8
- const params = new URLSearchParams({
9
- path: path || "/",
10
- domain,
11
- });
31
+ }
32
+ catch (error) {
33
+ deleteTempDirectory();
34
+ throw error;
35
+ }
36
+ }
37
+ export function batchMarkupFetcher(platform, fetchImplementation = fetch) {
38
+ return async ({ tz = undefined, domain, apiKey, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), page = 1, }) => {
39
+ const response = await makeMarkupRequest({ tz, domain, apiKey, baseUrl, environment, page }, "/api/v3/markup/all", fetchImplementation, platform);
40
+ return response.json();
41
+ };
42
+ }
43
+ export async function fetchFreshMarkup({ tz = undefined, path, domain, apiKey, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), }, fetchImplementation = fetch, platform) {
44
+ const response = await makeMarkupRequest({ tz, path, domain, apiKey, baseUrl, environment }, "/api/v3/markup", fetchImplementation, platform);
45
+ return response.text();
46
+ }
47
+ export async function makeMarkupRequest({ tz = undefined, path = undefined, domain, apiKey, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), page = undefined, }, baseServicePath, fetchImplementation = fetch, platform) {
48
+ const trimmedTimezone = tz?.trim();
49
+ if (trimmedTimezone && !isValidTimezone(trimmedTimezone)) {
50
+ throw new Error(`The timezone passed to JamComments is invalid: ${trimmedTimezone}`);
51
+ }
52
+ const params = new URLSearchParams({
53
+ domain,
54
+ });
55
+ if (path) {
56
+ params.set("path", path);
57
+ }
58
+ if (page) {
59
+ params.set("page", page.toString());
60
+ }
61
+ if (trimmedTimezone) {
62
+ params.set("tz", trimmedTimezone);
63
+ }
64
+ if (environment !== "production") {
65
+ params.set("stub", "true");
66
+ }
67
+ const requestUrl = `${baseUrl}${baseServicePath}?${params}`;
68
+ const response = await fetchImplementation(requestUrl, {
69
+ method: "GET",
70
+ headers: {
71
+ Authorization: `Bearer ${apiKey}`,
72
+ Accept: "application/json",
73
+ "X-Platform": platform,
74
+ },
75
+ });
76
+ if (response.status === 401) {
77
+ throw new Error(`Unauthorized! Are your domain and JamComments API key set correctly?`);
78
+ }
79
+ if (!response.ok) {
80
+ throw new Error(`JamComments request failed! Status code: ${response.status}, message: ${response.statusText}, request URL: ${requestUrl}`);
81
+ }
82
+ return response;
83
+ }
84
+ export function markupFetcher(platform, fetchImplementation = fetch) {
85
+ return async ({ tz = undefined, path, domain, apiKey, schema, baseUrl = "https://go.jamcomments.com", environment = getEnvironment(), }) => {
86
+ path = path || "/";
87
+ const savedFile = readFile(path);
88
+ const markup = savedFile
89
+ ? savedFile
90
+ : await fetchFreshMarkup({ tz, path, domain, apiKey, baseUrl, environment }, fetchImplementation, platform);
12
91
  if (schema) {
13
92
  const preparedSchema = typeof schema !== "string" ? JSON.stringify(schema) : schema;
14
- params.set("schema", preparedSchema);
15
- }
16
- if (trimmedTimezone) {
17
- params.set("tz", trimmedTimezone);
18
- }
19
- if (environment !== "production") {
20
- params.set("stub", "true");
21
- }
22
- const requestUrl = `${baseUrl}/api/v3/markup?${params}`;
23
- const response = await fetchImplementation(requestUrl, {
24
- method: "GET",
25
- headers: {
26
- Authorization: `Bearer ${apiKey}`,
27
- Accept: "application/json",
28
- "X-Platform": platform,
29
- },
30
- });
31
- if (response.status === 401) {
32
- throw new Error(`Unauthorized! Are your domain and JamComments API key set correctly?`);
33
- }
34
- if (!response.ok) {
35
- throw new Error(`JamComments request failed! Status code: ${response.status}, message: ${response.statusText}, request URL: ${requestUrl}`);
93
+ const parsedSchema = parseJson(preparedSchema);
94
+ if (!parsedSchema) {
95
+ return markup;
96
+ }
97
+ return injectSchema(markup, parsedSchema);
36
98
  }
37
- return await response.text();
99
+ return markup;
38
100
  };
39
- };
101
+ }
@@ -1,7 +1,204 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { markupFetcher } from "./markupFetcher";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as injectSchema from "./injectSchema";
3
+ import * as fetcherExports from "./markupFetcher";
4
+ import { afterEach } from "node:test";
5
+ import * as utilsExports from "./utils";
6
+ const { deleteTempDirectory } = utilsExports;
7
+ const { markupFetcher, batchMarkupFetcher } = fetcherExports;
8
+ beforeEach(() => {
9
+ deleteTempDirectory();
10
+ });
11
+ afterEach(() => {
12
+ vi.resetAllMocks();
13
+ });
14
+ describe("fetchAll()", () => {
15
+ it("fetches all comments in a single request", async () => {
16
+ const saveFileSpy = vi.spyOn(utilsExports, "saveFile");
17
+ const mockBatchFetcher = vi.fn().mockReturnValue({
18
+ data: [
19
+ { path: "/test", markup: "markup1" },
20
+ { path: "/test2", markup: "markup2" },
21
+ ],
22
+ meta: {
23
+ current_page: 1,
24
+ from: 1,
25
+ last_page: 1,
26
+ path: "/test",
27
+ per_page: 10,
28
+ to: 2,
29
+ total: 2,
30
+ },
31
+ });
32
+ const batchMarkupFetcherMock = vi.fn().mockImplementation((a, b) => {
33
+ return mockBatchFetcher;
34
+ });
35
+ await fetcherExports.fetchAll({
36
+ domain: "test.com",
37
+ apiKey: "123abc",
38
+ environment: "production",
39
+ }, "test_platform", vi.fn(), batchMarkupFetcherMock);
40
+ expect(batchMarkupFetcherMock).toHaveBeenCalledWith("test_platform", expect.anything());
41
+ expect(mockBatchFetcher).toHaveBeenCalledWith({
42
+ domain: "test.com",
43
+ apiKey: "123abc",
44
+ baseUrl: "https://go.jamcomments.com",
45
+ environment: "production",
46
+ page: 1,
47
+ tz: undefined,
48
+ });
49
+ expect(saveFileSpy).toHaveBeenCalledTimes(2);
50
+ expect(saveFileSpy).toHaveBeenCalledWith("/test", "markup1");
51
+ expect(saveFileSpy).toHaveBeenCalledWith("/test2", "markup2");
52
+ });
53
+ it("fetches all comments in multiple requests", async () => {
54
+ const saveFileSpy = vi.spyOn(utilsExports, "saveFile");
55
+ const mockBatchFetcher = vi
56
+ .fn()
57
+ .mockReturnValueOnce({
58
+ data: [
59
+ { path: "/test", markup: "markup1" },
60
+ { path: "/test2", markup: "markup2" },
61
+ ],
62
+ meta: {
63
+ current_page: 1,
64
+ from: 1,
65
+ last_page: 2,
66
+ path: "/test",
67
+ per_page: 2,
68
+ to: 2,
69
+ },
70
+ })
71
+ .mockReturnValueOnce({
72
+ data: [
73
+ { path: "/test3", markup: "markup3" },
74
+ { path: "/test4", markup: "markup4" },
75
+ ],
76
+ meta: {
77
+ current_page: 2,
78
+ from: 3,
79
+ last_page: 2,
80
+ path: "/test",
81
+ per_page: 2,
82
+ to: 4,
83
+ },
84
+ });
85
+ const batchMarkupFetcherMock = vi.fn().mockImplementation((a, b) => {
86
+ return mockBatchFetcher;
87
+ });
88
+ await fetcherExports.fetchAll({
89
+ domain: "test.com",
90
+ apiKey: "123abc",
91
+ environment: "production",
92
+ }, "test_platform", vi.fn(), batchMarkupFetcherMock);
93
+ expect(batchMarkupFetcherMock).toHaveBeenCalledWith("test_platform", expect.anything());
94
+ expect(mockBatchFetcher).toHaveBeenCalledWith({
95
+ domain: "test.com",
96
+ apiKey: "123abc",
97
+ baseUrl: "https://go.jamcomments.com",
98
+ environment: "production",
99
+ page: 1,
100
+ tz: undefined,
101
+ });
102
+ expect(mockBatchFetcher).toHaveBeenCalledTimes(2);
103
+ expect(mockBatchFetcher).toHaveBeenCalledWith({
104
+ domain: "test.com",
105
+ apiKey: "123abc",
106
+ baseUrl: "https://go.jamcomments.com",
107
+ environment: "production",
108
+ page: 2,
109
+ tz: undefined,
110
+ });
111
+ expect(saveFileSpy).toHaveBeenCalledTimes(4);
112
+ expect(saveFileSpy).toHaveBeenCalledWith("/test", "markup1");
113
+ expect(saveFileSpy).toHaveBeenCalledWith("/test2", "markup2");
114
+ });
115
+ it("deletes the temp directory of anything fails", async () => {
116
+ const deleteTempDirectorySpy = vi.spyOn(utilsExports, "deleteTempDirectory");
117
+ const mockBatchFetcher = vi.fn().mockImplementation(() => {
118
+ throw new Error("test error");
119
+ });
120
+ const batchMarkupFetcherMock = vi.fn().mockImplementation((a, b) => {
121
+ return mockBatchFetcher;
122
+ });
123
+ try {
124
+ await fetcherExports.fetchAll({
125
+ domain: "test.com",
126
+ apiKey: "123abc",
127
+ environment: "production",
128
+ }, "test_platform", vi.fn(), batchMarkupFetcherMock);
129
+ }
130
+ catch (e) {
131
+ expect(e.message).toEqual("test error");
132
+ }
133
+ expect(deleteTempDirectorySpy).toHaveBeenCalledOnce();
134
+ });
135
+ });
136
+ describe("batchMarkupFetcher", () => {
137
+ it("constructs fetch request correctly", async () => {
138
+ const injectSchemaSpy = vi.spyOn(injectSchema, "injectSchema");
139
+ const fetchMock = vi.fn().mockImplementation(() => {
140
+ return {
141
+ status: 200,
142
+ ok: true,
143
+ json: () => "results!",
144
+ };
145
+ });
146
+ const fetcher = batchMarkupFetcher("test", fetchMock);
147
+ const result = await fetcher({
148
+ domain: "test.com",
149
+ apiKey: "123abc",
150
+ environment: "production",
151
+ });
152
+ expect(injectSchemaSpy).not.toHaveBeenCalled();
153
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup/all?domain=test.com&page=1", expect.objectContaining({
154
+ headers: expect.objectContaining({
155
+ Accept: "application/json",
156
+ Authorization: "Bearer 123abc",
157
+ "X-Platform": "test",
158
+ }),
159
+ method: "GET",
160
+ }));
161
+ expect(result).toEqual("results!");
162
+ });
163
+ it("stubs comments", async () => {
164
+ const fetchMock = vi.fn().mockImplementation(() => {
165
+ return {
166
+ status: 200,
167
+ ok: true,
168
+ json: () => "results!",
169
+ };
170
+ });
171
+ const fetcher = batchMarkupFetcher("test", fetchMock);
172
+ const result = await fetcher({
173
+ domain: "test.com",
174
+ apiKey: "123abc",
175
+ environment: "development",
176
+ });
177
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup/all?domain=test.com&page=1&stub=true", expect.anything());
178
+ expect(result).toEqual("results!");
179
+ });
180
+ it("uses different base URL", async () => {
181
+ const fetchMock = vi.fn().mockImplementation(() => {
182
+ return {
183
+ status: 200,
184
+ ok: true,
185
+ json: () => "results!",
186
+ };
187
+ });
188
+ const fetcher = batchMarkupFetcher("test", fetchMock);
189
+ const result = await fetcher({
190
+ domain: "test.com",
191
+ apiKey: "123abc",
192
+ baseUrl: "http://ur-mom.com",
193
+ environment: "production",
194
+ });
195
+ expect(fetchMock).toHaveBeenCalledWith("http://ur-mom.com/api/v3/markup/all?domain=test.com&page=1", expect.anything());
196
+ expect(result).toEqual("results!");
197
+ });
198
+ });
3
199
  describe("markupFetcher", () => {
4
200
  it("constructs fetch request correctly", async () => {
201
+ const injectSchemaSpy = vi.spyOn(injectSchema, "injectSchema");
5
202
  const fetchMock = vi.fn().mockImplementation(() => {
6
203
  return {
7
204
  status: 200,
@@ -16,7 +213,8 @@ describe("markupFetcher", () => {
16
213
  apiKey: "123abc",
17
214
  environment: "production",
18
215
  });
19
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2Ftest&domain=test.com", expect.objectContaining({
216
+ expect(injectSchemaSpy).not.toHaveBeenCalled();
217
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.objectContaining({
20
218
  headers: expect.objectContaining({
21
219
  Accept: "application/json",
22
220
  Authorization: "Bearer 123abc",
@@ -41,7 +239,7 @@ describe("markupFetcher", () => {
41
239
  apiKey: "123abc",
42
240
  environment: "development",
43
241
  });
44
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2Ftest&domain=test.com&stub=true", expect.anything());
242
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Ftest&stub=true", expect.anything());
45
243
  expect(result).toEqual("results!");
46
244
  });
47
245
  it("uses different base URL", async () => {
@@ -60,7 +258,7 @@ describe("markupFetcher", () => {
60
258
  baseUrl: "http://ur-mom.com",
61
259
  environment: "production",
62
260
  });
63
- expect(fetchMock).toHaveBeenCalledWith("http://ur-mom.com/api/v3/markup?path=%2Ftest&domain=test.com", expect.anything());
261
+ expect(fetchMock).toHaveBeenCalledWith("http://ur-mom.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.anything());
64
262
  expect(result).toEqual("results!");
65
263
  });
66
264
  it("respects production!!!", async () => {
@@ -79,7 +277,7 @@ describe("markupFetcher", () => {
79
277
  baseUrl: "http://ur-mom.com",
80
278
  environment: "production",
81
279
  });
82
- expect(fetchMock).toHaveBeenCalledWith("http://ur-mom.com/api/v3/markup?path=%2Ftest&domain=test.com", expect.anything());
280
+ expect(fetchMock).toHaveBeenCalledWith("http://ur-mom.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.anything());
83
281
  expect(result).toEqual("results!");
84
282
  });
85
283
  it("falls back to root path", async () => {
@@ -97,7 +295,7 @@ describe("markupFetcher", () => {
97
295
  apiKey: "123abc",
98
296
  environment: "production",
99
297
  });
100
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2F&domain=test.com", expect.anything());
298
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2F", expect.anything());
101
299
  expect(result).toEqual("results!");
102
300
  });
103
301
  it("credentials are invalid", async () => {
@@ -132,9 +330,9 @@ describe("markupFetcher", () => {
132
330
  });
133
331
  describe("timezone validation", () => {
134
332
  it("throws error when invalid timezone is provided", async () => {
135
- const fetchMock = vi.fn();
136
- const fetcher = markupFetcher("test", fetchMock);
137
- expect(fetchMock).not.toHaveBeenCalled();
333
+ const fetchImplMock = vi.fn();
334
+ const fetcher = markupFetcher("test", fetchImplMock);
335
+ expect(fetchImplMock).not.toHaveBeenCalled();
138
336
  expect(fetcher({
139
337
  path: "/test",
140
338
  domain: "test.com",
@@ -153,13 +351,13 @@ describe("markupFetcher", () => {
153
351
  });
154
352
  const fetcher = markupFetcher("test", fetchMock);
155
353
  const result = await fetcher({
156
- path: null,
354
+ path: "/some/path",
157
355
  domain: "test.com",
158
356
  apiKey: "123abc",
159
357
  tz: "America/New_York",
160
358
  environment: "production",
161
359
  });
162
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2F&domain=test.com&tz=America%2FNew_York", expect.anything());
360
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Fsome%2Fpath&tz=America%2FNew_York", expect.anything());
163
361
  expect(result).toEqual("results!");
164
362
  });
165
363
  it("trims a valid timezone", async () => {
@@ -172,19 +370,20 @@ describe("markupFetcher", () => {
172
370
  });
173
371
  const fetcher = markupFetcher("test", fetchMock);
174
372
  const result = await fetcher({
175
- path: null,
373
+ path: "/some/path",
176
374
  domain: "test.com",
177
375
  apiKey: "123abc",
178
376
  tz: " America/Chicago ",
179
377
  environment: "production",
180
378
  });
181
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2F&domain=test.com&tz=America%2FChicago", expect.anything());
379
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Fsome%2Fpath&tz=America%2FChicago", expect.anything());
182
380
  expect(result).toEqual("results!");
183
381
  });
184
382
  });
185
383
  });
186
384
  describe("passing schema", function () {
187
385
  it("first stringifies schema if given an object", async () => {
386
+ const injectSchemaSpy = vi.spyOn(injectSchema, "injectSchema");
188
387
  const fetchMock = vi.fn().mockImplementation(() => {
189
388
  return {
190
389
  status: 200,
@@ -200,10 +399,12 @@ describe("passing schema", function () {
200
399
  schema: { foo: "bar" },
201
400
  environment: "production",
202
401
  });
203
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2Ftest&domain=test.com&schema=%7B%22foo%22%3A%22bar%22%7D", expect.anything());
402
+ expect(injectSchemaSpy).toHaveBeenCalledWith("results!", { foo: "bar" });
403
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.anything());
204
404
  expect(result).toEqual("results!");
205
405
  });
206
406
  it("does not stringify schema if given a string", async () => {
407
+ const injectSchemaSpy = vi.spyOn(injectSchema, "injectSchema");
207
408
  const fetchMock = vi.fn().mockImplementation(() => {
208
409
  return {
209
410
  status: 200,
@@ -219,7 +420,29 @@ describe("passing schema", function () {
219
420
  schema: '{"foo":"bar"}',
220
421
  environment: "production",
221
422
  });
222
- expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?path=%2Ftest&domain=test.com&schema=%7B%22foo%22%3A%22bar%22%7D", expect.anything());
423
+ expect(injectSchemaSpy).toHaveBeenCalledWith("results!", { foo: "bar" });
424
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.anything());
425
+ expect(result).toEqual("results!");
426
+ });
427
+ it("returns markup if invalid json is provided", async () => {
428
+ const injectSchemaSpy = vi.spyOn(injectSchema, "injectSchema");
429
+ const fetchMock = vi.fn().mockImplementation(() => {
430
+ return {
431
+ status: 200,
432
+ ok: true,
433
+ text: () => "results!",
434
+ };
435
+ });
436
+ const fetcher = markupFetcher("test", fetchMock);
437
+ const result = await fetcher({
438
+ path: "/test",
439
+ domain: "test.com",
440
+ apiKey: "123abc",
441
+ schema: "not-valid-json",
442
+ environment: "production",
443
+ });
444
+ expect(injectSchemaSpy).not.toHaveBeenCalled();
445
+ expect(fetchMock).toHaveBeenCalledWith("https://go.jamcomments.com/api/v3/markup?domain=test.com&path=%2Ftest", expect.anything());
223
446
  expect(result).toEqual("results!");
224
447
  });
225
448
  });
package/dist/esm/utils.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmdirSync, writeFile } from "fs";
2
+ import { TEMP_DIRECTORY } from ".";
1
3
  export function isValidTimezone(tz) {
2
4
  try {
3
5
  Intl.DateTimeFormat(undefined, { timeZone: tz });
@@ -22,3 +24,72 @@ export function reAppendMarkup(element, markup) {
22
24
  element.innerHTML = "";
23
25
  element.append(documentFragment);
24
26
  }
27
+ export function parseJson(json) {
28
+ try {
29
+ return JSON.parse(json);
30
+ }
31
+ catch (ex) {
32
+ return null;
33
+ }
34
+ }
35
+ export function unescapeHTML(str) {
36
+ var htmlEntities = {
37
+ nbsp: " ",
38
+ cent: "¢",
39
+ pound: "£",
40
+ yen: "¥",
41
+ euro: "€",
42
+ copy: "©",
43
+ reg: "®",
44
+ lt: "<",
45
+ gt: ">",
46
+ quot: '"',
47
+ amp: "&",
48
+ apos: "'",
49
+ };
50
+ return str.replace(/\&([^;]+);/g, function (entity, entityCode) {
51
+ var match;
52
+ if (entityCode in htmlEntities) {
53
+ return htmlEntities[entityCode];
54
+ }
55
+ else if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) {
56
+ return String.fromCharCode(parseInt(match[1], 16));
57
+ }
58
+ else if ((match = entityCode.match(/^#(\d+)$/))) {
59
+ return String.fromCharCode(~~match[1]);
60
+ }
61
+ else {
62
+ return entity;
63
+ }
64
+ });
65
+ }
66
+ export function deleteTempDirectory() {
67
+ if (existsSync(TEMP_DIRECTORY)) {
68
+ rmdirSync(TEMP_DIRECTORY, { recursive: true });
69
+ }
70
+ }
71
+ export function createTempDirectory(mkdirSyncImpl = mkdirSync, existsSyncImpl = existsSync) {
72
+ if (!existsSyncImpl(TEMP_DIRECTORY)) {
73
+ mkdirSyncImpl(TEMP_DIRECTORY);
74
+ }
75
+ }
76
+ export function toSavedFileName(name) {
77
+ return name.replace(/^\//, "").replace(/\//g, "::");
78
+ }
79
+ export function readFile(name, readFileSyncImpl = readFileSync) {
80
+ const fileName = toSavedFileName(name);
81
+ try {
82
+ return readFileSyncImpl(`${TEMP_DIRECTORY}/${fileName}`, "utf8");
83
+ }
84
+ catch (error) {
85
+ return null;
86
+ }
87
+ }
88
+ export function saveFile(name, data, writeFileImpl = writeFile) {
89
+ const fileName = toSavedFileName(name);
90
+ return new Promise((resolve) => {
91
+ writeFileImpl(`${TEMP_DIRECTORY}/${fileName}`, data, () => {
92
+ resolve();
93
+ });
94
+ });
95
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { getEnvironment } from "./utils";
2
+ import { getEnvironment, createTempDirectory, toSavedFileName, readFile, saveFile, } from "./utils";
3
+ import { TEMP_DIRECTORY } from ".";
3
4
  const env = process.env;
4
5
  beforeEach(() => {
5
6
  vi.resetModules();
@@ -42,3 +43,45 @@ describe("getEnvironment()", () => {
42
43
  });
43
44
  });
44
45
  });
46
+ describe("createTempDirectory()", () => {
47
+ it("creates temp directory fresh", () => {
48
+ const mkdirSyncMock = vi.fn();
49
+ const existsSyncMock = vi.fn().mockReturnValue(false);
50
+ expect(() => createTempDirectory(mkdirSyncMock, existsSyncMock)).not.toThrow();
51
+ expect(mkdirSyncMock).toHaveBeenCalledWith(TEMP_DIRECTORY);
52
+ });
53
+ it("does not throw error when it already exists", () => {
54
+ const mkdirSyncMock = vi.fn().mockImplementation(() => {
55
+ throw new Error("Directory already exists");
56
+ });
57
+ const existsSyncMock = vi.fn().mockReturnValue(true);
58
+ expect(() => createTempDirectory(mkdirSyncMock, existsSyncMock)).not.toThrow();
59
+ expect(mkdirSyncMock).not.toHaveBeenCalledWith();
60
+ });
61
+ });
62
+ describe("toSavedFileName()", () => {
63
+ it("replaces slashes with double colons", () => {
64
+ expect(toSavedFileName("/hey/there")).toEqual("hey::there");
65
+ });
66
+ });
67
+ describe("readFile()", () => {
68
+ it("reads the file", () => {
69
+ const readFileSyncMock = vi.fn().mockReturnValue("file contents");
70
+ expect(readFile("hey/there", readFileSyncMock)).toEqual("file contents");
71
+ expect(readFileSyncMock).toHaveBeenCalledWith(`${TEMP_DIRECTORY}/hey::there`, "utf8");
72
+ });
73
+ it("returns null when file doesn't exist", () => {
74
+ const readFileSyncMock = vi.fn().mockImplementation(() => {
75
+ throw new Error("File not found");
76
+ });
77
+ expect(readFile("hey/there", readFileSyncMock)).toEqual(null);
78
+ });
79
+ });
80
+ describe("saveFile()", () => {
81
+ it("saves the file", async () => {
82
+ const writeFileMock = vi.fn();
83
+ const result = saveFile("hey/there", "file contents", writeFileMock);
84
+ expect(writeFileMock).toHaveBeenCalledWith(`${TEMP_DIRECTORY}/hey::there`, "file contents", expect.any(Function));
85
+ expect(result).toBeInstanceOf(Promise);
86
+ });
87
+ });