@cosla/sensemaking-web-ui 1.0.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.
Files changed (41) hide show
  1. package/README.md +35 -0
  2. package/angular.json +116 -0
  3. package/health_check.js +68 -0
  4. package/karma.conf.js +43 -0
  5. package/package.json +93 -0
  6. package/public/favicon.ico +0 -0
  7. package/server.ts +60 -0
  8. package/single-html-build.js +100 -0
  9. package/site-build.ts +54 -0
  10. package/src/app/app.component.html +1 -0
  11. package/src/app/app.component.scss +0 -0
  12. package/src/app/app.component.spec.ts +32 -0
  13. package/src/app/app.component.ts +21 -0
  14. package/src/app/app.config.server.ts +9 -0
  15. package/src/app/app.config.ts +19 -0
  16. package/src/app/app.routes.ts +8 -0
  17. package/src/app/components/dialog/dialog.component.html +15 -0
  18. package/src/app/components/dialog/dialog.component.scss +42 -0
  19. package/src/app/components/dialog/dialog.component.spec.ts +27 -0
  20. package/src/app/components/dialog/dialog.component.ts +40 -0
  21. package/src/app/components/sensemaking-chart-wrapper/sensemaking-chart-wrapper.component.ts +66 -0
  22. package/src/app/components/statement-card/statement-card.component.html +51 -0
  23. package/src/app/components/statement-card/statement-card.component.scss +134 -0
  24. package/src/app/components/statement-card/statement-card.component.spec.ts +23 -0
  25. package/src/app/components/statement-card/statement-card.component.ts +50 -0
  26. package/src/app/directives/custom-tooltip/custom-tooltip.directive.spec.ts +39 -0
  27. package/src/app/directives/custom-tooltip/custom-tooltip.directive.ts +89 -0
  28. package/src/app/models/report.model.ts +48 -0
  29. package/src/app/pages/report/report.component.html +363 -0
  30. package/src/app/pages/report/report.component.scss +600 -0
  31. package/src/app/pages/report/report.component.spec.ts +29 -0
  32. package/src/app/pages/report/report.component.ts +276 -0
  33. package/src/environments/environment.ts +5 -0
  34. package/src/index.html +17 -0
  35. package/src/main.server.ts +7 -0
  36. package/src/main.ts +5 -0
  37. package/src/style-vars.scss +40 -0
  38. package/src/styles.scss +23 -0
  39. package/tsconfig.app.json +19 -0
  40. package/tsconfig.json +32 -0
  41. package/tsconfig.spec.json +15 -0
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # web-ui
2
+
3
+ This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.1.1.
4
+
5
+ For setup and basic usage, see the `README.md` file in the root of this repository.
6
+ What follows is web-ui-side developer-specific information, autogenerated from Angular.
7
+
8
+ ## Running
9
+
10
+ ```bash
11
+ cd web-ui
12
+ npm run dev
13
+ ```
14
+
15
+ Note that this will run both the web-ui build & serve process, and the backend server.
16
+
17
+ ## Code scaffolding
18
+
19
+ Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
20
+
21
+ ## Build
22
+
23
+ Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
24
+
25
+ ## Running unit tests
26
+
27
+ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
28
+
29
+ ## Running end-to-end tests
30
+
31
+ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
32
+
33
+ ## Further help
34
+
35
+ To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
package/angular.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3
+ "version": 1,
4
+ "newProjectRoot": "projects",
5
+ "projects": {
6
+ "web-ui": {
7
+ "projectType": "application",
8
+ "schematics": {
9
+ "@schematics/angular:component": {
10
+ "style": "scss"
11
+ }
12
+ },
13
+ "root": "",
14
+ "sourceRoot": "src",
15
+ "prefix": "app",
16
+ "architect": {
17
+ "build": {
18
+ "builder": "@angular-devkit/build-angular:application",
19
+ "options": {
20
+ "outputPath": "dist/web-ui",
21
+ "index": "src/index.html",
22
+ "browser": "src/main.ts",
23
+ "polyfills": [
24
+ "zone.js"
25
+ ],
26
+ "tsConfig": "tsconfig.app.json",
27
+ "inlineStyleLanguage": "scss",
28
+ "assets": [
29
+ {
30
+ "glob": "**/*",
31
+ "input": "public"
32
+ }
33
+ ],
34
+ "styles": [
35
+ "@angular/material/prebuilt-themes/azure-blue.css",
36
+ "src/styles.scss"
37
+ ],
38
+ "scripts": [],
39
+ "server": "src/main.server.ts",
40
+ "prerender": true,
41
+ "ssr": {
42
+ "entry": "server.ts"
43
+ }
44
+ },
45
+ "configurations": {
46
+ "production": {
47
+ "budgets": [
48
+ {
49
+ "type": "initial",
50
+ "maximumWarning": "20MB",
51
+ "maximumError": "20MB"
52
+ },
53
+ {
54
+ "type": "anyComponentStyle",
55
+ "maximumWarning": "100kB",
56
+ "maximumError": "100kB"
57
+ }
58
+ ],
59
+ "outputHashing": "all"
60
+ },
61
+ "development": {
62
+ "optimization": false,
63
+ "extractLicenses": false,
64
+ "sourceMap": true
65
+ }
66
+ },
67
+ "defaultConfiguration": "production"
68
+ },
69
+ "serve": {
70
+ "builder": "@angular-devkit/build-angular:dev-server",
71
+ "configurations": {
72
+ "production": {
73
+ "buildTarget": "web-ui:build:production"
74
+ },
75
+ "development": {
76
+ "buildTarget": "web-ui:build:development"
77
+ }
78
+ },
79
+ "defaultConfiguration": "development",
80
+ "options": {
81
+ "host": "0.0.0.0",
82
+ "port": 4200
83
+ }
84
+ },
85
+ "extract-i18n": {
86
+ "builder": "@angular-devkit/build-angular:extract-i18n"
87
+ },
88
+ "test": {
89
+ "builder": "@angular-devkit/build-angular:karma",
90
+ "options": {
91
+ "polyfills": [
92
+ "zone.js",
93
+ "zone.js/testing"
94
+ ],
95
+ "tsConfig": "tsconfig.spec.json",
96
+ "inlineStyleLanguage": "scss",
97
+ "assets": [
98
+ {
99
+ "glob": "**/*",
100
+ "input": "public"
101
+ }
102
+ ],
103
+ "styles": [
104
+ "@angular/material/prebuilt-themes/azure-blue.css",
105
+ "src/styles.scss"
106
+ ],
107
+ "scripts": []
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "cli": {
114
+ "analytics": "6ff4f325-17ba-4fbb-ae7e-d93e98737dc5"
115
+ }
116
+ }
@@ -0,0 +1,68 @@
1
+ const esbuild = require("esbuild");
2
+ const jsdom = require("jsdom");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const { JSDOM } = jsdom;
7
+
8
+ const argv = process.argv.slice(2);
9
+ function getArgValue(names) {
10
+ for (let i = 0; i < argv.length; i++) {
11
+ const a = argv[i];
12
+ for (const name of names) {
13
+ if (a === name && argv[i + 1]) return argv[i + 1];
14
+ if (a.startsWith(name + "=")) return a.split("=", 2)[1];
15
+ }
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ const outputFilePath = getArgValue(["-o", "--outputFile"]) || "dist/health-check.html";
21
+ const outputDir = path.dirname(outputFilePath);
22
+
23
+ fs.mkdirSync(outputDir, { recursive: true });
24
+
25
+ const dom = new JSDOM(`<!DOCTYPE html>
26
+ <html>
27
+ <head>
28
+ <title>Web-UI Health Check</title>
29
+ </head>
30
+ <body>
31
+ <h1>Hello World</h1>
32
+ <p>Web-UI health check passed!</p>
33
+ </body>
34
+ </html>`);
35
+
36
+ const simpleScript = `
37
+ console.log('Health check: Web-UI dependencies are working!');
38
+ `;
39
+
40
+ esbuild
41
+ .build({
42
+ stdin: {
43
+ contents: simpleScript,
44
+ loader: "js",
45
+ },
46
+ bundle: true,
47
+ minify: true,
48
+ format: "iife",
49
+ write: false,
50
+ })
51
+ .then(
52
+ (result) => {
53
+ const scriptSrc = result.outputFiles[0].text;
54
+ const script = dom.window.document.createElement("script");
55
+ script.innerHTML = scriptSrc;
56
+ dom.window.document.body.appendChild(script);
57
+
58
+ fs.writeFileSync(outputFilePath, dom.serialize());
59
+ console.log(`Health check HTML file created at ${path.resolve(outputFilePath)}`);
60
+ console.log("✓ Web-UI health check passed");
61
+ process.exit(0);
62
+ },
63
+ (error) => {
64
+ console.error("Health check failed:", error);
65
+ process.exit(1);
66
+ }
67
+ );
68
+
package/karma.conf.js ADDED
@@ -0,0 +1,43 @@
1
+ // karma.conf.js
2
+ module.exports = function (config) {
3
+ config.set({
4
+ basePath: "",
5
+ frameworks: ["jasmine", "@angular-devkit/build-angular"],
6
+ plugins: [
7
+ require("karma-jasmine"),
8
+ require("karma-chrome-launcher"),
9
+ require("karma-jasmine-html-reporter"),
10
+ require("karma-coverage"),
11
+ require("@angular-devkit/build-angular/plugins/karma"),
12
+ ],
13
+ client: {
14
+ jasmine: {
15
+ // you can add configuration options for Jasmine here
16
+ // the possible options are listed at https://jasmine.github.io/api/edge/Jasmine/Jasmine
17
+ },
18
+ clearContext: false, // leave Jasmine Spec Runner output visible in browser
19
+ },
20
+ jasmineHtmlReporter: {
21
+ suppressAll: true, // removes the duplicated traces
22
+ },
23
+ coverageReporter: {
24
+ dir: require("path").join(__dirname, "./coverage/web-ui"),
25
+ subdir: ".",
26
+ reporters: [{ type: "html" }, { type: "text-summary" }],
27
+ },
28
+ reporters: ["progress", "kjhtml"],
29
+ port: 9876,
30
+ colors: true,
31
+ logLevel: config.LOG_INFO,
32
+ autoWatch: true, //this will be overriden by the test command
33
+ customLaunchers: {
34
+ ChromeHeadlessNoSandbox: {
35
+ base: "ChromeHeadless",
36
+ flags: ["--no-sandbox"],
37
+ },
38
+ },
39
+ browsers: ["ChromeHeadlessNoSandbox"], // Run in Headless Chrome
40
+ singleRun: false, // Set to false for continuous testing, true to run once,
41
+ restartOnFileChange: true,
42
+ });
43
+ };
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@cosla/sensemaking-web-ui",
3
+ "version": "1.0.0",
4
+ "description": "Provides interactive visualizations and report from sensemaking-tools output. Fork of Jigsaw's sensemaking-tools web-ui (https://github.com/Jigsaw-Code/sensemaking-tools).",
5
+ "homepage": "https://github.com/CoslaDigital/sensemaking-tools#readme",
6
+ "scripts": {
7
+ "ng": "ng",
8
+ "build": "ng build",
9
+ "watch": "ng build --watch --configuration development",
10
+ "test": "ng test --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage",
11
+ "serve:ssr:web-ui": "node dist/web-ui/server/server.mjs",
12
+ "start:api-server": "nodemon --watch ../api-server --watch ../library ../api-server/index.ts",
13
+ "start:web-ui": "ng serve",
14
+ "dev": "concurrently --kill-others \"npm run start:api-server\" \"npm run start:web-ui\"",
15
+ "prepublishOnly": "echo 'Publishing @cosla/sensemaking-web-ui'"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/CoslaDigital/sensemaking-tools.git"
20
+ },
21
+ "keywords": [
22
+ "sensemaking",
23
+ "angular",
24
+ "visualization",
25
+ "civic-tech",
26
+ "public-input",
27
+ "data-visualization",
28
+ "web-ui",
29
+ "dashboard"
30
+ ],
31
+ "author": "CoslaDigital",
32
+ "license": "ISC",
33
+ "files": [
34
+ "src/**/*",
35
+ "public/**/*",
36
+ "angular.json",
37
+ "tsconfig.json",
38
+ "tsconfig.app.json",
39
+ "tsconfig.spec.json",
40
+ "karma.conf.js",
41
+ "server.ts",
42
+ "site-build.ts",
43
+ "single-html-build.js",
44
+ "health_check.js",
45
+ "package.json",
46
+ "README.md"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "dependencies": {
52
+ "@angular/animations": "18.2.13",
53
+ "@angular/cdk": "18.2.13",
54
+ "@angular/common": "18.2.13",
55
+ "@angular/compiler": "18.2.13",
56
+ "@angular/core": "18.2.13",
57
+ "@angular/forms": "18.2.13",
58
+ "@angular/material": "18.2.13",
59
+ "@angular/platform-browser": "18.2.13",
60
+ "@angular/platform-browser-dynamic": "18.2.13",
61
+ "@angular/platform-server": "18.2.13",
62
+ "@angular/router": "18.2.13",
63
+ "@angular/ssr": "18.2.13",
64
+ "@conversationai/sensemaker-visualizations": "^1.0.46",
65
+ "@sinclair/typebox": "^0.34.27",
66
+ "express": "^4.18.2",
67
+ "ngx-markdown": "^18.0.0",
68
+ "papaparse": "^5.4.1",
69
+ "rxjs": "~7.8.0",
70
+ "tslib": "^2.3.0",
71
+ "zone.js": "~0.14.3"
72
+ },
73
+ "devDependencies": {
74
+ "@angular-devkit/build-angular": "18.2.13",
75
+ "@angular/cli": "18.2.13",
76
+ "@angular/compiler-cli": "18.2.13",
77
+ "@types/express": "^4.17.17",
78
+ "@types/jasmine": "~5.1.0",
79
+ "@types/node": "^18.18.0",
80
+ "@types/papaparse": "^5.3.14",
81
+ "concurrently": "^9.0.1",
82
+ "jasmine-core": "~5.1.0",
83
+ "jsdom": "^26.1.0",
84
+ "karma": "~6.4.0",
85
+ "karma-chrome-launcher": "~3.2.0",
86
+ "karma-coverage": "~2.2.0",
87
+ "karma-jasmine": "~5.1.0",
88
+ "karma-jasmine-html-reporter": "~2.1.0",
89
+ "nodemon": "^3.1.4",
90
+ "ts-node": "^10.9.2",
91
+ "typescript": "~5.5.2"
92
+ }
93
+ }
Binary file
package/server.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { APP_BASE_HREF } from '@angular/common';
2
+ import { CommonEngine } from '@angular/ssr';
3
+ import express from 'express';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import bootstrap from './src/main.server';
7
+
8
+ // The Express app is exported so that it can be used by serverless Functions.
9
+ export function app(): express.Express {
10
+ const server = express();
11
+ const serverDistFolder = dirname(fileURLToPath(import.meta.url));
12
+ const browserDistFolder = resolve(serverDistFolder, '../browser');
13
+ const indexHtml = join(serverDistFolder, 'index.server.html');
14
+
15
+ const commonEngine = new CommonEngine();
16
+
17
+ server.set('view engine', 'html');
18
+ server.set('views', browserDistFolder);
19
+
20
+ // Example Express Rest API endpoints
21
+ // server.get('/api/**', (req, res) => { });
22
+ // Serve static files from /browser
23
+ server.get(
24
+ '**',
25
+ express.static(browserDistFolder, {
26
+ maxAge: '1y',
27
+ index: 'index.html',
28
+ })
29
+ );
30
+
31
+ // All regular routes use the Angular engine
32
+ server.get('**', (req, res, next) => {
33
+ const { protocol, originalUrl, baseUrl, headers } = req;
34
+
35
+ commonEngine
36
+ .render({
37
+ bootstrap,
38
+ documentFilePath: indexHtml,
39
+ url: `${protocol}://${headers.host}${originalUrl}`,
40
+ publicPath: browserDistFolder,
41
+ providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
42
+ })
43
+ .then((html) => res.send(html))
44
+ .catch((err) => next(err));
45
+ });
46
+
47
+ return server;
48
+ }
49
+
50
+ function run(): void {
51
+ const port = process.env['PORT'] || 4200;
52
+
53
+ // Start up the Node server
54
+ const server = app();
55
+ server.listen(port, () => {
56
+ console.log(`Node Express server listening on http://localhost:${port}`);
57
+ });
58
+ }
59
+
60
+ run();
@@ -0,0 +1,100 @@
1
+ const esbuild = require("esbuild");
2
+ const jsdom = require("jsdom");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const { JSDOM } = jsdom;
7
+ const srcDir = "dist/web-ui/browser";
8
+ const defaultDestDir = "dist/bundled";
9
+
10
+ // Parse CLI args for output file path (supports -o value, --outputFile value, and --outputFile=value)
11
+ const argv = process.argv.slice(2);
12
+ function getArgValue(names) {
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const a = argv[i];
15
+ for (const name of names) {
16
+ if (a === name && argv[i + 1]) return argv[i + 1];
17
+ if (a.startsWith(name + "=")) return a.split("=", 2)[1];
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ const outputFilePath = getArgValue(["-o", "--outputFile"]) || `${defaultDestDir}/report.html`;
24
+ const outputDir = path.dirname(outputFilePath);
25
+
26
+ fs.mkdirSync(outputDir, { recursive: true });
27
+
28
+ const indexFilePath = srcDir + "/index.csr.html"; // this is the root html file from the build
29
+ const htmlSource = fs.readFileSync(indexFilePath);
30
+ const dom = new JSDOM(htmlSource.toString());
31
+
32
+ let mainScript;
33
+ let scriptsToInject = [];
34
+
35
+ const isUrl = string => {
36
+ try {
37
+ return Boolean(new URL(string));
38
+ } catch (e) {
39
+ return false;
40
+ }
41
+ };
42
+
43
+ const scriptTags = Array.from(dom.window.document.getElementsByTagName("script"));
44
+ scriptTags.forEach((e) => {
45
+ let fileName = e.getAttribute("src");
46
+ if(fileName && !isUrl(fileName)) {
47
+ let scriptPath = srcDir + "/" + fileName;
48
+ if(fileName.includes("main-")) {
49
+ mainScript = scriptPath;
50
+ } else {
51
+ scriptsToInject.push(scriptPath);
52
+ }
53
+ e.remove();
54
+ }
55
+ });
56
+
57
+ const linkTags = Array.from(dom.window.document.getElementsByTagName("link"));
58
+ linkTags.forEach((e) => {
59
+ const rel = e.getAttribute("rel");
60
+ const href = e.getAttribute("href");
61
+ const media = e.getAttribute("media");
62
+ if(rel === "stylesheet" && !isUrl(href) && media === "print") {
63
+ e.remove();
64
+ let styleFile = srcDir + "/" + href;
65
+ // Add the stylesheet to dom as inline.
66
+ const style = dom.window.document.createElement("style");
67
+ style.innerHTML = fs.readFileSync(styleFile).toString();
68
+ dom.window.document.body.appendChild(style);
69
+ }
70
+ // find and remove unused resources to prevent console errors
71
+ if(rel === "modulepreload" || rel === "icon") {
72
+ e.remove();
73
+ }
74
+ });
75
+
76
+ // write JS to memory, then add to DOM, then write HTML file
77
+ esbuild
78
+ .build({
79
+ entryPoints: [mainScript],
80
+ inject: scriptsToInject,
81
+ bundle: true,
82
+ minify: true,
83
+ sourcemap: false,
84
+ outfile: path.join(outputDir, "bundled.js"), // file won't be created, but property is still required when writing to memory
85
+ format: "esm",
86
+ write: false, // places JS in memory instead of writing to file
87
+ })
88
+ .then(
89
+ (result) => {
90
+ const scriptSrc = result.outputFiles[0].text;
91
+ const script = dom.window.document.createElement("script");
92
+ script.innerHTML = scriptSrc;
93
+ dom.window.document.body.appendChild(script);
94
+
95
+ // do not rename file to "index.html"; causes a break
96
+ fs.writeFileSync(outputFilePath, dom.serialize());
97
+ console.log(`Report HTML file created at ${path.resolve(outputFilePath)}`);
98
+ },
99
+ (error) => console.error(error),
100
+ );
package/site-build.ts ADDED
@@ -0,0 +1,54 @@
1
+ const { Command } = require("commander");
2
+ const { copyFileSync, writeFileSync } = require("fs");
3
+ const path = require("path");
4
+ const { exec, ExecException } = require("child_process");
5
+
6
+ async function main(): Promise<void> {
7
+ // Parse command line arguments.
8
+ const program = new Command();
9
+ program.option("-t, --topics <file>", "The topics file location.");
10
+ program.option("-s, --summary <file>", "The summary file location.");
11
+ program.option("-c, --comments <file>", "The comments file location.");
12
+ program.option("-r, --reportTitle <title>", "The title of the report.");
13
+ program.parse(process.argv);
14
+ const options = program.opts();
15
+
16
+ if(!options["topics"]) {
17
+ throw Error("Topics file path not specified");
18
+ }
19
+ if(!options["summary"]) {
20
+ throw Error("Summary file path not specified");
21
+ }
22
+ if(!options["comments"]) {
23
+ throw Error("Comments file path not specified");
24
+ }
25
+ if(!options["reportTitle"]) {
26
+ throw Error("Report title not specified");
27
+ }
28
+
29
+ const reportMetadata = {
30
+ title: options["reportTitle"],
31
+ };
32
+
33
+ // path to "data" folder
34
+ const baseOutputPath = path.join(__dirname, "./data");
35
+
36
+ copyFileSync(options["topics"], path.join(baseOutputPath, "/topic-stats.json"));
37
+ copyFileSync(options["summary"], path.join(baseOutputPath, "/summary.json"));
38
+ copyFileSync(options["comments"], path.join(baseOutputPath, "/comments.json"));
39
+ writeFileSync(path.join(baseOutputPath, "/metadata.json"), JSON.stringify(reportMetadata, null, 2));
40
+
41
+ await exec("npm run build", (error: typeof ExecException | null, stdout: string, stderr: string) => {
42
+ if(error) {
43
+ console.error(`Build failed: ${error.message}`);
44
+ return;
45
+ }
46
+ if(stderr) {
47
+ console.error(`Build errors/warnings: ${stderr}`);
48
+ }
49
+ console.log(`Build output: ${stdout}`);
50
+ console.log("Build complete");
51
+ });
52
+ }
53
+
54
+ main();
@@ -0,0 +1 @@
1
+ <router-outlet></router-outlet>
File without changes
@@ -0,0 +1,32 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { AppComponent } from './app.component';
3
+ import { CommonModule } from '@angular/common';
4
+
5
+ describe('AppComponent', () => {
6
+ let component: AppComponent;
7
+ let fixture: ComponentFixture<AppComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [
12
+ CommonModule,
13
+ AppComponent,
14
+ ],
15
+ declarations: [],
16
+ providers: [],
17
+ })
18
+ .overrideComponent(AppComponent, {
19
+ remove: { imports: [] },
20
+ add: { imports: [] },
21
+ })
22
+ .compileComponents();
23
+
24
+ fixture = TestBed.createComponent(AppComponent);
25
+ component = fixture.componentInstance;
26
+ fixture.detectChanges();
27
+ });
28
+
29
+ it('should create the app', () => {
30
+ expect(component).toBeTruthy();
31
+ });
32
+ });
@@ -0,0 +1,21 @@
1
+ import { Component } from '@angular/core';
2
+ import { MatIconRegistry } from "@angular/material/icon";
3
+ import { RouterOutlet } from '@angular/router';
4
+
5
+ @Component({
6
+ selector: 'app-root',
7
+ standalone: true,
8
+ imports: [
9
+ RouterOutlet,
10
+ ],
11
+ templateUrl: './app.component.html',
12
+ styleUrl: './app.component.scss',
13
+ })
14
+ export class AppComponent {
15
+ constructor(private matIconRegistry: MatIconRegistry) {
16
+ // set defaults for all material icons:
17
+ // use symbols (instead of icons)
18
+ // use "outlined" variation
19
+ this.matIconRegistry.setDefaultFontSetClass("material-symbols-outlined");
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
2
+ import { provideServerRendering } from '@angular/platform-server';
3
+ import { appConfig } from './app.config';
4
+
5
+ const serverConfig: ApplicationConfig = {
6
+ providers: [provideServerRendering()],
7
+ };
8
+
9
+ export const config = mergeApplicationConfig(appConfig, serverConfig);
@@ -0,0 +1,19 @@
1
+ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2
+ import { provideRouter } from '@angular/router';
3
+
4
+ import { routes } from './app.routes';
5
+ import { provideClientHydration } from '@angular/platform-browser';
6
+ import { provideAnimations } from '@angular/platform-browser/animations';
7
+ import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
8
+ import { provideMarkdown } from 'ngx-markdown';
9
+ export const appConfig: ApplicationConfig = {
10
+ providers: [
11
+ provideMarkdown(),
12
+ provideAnimations(),
13
+ provideZoneChangeDetection({ eventCoalescing: true }),
14
+ provideRouter(routes),
15
+ provideClientHydration(),
16
+ provideAnimationsAsync(),
17
+ provideAnimationsAsync(),
18
+ ],
19
+ };
@@ -0,0 +1,8 @@
1
+ import { Routes } from '@angular/router';
2
+ import { ReportComponent } from './pages/report/report.component';
3
+
4
+ export const routes: Routes = [
5
+ // Wildcard route is necessary for building "single HTML file" report.
6
+ // Do not change path to anything else.
7
+ { path: "**", component: ReportComponent },
8
+ ];