@lexho111/plainblog 0.5.10 → 0.5.12

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.
package/Formatter.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * generates the header of the generated html file
3
+ * @param {*} title title of the blog
4
+ * @returns the header for the generated html file
5
+ */
1
6
  export function header(title) {
2
7
  return `<!DOCTYPE html>
3
8
  <html lang="de">
@@ -11,10 +16,12 @@ export function header(title) {
11
16
  </head>`;
12
17
  }
13
18
 
14
- /** format content to html */
19
+ /**
20
+ * renders content like articles into a browser-ready HTML string.
21
+ * @param {*} data blog data like blogtitle, articles, login information
22
+ * @returns valid html code with article data implanted
23
+ */
15
24
  export function formatHTML(data) {
16
- //console.log(`${data} ${script} ${style}`);
17
- //export function formatHTML(data) {
18
25
  //const button = `<button type="button" onClick="fillWithContent();" style="margin: 4px;">generate random text</button>`;
19
26
  const button = "";
20
27
  let form1 = "";
@@ -59,6 +66,11 @@ export function formatHTML(data) {
59
66
  </html>`;
60
67
  }
61
68
 
69
+ /**
70
+ * format content like articles to markdown
71
+ * @param {*} data blog data like blogtitle and articles
72
+ * @returns valid markdown
73
+ */
62
74
  export function formatMarkdown(data) {
63
75
  let markdown = "";
64
76
  markdown += `# ${data.title}\n`;
@@ -70,6 +82,11 @@ export function formatMarkdown(data) {
70
82
  return markdown;
71
83
  }
72
84
 
85
+ /**
86
+ * html validator
87
+ * @param {*} html
88
+ * @returns true if param html is valid html
89
+ */
73
90
  export function validate(html) {
74
91
  let test = true; // all tests passed
75
92
  if (!(html.includes("<html") && html.includes("</html"))) {
package/build-styles.js CHANGED
@@ -1,37 +1,35 @@
1
1
  import path from "path";
2
- import { pathToFileURL } from "url";
3
2
  import postcss from "postcss";
4
3
  import autoprefixer from "autoprefixer";
5
4
  import cssnano from "cssnano";
6
5
 
7
- // array of files or a single file
6
+ /**
7
+ * Compiles CSS styles from file content objects.
8
+ * @param {Array<{path: string, content: string}>} fileData - An array of objects containing file paths and content.
9
+ * @returns {Promise<string>} The compiled and minified CSS.
10
+ */
8
11
  export async function compileStyles(fileData) {
9
12
  try {
10
13
  let combinedCss = "";
11
14
 
15
+ // 1. filter out css files
12
16
  if (fileData) {
13
17
  const scssFiles = fileData.filter(
14
18
  (f) =>
15
19
  f.path.endsWith(".scss") && !path.basename(f.path).startsWith("_")
16
20
  );
21
+ if (scssFiles.length > 0) console.error("sass files are not supported.");
17
22
 
18
- for (const file of scssFiles) {
19
- console.error("sass files are not supported.");
20
- }
21
-
23
+ // make one big css file
22
24
  const cssFiles = fileData.filter((f) => f.path.endsWith(".css"));
23
25
  for (const file of cssFiles) {
24
26
  combinedCss += file.content + "\n";
25
27
  }
26
28
  }
27
29
 
28
- // 2. PostCSS (Autoprefixer + CSSNano)
30
+ // 2. minify and uglify with PostCSS
29
31
  if (combinedCss) {
30
- const plugins = [autoprefixer(), cssnano()];
31
- const result = await postcss(plugins).process(combinedCss, {
32
- from: undefined,
33
- });
34
- return result.css;
32
+ return postcss2(combinedCss);
35
33
  }
36
34
  return "";
37
35
  } catch (error) {
@@ -40,16 +38,25 @@ export async function compileStyles(fileData) {
40
38
  }
41
39
  }
42
40
 
41
+ async function postcss2(css) {
42
+ const plugins = [autoprefixer(), cssnano()];
43
+ const result = await postcss(plugins).process(css, {
44
+ from: undefined, // do not print source map warning
45
+ });
46
+ return result.css; // final result
47
+ }
48
+
49
+ /**
50
+ * Merges and minifies multiple CSS content strings.
51
+ * @param {...string} cssContents - CSS strings to merge.
52
+ * @returns {Promise<string>} The merged and minified CSS.
53
+ */
43
54
  export async function mergeStyles(...cssContents) {
44
55
  try {
45
56
  const combinedCss = cssContents.join("\n");
46
57
 
47
58
  if (combinedCss) {
48
- const plugins = [autoprefixer(), cssnano()];
49
- const result = await postcss(plugins).process(combinedCss, {
50
- from: undefined,
51
- });
52
- return result.css;
59
+ return postcss2(combinedCss);
53
60
  }
54
61
  return "";
55
62
  } catch (error) {
package/eslint.config.js CHANGED
@@ -1,45 +1,27 @@
1
- import globals from "globals";
2
- import pluginJs from "@eslint/js";
3
- import pluginJest from "eslint-plugin-jest";
4
-
5
- export default [
6
- {
7
- // Configuration for all JavaScript files
8
- files: ["**/*.js"],
9
- languageOptions: {
10
- ecmaVersion: 2022, // Supports modern JavaScript features
11
- sourceType: "module",
12
- globals: {
13
- ...globals.node, // Defines Node.js global variables (e.g., `process`, `require`)
14
- },
15
- },
16
- // ESLint's recommended rules for general JavaScript
17
- rules: {
18
- ...pluginJs.configs.recommended.rules,
19
- // Add or override general JavaScript rules here.
20
- // Example: Enforce semicolons at the end of statements
21
- semi: ["error", "always"],
22
- // Example: Prefer `const` over `let` where variables are not reassigned
23
- "prefer-const": "error",
24
- // Example: Prevent unused variables (can be configured further)
25
- "no-unused-vars": ["warn", { args: "none" }],
26
- },
27
- },
28
- {
29
- // Configuration specifically for Jest test files
30
- files: ["**/*.test.js", "**/*.spec.js"],
31
- languageOptions: {
32
- globals: {
33
- ...globals.jest, // Defines Jest global variables (e.g., `describe`, `it`, `expect`)
34
- },
35
- },
36
- plugins: {
37
- jest: pluginJest,
38
- },
39
- // Recommended Jest rules from `eslint-plugin-jest`
40
- rules: {
41
- ...pluginJest.configs.recommended.rules,
42
- // Add or override Jest-specific rules here.
43
- },
44
- },
45
- ];
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import { defineConfig } from "eslint/config";
4
+ import pluginJest from "eslint-plugin-jest";
5
+
6
+ export default defineConfig([
7
+ {
8
+ // Must be in a separate object to apply globally
9
+ ignores: ["public/scripts.min.js", "dist/**/*"],
10
+ },
11
+ {
12
+ files: ["**/*.{js,mjs,cjs}"],
13
+ plugins: { js },
14
+ extends: ["js/recommended"],
15
+ languageOptions: { globals: globals.browser },
16
+ },
17
+ {
18
+ files: ["**/*.test.js", "**/*.spec.js"],
19
+ plugins: { jest: pluginJest },
20
+ languageOptions: {
21
+ globals: pluginJest.environments.globals.globals, // Loads all Jest globals
22
+ },
23
+ rules: {
24
+ ...pluginJest.configs["flat/recommended"].rules,
25
+ },
26
+ },
27
+ ]);
package/knip.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://unpkg.com/knip@5/schema.json",
3
+ "ignoreExportsUsedInFile": {
4
+ "interface": true,
5
+ "type": true
6
+ },
7
+ "tags": [
8
+ "-lintignore"
9
+ ]
10
+ }
@@ -8,9 +8,9 @@ import {
8
8
 
9
9
  export default class FileAdapter {
10
10
  dbtype = "file";
11
- constructor(options) {
12
- this.infoFile = "bloginfo.json";
13
- this.articlesFile = "articles.txt";
11
+ constructor(options = {}) {
12
+ this.infoFile = options.infoFilename || "bloginfo.json";
13
+ this.articlesFile = options.articlesFilename || "articles.txt";
14
14
  }
15
15
 
16
16
  async initialize() {
@@ -41,8 +41,8 @@ export default class FileAdapter {
41
41
  async findAll(
42
42
  limit = 4,
43
43
  offset = 0,
44
- startId = null,
45
- endId = null,
44
+ //startId = null,
45
+ //endId = null,
46
46
  order = "DESC"
47
47
  ) {
48
48
  let dbArticles = [];
@@ -11,6 +11,7 @@ export async function loadInfo(filename) {
11
11
  const data = await fs.readFile(filename, "utf8");
12
12
  return JSON.parse(data);
13
13
  } catch (err) {
14
+ console.error(err);
14
15
  return { title: "Blog" };
15
16
  }
16
17
  }
@@ -29,6 +30,7 @@ export async function loadArticles(filename) {
29
30
  .filter((line) => line.trim() !== "")
30
31
  .map((line) => JSON.parse(line));
31
32
  } catch (err) {
33
+ console.error(err);
32
34
  return [];
33
35
  }
34
36
  }
@@ -38,12 +40,14 @@ export async function initFiles(infoFilename, articlesFilename) {
38
40
  try {
39
41
  await fs.access(infoFilename);
40
42
  } catch (err) {
43
+ console.error(err);
41
44
  await saveInfo(infoFilename, { title: "Blog" });
42
45
  }
43
46
 
44
47
  try {
45
48
  await fs.access(articlesFilename);
46
49
  } catch (err) {
50
+ console.error(err);
47
51
  await fs.writeFile(articlesFilename, "");
48
52
  }
49
53
  }
@@ -1,4 +1,3 @@
1
- import { Sequelize, DataTypes, Op } from "sequelize";
2
1
  import SequelizeAdapter from "./SequelizeAdapter.js";
3
2
 
4
3
  export default class PostgresAdapter extends SequelizeAdapter {
@@ -22,6 +21,7 @@ export default class PostgresAdapter extends SequelizeAdapter {
22
21
 
23
22
  async initialize() {
24
23
  console.log("initialize database");
24
+ await this.loadSequelize();
25
25
  const maxRetries = 10;
26
26
  const retryDelay = 3000;
27
27
 
@@ -31,7 +31,7 @@ export default class PostgresAdapter extends SequelizeAdapter {
31
31
  `postgres://${this.username}:${this.password}@${this.host}:${this.dbport}/${this.dbname}`
32
32
  );
33
33
 
34
- this.sequelize = new Sequelize(
34
+ this.sequelize = new this.Sequelize(
35
35
  `postgres://${this.username}:${this.password}@${this.host}:${this.dbport}/${this.dbname}`,
36
36
  { logging: false }
37
37
  );
@@ -1,5 +1,3 @@
1
- import { Sequelize, DataTypes, Op } from "sequelize";
2
-
3
1
  export default class SequelizeAdapter {
4
2
  username;
5
3
  password;
@@ -11,38 +9,44 @@ export default class SequelizeAdapter {
11
9
  Article;
12
10
  BlogInfo;
13
11
 
12
+ // Dynamic properties
13
+ Sequelize;
14
+ DataTypes;
15
+ Op;
16
+
14
17
  constructor(options = {}) {
15
18
  console.log(JSON.stringify(options));
19
+ }
16
20
 
17
- //let Sequelize, DataTypes, Op;
18
- /*try {
21
+ async loadSequelize() {
22
+ if (this.Sequelize) return;
23
+ try {
19
24
  const sequelizePkg = await import("sequelize");
20
- Sequelize = sequelizePkg.Sequelize;
21
- DataTypes = sequelizePkg.DataTypes;
22
- //Op = sequelizePkg.Op;
23
- //this.#Op = Op;
25
+ this.Sequelize = sequelizePkg.Sequelize;
26
+ this.DataTypes = sequelizePkg.DataTypes;
27
+ this.Op = sequelizePkg.Op;
24
28
  } catch (err) {
29
+ console.error(err);
25
30
  throw new Error(
26
31
  "Sequelize is not installed. Please install it to use PostgresAdapter: npm install sequelize"
27
32
  );
28
- }*/
29
-
30
- // throw new Error(`Error! ${databasetype} is an unknown database type.`);
33
+ }
31
34
  }
32
35
 
33
36
  async initializeModels() {
37
+ await this.loadSequelize();
34
38
  this.Article = this.sequelize.define(
35
39
  "Article",
36
40
  {
37
- title: DataTypes.STRING,
38
- content: DataTypes.TEXT,
41
+ title: this.DataTypes.STRING,
42
+ content: this.DataTypes.TEXT,
39
43
  createdAt: {
40
- type: DataTypes.DATE,
41
- defaultValue: DataTypes.NOW,
44
+ type: this.DataTypes.DATE,
45
+ defaultValue: this.DataTypes.NOW,
42
46
  },
43
47
  updatedAt: {
44
- type: DataTypes.DATE,
45
- defaultValue: DataTypes.NOW,
48
+ type: this.DataTypes.DATE,
49
+ defaultValue: this.DataTypes.NOW,
46
50
  },
47
51
  },
48
52
  {
@@ -53,7 +57,7 @@ export default class SequelizeAdapter {
53
57
  this.BlogInfo = this.sequelize.define(
54
58
  "BlogInfo",
55
59
  {
56
- title: DataTypes.STRING,
60
+ title: this.DataTypes.STRING,
57
61
  },
58
62
  {
59
63
  timestamps: false,
@@ -80,13 +84,14 @@ export default class SequelizeAdapter {
80
84
  endId = null,
81
85
  order = "DESC"
82
86
  ) {
87
+ await this.loadSequelize();
83
88
  const where = {};
84
89
  if (startId !== null && endId !== null) {
85
90
  where.id = {
86
- [Op.between]: [Math.min(startId, endId), Math.max(startId, endId)],
91
+ [this.Op.between]: [Math.min(startId, endId), Math.max(startId, endId)],
87
92
  };
88
93
  } else if (startId !== null) {
89
- where.id = { [order === "DESC" ? Op.lte : Op.gte]: startId };
94
+ where.id = { [order === "DESC" ? this.Op.lte : this.Op.gte]: startId };
90
95
  }
91
96
  const options = {
92
97
  where,
@@ -1,4 +1,3 @@
1
- import { Sequelize } from "sequelize";
2
1
  import SequelizeAdapter from "./SequelizeAdapter.js";
3
2
 
4
3
  export default class SqliteAdapter extends SequelizeAdapter {
@@ -11,8 +10,9 @@ export default class SqliteAdapter extends SequelizeAdapter {
11
10
  }
12
11
 
13
12
  async initialize() {
13
+ await this.loadSequelize();
14
14
  try {
15
- this.sequelize = new Sequelize({
15
+ this.sequelize = new this.Sequelize({
16
16
  dialect: "sqlite",
17
17
  storage: this.dbname + ".db",
18
18
  logging: false,
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "node index.js",
9
9
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
10
- "lint": "eslint ."
10
+ "lint": "eslint .",
11
+ "knip": "knip"
11
12
  },
12
13
  "keywords": [
13
14
  "blog",
@@ -18,26 +19,21 @@
18
19
  "license": "ISC",
19
20
  "dependencies": {
20
21
  "autoprefixer": "^10.4.23",
21
- "child_process": "^1.0.2",
22
22
  "cssnano": "^7.1.2",
23
- "fs": "^0.0.1-security",
24
- "http": "^0.0.1-security",
25
23
  "node-fetch": "^3.3.2",
26
- "path": "^0.12.7",
27
- "postcss": "^8.5.6",
28
- "postcss-preset-env": "^10.6.0",
29
- "sass": "^1.97.1",
30
- "url": "^0.11.4",
31
- "util": "^0.12.5"
24
+ "postcss": "^8.5.6"
32
25
  },
33
26
  "devDependencies": {
34
- "dom-parser": "^1.1.5",
35
- "eslint": "^9.8.0",
27
+ "@eslint/js": "^9.39.2",
28
+ "@types/node": "^25.0.3",
29
+ "eslint": "^9.39.2",
36
30
  "eslint-plugin-jest": "^28.6.0",
31
+ "globals": "^17.0.0",
37
32
  "jest": "^29.7.0",
38
- "sqlite3": "^5.1.7"
33
+ "typescript": "^5.9.3"
39
34
  },
40
35
  "optionalDependencies": {
36
+ "sqlite3": "^5.1.7",
41
37
  "pg": "^8.16.3",
42
38
  "pg-hstore": "^2.3.4",
43
39
  "sequelize": "^6.37.7"
@@ -1,2 +1,2 @@
1
- body{font-family:Arial;font-family:Arial,sans-serif}h1{color:#333}.grid{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}#wrapper{max-width:500px;width:100%}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
2
- /* source-hash: a07f631befba4b6bc703f8709f5ef455faafeff4e5f00b62f835576eea7fb529 */
1
+ body{background-color:#fdfdfd;font-family:Arial}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}.datetime{color:#434343;font-style:normal}h2{color:#a9a9a9;margin:0 0 5px}p{margin-top:10px}span{margin:0}
2
+ /* source-hash: fa9deb7a7f0781f463cd3e8fd3c3ceddec518535b0a6d13af7309ef9a2f76c32 */
package/test/blog.test.js CHANGED
@@ -4,8 +4,49 @@ import fs from "node:fs";
4
4
  import Blog from "../Blog.js";
5
5
  import Article from "../Article.js";
6
6
  import { jest } from "@jest/globals";
7
+ import DatabaseModel from "../model/DatabaseModel.js";
7
8
 
8
9
  describe("test blog", () => {
10
+ test("blog bootstrap stage 1", () => {
11
+ const myblog = new Blog();
12
+ const json = myblog.json();
13
+ expect(json.version).toContain(".");
14
+ const database = json.database;
15
+ expect(database.type).toBe("file");
16
+ expect(database.username).toBe("user");
17
+ expect(database.password).toBe("password");
18
+ expect(database.host).toBe("localhost");
19
+ expect(database.dbname).toBe("articles.txt"); //TODO switch blog.json to articles.txt, bloginfo.json
20
+ expect(json.password).toBe("admin");
21
+ expect(json.styles).toContain("body { font-family: Arial; }");
22
+ expect(json.reloadStylesOnGET).not.toBeTruthy();
23
+ });
24
+ test("blog bootstrap stage 2", async () => {
25
+ const myblog = new Blog();
26
+ await myblog.init();
27
+ const json = myblog.json();
28
+ console.log(json);
29
+ expect(json.title).toBe("Test Blog Title");
30
+ expect(json.articles.length).toBeGreaterThan(2);
31
+ expect(json.server).toBeNull();
32
+ });
33
+ test("blog bootstrap stage 3", async () => {
34
+ const myblog = new Blog();
35
+ await myblog.init();
36
+ try {
37
+ await myblog.startServer(8080);
38
+ const json = myblog.json();
39
+ console.log(json);
40
+ expect(json.title).toBe("Test Blog Title"); // from bloginfo.json
41
+ expect(json.articles.length).toBeGreaterThan(2);
42
+ expect(json.server.listening).toBeTruthy();
43
+ expect(json.server.address.address).toBe("127.0.0.1");
44
+ expect(json.server.address.family).toBe("IPv4");
45
+ expect(json.server.address.port).toBe(8080);
46
+ } finally {
47
+ await myblog.closeServer();
48
+ }
49
+ });
9
50
  test("is valid html", async () => {
10
51
  const myblog = new Blog();
11
52
  const html = await myblog.toHTML();
@@ -35,21 +76,27 @@ describe("test blog", () => {
35
76
  const article = new Article("", "");
36
77
  myblog.addArticle(article);
37
78
  const html = await myblog.toHTML();
79
+ const json = myblog.json();
38
80
  expect(html).toContain("<article");
39
- expect(myblog.articles.length).toBe(1);
81
+ expect(json.articles).toHaveLength(1);
40
82
  });
41
83
  test("add articles", async () => {
42
84
  const myblog = new Blog();
43
- expect(myblog.articles.length).toBe(0);
85
+ const json = myblog.json();
86
+ expect(json.articles).toHaveLength(0);
44
87
  const size = 10;
45
88
  for (let i = 1; i <= size; i++) {
46
89
  const article = new Article("", "");
47
90
  myblog.addArticle(article);
48
- expect(myblog.articles.length).toBe(i);
91
+ const json = myblog.json();
92
+ expect(json.articles).toHaveLength(i);
93
+ }
94
+ {
95
+ const html = await myblog.toHTML();
96
+ expect(html).toContain("<article");
97
+ const json = myblog.json();
98
+ expect(json.articles).toHaveLength(size);
49
99
  }
50
- const html = await myblog.toHTML();
51
- expect(html).toContain("<article");
52
- expect(myblog.articles.length).toBe(size);
53
100
  });
54
101
  const __filename = fileURLToPath(import.meta.url);
55
102
  const __dirname = path.dirname(__filename);
@@ -101,6 +148,73 @@ describe("test blog", () => {
101
148
  // Clean up the spy to restore the original console.log
102
149
  consoleSpy.mockRestore();
103
150
  });
151
+ test("json() returns a deep copy (immutability check)", () => {
152
+ const myblog = new Blog();
153
+ const article = new Article("Original Title", "Content", new Date());
154
+ myblog.addArticle(article);
155
+
156
+ const json = myblog.json();
157
+
158
+ // Attempt to modify the returned JSON
159
+ json.title = "Modified Title";
160
+ json.articles[0].title = "Modified Article";
161
+ json.database.host = "evil.com";
162
+
163
+ // Verify the internal state is unchanged
164
+ const newJson = myblog.json();
165
+ expect(newJson.title).not.toBe("Modified Title");
166
+ expect(newJson.articles[0].title).toBe("Original Title");
167
+ expect(newJson.database.host).toBe("localhost");
168
+ });
169
+
170
+ test("closeServer handles non-running server gracefully", async () => {
171
+ const myblog = new Blog();
172
+ // Should not throw even if server was never started
173
+ await expect(myblog.closeServer()).resolves.not.toThrow();
174
+ });
175
+
176
+ test("init() runs database operations concurrently", async () => {
177
+ const myblog = new Blog();
178
+ // Avoid file operations to isolate database timing
179
+ myblog.stylesheetPath = [];
180
+
181
+ const delay = 100;
182
+
183
+ // Mock DatabaseModel methods to simulate slow DB operations
184
+ const getTitleSpy = jest
185
+ .spyOn(DatabaseModel.prototype, "getBlogTitle")
186
+ .mockImplementation(async () => {
187
+ await new Promise((resolve) => setTimeout(resolve, delay));
188
+ return "Mock Title";
189
+ });
190
+
191
+ const findAllSpy = jest
192
+ .spyOn(DatabaseModel.prototype, "findAll")
193
+ .mockImplementation(async () => {
194
+ await new Promise((resolve) => setTimeout(resolve, delay));
195
+ return [];
196
+ });
197
+
198
+ // Mock initialize to avoid side effects
199
+ const initSpy = jest
200
+ .spyOn(DatabaseModel.prototype, "initialize")
201
+ .mockResolvedValue();
202
+
203
+ const start = Date.now();
204
+ await myblog.init();
205
+ const end = Date.now();
206
+ const duration = end - start;
207
+
208
+ // If sequential: delay + delay = 200ms. If concurrent: ~100ms.
209
+ expect(duration).toBeLessThan(delay * 1.8);
210
+ expect(getTitleSpy).toHaveBeenCalled();
211
+ expect(findAllSpy).toHaveBeenCalled();
212
+
213
+ // Cleanup
214
+ getTitleSpy.mockRestore();
215
+ findAllSpy.mockRestore();
216
+ initSpy.mockRestore();
217
+ });
104
218
  });
105
219
 
106
220
  /*
@@ -1,7 +1,5 @@
1
1
  import http from "http";
2
2
 
3
- const port = 8081;
4
-
5
3
  export const server = http.createServer((req, res) => {
6
4
  // Set CORS headers to allow requests from different origins if needed
7
5
  res.setHeader("Access-Control-Allow-Origin", "*");
package/.eslintignore DELETED
File without changes
package/.eslintrc.json DELETED
File without changes
package/articles.txt DELETED
@@ -1,11 +0,0 @@
1
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:19:39.939Z"}
2
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:34:38.866Z"}
3
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:43:32.343Z"}
4
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:48:23.123Z"}
5
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:50:06.993Z"}
6
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:56:28.369Z"}
7
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:57:53.780Z"}
8
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:58:54.261Z"}
9
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:01:02.613Z"}
10
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:01:30.473Z"}
11
- {"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:03:33.773Z"}
package/blog.db DELETED
Binary file