@networkpro/web 1.8.3 β†’ 1.9.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.
package/README.md CHANGED
@@ -51,34 +51,41 @@ All infrastructure and data flows are designed with **maximum transparency, self
51
51
  ## πŸ“ Repository Structure
52
52
 
53
53
  ```bash
54
- .
55
- β”œβ”€β”€ .github/workflows/ # CI workflows and automation
56
- β”œβ”€β”€ .vscode/
57
- β”‚ β”œβ”€β”€ customData.json # Custom CSS data for FontAwesome icons
58
- β”‚ β”œβ”€β”€ extensions.json # Recommended VSCodium / VS Code extensions
59
- β”‚ β”œβ”€β”€ extensions.jsonc # Commented version of extensions.json for reference
60
- β”‚ └── settings.json # User settings for VSCodium / VS Code
61
- β”œβ”€β”€ netlify/
62
- β”‚ └── edge-functions/ # Netlify Edge Functions directory
63
- β”‚ └── csp-report.js # Edge Function to receive and log CSP violation reports
64
- β”œβ”€β”€ scripts/ # General utility scripts
65
- β”œβ”€β”€ src/
66
- β”‚ β”œβ”€β”€ lib/ # Reusable components, styles, utilities
67
- β”‚ β”œβ”€β”€ routes/ # SvelteKit routes (+page.svelte, +page.server.js)
68
- β”‚ β”œβ”€β”€ hooks.client.ts # Handles PWA install prompt and logs client errors
69
- β”‚ β”œβ”€β”€ hooks.server.js # Injects CSP headers and permissions policy
70
- β”‚ β”œβ”€β”€ app.html # SvelteKit entry HTML with CSP/meta/bootentry
71
- β”‚ └── service-worker.js # Custom Service Worker
72
- β”œβ”€β”€ static/ # Static assets served at root
73
- β”‚ β”œβ”€β”€ manifest.json # Manifest file for PWA configuration
74
- β”‚ β”œβ”€β”€ robots.txt # Instructions for web robots
75
- β”‚ └── sitemap.xml # Sitemap for search engines
76
- β”œβ”€β”€ tests/
77
- β”‚ β”œβ”€β”€ e2e/ # End-to-end Playwright tests
78
- β”‚ └── unit/ # Vite unit tests
79
- β”œβ”€β”€ _redirects # Netlify redirects
80
- β”œβ”€β”€ netlify.toml # Netlify configuration
81
- └── ...
54
+ .
55
+ β”œβ”€β”€ .github/
56
+ β”‚ └── workflows/ # CI workflows (e.g. test, deploy)
57
+ β”œβ”€β”€ .vscode/
58
+ β”‚ β”œβ”€β”€ customData.json # Custom CSS IntelliSense (e.g. FontAwesome)
59
+ β”‚ β”œβ”€β”€ extensions.json # Recommended VS Code / VSCodium extensions
60
+ β”‚ β”œβ”€β”€ extensions.jsonc # Commented version of extensions.json
61
+ β”‚ └── settings.json # Workspace settings
62
+ β”œβ”€β”€ netlify/
63
+ β”‚ β”œβ”€β”€ edge-functions/
64
+ β”‚ β”‚ └── csp-report.js # Receives CSP violation reports
65
+ β”‚ └── netlify.toml # Netlify configuration
66
+ β”œβ”€β”€ scripts/ # General-purpose utility scripts
67
+ β”œβ”€β”€ src/
68
+ β”‚ β”œβ”€β”€ app.html # Entry HTML (CSP meta, bootstrapping)
69
+ β”‚ β”œβ”€β”€ hooks.client.ts # PWA install prompt & client-side logging
70
+ β”‚ β”œβ”€β”€ hooks.server.js # Injects CSP headers and permissions policy
71
+ β”‚ β”œβ”€β”€ lib/ # Components, utilities, types, styles
72
+ β”‚ β”‚ β”œβ”€β”€ components/ # Svelte components
73
+ β”‚ β”‚ β”œβ”€β”€ data/ # Custom data (e.g. JSON, metadata, constants)
74
+ β”‚ β”‚ └── utils/ # Helper utilities
75
+ β”‚ β”œβ”€β”€ routes/ # SvelteKit pages (+page.svelte, +server.js)
76
+ β”‚ └── service-worker.js # Custom PWA service worker
77
+ β”œβ”€β”€ static/ # Public assets served at site root
78
+ β”‚ β”œβ”€β”€ disableSw.js # Service worker bypass (via ?nosw param)
79
+ β”‚ β”œβ”€β”€ manifest.json # PWA metadata
80
+ β”‚ β”œβ”€β”€ robots.txt # SEO: allow/disallow crawlers
81
+ β”‚ └── sitemap.xml # SEO: full site map
82
+ β”œβ”€β”€ tests/
83
+ β”‚ β”œβ”€β”€ e2e/ # Playwright end-to-end tests
84
+ β”‚ β”œβ”€β”€ internal/ # Internal audit/test helpers
85
+ β”‚ β”‚ └── auditCoverage.test.js # Warns about untested source modules
86
+ β”‚ └── unit/ # Vitest unit tests
87
+ β”œβ”€β”€ _redirects # Netlify redirect rules
88
+ └── package.json # Project manifest (scripts, deps, etc.)
82
89
  ```
