Adjusted pathing and checks to hopefully fix problems with testing. Added coverage functionality.

This commit is contained in:
LosEvons
2025-11-20 12:00:58 +00:00
parent 75e0c97695
commit 458ff4e0a0
7 changed files with 766 additions and 38 deletions
+7 -16
View File
@@ -119,15 +119,6 @@ ALTER TABLE public.click_to_exercises
ADD CONSTRAINT click_to_exercises_exercise_id_fkey ADD CONSTRAINT click_to_exercises_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id); FOREIGN KEY (exercise_id) REFERENCES exercises(id);
CREATE SEQUENCE public.communications_pages_html_id_seq;
CREATE TABLE public.communications_pages_html (
id integer PRIMARY KEY NOT NULL DEFAULT nextval('communications_pages_html_id_seq'::regclass),
content text NOT NULL
);
ALTER SEQUENCE public.communications_pages_html_id_seq OWNED BY public.communications_pages_html.id;
CREATE TABLE public.dnd_categories ( CREATE TABLE public.dnd_categories (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(), created_at timestamp with time zone NOT NULL DEFAULT now(),
@@ -348,15 +339,15 @@ CREATE TABLE public.glossary_entries (
CREATE TRIGGER update_glossary_entries_updated_at BEFORE UPDATE ON public.glossary_entries FOR EACH ROW EXECUTE FUNCTION updated_at(); CREATE TRIGGER update_glossary_entries_updated_at BEFORE UPDATE ON public.glossary_entries FOR EACH ROW EXECUTE FUNCTION updated_at();
CREATE SEQUENCE public.learning_pages_html_id_seq; CREATE TABLE public.html_pages (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
CREATE TABLE public.learning_pages_html ( title text,
id integer PRIMARY KEY NOT NULL DEFAULT nextval('learning_pages_html_id_seq'::regclass), content text,
content text NOT NULL page_order integer,
slug text,
page_group text
); );
ALTER SEQUENCE public.learning_pages_html_id_seq OWNED BY public.learning_pages_html.id;
CREATE TABLE public.multichoice_content ( CREATE TABLE public.multichoice_content (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
multichoice_exercise_id bigint NOT NULL, multichoice_exercise_id bigint NOT NULL,
+631
View File
@@ -36,6 +36,8 @@
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.6",
"@vitest/ui": "^3.0.6",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.1.5", "eslint-config-next": "15.1.5",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
@@ -364,6 +366,16 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@cspotcode/source-map-support": { "node_modules/@cspotcode/source-map-support": {
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -1583,6 +1595,34 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -1819,6 +1859,17 @@
"node": ">=12.4.0" "node": ">=12.4.0"
} }
}, },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.56.1", "version": "1.56.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
@@ -1834,6 +1885,13 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
"dev": true,
"license": "MIT"
},
"node_modules/@radix-ui/colors": { "node_modules/@radix-ui/colors": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz",
@@ -4037,6 +4095,39 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
} }
}, },
"node_modules/@vitest/coverage-v8": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.6.tgz",
"integrity": "sha512-JRTlR8Bw+4BcmVTICa7tJsxqphAktakiLsAmibVLAWbu1lauFddY/tXeM6sAyl1cgkPuXtpnUgaCPhTdz1Qapg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
"debug": "^4.4.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.1.7",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"std-env": "^3.8.0",
"test-exclude": "^7.0.1",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "3.0.6",
"vitest": "3.0.6"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.6.tgz",
@@ -4135,6 +4226,28 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/ui": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.0.6.tgz",
"integrity": "sha512-N4M2IUG2Q5LCeX4OWs48pQF4P3qsFejmDTc6QWGRFTLPrEe5EvM5HN0WSUnGAmuzQpSWv7ItfSsIJIWaEM2wpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.0.6",
"fflate": "^0.8.2",
"flatted": "^3.3.2",
"pathe": "^2.0.3",
"sirv": "^3.0.1",
"tinyglobby": "^0.2.11",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"vitest": "3.0.6"
}
},
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.6.tgz",
@@ -5057,6 +5170,13 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.102", "version": "1.5.102",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz",
@@ -5883,6 +6003,13 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"dev": true,
"license": "MIT"
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -5983,6 +6110,23 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
@@ -6179,6 +6323,27 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
} }
}, },
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -6192,6 +6357,32 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globals": { "node_modules/globals": {
"version": "14.0.0", "version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -6404,6 +6595,13 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/http-proxy-agent": { "node_modules/http-proxy-agent": {
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -6684,6 +6882,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-generator-function": { "node_modules/is-generator-function": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
@@ -6943,6 +7151,60 @@
"unfetch": "^4.2.0" "unfetch": "^4.2.0"
} }
}, },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-lib-source-maps": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.23",
"debug": "^4.1.1",
"istanbul-lib-coverage": "^3.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/iterator.prototype": { "node_modules/iterator.prototype": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -6961,6 +7223,22 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jose": { "node_modules/jose": {
"version": "5.10.0", "version": "5.10.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
@@ -7223,6 +7501,18 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
} }
}, },
"node_modules/magicast": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.25.4",
"@babel/types": "^7.25.4",
"source-map-js": "^1.2.0"
}
},
"node_modules/mailersend": { "node_modules/mailersend": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz", "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz",
@@ -7234,6 +7524,35 @@
"qs": "^6.11.0" "qs": "^6.11.0"
} }
}, },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -7318,6 +7637,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
"integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -7642,6 +7981,13 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parchment": { "node_modules/parchment": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
@@ -7699,6 +8045,30 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/pathe": { "node_modules/pathe": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -8779,6 +9149,34 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
"totalist": "^3.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -8818,6 +9216,60 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string.prototype.includes": { "node_modules/string.prototype.includes": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@@ -8931,6 +9383,49 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/strip-bom": { "node_modules/strip-bom": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -9019,6 +9514,47 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/test-exclude": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
"integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^10.4.1",
"minimatch": "^9.0.4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/tinybench": { "node_modules/tinybench": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -9139,6 +9675,16 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz",
@@ -9918,6 +10464,91 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ws": { "node_modules/ws": {
"version": "8.18.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+5 -1
View File
@@ -8,7 +8,9 @@
"build": "next build", "build": "next build",
"start": "sh ./data/migrate.sh --skip-schema-gen && next start", "start": "sh ./data/migrate.sh --skip-schema-gen && next start",
"lint": "next lint", "lint": "next lint",
"test": "vitest --disable-console-intercept ", "test": "vitest --disable-console-intercept",
"test:coverage": "vitest --disable-console-intercept --coverage.enabled=true",
"test:coverage:ui": "vitest --ui --api.port=51204 --api.host=0.0.0.0 --open=false --disable-console-intercept --coverage.enabled=true",
"test:e2e": "NODE_ENV=test playwright test", "test:e2e": "NODE_ENV=test playwright test",
"test:e2e:ui": "NODE_ENV=test playwright test --ui", "test:e2e:ui": "NODE_ENV=test playwright test --ui",
"test:e2e:debug": "NODE_ENV=test playwright test --debug" "test:e2e:debug": "NODE_ENV=test playwright test --debug"
@@ -42,6 +44,8 @@
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.6",
"@vitest/ui": "^3.0.6",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.1.5", "eslint-config-next": "15.1.5",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
@@ -2,7 +2,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
vi.mock("@/backend/html-services", () => ({ vi.mock("@/backend/html-services", () => ({
// Mock exports used by the route under test. `getPageData` is used by the
// GET and non-content PUT flows; `updateHTMLContent` is used for content-only
// PUT updates (allows empty-string content handling).
getHTMLContent: vi.fn(), getHTMLContent: vi.fn(),
getPageData: vi.fn(),
updateHTMLContent: vi.fn(), updateHTMLContent: vi.fn(),
})); }));
@@ -44,7 +48,7 @@ describe("GET /api/html-content/[type]/[id]", () => {
const response = await getRequest("resources", 1); const response = await getRequest("resources", 1);
const body = await response.json(); const body = await response.json();
expect(getPageData).toHaveBeenCalledWith("learning_pages_html", 1); expect(getPageData).toHaveBeenCalledWith("resources", 1);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(body).toEqual({ content: "<p>Mocked HTML</p>" }); expect(body).toEqual({ content: "<p>Mocked HTML</p>" });
}); });
@@ -55,7 +59,7 @@ describe("GET /api/html-content/[type]/[id]", () => {
const response = await getRequest("communications", 5); const response = await getRequest("communications", 5);
await response.json(); await response.json();
expect(getPageData).toHaveBeenCalledWith("communications_pages_html", 5); expect(getPageData).toHaveBeenCalledWith("communications", 5);
}); });
it("should return empty string table if type is invalid", async () => { it("should return empty string table if type is invalid", async () => {
@@ -89,7 +93,7 @@ describe("PUT /api/html-content/[type]/[id]", () => {
const response = await putRequest("resources", 1, "<p>Mocked HTML</p>"); const response = await putRequest("resources", 1, "<p>Mocked HTML</p>");
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(updateHTMLContent).toHaveBeenCalledWith( expect(updateHTMLContent).toHaveBeenCalledWith(
"learning_pages_html", "resources",
1, 1,
"<p>Mocked HTML</p>" "<p>Mocked HTML</p>"
); );
@@ -106,7 +110,7 @@ describe("PUT /api/html-content/[type]/[id]", () => {
ADD_ATTR: ["target"], ADD_ATTR: ["target"],
}); });
expect(updateHTMLContent).toHaveBeenCalledWith( expect(updateHTMLContent).toHaveBeenCalledWith(
"learning_pages_html", "resources",
1, 1,
"<p>clean</p>" "<p>clean</p>"
); );
@@ -116,11 +120,7 @@ describe("PUT /api/html-content/[type]/[id]", () => {
it("should allow an admin to save an empty editor file", async () => { it("should allow an admin to save an empty editor file", async () => {
updateHTMLContent.mockResolvedValueOnce(true); updateHTMLContent.mockResolvedValueOnce(true);
const response = await putRequest("resources", 1, ""); const response = await putRequest("resources", 1, "");
expect(updateHTMLContent).toHaveBeenCalledWith( expect(updateHTMLContent).toHaveBeenCalledWith("resources", 1, "");
"learning_pages_html",
1,
""
);
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
@@ -1,15 +1,46 @@
import { getPageData, setPageData } from "@/backend/html-services"; import {
getPageData,
setPageData,
updateHTMLContent,
} from "@/backend/html-services";
import { withAuth } from "@/backend/middleware/withAuth"; import { withAuth } from "@/backend/middleware/withAuth";
import { JSDOM } from "jsdom"; import { JSDOM } from "jsdom";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
export async function GET(req, { params }) { // Allowed page_group values stored in the consolidated `html_pages` table.
const { type, slug } = await params; const VALID_PAGE_GROUPS = new Set(["resources", "communications"]);
try {
const page = await getPageData(type, slug);
return new NextResponse(JSON.stringify({ page }), { /*
Route behavior (summary):
- The `type` route param maps to the `page_group` value stored in the
consolidated `html_pages` table (values: `resources`, `communications`).
- GET returns JSON `{ content: string }`. `getPageData` may return either
a DB row object or (in tests) a raw HTML string; this handler normalizes
both into the `content` string.
- PUT has two flows:
* Content-only updates: when the request body includes the `content`
property (including an explicit empty string) the route sanitizes the
value and calls `updateHTMLContent(page_group, slug, cleaned)`. A
failed update returns HTTP 400.
* Full-page updates: when other fields (title/slug/page_order) are
provided the handler fetches the page, applies changes and calls
`setPageData` with slug collision checks.
- The route requires an authenticated admin (`withAuth(..., { requireAdmin: true })`).
*/
export async function GET(req, { params }) {
const p = await params;
const { type } = p;
const slug = p.slug ?? p.id;
const table = VALID_PAGE_GROUPS.has(type) ? type : "";
try {
const page = await getPageData(table, slug);
// getPageData may return the DB row object or (in tests/mocks) a raw HTML
// string. Normalize to a `content` string for the API response.
const content = typeof page === "string" ? page : (page?.content ?? "");
return new NextResponse(JSON.stringify({ content }), {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
} catch { } catch {
@@ -20,27 +51,39 @@ export async function GET(req, { params }) {
export const PUT = withAuth( export const PUT = withAuth(
async (req, { params }) => { async (req, { params }) => {
const data = await req.json(); const data = await req.json();
const { type, slug } = await params; const p = await params;
const { type } = p;
const slug = p.slug ?? p.id;
const table = VALID_PAGE_GROUPS.has(type) ? type : "";
// If content is present (including empty string), sanitize and update only content
if (Object.prototype.hasOwnProperty.call(data, "content")) {
const cleaned = sanitize(data.content);
if (!(await updateHTMLContent(table, slug, cleaned))) {
return new NextResponse("Error updating HTML content", { status: 400 });
}
return new NextResponse("", { status: 200 });
}
// For other updates (title, slug, page_order) fall back to full page update
let newData; let newData;
try { try {
newData = await getPageData(type, slug); newData = await getPageData(table, slug);
} catch { } catch {
return new NextResponse("not found", { status: 404 }); return new NextResponse("not found", { status: 404 });
} }
if (data.title) newData.title = data.title; if (data.title) newData.title = data.title;
if (data.page_order) newData.page_order = data.page_order; if (data.page_order) newData.page_order = data.page_order;
if (data.slug) newData.slug = data.slug;
if (data.content) newData.content = sanitize(data.content);
if (data.slug) newData.slug = data.slug.replace(/[^A-Za-z0-9\-\_\+]/g, ""); if (data.slug) newData.slug = data.slug.replace(/[^A-Za-z0-9\-\_\+]/g, "");
if (newData.slug !== slug && (await slugIsInUse(type, newData.slug))) { if (newData.slug !== slug && (await slugIsInUse(table, newData.slug))) {
return new NextResponse("Page slug already in use", { return new NextResponse("Page slug already in use", {
status: 400, status: 400,
}); });
} }
if (!(await setPageData(type, slug, newData))) { if (!(await setPageData(table, slug, newData))) {
return new NextResponse("Error updating HTML content", { return new NextResponse("Error updating HTML content", {
status: 400, status: 400,
}); });
+22
View File
@@ -20,6 +20,28 @@ export async function setPageData(type, slug, newData) {
return result.rowCount == 1; return result.rowCount == 1;
} }
export async function updateHTMLContent(type, slug, content) {
// Update only the `content` column for the given `page_group` and `slug`.
// Returns `true` when exactly one row was affected.
const result = await DB.pool(
"UPDATE html_pages SET content = $1 WHERE page_group = $2 AND slug = $3",
[content, type, slug]
);
// Helpful debug output when running locally or in dev; keep non-intrusive.
if (typeof console !== "undefined" && console.debug) {
console.debug("updateHTMLContent:", {
type,
slug,
contentLength: content?.length ?? 0,
rowCount: result.rowCount,
});
}
return result.rowCount == 1;
}
// Note: `updateHTMLContent` updates only the `content` column and mirrors the
// boolean return convention used by `setPageData` (true when rowCount == 1).
export async function getPageList(type) { export async function getPageList(type) {
const result = await DB.pool( const result = await DB.pool(
"SELECT title, slug FROM html_pages WHERE page_group = $1 ORDER BY page_order ASC", "SELECT title, slug FROM html_pages WHERE page_group = $1 ORDER BY page_order ASC",
+37
View File
@@ -7,6 +7,43 @@ export default defineConfig({
test: { test: {
environment: "happy-dom", environment: "happy-dom",
exclude: ["playwright/e2e/**/*", "node_modules/**/*"], exclude: ["playwright/e2e/**/*", "node_modules/**/*"],
coverage: {
clean: true,
enabled: true,
subdir: true,
reporters: ["text", "json", "html"],
// Collect coverage only for server/backend code and API handlers.
// Excluding Next.js app pages, client components and other UI-only
// files prevents the report from being flooded with frontend files
// that aren't exercised by the Node test environment.
include: [
"src/backend/**/*.{js,ts}",
"src/app/api/**/*.{js,ts}",
// include only necessary shared files (schemas/constants) used by backend tests
"src/shared/const.{js,ts}",
"src/shared/schemas/**/*.{js,ts}",
],
// Exclude tests, Playwright e2e helpers and client-only code.
exclude: [
"**/*.test.*",
"**/test/**",
"node_modules/**",
// exclude Next.js app pages and client components
"src/app/**/page.*",
"src/app/**/layout.*",
"src/app/**/client**/*",
"src/components/**",
"src/app/**/editor-section.*",
"src/app/**/page.module.css",
// exclude client-only shared helpers and hooks
"src/shared/hooks/**",
"src/shared/utils/**",
],
// Include untested files in the report so missing coverage is visible
// for all files matched by the `include` globs.
all: true,
provider: "v8",
},
}, },
resolve: { resolve: {
alias: { alias: {