OK
diff --git a/apps/testing/modal/src/app/profil-confirmation.dialog.ts b/apps/testing/20-modal/src/app/profil-confirmation.dialog.ts
similarity index 97%
rename from apps/testing/modal/src/app/profil-confirmation.dialog.ts
rename to apps/testing/20-modal/src/app/profil-confirmation.dialog.ts
index 6af2d41dd..e77211e24 100644
--- a/apps/testing/modal/src/app/profil-confirmation.dialog.ts
+++ b/apps/testing/20-modal/src/app/profil-confirmation.dialog.ts
@@ -4,7 +4,6 @@ import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
@Component({
- standalone: true,
imports: [MatButtonModule, MatDialogModule],
template: `
Profil
diff --git a/apps/testing/20-modal/src/assets/.gitkeep b/apps/testing/20-modal/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/testing/20-modal/src/favicon.ico b/apps/testing/20-modal/src/favicon.ico
new file mode 100644
index 000000000..317ebcb23
Binary files /dev/null and b/apps/testing/20-modal/src/favicon.ico differ
diff --git a/apps/testing/20-modal/src/index.html b/apps/testing/20-modal/src/index.html
new file mode 100644
index 000000000..362733ce1
--- /dev/null
+++ b/apps/testing/20-modal/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
testing-modal
+
+
+
+
+
+
+
+
diff --git a/apps/testing/20-modal/src/main.ts b/apps/testing/20-modal/src/main.ts
new file mode 100644
index 000000000..7961924bf
--- /dev/null
+++ b/apps/testing/20-modal/src/main.ts
@@ -0,0 +1,8 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { appConfig } from './app/app.config';
+
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err),
+);
diff --git a/apps/testing/20-modal/src/styles.scss b/apps/testing/20-modal/src/styles.scss
new file mode 100644
index 000000000..77e408aa8
--- /dev/null
+++ b/apps/testing/20-modal/src/styles.scss
@@ -0,0 +1,5 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* You can add global styles to this file, and also import other style files */
diff --git a/apps/testing/20-modal/src/test-setup.ts b/apps/testing/20-modal/src/test-setup.ts
new file mode 100644
index 000000000..15de72a3c
--- /dev/null
+++ b/apps/testing/20-modal/src/test-setup.ts
@@ -0,0 +1,2 @@
+import '@testing-library/jest-dom';
+import 'jest-preset-angular/setup-jest';
diff --git a/apps/testing/20-modal/tailwind.config.js b/apps/testing/20-modal/tailwind.config.js
new file mode 100644
index 000000000..38183db2c
--- /dev/null
+++ b/apps/testing/20-modal/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/testing/router-outlet/tsconfig.app.json b/apps/testing/20-modal/tsconfig.app.json
similarity index 100%
rename from apps/testing/router-outlet/tsconfig.app.json
rename to apps/testing/20-modal/tsconfig.app.json
diff --git a/apps/testing/todos-list/tsconfig.editor.json b/apps/testing/20-modal/tsconfig.editor.json
similarity index 100%
rename from apps/testing/todos-list/tsconfig.editor.json
rename to apps/testing/20-modal/tsconfig.editor.json
diff --git a/apps/testing/nested/tsconfig.json b/apps/testing/20-modal/tsconfig.json
similarity index 100%
rename from apps/testing/nested/tsconfig.json
rename to apps/testing/20-modal/tsconfig.json
diff --git a/apps/testing/nested/tsconfig.spec.json b/apps/testing/20-modal/tsconfig.spec.json
similarity index 100%
rename from apps/testing/nested/tsconfig.spec.json
rename to apps/testing/20-modal/tsconfig.spec.json
diff --git a/apps/testing/input-output/.eslintrc.json b/apps/testing/23-harness/.eslintrc.json
similarity index 100%
rename from apps/testing/input-output/.eslintrc.json
rename to apps/testing/23-harness/.eslintrc.json
diff --git a/apps/testing/23-harness/README.md b/apps/testing/23-harness/README.md
new file mode 100644
index 000000000..541da1ac8
--- /dev/null
+++ b/apps/testing/23-harness/README.md
@@ -0,0 +1,13 @@
+# Harness
+
+> author: thomas-laforge
+
+### Run Application
+
+```bash
+npx nx serve testing-harness
+```
+
+### Documentation and Instruction
+
+Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/testing/23-harness/).
diff --git a/apps/testing/harness/jest.config.ts b/apps/testing/23-harness/jest.config.ts
similarity index 100%
rename from apps/testing/harness/jest.config.ts
rename to apps/testing/23-harness/jest.config.ts
diff --git a/apps/testing/23-harness/project.json b/apps/testing/23-harness/project.json
new file mode 100644
index 000000000..23e5b04f4
--- /dev/null
+++ b/apps/testing/23-harness/project.json
@@ -0,0 +1,86 @@
+{
+ "name": "testing-harness",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "prefix": "app",
+ "sourceRoot": "apps/testing/23-harness/src",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/testing/23-harness",
+ "index": "apps/testing/23-harness/src/index.html",
+ "main": "apps/testing/23-harness/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/testing/23-harness/tsconfig.app.json",
+ "assets": [
+ "apps/testing/23-harness/src/favicon.ico",
+ "apps/testing/23-harness/src/assets"
+ ],
+ "styles": ["apps/testing/23-harness/src/styles.scss"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "buildOptimizer": false,
+ "optimization": false,
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "namedChunks": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "testing-harness:build:production"
+ },
+ "development": {
+ "buildTarget": "testing-harness:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "testing-harness:build"
+ }
+ },
+ "test": {
+ "outputs": [
+ "{workspaceRoot}/coverage/{projectRoot}",
+ "{projectRoot}/coverage"
+ ],
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "coverage": true
+ }
+ }
+ }
+ }
+}
diff --git a/apps/testing/23-harness/src/app/app.component.ts b/apps/testing/23-harness/src/app/app.component.ts
new file mode 100644
index 000000000..7ecf1998d
--- /dev/null
+++ b/apps/testing/23-harness/src/app/app.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+import { ChildComponent } from './child.component';
+
+@Component({
+ imports: [ChildComponent],
+ selector: 'app-root',
+ template: `
+
+ `,
+ styles: [''],
+})
+export class AppComponent {}
diff --git a/apps/testing/create-harness/src/app/app.config.ts b/apps/testing/23-harness/src/app/app.config.ts
similarity index 100%
rename from apps/testing/create-harness/src/app/app.config.ts
rename to apps/testing/23-harness/src/app/app.config.ts
diff --git a/apps/testing/harness/src/app/child.component.spec.ts b/apps/testing/23-harness/src/app/child.component.spec.ts
similarity index 95%
rename from apps/testing/harness/src/app/child.component.spec.ts
rename to apps/testing/23-harness/src/app/child.component.spec.ts
index cb45d2740..1c4b236c8 100644
--- a/apps/testing/harness/src/app/child.component.spec.ts
+++ b/apps/testing/23-harness/src/app/child.component.spec.ts
@@ -18,7 +18,7 @@ describe('ChildComponent', () => {
});
});
- describe('When disabled chebkbox is toggle', () => {
+ describe('When disabled checkbox is toggled', () => {
test('Then slider is disabled', async () => {
await render(ChildComponent);
});
diff --git a/apps/testing/23-harness/src/app/child.component.ts b/apps/testing/23-harness/src/app/child.component.ts
new file mode 100644
index 000000000..935f08f89
--- /dev/null
+++ b/apps/testing/23-harness/src/app/child.component.ts
@@ -0,0 +1,119 @@
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { MatCardModule } from '@angular/material/card';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatSliderModule } from '@angular/material/slider';
+
+@Component({
+ selector: 'app-child',
+ template: `
+
+
+ Slider configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ arrow_back_ios
+
+
+
+
+
+ arrow_forward_ios
+
+
+
+
+ `,
+ styles: [
+ `
+ .mat-mdc-slider {
+ max-width: 300px;
+ width: 100%;
+ }
+
+ .mat-mdc-card + .mat-mdc-card {
+ margin-top: 8px;
+ }
+ `,
+ ],
+ imports: [
+ MatCardModule,
+ MatFormFieldModule,
+ MatInputModule,
+ FormsModule,
+ MatCheckboxModule,
+ MatSliderModule,
+ MatIconModule,
+ ],
+})
+export class ChildComponent {
+ disabled = false;
+ max = 100;
+ min = 0;
+ showTicks = false;
+ step = 1;
+ thumbLabel = false;
+ value = 0;
+
+ back() {
+ if (this.value - this.step >= this.min) {
+ this.value -= this.step;
+ }
+ }
+
+ forward() {
+ if (this.value + this.step <= this.max) {
+ this.value += this.step;
+ }
+ }
+}
diff --git a/apps/testing/23-harness/src/assets/.gitkeep b/apps/testing/23-harness/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/testing/23-harness/src/favicon.ico b/apps/testing/23-harness/src/favicon.ico
new file mode 100644
index 000000000..317ebcb23
Binary files /dev/null and b/apps/testing/23-harness/src/favicon.ico differ
diff --git a/apps/testing/23-harness/src/index.html b/apps/testing/23-harness/src/index.html
new file mode 100644
index 000000000..9b55da74f
--- /dev/null
+++ b/apps/testing/23-harness/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
testing-harness
+
+
+
+
+
+
+
+
+
diff --git a/apps/testing/23-harness/src/main.ts b/apps/testing/23-harness/src/main.ts
new file mode 100644
index 000000000..f3a7223da
--- /dev/null
+++ b/apps/testing/23-harness/src/main.ts
@@ -0,0 +1,7 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err),
+);
diff --git a/apps/testing/23-harness/src/styles.scss b/apps/testing/23-harness/src/styles.scss
new file mode 100644
index 000000000..9a29b71e6
--- /dev/null
+++ b/apps/testing/23-harness/src/styles.scss
@@ -0,0 +1,29 @@
+@use '@angular/material' as mat;
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* You can add global styles to this file, and also import other style files */
+
+@include mat.elevation-classes();
+@include mat.app-background();
+
+$theme-primary: mat.m2-define-palette(mat.$m2-indigo-palette);
+$theme-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
+
+$theme-warn: mat.m2-define-palette(mat.$m2-red-palette);
+
+$theme: mat.m2-define-light-theme(
+ (
+ color: (
+ primary: $theme-primary,
+ accent: $theme-accent,
+ warn: $theme-warn,
+ ),
+ typography: mat.m2-define-typography-config(),
+ )
+);
+
+@include mat.dialog-theme($theme);
+@include mat.all-component-themes($theme);
diff --git a/apps/testing/23-harness/src/test-setup.ts b/apps/testing/23-harness/src/test-setup.ts
new file mode 100644
index 000000000..15de72a3c
--- /dev/null
+++ b/apps/testing/23-harness/src/test-setup.ts
@@ -0,0 +1,2 @@
+import '@testing-library/jest-dom';
+import 'jest-preset-angular/setup-jest';
diff --git a/apps/testing/23-harness/tailwind.config.js b/apps/testing/23-harness/tailwind.config.js
new file mode 100644
index 000000000..38183db2c
--- /dev/null
+++ b/apps/testing/23-harness/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/testing/23-harness/tsconfig.app.json b/apps/testing/23-harness/tsconfig.app.json
new file mode 100644
index 000000000..58220429a
--- /dev/null
+++ b/apps/testing/23-harness/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"],
+ "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
+}
diff --git a/apps/testing/harness/tsconfig.editor.json b/apps/testing/23-harness/tsconfig.editor.json
similarity index 100%
rename from apps/testing/harness/tsconfig.editor.json
rename to apps/testing/23-harness/tsconfig.editor.json
diff --git a/apps/testing/create-harness/tsconfig.json b/apps/testing/23-harness/tsconfig.json
similarity index 100%
rename from apps/testing/create-harness/tsconfig.json
rename to apps/testing/23-harness/tsconfig.json
diff --git a/apps/testing/create-harness/tsconfig.spec.json b/apps/testing/23-harness/tsconfig.spec.json
similarity index 100%
rename from apps/testing/create-harness/tsconfig.spec.json
rename to apps/testing/23-harness/tsconfig.spec.json
diff --git a/apps/testing/modal/.eslintrc.json b/apps/testing/24-harness-creation/.eslintrc.json
similarity index 100%
rename from apps/testing/modal/.eslintrc.json
rename to apps/testing/24-harness-creation/.eslintrc.json
diff --git a/apps/testing/24-harness-creation/README.md b/apps/testing/24-harness-creation/README.md
new file mode 100644
index 000000000..928bb1aab
--- /dev/null
+++ b/apps/testing/24-harness-creation/README.md
@@ -0,0 +1,13 @@
+# Harness Creation
+
+> author: thomas-laforge
+
+### Run Application
+
+```bash
+npx nx serve testing-harness-creation
+```
+
+### Documentation and Instruction
+
+Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/testing/24-harness-creation/).
diff --git a/apps/testing/24-harness-creation/jest.config.ts b/apps/testing/24-harness-creation/jest.config.ts
new file mode 100644
index 000000000..c4b9f08db
--- /dev/null
+++ b/apps/testing/24-harness-creation/jest.config.ts
@@ -0,0 +1,21 @@
+/* eslint-disable */
+export default {
+ displayName: 'testing-harness-creation',
+ preset: '../../../jest.preset.js',
+ setupFilesAfterEnv: ['
/src/test-setup.ts'],
+ transform: {
+ '^.+\\.(ts|mjs|js|html)$': [
+ 'jest-preset-angular',
+ {
+ tsconfig: '/tsconfig.spec.json',
+ stringifyContentPathRegex: '\\.(html|svg)$',
+ },
+ ],
+ },
+ transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
+ snapshotSerializers: [
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
+ 'jest-preset-angular/build/serializers/ng-snapshot',
+ 'jest-preset-angular/build/serializers/html-comment',
+ ],
+};
diff --git a/apps/testing/24-harness-creation/project.json b/apps/testing/24-harness-creation/project.json
new file mode 100644
index 000000000..f65194122
--- /dev/null
+++ b/apps/testing/24-harness-creation/project.json
@@ -0,0 +1,86 @@
+{
+ "name": "testing-harness-creation",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "prefix": "app",
+ "sourceRoot": "apps/testing/24-harness-creation/src",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/testing/24-harness-creation",
+ "index": "apps/testing/24-harness-creation/src/index.html",
+ "main": "apps/testing/24-harness-creation/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/testing/24-harness-creation/tsconfig.app.json",
+ "assets": [
+ "apps/testing/24-harness-creation/src/favicon.ico",
+ "apps/testing/24-harness-creation/src/assets"
+ ],
+ "styles": ["apps/testing/24-harness-creation/src/styles.scss"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "buildOptimizer": false,
+ "optimization": false,
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "namedChunks": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "testing-harness-creation:build:production"
+ },
+ "development": {
+ "buildTarget": "testing-harness-creation:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "testing-harness-creation:build"
+ }
+ },
+ "test": {
+ "outputs": [
+ "{workspaceRoot}/coverage/{projectRoot}",
+ "{projectRoot}/coverage"
+ ],
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "coverage": true
+ }
+ }
+ }
+ }
+}
diff --git a/apps/testing/create-harness/src/app/app.component.spec.ts b/apps/testing/24-harness-creation/src/app/app.component.spec.ts
similarity index 100%
rename from apps/testing/create-harness/src/app/app.component.spec.ts
rename to apps/testing/24-harness-creation/src/app/app.component.spec.ts
diff --git a/apps/testing/24-harness-creation/src/app/app.component.ts b/apps/testing/24-harness-creation/src/app/app.component.ts
new file mode 100644
index 000000000..b1c16240e
--- /dev/null
+++ b/apps/testing/24-harness-creation/src/app/app.component.ts
@@ -0,0 +1,27 @@
+import { Component, signal } from '@angular/core';
+import { SliderComponent } from './slider.component';
+
+@Component({
+ imports: [SliderComponent],
+ selector: 'app-root',
+ template: `
+ Slider 1: {{ slider1Value() }}
+
+ Slider 2: {{ slider2Value() }}
+ Enabled only if Slider 1 > 20
+
+ `,
+ styles: [''],
+})
+export class AppComponent {
+ slider1Value = signal(10);
+ slider2Value = signal(0);
+}
diff --git a/apps/testing/harness/src/app/app.config.ts b/apps/testing/24-harness-creation/src/app/app.config.ts
similarity index 100%
rename from apps/testing/harness/src/app/app.config.ts
rename to apps/testing/24-harness-creation/src/app/app.config.ts
diff --git a/apps/testing/create-harness/src/app/slider.component.ts b/apps/testing/24-harness-creation/src/app/slider.component.ts
similarity index 96%
rename from apps/testing/create-harness/src/app/slider.component.ts
rename to apps/testing/24-harness-creation/src/app/slider.component.ts
index 1c07c111c..4a3e429bb 100644
--- a/apps/testing/create-harness/src/app/slider.component.ts
+++ b/apps/testing/24-harness-creation/src/app/slider.component.ts
@@ -11,7 +11,7 @@ import { skip } from 'rxjs';
template: `
-
+
arrow_back_ios
@@ -48,7 +48,6 @@ import { skip } from 'rxjs';
}
`,
],
- standalone: true,
imports: [MatCardModule, MatSliderModule, MatIconModule, FormsModule],
})
export class SliderComponent implements OnInit {
diff --git a/apps/testing/create-harness/src/app/slider.harness.ts b/apps/testing/24-harness-creation/src/app/slider.harness.ts
similarity index 100%
rename from apps/testing/create-harness/src/app/slider.harness.ts
rename to apps/testing/24-harness-creation/src/app/slider.harness.ts
diff --git a/apps/testing/24-harness-creation/src/assets/.gitkeep b/apps/testing/24-harness-creation/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/testing/24-harness-creation/src/favicon.ico b/apps/testing/24-harness-creation/src/favicon.ico
new file mode 100644
index 000000000..317ebcb23
Binary files /dev/null and b/apps/testing/24-harness-creation/src/favicon.ico differ
diff --git a/apps/testing/24-harness-creation/src/index.html b/apps/testing/24-harness-creation/src/index.html
new file mode 100644
index 000000000..c66440e5c
--- /dev/null
+++ b/apps/testing/24-harness-creation/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
testing-harness-creation
+
+
+
+
+
+
+
+
+
diff --git a/apps/testing/24-harness-creation/src/main.ts b/apps/testing/24-harness-creation/src/main.ts
new file mode 100644
index 000000000..f3a7223da
--- /dev/null
+++ b/apps/testing/24-harness-creation/src/main.ts
@@ -0,0 +1,7 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err),
+);
diff --git a/apps/testing/24-harness-creation/src/styles.scss b/apps/testing/24-harness-creation/src/styles.scss
new file mode 100644
index 000000000..1430d2f9b
--- /dev/null
+++ b/apps/testing/24-harness-creation/src/styles.scss
@@ -0,0 +1,30 @@
+/* You can add global styles to this file, and also import other style files */
+@use '@angular/material' as mat;
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* You can add global styles to this file, and also import other style files */
+
+@include mat.elevation-classes();
+@include mat.app-background();
+
+$theme-primary: mat.m2-define-palette(mat.$m2-indigo-palette);
+$theme-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
+
+$theme-warn: mat.m2-define-palette(mat.$m2-red-palette);
+
+$theme: mat.m2-define-light-theme(
+ (
+ color: (
+ primary: $theme-primary,
+ accent: $theme-accent,
+ warn: $theme-warn,
+ ),
+ typography: mat.m2-define-typography-config(),
+ )
+);
+
+@include mat.dialog-theme($theme);
+@include mat.all-component-themes($theme);
diff --git a/apps/testing/24-harness-creation/src/test-setup.ts b/apps/testing/24-harness-creation/src/test-setup.ts
new file mode 100644
index 000000000..15de72a3c
--- /dev/null
+++ b/apps/testing/24-harness-creation/src/test-setup.ts
@@ -0,0 +1,2 @@
+import '@testing-library/jest-dom';
+import 'jest-preset-angular/setup-jest';
diff --git a/apps/testing/24-harness-creation/tailwind.config.js b/apps/testing/24-harness-creation/tailwind.config.js
new file mode 100644
index 000000000..38183db2c
--- /dev/null
+++ b/apps/testing/24-harness-creation/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/testing/24-harness-creation/tsconfig.app.json b/apps/testing/24-harness-creation/tsconfig.app.json
new file mode 100644
index 000000000..58220429a
--- /dev/null
+++ b/apps/testing/24-harness-creation/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"],
+ "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
+}
diff --git a/apps/testing/24-harness-creation/tsconfig.editor.json b/apps/testing/24-harness-creation/tsconfig.editor.json
new file mode 100644
index 000000000..4ee639340
--- /dev/null
+++ b/apps/testing/24-harness-creation/tsconfig.editor.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "compilerOptions": {
+ "types": []
+ }
+}
diff --git a/apps/testing/harness/tsconfig.json b/apps/testing/24-harness-creation/tsconfig.json
similarity index 100%
rename from apps/testing/harness/tsconfig.json
rename to apps/testing/24-harness-creation/tsconfig.json
diff --git a/apps/testing/harness/tsconfig.spec.json b/apps/testing/24-harness-creation/tsconfig.spec.json
similarity index 100%
rename from apps/testing/harness/tsconfig.spec.json
rename to apps/testing/24-harness-creation/tsconfig.spec.json
diff --git a/apps/testing/router-outlet/.eslintrc.json b/apps/testing/28-checkbox/.eslintrc.json
similarity index 100%
rename from apps/testing/router-outlet/.eslintrc.json
rename to apps/testing/28-checkbox/.eslintrc.json
diff --git a/apps/testing/28-checkbox/README.md b/apps/testing/28-checkbox/README.md
new file mode 100644
index 000000000..905db5f52
--- /dev/null
+++ b/apps/testing/28-checkbox/README.md
@@ -0,0 +1,13 @@
+# Checkbox
+
+> author: thomas-laforge
+
+### Run Application
+
+```bash
+npx nx serve testing-checkbox
+```
+
+### Documentation and Instruction
+
+Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/testing/28-checkbox/).
diff --git a/apps/testing/28-checkbox/jest.config.ts b/apps/testing/28-checkbox/jest.config.ts
new file mode 100644
index 000000000..c27a02c1e
--- /dev/null
+++ b/apps/testing/28-checkbox/jest.config.ts
@@ -0,0 +1,22 @@
+/* eslint-disable */
+export default {
+ displayName: 'testing-checkbox',
+ preset: '../../../jest.preset.js',
+ setupFilesAfterEnv: ['
/src/test-setup.ts'],
+ coverageDirectory: '../../../coverage/apps/testing/28-checkbox',
+ transform: {
+ '^.+\\.(ts|mjs|js|html)$': [
+ 'jest-preset-angular',
+ {
+ tsconfig: '/tsconfig.spec.json',
+ stringifyContentPathRegex: '\\.(html|svg)$',
+ },
+ ],
+ },
+ transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
+ snapshotSerializers: [
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
+ 'jest-preset-angular/build/serializers/ng-snapshot',
+ 'jest-preset-angular/build/serializers/html-comment',
+ ],
+};
diff --git a/apps/testing/28-checkbox/project.json b/apps/testing/28-checkbox/project.json
new file mode 100644
index 000000000..982e85c2e
--- /dev/null
+++ b/apps/testing/28-checkbox/project.json
@@ -0,0 +1,82 @@
+{
+ "name": "testing-checkbox",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "prefix": "app",
+ "sourceRoot": "apps/testing/28-checkbox/src",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/testing/28-checkbox",
+ "index": "apps/testing/28-checkbox/src/index.html",
+ "main": "apps/testing/28-checkbox/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/testing/28-checkbox/tsconfig.app.json",
+ "assets": [
+ "apps/testing/28-checkbox/src/favicon.ico",
+ "apps/testing/28-checkbox/src/assets"
+ ],
+ "styles": ["apps/testing/28-checkbox/src/styles.scss"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "buildOptimizer": false,
+ "optimization": false,
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "namedChunks": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "testing-checkbox:build:production"
+ },
+ "development": {
+ "buildTarget": "testing-checkbox:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "testing-checkbox:build"
+ }
+ },
+ "test": {
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "coverage": true
+ }
+ }
+ }
+ }
+}
diff --git a/apps/testing/checkbox/src/app/app.component.spec.ts b/apps/testing/28-checkbox/src/app/app.component.spec.ts
similarity index 100%
rename from apps/testing/checkbox/src/app/app.component.spec.ts
rename to apps/testing/28-checkbox/src/app/app.component.spec.ts
diff --git a/apps/testing/28-checkbox/src/app/app.component.ts b/apps/testing/28-checkbox/src/app/app.component.ts
new file mode 100644
index 000000000..936cc781e
--- /dev/null
+++ b/apps/testing/28-checkbox/src/app/app.component.ts
@@ -0,0 +1,29 @@
+import { Component } from '@angular/core';
+
+@Component({
+ standalone: true,
+ selector: 'app-root',
+ template: `
+ Agreed
+
+
+ Submit
+
+ `,
+})
+export class AppComponent {
+ check = false;
+
+ toggleCheck() {
+ this.check = !this.check;
+ }
+}
diff --git a/apps/testing/28-checkbox/src/assets/.gitkeep b/apps/testing/28-checkbox/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/testing/28-checkbox/src/favicon.ico b/apps/testing/28-checkbox/src/favicon.ico
new file mode 100644
index 000000000..317ebcb23
Binary files /dev/null and b/apps/testing/28-checkbox/src/favicon.ico differ
diff --git a/apps/testing/28-checkbox/src/index.html b/apps/testing/28-checkbox/src/index.html
new file mode 100644
index 000000000..75e95fe9f
--- /dev/null
+++ b/apps/testing/28-checkbox/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ testing-checkbox
+
+
+
+
+
+
+
+
diff --git a/apps/testing/28-checkbox/src/main.ts b/apps/testing/28-checkbox/src/main.ts
new file mode 100644
index 000000000..31c5da482
--- /dev/null
+++ b/apps/testing/28-checkbox/src/main.ts
@@ -0,0 +1,4 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent).catch((err) => console.error(err));
diff --git a/apps/testing/28-checkbox/src/styles.scss b/apps/testing/28-checkbox/src/styles.scss
new file mode 100644
index 000000000..77e408aa8
--- /dev/null
+++ b/apps/testing/28-checkbox/src/styles.scss
@@ -0,0 +1,5 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* You can add global styles to this file, and also import other style files */
diff --git a/apps/testing/28-checkbox/src/test-setup.ts b/apps/testing/28-checkbox/src/test-setup.ts
new file mode 100644
index 000000000..15de72a3c
--- /dev/null
+++ b/apps/testing/28-checkbox/src/test-setup.ts
@@ -0,0 +1,2 @@
+import '@testing-library/jest-dom';
+import 'jest-preset-angular/setup-jest';
diff --git a/apps/testing/28-checkbox/tailwind.config.js b/apps/testing/28-checkbox/tailwind.config.js
new file mode 100644
index 000000000..38183db2c
--- /dev/null
+++ b/apps/testing/28-checkbox/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/testing/28-checkbox/tsconfig.app.json b/apps/testing/28-checkbox/tsconfig.app.json
new file mode 100644
index 000000000..58220429a
--- /dev/null
+++ b/apps/testing/28-checkbox/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"],
+ "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
+}
diff --git a/apps/testing/28-checkbox/tsconfig.editor.json b/apps/testing/28-checkbox/tsconfig.editor.json
new file mode 100644
index 000000000..8ae117d96
--- /dev/null
+++ b/apps/testing/28-checkbox/tsconfig.editor.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "compilerOptions": {
+ "types": ["jest", "node"]
+ }
+}
diff --git a/apps/testing/checkbox/tsconfig.json b/apps/testing/28-checkbox/tsconfig.json
similarity index 100%
rename from apps/testing/checkbox/tsconfig.json
rename to apps/testing/28-checkbox/tsconfig.json
diff --git a/apps/testing/router-outlet/tsconfig.spec.json b/apps/testing/28-checkbox/tsconfig.spec.json
similarity index 100%
rename from apps/testing/router-outlet/tsconfig.spec.json
rename to apps/testing/28-checkbox/tsconfig.spec.json
diff --git a/apps/testing/table/.eslintrc.json b/apps/testing/29-real-life-application/.eslintrc.json
similarity index 100%
rename from apps/testing/table/.eslintrc.json
rename to apps/testing/29-real-life-application/.eslintrc.json
diff --git a/apps/testing/29-real-life-application/README.md b/apps/testing/29-real-life-application/README.md
new file mode 100644
index 000000000..8dcfe21e6
--- /dev/null
+++ b/apps/testing/29-real-life-application/README.md
@@ -0,0 +1,13 @@
+# Real-life Application
+
+> author: thomas-laforge
+
+### Run Application
+
+```bash
+npx nx serve testing-real-life-application
+```
+
+### Documentation and Instruction
+
+Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/testing/29-real-application/).
diff --git a/apps/testing/29-real-life-application/cypress.config.ts b/apps/testing/29-real-life-application/cypress.config.ts
new file mode 100644
index 000000000..1abef9c0c
--- /dev/null
+++ b/apps/testing/29-real-life-application/cypress.config.ts
@@ -0,0 +1,6 @@
+import { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+ component: nxComponentTestingPreset(__filename),
+});
diff --git a/apps/testing/table/cypress/fixtures/example.json b/apps/testing/29-real-life-application/cypress/fixtures/example.json
similarity index 100%
rename from apps/testing/table/cypress/fixtures/example.json
rename to apps/testing/29-real-life-application/cypress/fixtures/example.json
diff --git a/apps/testing/29-real-life-application/cypress/support/commands.ts b/apps/testing/29-real-life-application/cypress/support/commands.ts
new file mode 100644
index 000000000..b5d8a9582
--- /dev/null
+++ b/apps/testing/29-real-life-application/cypress/support/commands.ts
@@ -0,0 +1,25 @@
+///
+import { mount } from 'cypress/angular';
+
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Cypress {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ interface Chainable {
+ login(email: string, password: string): void;
+ mount: typeof mount;
+ }
+ }
+}
+
+Cypress.Commands.add('mount', mount);
diff --git a/apps/testing/29-real-life-application/cypress/support/component-index.html b/apps/testing/29-real-life-application/cypress/support/component-index.html
new file mode 100644
index 000000000..57c8a3df4
--- /dev/null
+++ b/apps/testing/29-real-life-application/cypress/support/component-index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ testing-todos-list Components App
+
+
+
+
+
diff --git a/apps/testing/29-real-life-application/cypress/support/component.ts b/apps/testing/29-real-life-application/cypress/support/component.ts
new file mode 100644
index 000000000..e7c3e3cbc
--- /dev/null
+++ b/apps/testing/29-real-life-application/cypress/support/component.ts
@@ -0,0 +1,17 @@
+// ***********************************************************
+// This example support/component.ts is processed and
+// loaded automatically before your subscription files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.ts using ES2015 syntax:
+import './commands';
diff --git a/apps/testing/table/cypress/tsconfig.json b/apps/testing/29-real-life-application/cypress/tsconfig.json
similarity index 100%
rename from apps/testing/table/cypress/tsconfig.json
rename to apps/testing/29-real-life-application/cypress/tsconfig.json
diff --git a/apps/testing/29-real-life-application/jest.config.ts b/apps/testing/29-real-life-application/jest.config.ts
new file mode 100644
index 000000000..b78a06561
--- /dev/null
+++ b/apps/testing/29-real-life-application/jest.config.ts
@@ -0,0 +1,21 @@
+/* eslint-disable */
+export default {
+ displayName: 'testing-real-life-application',
+ preset: '../../../jest.preset.js',
+ setupFilesAfterEnv: ['/src/test-setup.ts'],
+ transform: {
+ '^.+\\.(ts|mjs|js|html)$': [
+ 'jest-preset-angular',
+ {
+ tsconfig: '/tsconfig.spec.json',
+ stringifyContentPathRegex: '\\.(html|svg)$',
+ },
+ ],
+ },
+ transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
+ snapshotSerializers: [
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
+ 'jest-preset-angular/build/serializers/ng-snapshot',
+ 'jest-preset-angular/build/serializers/html-comment',
+ ],
+};
diff --git a/apps/testing/29-real-life-application/project.json b/apps/testing/29-real-life-application/project.json
new file mode 100644
index 000000000..c6f1c12d9
--- /dev/null
+++ b/apps/testing/29-real-life-application/project.json
@@ -0,0 +1,99 @@
+{
+ "name": "testing-real-life-application",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "sourceRoot": "apps/testing/29-real-life-application/src",
+ "prefix": "app",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/testing/29-real-life-application",
+ "index": "apps/testing/29-real-life-application/src/index.html",
+ "main": "apps/testing/29-real-life-application/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/testing/29-real-life-application/tsconfig.app.json",
+ "inlineStyleLanguage": "scss",
+ "assets": [
+ "apps/testing/29-real-life-application/src/favicon.ico",
+ "apps/testing/29-real-life-application/src/assets"
+ ],
+ "styles": [
+ "apps/testing/29-real-life-application/src/styles.scss",
+ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "buildOptimizer": false,
+ "optimization": false,
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "namedChunks": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "testing-real-life-application:build:production"
+ },
+ "development": {
+ "buildTarget": "testing-real-life-application:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "testing-real-life-application:build"
+ }
+ },
+ "test": {
+ "outputs": [
+ "{workspaceRoot}/coverage/{projectRoot}",
+ "{projectRoot}/coverage"
+ ],
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "coverage": true
+ }
+ }
+ },
+ "component-test": {
+ "executor": "@nx/cypress:cypress",
+ "options": {
+ "cypressConfig": "apps/testing/29-real-life-application/cypress.config.ts",
+ "testingType": "component",
+ "skipServe": true,
+ "devServerTarget": "testing-real-life-application:build"
+ }
+ }
+ }
+}
diff --git a/apps/testing/29-real-life-application/src/app/app.component.ts b/apps/testing/29-real-life-application/src/app/app.component.ts
new file mode 100644
index 000000000..a4b00aaec
--- /dev/null
+++ b/apps/testing/29-real-life-application/src/app/app.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ imports: [RouterOutlet],
+ template: `
+
+ `,
+})
+export class AppComponent {}
diff --git a/apps/testing/todos-list/src/app/app.config.ts b/apps/testing/29-real-life-application/src/app/app.config.ts
similarity index 100%
rename from apps/testing/todos-list/src/app/app.config.ts
rename to apps/testing/29-real-life-application/src/app/app.config.ts
diff --git a/apps/testing/todos-list/src/app/app.route.ts b/apps/testing/29-real-life-application/src/app/app.route.ts
similarity index 100%
rename from apps/testing/todos-list/src/app/app.route.ts
rename to apps/testing/29-real-life-application/src/app/app.route.ts
diff --git a/apps/testing/todos-list/src/app/backend.service.ts b/apps/testing/29-real-life-application/src/app/backend.service.ts
similarity index 98%
rename from apps/testing/todos-list/src/app/backend.service.ts
rename to apps/testing/29-real-life-application/src/app/backend.service.ts
index 2af814b73..08929eda4 100644
--- a/apps/testing/todos-list/src/app/backend.service.ts
+++ b/apps/testing/29-real-life-application/src/app/backend.service.ts
@@ -101,7 +101,7 @@ export class BackendService {
const updatedTicket = { ...foundTicket, ...updates };
this.storedTickets = this.storedTickets.map((t) =>
- t.id === ticketId ? updatedTicket : t
+ t.id === ticketId ? updatedTicket : t,
);
return of(updatedTicket).pipe(delay(randomDelay()));
diff --git a/apps/testing/29-real-life-application/src/app/detail/detail.component.ts b/apps/testing/29-real-life-application/src/app/detail/detail.component.ts
new file mode 100644
index 000000000..1afdfb31f
--- /dev/null
+++ b/apps/testing/29-real-life-application/src/app/detail/detail.component.ts
@@ -0,0 +1,63 @@
+import { AsyncPipe, NgIf } from '@angular/common';
+import { Component, inject } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { RouterLink } from '@angular/router';
+import { LetDirective } from '@ngrx/component';
+import { provideComponentStore } from '@ngrx/component-store';
+import { DetailStore } from './detail.store';
+
+@Component({
+ selector: 'app-detail',
+ imports: [
+ MatButtonModule,
+ RouterLink,
+ NgIf,
+ AsyncPipe,
+ MatProgressBarModule,
+ LetDirective,
+ ],
+ template: `
+ Ticket Detail:
+
+
+
+
+ Ticket:
+ {{ ticket.id }}
+
+
+ Description:
+ {{ ticket.description }}
+
+
+ AssigneeId:
+ {{ ticket.assigneeId }}
+
+
+ Is done:
+ {{ ticket.completed }}
+
+
+
+
+
+ Back
+
+ `,
+ providers: [provideComponentStore(DetailStore)],
+ host: {
+ class: 'p-5 block',
+ },
+})
+export class DetailComponent {
+ vm$ = inject(DetailStore).vm$;
+}
diff --git a/apps/testing/todos-list/src/app/detail/detail.store.ts b/apps/testing/29-real-life-application/src/app/detail/detail.store.ts
similarity index 84%
rename from apps/testing/todos-list/src/app/detail/detail.store.ts
rename to apps/testing/29-real-life-application/src/app/detail/detail.store.ts
index 9d0f421ee..9425b25b2 100644
--- a/apps/testing/todos-list/src/app/detail/detail.store.ts
+++ b/apps/testing/29-real-life-application/src/app/detail/detail.store.ts
@@ -36,14 +36,17 @@ export class DetailStore
loading: this.loading$,
});
- constructor(private backend: BackendService, private route: ActivatedRoute) {
+ constructor(
+ private backend: BackendService,
+ private route: ActivatedRoute,
+ ) {
super(initialState);
}
readonly loadTicket = this.effect(
pipe(
concatLatestFrom(() =>
- this.route.params.pipe(map((p) => p[PARAM_TICKET_ID]))
+ this.route.params.pipe(map((p) => p[PARAM_TICKET_ID])),
),
tap(() => this.patchState({ loading: true, error: '' })),
mergeMap(([, id]) =>
@@ -54,11 +57,11 @@ export class DetailStore
loading: false,
ticket,
}),
- (error: unknown) => this.patchState({ error })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error }),
+ ),
+ ),
+ ),
+ ),
);
ngrxOnStateInit() {
diff --git a/apps/testing/todos-list/src/app/list/list.component.spec.ts b/apps/testing/29-real-life-application/src/app/list/list.component.spec.ts
similarity index 100%
rename from apps/testing/todos-list/src/app/list/list.component.spec.ts
rename to apps/testing/29-real-life-application/src/app/list/list.component.spec.ts
diff --git a/apps/testing/29-real-life-application/src/app/list/list.component.ts b/apps/testing/29-real-life-application/src/app/list/list.component.ts
new file mode 100644
index 000000000..64b8f6e63
--- /dev/null
+++ b/apps/testing/29-real-life-application/src/app/list/list.component.ts
@@ -0,0 +1,74 @@
+import { NgFor, NgIf } from '@angular/common';
+import { Component, OnInit, inject } from '@angular/core';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { LetDirective } from '@ngrx/component';
+import { provideComponentStore } from '@ngrx/component-store';
+import { TicketStore } from './ticket.store';
+import { AddComponent } from './ui/add.component';
+import { RowComponent } from './ui/row.component';
+
+@Component({
+ selector: 'app-list',
+ imports: [
+ ReactiveFormsModule,
+ AddComponent,
+ RowComponent,
+ MatFormFieldModule,
+ MatProgressBarModule,
+ NgIf,
+ NgFor,
+ MatInputModule,
+ LetDirective,
+ ],
+ template: `
+ Tickets
+
+
+ Search
+
+
+
+
+
+
+
+
+
+
+ `,
+ providers: [provideComponentStore(TicketStore)],
+ host: {
+ class: 'p-5 block',
+ },
+})
+export class ListComponent implements OnInit {
+ ticketStore = inject(TicketStore);
+ readonly vm$ = this.ticketStore.vm$;
+
+ search = new FormControl();
+
+ ngOnInit(): void {
+ this.ticketStore.search(this.search.valueChanges);
+ }
+}
diff --git a/apps/testing/todos-list/src/app/list/ticket.store.spec.ts b/apps/testing/29-real-life-application/src/app/list/ticket.store.spec.ts
similarity index 100%
rename from apps/testing/todos-list/src/app/list/ticket.store.spec.ts
rename to apps/testing/29-real-life-application/src/app/list/ticket.store.spec.ts
diff --git a/apps/testing/todos-list/src/app/list/ticket.store.ts b/apps/testing/29-real-life-application/src/app/list/ticket.store.ts
similarity index 93%
rename from apps/testing/todos-list/src/app/list/ticket.store.ts
rename to apps/testing/29-real-life-application/src/app/list/ticket.store.ts
index b00d049b4..f8bf979a9 100644
--- a/apps/testing/todos-list/src/app/list/ticket.store.ts
+++ b/apps/testing/29-real-life-application/src/app/list/ticket.store.ts
@@ -47,7 +47,7 @@ export class TicketStore
users.find((user) => user.id === ticket.assigneeId)?.name ??
'unassigned',
}))
- : tickets
+ : tickets,
);
readonly tickets$ = this.select(
@@ -55,8 +55,8 @@ export class TicketStore
this.search$,
(tickets, search) =>
tickets.filter((t) =>
- t.description.toLowerCase().includes(search.toLowerCase())
- )
+ t.description.toLowerCase().includes(search.toLowerCase()),
+ ),
);
readonly vm$ = this.select(
@@ -66,7 +66,7 @@ export class TicketStore
loading: this.loading$,
error: this.error$,
},
- { debounce: true }
+ { debounce: true },
);
readonly updateAssignee = this.updater((state, ticket: Ticket) => {
@@ -107,11 +107,11 @@ export class TicketStore
loading: false,
tickets,
}),
- (error: unknown) => this.patchState({ error, loading: false })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error, loading: false }),
+ ),
+ ),
+ ),
+ ),
);
readonly loadUsers = this.effect(
@@ -125,11 +125,11 @@ export class TicketStore
loading: false,
users,
}),
- (error: unknown) => this.patchState({ error, loading: false })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error, loading: false }),
+ ),
+ ),
+ ),
+ ),
);
readonly addTicket = this.effect(
@@ -143,11 +143,11 @@ export class TicketStore
loading: false,
tickets: [...state.tickets, newTicket],
})),
- (error: unknown) => this.patchState({ error, loading: false })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error, loading: false }),
+ ),
+ ),
+ ),
+ ),
);
readonly assignTicket = this.effect<{ userId: number; ticketId: number }>(
@@ -157,11 +157,11 @@ export class TicketStore
this.backend.assign(info.ticketId, Number(info.userId)).pipe(
tapResponse(
(newTicket) => this.updateAssignee(newTicket),
- (error: unknown) => this.patchState({ error, loading: false })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error, loading: false }),
+ ),
+ ),
+ ),
+ ),
);
readonly done = this.effect(
@@ -171,10 +171,10 @@ export class TicketStore
this.backend.complete(ticketId, true).pipe(
tapResponse(
(newTicket) => this.updateAssignee(newTicket),
- (error: unknown) => this.patchState({ error, loading: false })
- )
- )
- )
- )
+ (error: unknown) => this.patchState({ error, loading: false }),
+ ),
+ ),
+ ),
+ ),
);
}
diff --git a/apps/testing/29-real-life-application/src/app/list/ui/add.component.ts b/apps/testing/29-real-life-application/src/app/list/ui/add.component.ts
new file mode 100644
index 000000000..c48e85a9a
--- /dev/null
+++ b/apps/testing/29-real-life-application/src/app/list/ui/add.component.ts
@@ -0,0 +1,61 @@
+import { NgIf } from '@angular/common';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import {
+ FormControl,
+ FormGroup,
+ ReactiveFormsModule,
+ Validators,
+} from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+
+@Component({
+ selector: 'app-add',
+ imports: [
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatButtonModule,
+ NgIf,
+ ],
+ template: `
+
+ `,
+})
+export class AddComponent {
+ @Input() loading = false;
+
+ @Output() addTicket = new EventEmitter();
+
+ form = new FormGroup({
+ description: new FormControl(null, Validators.required),
+ });
+
+ submit() {
+ if (this.form.valid) {
+ this.addTicket.emit(this.form.value.description ?? '');
+ }
+ }
+}
diff --git a/apps/testing/todos-list/src/app/list/ui/row.component.spec.ts b/apps/testing/29-real-life-application/src/app/list/ui/row.component.spec.ts
similarity index 100%
rename from apps/testing/todos-list/src/app/list/ui/row.component.spec.ts
rename to apps/testing/29-real-life-application/src/app/list/ui/row.component.spec.ts
diff --git a/apps/testing/todos-list/src/app/list/ui/row.component.ts b/apps/testing/29-real-life-application/src/app/list/ui/row.component.ts
similarity index 78%
rename from apps/testing/todos-list/src/app/list/ui/row.component.ts
rename to apps/testing/29-real-life-application/src/app/list/ui/row.component.ts
index 1132890bb..12c7d5367 100644
--- a/apps/testing/todos-list/src/app/list/ui/row.component.ts
+++ b/apps/testing/29-real-life-application/src/app/list/ui/row.component.ts
@@ -10,7 +10,6 @@ import { Ticket, TicketUser, User } from '../../backend.service';
@Component({
selector: 'app-row',
- standalone: true,
imports: [
RouterLink,
ReactiveFormsModule,
@@ -22,30 +21,38 @@ import { Ticket, TicketUser, User } from '../../backend.service';
],
template: `
- Ticket: {{ ticket.id }}
- Description: {{ ticket.description }}
+ Ticket:
+ {{ ticket.id }}
- Assignee: {{ $any(ticket).assignee }}
+ Description:
+ {{ ticket.description }}
+
+
+ Assignee:
+ {{ $any(ticket).assignee }}
+
+
+ Done:
+ {{ ticket.completed }}
- Done: {{ ticket.completed }}