83
90
 
84
91
   
@@ -307,7 +314,7 @@ To use:
307
314
  https://netwk.pro/?nosw
308
315
  ```
309
316
 
310
- > πŸ’‘ `disableSw.js` is loaded from the static directory and runs early, ensuring the `__DISABLE_SW__` flag is available before service worker logic executes.
317
+ > πŸ’‘ `disableSw.js` is loaded via a `<script>` tag in `app.html` from the `static` directory. This ensures the `__DISABLE_SW__` flag is set before any service worker logic runs.
311
318
 
312
319
  &nbsp;
313
320
 
@@ -415,8 +422,14 @@ npm run test:coverage # Collect code coverage reports
415
422
  npm run test:e2e # Runs Playwright E2E tests (with one retry on failure)
416
423
  ```
417
424
 
425
+ <!-- markdownlint-disable MD028 -->
426
+
427
+ > The unit test suite includes a coverage audit (`auditCoverage.test.js`) that warns when source files in `src/` or `scripts/` do not have corresponding unit tests. This helps track test completeness without failing CI.
428
+
418
429
  > Playwright will retry failed tests once `(--retries=1)` to reduce false negatives from transient flakiness (network, render delay, etc.).
419
430
 
431
+ <!-- markdownlint-enable MD028 -->
432
+
420
433
  Audit your app using Lighthouse:
421
434
 
422
435
  ```bash
@@ -550,14 +563,14 @@ The following CLI commands are available via `npm run <script>` or `pnpm run <sc
550
563
 
551
564
  ### βœ… Pre-check / Sync
552
565
 
553
- | Script | Description |
554
- | ------------- | ------------------------------------------------------------ |
555
- | `prepare` | Run SvelteKit sync |
556
- | `check` | Run SvelteKit sync and type check with `svelte-check` |
557
- | `check:watch` | Watch mode for type checks |
558
- | `check:node` | Validate Node & npm versions match package.json `engines` |
559
- | `checkout` | Full local validation: check versions, test, lint, typecheck |
560
- | `verify` | Alias for `checkout` |
566
+ | Script | Description |
567
+ | ------------- | ----------------------------------------------------------------------------------- |
568
+ | `prepare` | Run SvelteKit sync |
569
+ | `check` | Run SvelteKit sync and type check with `svelte-check` |
570
+ | `check:watch` | Watch mode for type checks |
571
+ | `check:node` | Validate Node & npm versions match package.json `engines` |
572
+ | `checkout` | Full local validation: check versions, test (incl. audit coverage), lint, typecheck |
573
+ | `verify` | Alias for `checkout` |
561
574
 
562
575
  &nbsp;
563
576
 
