diff --git a/README.md b/README.md
index a2f98b8..6e56fc8 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,7 @@ This goal of this project is to help you get better at Angular and NgRx by resol
+
## Contributors ✨
@@ -72,7 +73,7 @@ This goal of this project is to help you get better at Angular and NgRx by resol
diff --git a/apps/testing-harness/.eslintrc.json b/apps/testing-harness/.eslintrc.json
new file mode 100644
index 0000000..b428c22
--- /dev/null
+++ b/apps/testing-harness/.eslintrc.json
@@ -0,0 +1,36 @@
+{
+ "extends": ["../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts"],
+ "rules": {
+ "@angular-eslint/directive-selector": [
+ "error",
+ {
+ "type": "attribute",
+ "prefix": "app",
+ "style": "camelCase"
+ }
+ ],
+ "@angular-eslint/component-selector": [
+ "error",
+ {
+ "type": "element",
+ "prefix": "app",
+ "style": "kebab-case"
+ }
+ ]
+ },
+ "extends": [
+ "plugin:@nx/angular",
+ "plugin:@angular-eslint/template/process-inline-templates"
+ ]
+ },
+ {
+ "files": ["*.html"],
+ "extends": ["plugin:@nx/angular-template"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/apps/testing-harness/README.md b/apps/testing-harness/README.md
new file mode 100644
index 0000000..75182b8
--- /dev/null
+++ b/apps/testing-harness/README.md
@@ -0,0 +1,32 @@
+Testing with Angular CDK Harness
+
+> Author: Thomas Laforge
+
+### Statement:
+
+The objective of this challenge is to have a better understanding of the CDK test harness API. In this initial challenge, we will only use Angular Material's built-in harnesses.
+
+The goal is to test the functionality of `child.component.ts`. I have prepared a test suite that you need to implement, but feel free to include additional tests as well.
+
+**Note:** You are welcome to use Testing Library if you wish.
+
+Documentation for CDK Component Harness is [here](https://material.angular.io/cdk/test-harnesses/overview#api-for-test-authors)
+Documentation for Angular Material component is [here](https://material.angular.io/components/button/overview)
+
+### Submitting your work
+
+1. Fork the project
+2. clone it
+3. npm install
+4. `npx nx serve testing-harness` to play with the application
+5. `npx nx test testing-harness` to test your application with Testing Library
+6. _...work on it_
+7. Commit your work
+8. Submit a PR with a title beginning with **Answer:23** that I will review and other dev can review.
+
+
+
+
+
+
+_You can ask any question on_
diff --git a/apps/testing-harness/jest.config.ts b/apps/testing-harness/jest.config.ts
new file mode 100644
index 0000000..1a72238
--- /dev/null
+++ b/apps/testing-harness/jest.config.ts
@@ -0,0 +1,21 @@
+/* eslint-disable */
+export default {
+ displayName: 'testing-harness',
+ 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-harness/project.json b/apps/testing-harness/project.json
new file mode 100644
index 0000000..3b93c76
--- /dev/null
+++ b/apps/testing-harness/project.json
@@ -0,0 +1,95 @@
+{
+ "name": "testing-harness",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "prefix": "app",
+ "sourceRoot": "apps/testing-harness/src",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/testing-harness",
+ "index": "apps/testing-harness/src/index.html",
+ "main": "apps/testing-harness/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/testing-harness/tsconfig.app.json",
+ "assets": [
+ "apps/testing-harness/src/favicon.ico",
+ "apps/testing-harness/src/assets"
+ ],
+ "styles": ["apps/testing-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": {
+ "browserTarget": "testing-harness:build:production"
+ },
+ "development": {
+ "browserTarget": "testing-harness:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "testing-harness:build"
+ }
+ },
+ "test": {
+ "executor": "@nx/jest:jest",
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+ "options": {
+ "jestConfig": "apps/testing-harness/jest.config.ts",
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "codeCoverage": true
+ }
+ }
+ },
+ "lint": {
+ "executor": "@nx/linter:eslint",
+ "outputs": ["{options.outputFile}"],
+ "options": {
+ "lintFilePatterns": [
+ "apps/testing-harness/**/*.ts",
+ "apps/testing-harness/**/*.html"
+ ]
+ }
+ }
+ }
+}
diff --git a/apps/testing-harness/src/app/app.component.ts b/apps/testing-harness/src/app/app.component.ts
new file mode 100644
index 0000000..76ccd4d
--- /dev/null
+++ b/apps/testing-harness/src/app/app.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { ChildComponent } from './child.component';
+
+@Component({
+ standalone: true,
+ imports: [ChildComponent],
+ selector: 'app-root',
+ template: ` `,
+ styles: [''],
+})
+export class AppComponent {}
diff --git a/apps/testing-harness/src/app/app.config.ts b/apps/testing-harness/src/app/app.config.ts
new file mode 100644
index 0000000..59198e6
--- /dev/null
+++ b/apps/testing-harness/src/app/app.config.ts
@@ -0,0 +1,6 @@
+import { ApplicationConfig } from '@angular/core';
+import { provideAnimations } from '@angular/platform-browser/animations';
+
+export const appConfig: ApplicationConfig = {
+ providers: [provideAnimations()],
+};
diff --git a/apps/testing-harness/src/app/child.component.spec.ts b/apps/testing-harness/src/app/child.component.spec.ts
new file mode 100644
index 0000000..8e888b3
--- /dev/null
+++ b/apps/testing-harness/src/app/child.component.spec.ts
@@ -0,0 +1,28 @@
+import { render } from '@testing-library/angular';
+import { ChildComponent } from './child.component';
+
+describe('ChildComponent', () => {
+ test('should have 1 slider, 3 checkboxes, 4 inputs, 2 buttons', async () => {
+ await render(ChildComponent);
+ });
+
+ test('should get initial value of slider thumb', async () => {
+ await render(ChildComponent);
+ });
+
+ test('set maxValue to 109 and slider max value should be 109', async () => {
+ await render(ChildComponent);
+ });
+
+ test('toggle disabled checkbox and slider should be disabled', async () => {
+ await render(ChildComponent);
+ });
+
+ test('set step value to 5 and click to forward button two times, thumb value should be 10', async () => {
+ await render(ChildComponent);
+ });
+
+ test('slider value is 5, set step value to 6, click on back button, slider value should still be 5', async () => {
+ await render(ChildComponent);
+ });
+});
diff --git a/apps/testing-harness/src/app/child.component.ts b/apps/testing-harness/src/app/child.component.ts
new file mode 100644
index 0000000..178c06c
--- /dev/null
+++ b/apps/testing-harness/src/app/child.component.ts
@@ -0,0 +1,120 @@
+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;
+ }
+ `,
+ ],
+ standalone: true,
+ 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-harness/src/assets/.gitkeep b/apps/testing-harness/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/apps/testing-harness/src/favicon.ico b/apps/testing-harness/src/favicon.ico
new file mode 100644
index 0000000..317ebcb
Binary files /dev/null and b/apps/testing-harness/src/favicon.ico differ
diff --git a/apps/testing-harness/src/index.html b/apps/testing-harness/src/index.html
new file mode 100644
index 0000000..1141708
--- /dev/null
+++ b/apps/testing-harness/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ harness
+
+
+
+
+
+
+
+
+
diff --git a/apps/testing-harness/src/main.ts b/apps/testing-harness/src/main.ts
new file mode 100644
index 0000000..514c89a
--- /dev/null
+++ b/apps/testing-harness/src/main.ts
@@ -0,0 +1,7 @@
+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-harness/src/styles.scss b/apps/testing-harness/src/styles.scss
new file mode 100644
index 0000000..c9c067e
--- /dev/null
+++ b/apps/testing-harness/src/styles.scss
@@ -0,0 +1,28 @@
+@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.core();
+
+$theme-primary: mat.define-palette(mat.$indigo-palette);
+$theme-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
+
+$theme-warn: mat.define-palette(mat.$red-palette);
+
+$theme: mat.define-light-theme(
+ (
+ color: (
+ primary: $theme-primary,
+ accent: $theme-accent,
+ warn: $theme-warn,
+ ),
+ typography: mat.define-typography-config(),
+ )
+);
+
+@include mat.dialog-theme($theme);
+@include mat.all-component-themes($theme);
diff --git a/apps/testing-harness/src/test-setup.ts b/apps/testing-harness/src/test-setup.ts
new file mode 100644
index 0000000..15de72a
--- /dev/null
+++ b/apps/testing-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-harness/tailwind.config.js b/apps/testing-harness/tailwind.config.js
new file mode 100644
index 0000000..38183db
--- /dev/null
+++ b/apps/testing-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-harness/tsconfig.app.json b/apps/testing-harness/tsconfig.app.json
new file mode 100644
index 0000000..fff4a41
--- /dev/null
+++ b/apps/testing-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-harness/tsconfig.editor.json
new file mode 100644
index 0000000..6763401
--- /dev/null
+++ b/apps/testing-harness/tsconfig.editor.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*.ts", "jest.config.ts"],
+ "compilerOptions": {
+ "types": []
+ }
+}
diff --git a/apps/testing-harness/tsconfig.json b/apps/testing-harness/tsconfig.json
new file mode 100644
index 0000000..a91d453
--- /dev/null
+++ b/apps/testing-harness/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ "target": "es2022",
+ "useDefineForClassFields": false,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.editor.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ],
+ "extends": "../../tsconfig.base.json",
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/apps/testing-harness/tsconfig.spec.json b/apps/testing-harness/tsconfig.spec.json
new file mode 100644
index 0000000..6a6661c
--- /dev/null
+++ b/apps/testing-harness/tsconfig.spec.json
@@ -0,0 +1,16 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "target": "es2016",
+ "types": ["jest", "node", "@testing-library/jest-dom"]
+ },
+ "files": ["src/test-setup.ts"],
+ "include": [
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.d.ts"
+ ]
+}