@@ -577,15 +590,15 @@ The following CLI commands are available via `npm run <script>` or `pnpm run <sc
577
590
 
578
591
  <!-- markdownlint-enable MD024 -->
579
592
 
580
- | Script | Description |
581
- | --------------- | ------------------------------------------------------ |
582
- | `test` | Alias for `test:all` |
583
- | `test:all` | Run both client and server test suites |
584
- | `test:client` | Run client tests with Vitest |
585
- | `test:server` | Run server-side tests with Vitest |
586
- | `test:watch` | Watch mode for client tests |
587
- | `test:coverage` | Collect coverage from both client and server |
588
- | `test:e2e` | Runs E2E tests with up to 1 automatic retry on failure |
593
+ | Script | Description |
594
+ | --------------- | ------------------------------------------------------------- |
595
+ | `test` | Alias for `test:all` |
596
+ | `test:all` | Run both client and server test suites (incl. audit coverage) |
597
+ | `test:client` | Run client tests with Vitest |
598
+ | `test:server` | Run server-side tests with Vitest |
599
+ | `test:watch` | Watch mode for client tests |
600
+ | `test:coverage` | Collect coverage from both client and server |
601
+ | `test:e2e` | Runs E2E tests with up to 1 automatic retry on failure |
589
602
 
590
603
  &nbsp;
591
604
 
@@ -615,11 +628,11 @@ The following CLI commands are available via `npm run <script>` or `pnpm run <sc
615
628
 
616
629
  ### πŸ“‹ Audits / Validation
617
630
 
618
- | Script | Description |
619
- | --------------- | -------------------------------------------- |
620
- | `audit:scripts` | Check for untested utility scripts |
621
- | `head:flatten` | Flatten headers for Netlify |
622
- | `head:validate` | Validate headers file against project config |
631
+ | Script | Description |
632
+ | ---------------- | ---------------------------------------------------- |
633
+ | `audit:coverage` | Warn about untested modules in `src/` and `scripts/` |
634
+ | `head:flatten` | Flatten headers for Netlify |
635
+ | `head:validate` | Validate headers file against project config |
623
636
 
624
637
  &nbsp;
625
638
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "sideEffects": [
5
5
  "./.netlify/shims.js"
6
6
  ],
7
- "version": "1.8.3",
7
+ "version": "1.9.0",
8
8
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy β€” Network Pro Strategies",
9
9
  "keywords": [
10
10
  "advisory",
@@ -49,7 +49,7 @@
49
49
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
50
50
  "check:env": "node scripts/checkEnv.js",
51
51
  "check:node": "node scripts/checkNode.js",
52
- "checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check && npm run audit:scripts",
52
+ "checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check",
53
53
  "verify": "npm run checkout",
54
54
  "delete": "rm -rf build .svelte-kit node_modules package-lock.json",
55
55
  "clean": "npm run delete && npm cache clean --force && npm install",
@@ -71,13 +71,13 @@
71
71
  "format:fix": "prettier --write .",
72
72
  "lhci": "lhci",
73
73
  "lhci:run": "lhci autorun --config=.lighthouserc.cjs",
74
- "audit:scripts": "node scripts/auditScripts.js",
74
+ "audit:coverage": "vitest run tests/internal/auditCoverage.test.js",
75
75
  "head:flatten": "node scripts/flattenHeaders.js",
76
76
  "head:validate": "node scripts/validateHeaders.js",
77
77
  "postinstall": "npm run check:node"
78
78
  },
79
79
  "dependencies": {
80
- "nodemailer": "^7.0.3",
80
+ "dompurify": "^3.2.6",
81
81
  "posthog-js": "^1.249.0",
82
82
  "semver": "^7.7.2",
83
83
  "svelte": "5.33.11"
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* ==========================================================================
4
+ scripts/generateTest.js
5
+
6
+ Copyright Β© 2025 Network Pro Strategies (Network Proβ„’)
7
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
8
+ This file is part of Network Pro.
9
+ ========================================================================== */
10
+
11
+ /**
12
+ * @file generateTest.js
13
+ * @description Auto-generates a *.test.js scaffold for utilities and
14
+ * components.
15
+ * @module scripts
16
+ * @author SunDevil311
17
+ * @updated 2025-06-01
18
+ */
19
+
20
+ import fs from "fs";
21
+ import path from "path";
22
+
23
+ const [, , targetFile] = process.argv;
24
+
25
+ if (!targetFile) {
26
+ console.error("Usage: node generateTest.js <path/to/yourFile.js>");
27
+ process.exit(1);
28
+ }
29
+
30
+ const absolutePath = path.resolve(targetFile);
31
+ const parsed = path.parse(absolutePath);
32
+
33
+ const testFileName = `${parsed.name}.test.js`;
34
+ const testFilePath = path.join(
35
+ parsed.dir.replace("src", "tests/unit"),
36
+ testFileName,
37
+ );
38
+
39
+ // Example scaffold content
40
+ const scaffold = `/**
41
+ * Unit tests for ${parsed.base}
42
+ */
43
+
44
+ import { describe, it, expect } from "vitest";
45
+ import * as Module from "${path.relative(path.dirname(testFilePath), absolutePath).replace(/\\/g, "/")}";
46
+
47
+ describe("${parsed.name}", () => {
48
+ it("should have tests", () => {
49
+ expect(true).toBe(true);
50
+ });
51
+ });
52
+ `;
53
+
54
+ fs.mkdirSync(path.dirname(testFilePath), { recursive: true });
55
+
56
+ if (fs.existsSync(testFilePath)) {
57
+ console.warn(`Test file already exists at: ${testFilePath}`);
58
+ } else {
59
+ fs.writeFileSync(testFilePath, scaffold);
60
+ console.log(`βœ… Test scaffold created: ${testFilePath}`);
61
+ }
@@ -7,8 +7,11 @@ This file is part of Network Pro.
7
7
  ========================================================================== -->
8
8
 
9
9
  <script>
10
+ /* at-html is sanitized by DOMPurify */
10
11
  /* eslint-disable svelte/no-at-html-tags */
11
12
 
13
+ import { onMount } from "svelte";
14
+ import { sanitizeHtml } from "$lib/utils/purify.js";
12
15
  import FossFeatures from "$lib/components/foss/FossFeatures.svelte";
13
16
  // Import directly from $lib by way of image utility
14
17
  import { obtainiumPng, obtainiumWbp } from "$lib";
@@ -26,30 +29,9 @@ This file is part of Network Pro.
26
29
  /** @type {"lazy"} */
27
30
  const loading = "lazy";
28
31
 
29
- /**
30
- * @type {{
31
- * id: string,
32
- * title: string,
33
- * images: {
34
- * webp: string,
35
- * png: string
36
- * },
37
- * imgAlt: string,
38
- * headline: string,
39
- * headlineDescription: string,
40
- * detailsDescription: string,
41
- * features: any[],
42
- * notes: string[],
43
- * links: Array<{
44
- * label?: string,
45
- * href?: string,
46
- * imgAlt?: string,
47
- * downloadText?: string,
48
- * downloadHref?: string,
49
- * hideLabels?: boolean
50
- * }>
51
- * }}
52
- */
32
+ /// <reference path="$lib/types/fossTypes.js" />
33
+
34
+ /** @type {FossItem} */
53
35
  export let fossItem;
54
36
 
55
37
  /**
@@ -58,6 +40,18 @@ This file is part of Network Pro.
58
40
  * @type {boolean}
59
41
  */
60
42
  export let isFirst = false;
43
+
44
+ let safeHeadlineDescription = "";
45
+ let safeDetailsDescription = "";
46
+ /** @type {string[]} */
47
+ let safeNotes = [];
48
+
49
+ // Sanitize everything on mount
50
+ onMount(async () => {
51
+ safeHeadlineDescription = await sanitizeHtml(fossItem.headlineDescription);
52
+ safeDetailsDescription = await sanitizeHtml(fossItem.detailsDescription);
53
+ safeNotes = await Promise.all((fossItem.notes ?? []).map(sanitizeHtml));
54
+ });
61
55
  </script>
62
56
 
63
57
  <!-- BEGIN FOSS ITEMS -->
@@ -88,17 +82,21 @@ This file is part of Network Pro.
88
82
 
89
83
  <h3>{fossItem.headline}</h3>
90
84
 
91
- <!-- Trusted input, from internal CMS -->
92
- {@html fossItem.headlineDescription}
85
+ <!-- Sanitized input from DOMPurify -->
86
+ <div class="headline-description">
87
+ {@html safeHeadlineDescription}
88
+ </div>
93
89
 
94
90
  <FossFeatures features={fossItem.features} />
95
91
 
96
- <!-- Trusted input, from internal CMS -->
97
- {@html fossItem.detailsDescription}
92
+ <!-- Sanitized input from DOMPurify -->
93
+ <div class="details-description">
94
+ {@html safeDetailsDescription}
95
+ </div>
98
96
 
99
- {#each fossItem.notes as note}
97
+ {#each safeNotes as note}
100
98
  <blockquote class="bquote">
101
- <!-- Trusted input, from internal CMS -->
99
+ <!-- Sanitized input from DOMPurify -->
102
100
  {@html note}
103
101
  </blockquote>
104
102
  {/each}
@@ -8,6 +8,16 @@ This file is part of Network Pro.
8
8
 
9
9
  <script>
10
10
  import { pgpContact, pgpSupport, vcfSrc } from "$lib";
11
+ import { base } from "$app/paths";
12
+
13
+ // Log the base path to verify its value
14
+ //console.log("Base path:", base);
15
+
16
+ /**
17
+ * URL to the Contact Form route, using the base path
18
+ * @type {string}
19
+ */
20
+ const contactLink = `${base}/contact`;
11
21
 
12
22
  /**
13
23
  * Security attribute for external links
@@ -196,7 +206,7 @@ This file is part of Network Pro.
196
206
  <div class="spacer"></div>
197
207
 
198
208
  <p>
199
- <a href="https://netwk.pro/contact" target="_self">Let's connect</a>
209
+ <a href={contactLink} target="_self">Let's connect</a>
200
210
  to discuss how we can help secure and strengthen your business today.
201
211
  </p>
202
212
 
@@ -236,8 +246,7 @@ This file is part of Network Pro.
236
246
  type="application/pgp-keys"
237
247
  download
238
248
  target={tgtBlank}
239
- >asc
240
- <span class="fas fa-file-arrow-down"></span></a
249
+ >asc &nbsp;<span class="fas fa-file-arrow-down"></span></a
241
250
  ></strong>
242
251
  </p>
243
252
  <p
@@ -262,7 +271,7 @@ This file is part of Network Pro.
262
271
  type="application/pgp-keys"
263
272
  download
264
273
  target={tgtBlank}
265
- >asc <span class="fas fa-file-arrow-down"></span></a
274
+ >asc &nbsp;<span class="fas fa-file-arrow-down"></span></a
266
275
  ></strong>
267
276
  </p>
268
277
  <p
@@ -300,7 +309,8 @@ This file is part of Network Pro.
300
309
  type="text/vcard"
301
310
  download
302
311
  target={tgtBlank}>
303
- <strong>vcf</strong>
312
+ <strong
313
+ >vcf &nbsp;<span class="fas fa-file-arrow-down"></span></strong>
304
314
  </a>
305
315
  </p>
306
316
  </td>
@@ -218,7 +218,7 @@ This file is part of Network Pro.
218
218
  </ul>
219
219
  {:else if link.id === "cc-by"}
220
220
  <p class={constants.classSmall}>
221
- Formats:
221
+ Download:
222
222
  <a
223
223
  href="/assets/license/CC-BY-4.0.html"
224
224
  download
@@ -321,7 +321,7 @@ This file is part of Network Pro.
321
321
  </code>
322
322
  {:else if link.id === "gnu-gpl"}
323
323
  <p class={constants.classSmall}>
324
- Formats:
324
+ Download:
325
325
  <a
326
326
  href="/assets/license/COPYING.html"
327
327
  download
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @typedef {object} FossLink
3
+ * @property {string} [label]
4
+ * @property {string} [href]
5
+ * @property {string} [imgAlt]
6
+ * @property {string} [downloadText]
7
+ * @property {string} [downloadHref]
8
+ * @property {boolean} [hideLabels]
9
+ */
10
+
11
+ /**
12
+ * @typedef {object} FossItem
13
+ * @property {string} id
14
+ * @property {string} title
15
+ * @property {{ webp: string, png: string }} images
16
+ * @property {string} imgAlt
17
+ * @property {string} headline
18
+ * @property {string} headlineDescription
19
+ * @property {string} detailsDescription
20
+ * @property {Array<any>} features
21
+ * @property {Array<string>} notes
22
+ * @property {Array<FossLink>} links
23
+ */
@@ -0,0 +1,74 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/purify.js
3
+
4
+ Copyright Β© 2025 Network Pro Strategies (Network Proβ„’)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file purify.js
11
+ * @description Universal DOMPurify instance for SSR + client with safe build support.
12
+ * Secures untrusted HTML before injecting it into the DOM.
13
+ * @module src/lib/utils
14
+ * @author SunDevil311
15
+ * @updated 2025-06-01
16
+ */
17
+
18
+ import createDOMPurify from "dompurify";
19
+
20
+ /**
21
+ * @typedef {ReturnType<import('dompurify').default>} DOMPurifyInstance
22
+ */
23
+
24
+ /** @type {import('dompurify').DOMPurify | null} */
25
+ let DOMPurifyInstance = null;
26
+
27
+ /** @type {import('jsdom').JSDOM['window'] | null} */
28
+ let jsdomWindow = null;
29
+
30
+ /**
31
+ * SSR-safe + Vite-compatible init of DOMPurify.
32
+ *
33
+ * Caches DOMPurify across multiple calls to improve performance in tests or SSR environments.
34
+ *
35
+ * @returns {Promise<DOMPurifyInstance>}
36
+ */
37
+ export async function getDOMPurify() {
38
+ if (DOMPurifyInstance) return DOMPurifyInstance;
39
+
40
+ if (typeof window !== "undefined") {
41
+ // βœ… Client-side: use native window
42
+ DOMPurifyInstance = createDOMPurify(window);
43
+ } else {
44
+ // βœ… SSR: dynamically import jsdom to avoid bundling
45
+ const { JSDOM } = await import("jsdom");
46
+ jsdomWindow = jsdomWindow || new JSDOM("").window;
47
+ DOMPurifyInstance = createDOMPurify(jsdomWindow);
48
+ }
49
+
50
+ return DOMPurifyInstance;
51
+ }
52
+
53
+ /**
54
+ * Sanitizes HTML content to prevent XSS.
55
+ *
56
+ * @param {string} dirtyHtml
57
+ * @returns {Promise<string>} - A Promise resolving to sanitized HTML
58
+ */
59
+ export async function sanitizeHtml(dirtyHtml) {
60
+ const DOMPurify = await getDOMPurify();
61
+ return DOMPurify.sanitize(dirtyHtml, {
62
+ USE_PROFILES: { html: true },
63
+ ALLOW_DATA_ATTR: false,
64
+ ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):)/i,
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Optional helper to reset cache (for test isolation).
70
+ */
71
+ export function resetDOMPurifyCache() {
72
+ DOMPurifyInstance = null;
73
+ jsdomWindow = null;
74
+ }
@@ -0,0 +1,99 @@
1
+ /* ==========================================================================
2
+ tests/internal/auditCoverage.test.js
3
+
4
+ Copyright Β© 2025 Network Pro Strategies (Network Proβ„’)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file auditCoverage.test.js
11
+ * @description Scans all .js files in src/ and scripts/ for matching unit test
12
+ * @module tests/internal
13
+ * @author SunDevil311
14
+ * @updated 2025-06-01
15
+ */
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { describe, expect, it } from "vitest";
20
+
21
+ /**
22
+ * Recursively get all .js files in a directory
23
+ * @param {string} dir
24
+ * @param {object} [opts]
25
+ * @param {boolean} [opts.includeTests=false]
26
+ * @returns {string[]}
27
+ */
28
+ function getAllJsFiles(dir, { includeTests = false } = {}) {
29
+ let results = [];
30
+ const list = fs.readdirSync(dir);
31
+ for (const file of list) {
32
+ const fullPath = path.join(dir, file);
33
+ const stat = fs.statSync(fullPath);
34
+ if (stat.isDirectory()) {
35
+ results = results.concat(getAllJsFiles(fullPath, { includeTests }));
36
+ } else if (file.endsWith(".js")) {
37
+ if (
38
+ !includeTests &&
39
+ (file.endsWith(".test.js") || file.endsWith(".spec.js"))
40
+ ) {
41
+ continue;
42
+ }
43
+ results.push(fullPath);
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+
49
+ describe("auditCoverage", () => {
50
+ it("should have corresponding test files for all JS modules", () => {
51
+ const allowList = new Set([
52
+ "checkNode.js",
53
+ "auditScripts.js",
54
+ "vite.config.js",
55
+ "svelte.config.js",
56
+ ]);
57
+
58
+ const srcFiles = getAllJsFiles(path.resolve("src"));
59
+ const scriptsFiles = getAllJsFiles(path.resolve("scripts"));
60
+ const allFiles = [...srcFiles, ...scriptsFiles].map((f) =>
61
+ path
62
+ .relative(process.cwd(), f)
63
+ .replace(/\\/g, "/") // Normalize Windows slashes
64
+ .replace(/^src\//, "")
65
+ .replace(/^scripts\//, "")
66
+ .replace(/\.js$/, ""),
67
+ );
68
+
69
+ const testFiles = getAllJsFiles(path.resolve("tests/unit"), {
70
+ includeTests: true,
71
+ });
72
+
73
+ const testFilesNormalized = testFiles.map((f) =>
74
+ path
75
+ .relative(path.resolve("tests/unit"), f)
76
+ .replace(/\\/g, "/")
77
+ .replace(/\.test\.js$|\.spec\.js$/, ""),
78
+ );
79
+
80
+ const testedNames = new Set(testFilesNormalized);
81
+
82
+ const untested = allFiles.filter((file) => {
83
+ if (file.startsWith("tests/")) return false;
84
+ if ([...allowList].some((allowed) => file.endsWith(allowed)))
85
+ return false;
86
+ return !testedNames.has(file);
87
+ });
88
+
89
+ if (untested.length > 0) {
90
+ console.warn(
91
+ "\n[WARN] The following JS modules do not have corresponding tests:\n" +
92
+ untested.map((f) => ` - ${f}`).join("\n"),
93
+ );
94
+ }
95
+
96
+ // βœ… Always pass β€” warn only
97
+ expect(true).toBe(true);
98
+ });
99
+ });
@@ -0,0 +1,50 @@
1
+ /* ==========================================================================
2
+ tests/unit/lib/utils/purify.test.js
3
+
4
+ Copyright Β© 2025 Network Pro Strategies (Network Proβ„’)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file purify.test.js
11
+ * @description Unit test for src/lib/utils/purify.js
12
+ * @module tests/unit/lib/util
13
+ * @author SunDevil311
14
+ * @updated 2025-06-01
15
+ */
16
+
17
+ import { describe, expect, it } from "vitest";
18
+ import { sanitizeHtml } from "../../../../src/lib/utils/purify.js";
19
+
20
+ describe("sanitizeHtml", () => {
21
+ it("removes dangerous tags like <script>", async () => {
22
+ const dirty = `<div>Hello <script>alert("XSS")</script> world!</div>`;
23
+ const clean = await sanitizeHtml(dirty);
24
+ expect(clean).toBe("<div>Hello world!</div>");
25
+ }); // timeout in ms
26
+
27
+ it("preserves safe markup like <strong>", async () => {
28
+ const dirty = `<p>This is <strong>important</strong>.</p>`;
29
+ const clean = await sanitizeHtml(dirty);
30
+ expect(clean).toBe("<p>This is <strong>important</strong>.</p>");
31
+ });
32
+
33
+ it("removes dangerous attributes like onerror", async () => {
34
+ const dirty = `<img src="x" onerror="alert(1)">`;
35
+ const clean = await sanitizeHtml(dirty);
36
+ expect(clean).toBe('<img src="x">');
37
+ });
38
+
39
+ it("keeps valid external links", async () => {
40
+ const dirty = `<a href="https://example.com">Click</a>`;
41
+ const clean = await sanitizeHtml(dirty);
42
+ expect(clean).toBe('<a href="https://example.com">Click</a>');
43
+ });
44
+
45
+ it("blocks javascript: URLs", async () => {
46
+ const dirty = `<a href="javascript:alert('XSS')">bad</a>`;
47
+ const clean = await sanitizeHtml(dirty);
48
+ expect(clean).toBe("<a>bad</a>");
49
+ });
50
+ });
@@ -26,10 +26,14 @@ export default defineConfig({
26
26
  name: "client",
27
27
  environment: "jsdom",
28
28
  clearMocks: true,
29
- include: ["tests/unit/**/*.svelte.test.{js,mjs}"],
29
+ include: [
30
+ "tests/unit/**/*.test.{js,mjs,svelte}",
31
+ "tests/internal/**/*.test.{js,mjs,svelte}",
32
+ ],
30
33
  exclude: [],
31
34
  setupFiles: ["./vitest-setup-client.js"],
32
35
  reporters: ["default", "json"],
36
+ testTimeout: 10000,
33
37
  outputFile: {
34
38
  json: "./reports/client/results.json",
35
39
  },
@@ -26,6 +26,7 @@ export default defineConfig({
26
26
  include: ["tests/unit/**/*.test.{js,mjs}"],
27
27
  exclude: ["tests/unit/**/*.svelte.test.{js,mjs}"],
28
28
  reporters: ["default", "json"],
29
+ testTimeout: 10000,
29
30
  outputFile: {
30
31
  json: "./reports/server/results.json",
31
32
  },
@@ -1,43 +0,0 @@
1
- /* ==========================================================================
2
- tests/unit/auditScripts.test.js
3
-
4
- Copyright Β© 2025 Network Pro Strategies (Network Proβ„’)
5
- SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
- This file is part of Network Pro.
7
- ========================================================================== */
8
-
9
- /**
10
- * Unit test for scripts/auditScripts.js
11
- */
12
-
13
- import fs from "fs";
14
- import path from "path";
15
- import { describe, expect, it } from "vitest";
16
-
17
- describe("auditScripts.js", () => {
18
- it("should identify untested scripts correctly", () => {
19
- const scriptsDir = path.resolve("./scripts");
20
- const testsDir = path.resolve("./tests");
21
-
22
- const allowList = new Set(["checkNode.js", "auditScripts.js"]);
23
-
24
- const scriptFiles = fs
25
- .readdirSync(scriptsDir)
26
- .filter((file) => file.endsWith(".js"));
27
-
28
- const testFiles = fs
29
- .readdirSync(testsDir)
30
- .filter((file) => file.endsWith(".test.js") || file.endsWith(".spec.js"));
31
-
32
- const testedModules = new Set(
33
- testFiles.map((f) => f.replace(/\.test\.js$|\.spec\.js$/, "")),
34
- );
35
-
36
- const untested = scriptFiles.filter((file) => {
37
- const base = file.replace(/\.js$/, "");
38
- return !allowList.has(file) && !testedModules.has(base);
39
- });
40
-
41
- expect(untested).not.toContain("auditScripts.js");
42
- });
43
- });