Merge branch 'main' into answer29-links

This commit is contained in:
Laforge Thomas
2023-10-03 21:27:50 +02:00
committed by GitHub
83 changed files with 1113 additions and 956 deletions

View File

@@ -3,7 +3,9 @@
"projectOwner": "tomalaforge", "projectOwner": "tomalaforge",
"repoType": "github", "repoType": "github",
"repoHost": "https://github.com", "repoHost": "https://github.com",
"files": ["README.md"], "files": [
"README.md"
],
"imageSize": 100, "imageSize": 100,
"commit": true, "commit": true,
"commitConvention": "angular", "commitConvention": "angular",
@@ -13,31 +15,63 @@
"name": "Laforge Thomas", "name": "Laforge Thomas",
"avatar_url": "https://avatars.githubusercontent.com/u/30832608?v=4", "avatar_url": "https://avatars.githubusercontent.com/u/30832608?v=4",
"profile": "https://thomaslaforge.dev/home", "profile": "https://thomaslaforge.dev/home",
"contributions": ["code", "doc", "content", "ideas", "design"] "contributions": [
"code",
"doc",
"content",
"ideas",
"design"
]
}, },
{ {
"login": "alan-bio", "login": "alan-bio",
"name": "Alan Dragicevich", "name": "Alan Dragicevich",
"avatar_url": "https://avatars.githubusercontent.com/u/31838230?v=4", "avatar_url": "https://avatars.githubusercontent.com/u/31838230?v=4",
"profile": "https://github.com/alan-bio", "profile": "https://github.com/alan-bio",
"contributions": ["doc"] "contributions": [
"doc"
]
}, },
{ {
"login": "edimitchel", "login": "edimitchel",
"name": "Michel EDIGHOFFER", "name": "Michel EDIGHOFFER",
"avatar_url": "https://avatars.githubusercontent.com/u/2922851?v=4", "avatar_url": "https://avatars.githubusercontent.com/u/2922851?v=4",
"profile": "https://github.com/edimitchel", "profile": "https://github.com/edimitchel",
"contributions": ["doc"] "contributions": [
"doc"
]
}, },
{ {
"login": "gsgonzalez88", "login": "gsgonzalez88",
"name": "Gerardo Sebastian Gonzalez", "name": "Gerardo Sebastian Gonzalez",
"avatar_url": "https://avatars.githubusercontent.com/u/39884678?v=4", "avatar_url": "https://avatars.githubusercontent.com/u/39884678?v=4",
"profile": "https://github.com/gsgonzalez88", "profile": "https://github.com/gsgonzalez88",
"contributions": ["doc"] "contributions": [
"doc"
]
},
{
"login": "marryday",
"name": "Evseev Yuriy",
"avatar_url": "https://avatars.githubusercontent.com/u/57489315?v=4",
"profile": "https://github.com/marryday",
"contributions": [
"bug"
]
},
{
"login": "tomer953",
"name": "Tomer953",
"avatar_url": "https://avatars.githubusercontent.com/u/1807493?v=4",
"profile": "https://github.com/tomer953",
"contributions": [
"bug",
"doc",
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,
"linkToUsage": true "linkToUsage": true,
"commitType": "docs"
} }

View File

@@ -1,24 +1,3 @@
# Contributing # Contributing
> Thank you for considering contributing to this project. Your help is very much appreciated! Learn how to contribute [here](https://angular-challenges.vercel.app/guides/contribute/)
When contributing, it's better to first explain the challenge/exercice you are thinking about in the issue tab.
## Getting started
Please follow those step in order to succesfully make your contribution to this repository.
1. Fork the project
2. Install **Nx Console**, this will help you work with this repository
3. Run `npm ci` to install all dependencies
4. Generate a new app with Nx Console > Right Click on apps folder > `Nx Generate Application`
5. Copy/Paste **example.README.md** and fill it up.
6. Link the main **README** with your new challenge
7. Few days later, create a PR with your answer.
8. Optional: write a blog post explaining your Challenge and the solution you came up with.
## Pull Request Process
1. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/)
in our commit messages, i.e. `feat(core): improve typing`
2. When you are ready, create Pull Request of your fork into original repository with the title starting with **NEW CHALLENGE**

View File

@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
## Challenges ## Challenges
Check [all 35 challenges](https://angular-challenges.vercel.app/) Check [all 36 challenges](https://angular-challenges.vercel.app/)
## Contributors ✨ ## Contributors ✨
@@ -38,6 +38,8 @@ Check [all 35 challenges](https://angular-challenges.vercel.app/)
<td align="center" valign="top" width="14.28%"><a href="https://github.com/alan-bio"><img src="https://avatars.githubusercontent.com/u/31838230?v=4?s=100" width="100px;" alt="Alan Dragicevich"/><br /><sub><b>Alan Dragicevich</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=alan-bio" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/alan-bio"><img src="https://avatars.githubusercontent.com/u/31838230?v=4?s=100" width="100px;" alt="Alan Dragicevich"/><br /><sub><b>Alan Dragicevich</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=alan-bio" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/edimitchel"><img src="https://avatars.githubusercontent.com/u/2922851?v=4?s=100" width="100px;" alt="Michel EDIGHOFFER"/><br /><sub><b>Michel EDIGHOFFER</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=edimitchel" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/edimitchel"><img src="https://avatars.githubusercontent.com/u/2922851?v=4?s=100" width="100px;" alt="Michel EDIGHOFFER"/><br /><sub><b>Michel EDIGHOFFER</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=edimitchel" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gsgonzalez88"><img src="https://avatars.githubusercontent.com/u/39884678?v=4?s=100" width="100px;" alt="Gerardo Sebastian Gonzalez"/><br /><sub><b>Gerardo Sebastian Gonzalez</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=gsgonzalez88" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/gsgonzalez88"><img src="https://avatars.githubusercontent.com/u/39884678?v=4?s=100" width="100px;" alt="Gerardo Sebastian Gonzalez"/><br /><sub><b>Gerardo Sebastian Gonzalez</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=gsgonzalez88" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marryday"><img src="https://avatars.githubusercontent.com/u/57489315?v=4?s=100" width="100px;" alt="Evseev Yuriy"/><br /><sub><b>Evseev Yuriy</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Amarryday" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomer953"><img src="https://avatars.githubusercontent.com/u/1807493?v=4?s=100" width="100px;" alt="Tomer953"/><br /><sub><b>Tomer953</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Atomer953" title="Bug reports">🐛</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Code">💻</a></td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>

View File

@@ -1,38 +0,0 @@
<p align='center'>
<img src='./logo/angular-challenge.png' height="150px"/>
</p>
<p align='center' style='font-weight:bold'>Collection of Angular TESTING challenges</p>
<br>
## Intro
This project has been created with two purposes:
- The first purpose is to assist you in becoming better at Testing using **Testing Library** and **Cypress Component Testing**. 💪
- The second purpose is to share best practices and different implementations of the same problem to gain diverse perspectives. 📖
Thanks to all these challenges, you will learn how to create **Integration Tests** to test your UI. The goal is to test your component/application as a black box, just as an end user or developer would do.
> **Learning by reading is good but learning by doing is better.**
## Testing Challenges
> Click the following badges to join your next challenge.
>
> <img src="https://img.shields.io/badge/Easy--green" alt="Easy challenge"/>
> <img src="https://img.shields.io/badge/Intermediate--orange" alt="Easy challenge"/>
> <img src="https://img.shields.io/badge/Advanced--red" alt="Easy challenge"/>
</br>
<img src="https://img.shields.io/badge/Testing--gray" alt="testing"/>
<span style="display:flex;gap: 5px"><a href="./apps/testing-router-outlet/README.md"><img src="https://img.shields.io/badge/17-Router Testing-orange" alt="router outlet Testing"/></a>Testing a small application with a router</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-nested/README.md"><img src="https://img.shields.io/badge/18-Nested Comp Testing-orange" alt="nested component Testing"/></a> Testing a parent component with Child components</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-input-output/README.md"><img src="https://img.shields.io/badge/19-Input Output Testing-orange" alt="input output Testing"/></a>Testing a presentational component by setting Inputs and listening to the Outputs</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-modal/README.md"><img src="https://img.shields.io/badge/20-Modal Testing-orange" alt="modal Testing"/></a>Testing a modal</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-harness/README.md"><img src="https://img.shields.io/badge/23-Harness Testing-green" alt="harness Testing"/></a>Testing using Component Harnesses</span>
<span style="display:flex;gap: 5px"><a href="./apps/create-harness/README.md"><img src="https://img.shields.io/badge/24-Create Harness-orange" alt="Create harness"/></a>Creating a custom Component Harness and Testing your application by using this harness</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-checkbox/README.md"><img src="https://img.shields.io/badge/28-Checkbox Testing-green" alt="Test a simple checkbox"/></a>Testing a very simple checkbox</span>
<span style="display:flex;gap: 5px"><a href="./apps/testing-todos-list/README.md"><img src="https://img.shields.io/badge/29-Real application Testing-red" alt="Test a real application"/></a>Testing a real-life application by creating test double on Http request and handling all asynchronous part</span>

View File

@@ -1,32 +1,13 @@
<h1>memoized function</h1> # Memoization
> Author: Thomas Laforge > Author: Thomas Laforge
<!-- TODO: add Information/Statement/Rules/Constraint/Steps --> ### Run Application
## Information ```bash
npx nx serve performance-memoized
```
## Statement ### Documentation and Instruction
### Step 1 Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular-performance/35-memoize/).
### Step 2
### Constraints:
### Submitting your work
1. Fork the project
2. clone it
3. npm ci
4. `npx nx serve memoized`
5. _...work on it_
6. Commit your work
7. Submit a PR with a title beginning with **Answer:35** that I will review and other dev can review.
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A35+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="memoized"/></a>
<a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A35+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="memoized solution author"/></a>
<!-- <a href="{Blog post url}" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/-Blog post explanation-blue" alt="memoized blog article"/></a> -->
_You can ask any question on_ <a href="https://twitter.com/laforge_toma" target="_blank" rel="noopener noreferrer"><img src="./../../logo/twitter.svg" height=20px alt="twitter"/></a>

View File

@@ -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": {}
}
]
}

View File

@@ -0,0 +1,13 @@
# NgFor Optimization
> Author: Thomas Laforge
### Run Application
```bash
npx nx serve performance-ngfor-optimize
```
### Documentation and Instruction
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular-performance/36-ngfor-optimize/).

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'performance-ngfor-optimize',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/apps/performance/ngfor-optimize',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/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',
],
};

View File

@@ -0,0 +1,99 @@
{
"name": "performance-ngfor-optimize",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/performance/ngfor-optimize/src",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:browser",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/performance/ngfor-optimize",
"index": "apps/performance/ngfor-optimize/src/index.html",
"main": "apps/performance/ngfor-optimize/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/performance/ngfor-optimize/tsconfig.app.json",
"assets": [
"apps/performance/ngfor-optimize/src/favicon.ico",
"apps/performance/ngfor-optimize/src/assets"
],
"styles": [
"apps/performance/ngfor-optimize/src/styles.scss",
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
],
"scripts": [],
"allowedCommonJsDependencies": ["seedrandom"]
},
"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": "performance-ngfor-optimize:build:production"
},
"development": {
"browserTarget": "performance-ngfor-optimize:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "performance-ngfor-optimize:build"
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"apps/performance/ngfor-optimize/**/*.ts",
"apps/performance/ngfor-optimize/**/*.html"
]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/performance/ngfor-optimize/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
}
}

View File

@@ -0,0 +1,58 @@
import { Component, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { PersonService } from './list.service';
import { PersonListComponent } from './person-list.component';
@Component({
standalone: true,
imports: [
PersonListComponent,
FormsModule,
MatFormFieldModule,
MatInputModule,
],
providers: [PersonService],
selector: 'app-root',
template: `
<h1 class="font-semibold text-center text-3xl" title="Title">
List of Persons
</h1>
<mat-form-field class="w-3/4">
<input
placeholder="Add one member to the list"
matInput
type="text"
[(ngModel)]="label"
(keydown)="handleKey($event)" />
</mat-form-field>
<app-person-list
class="max-w-2xl w-3/4"
[persons]="persons()"
(delete)="personService.deletePerson($event)"
(update)="personService.updatePerson($event)" />
`,
host: {
class: 'flex items-center flex-col gap-5',
},
})
export class AppComponent implements OnInit {
readonly personService = inject(PersonService);
readonly persons = this.personService.persons;
label = '';
ngOnInit(): void {
this.personService.loadPersons();
}
handleKey(event: any) {
if (event.keyCode === 13) {
this.personService.addPerson(this.label);
this.label = '';
}
}
}

View File

@@ -0,0 +1,6 @@
import { ApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
export const appConfig: ApplicationConfig = {
providers: [provideAnimations()],
};

View File

@@ -0,0 +1,15 @@
import { randEmail, randFirstName } from '@ngneat/falso';
import { Person } from './person.model';
export function generateList() {
const arr: Person[] = [];
for (let i = 0; i < 50; i++) {
arr.push({
email: randEmail(),
name: randFirstName(),
});
}
return arr;
}

View File

@@ -0,0 +1,44 @@
import { Injectable, inject, signal } from '@angular/core';
import { randEmail, randFirstName } from '@ngneat/falso';
import { generateList } from './generateList';
import { Person } from './person.model';
@Injectable()
export class PersonService {
private readonly fakeBackend = inject(FakeBackendService);
readonly persons = signal<Person[]>([]);
loadPersons() {
this.persons.set(generateList());
}
deletePerson(email: string) {
this.persons.set(
this.fakeBackend
.returnNewList(this.persons())
.filter((p) => p.email !== email)
);
}
updatePerson(email: string) {
this.persons.set(
this.fakeBackend
.returnNewList(this.persons())
.map((p) => (p.email === email ? { email, name: randFirstName() } : p))
);
}
addPerson(name: string) {
this.persons.set([
{ email: randEmail(), name },
...this.fakeBackend.returnNewList(this.persons()),
]);
}
}
@Injectable({ providedIn: 'root' })
export class FakeBackendService {
returnNewList = (input: Person[]): Person[] => [
...input.map((i) => ({ ...i })),
];
}

View File

@@ -0,0 +1,37 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Person } from './person.model';
@Component({
selector: 'app-person-list',
standalone: true,
imports: [CommonModule],
template: `
<div
*ngFor="let person of persons"
class="flex justify-between items-center border-b">
<h3>{{ person.name }}</h3>
<div class="flex gap-10 py-1">
<button
class="border rounded-md p-2 bg-blue-500 text-white"
(click)="update.emit(person.email)">
UPDATE
</button>
<button
class="border rounded-md p-2 bg-red-500 text-white"
(click)="delete.emit(person.email)">
DELETE
</button>
</div>
</div>
`,
host: {
class: 'w-full flex flex-col',
},
})
export class PersonListComponent {
@Input() persons: Person[] = [];
@Output() delete = new EventEmitter<string>();
@Output() update = new EventEmitter<string>();
}

View File

@@ -0,0 +1,4 @@
export interface Person {
email: string;
name: string;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>performance-ngfor-optimize</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -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)
);

View File

@@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* You can add global styles to this file, and also import other style files */

View File

@@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-preset-angular/setup-jest';

View File

@@ -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: [],
};

View File

@@ -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"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"types": ["jest", "node"]
}
}

View File

@@ -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.spec.json"
},
{
"path": "./tsconfig.editor.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"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"
]
}

View File

@@ -36,7 +36,7 @@ export const randStudent = (): Student => ({
id: factoryStudent(), id: factoryStudent(),
firstname: randFirstName(), firstname: randFirstName(),
lastname: randLastName(), lastname: randLastName(),
mainTeacher: teachers[randNumber({ max: teachers.length })], mainTeacher: teachers[randNumber({ max: teachers.length - 1 })],
school: randWord(), school: randWord(),
}); });

View File

@@ -1,3 +1,6 @@
{ {
"total": 35 "total": 36,
"🟢": 13,
"🟠": 116,
"🔴": 207
} }

View File

@@ -1,15 +1,8 @@
import starlight from '@astrojs/starlight'; import starlight from '@astrojs/starlight';
import vercel from '@astrojs/vercel/static';
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
output: 'static',
adapter: vercel({
webAnalytics: {
enabled: true,
},
}),
integrations: [ integrations: [
starlight({ starlight({
title: 'Angular Challenges', title: 'Angular Challenges',
@@ -19,7 +12,7 @@ export default defineConfig({
}, },
favicon: './angular-challenge.ico', favicon: './angular-challenge.ico',
social: { social: {
github: 'https://github.com/withastro/starlight', github: 'https://github.com/tomalaforge/angular-challenges',
linkedin: 'https://www.linkedin.com/in/thomas-laforge-2b05a945/', linkedin: 'https://www.linkedin.com/in/thomas-laforge-2b05a945/',
twitter: 'https://twitter.com/laforge_toma', twitter: 'https://twitter.com/laforge_toma',
}, },

589
docs/package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@astrojs/starlight": "^0.10.0", "@astrojs/starlight": "^0.10.0",
"@astrojs/vercel": "^5.0.1",
"@fontsource/ibm-plex-serif": "^5.0.8", "@fontsource/ibm-plex-serif": "^5.0.8",
"astro": "^3.0.6", "astro": "^3.0.6",
"sharp": "^0.32.5" "sharp": "^0.32.5"
@@ -152,23 +151,6 @@
"node": ">=18.14.1" "node": ">=18.14.1"
} }
}, },
"node_modules/@astrojs/vercel": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-5.0.1.tgz",
"integrity": "sha512-qDXTBSRSzTRourx90QcB/uARUcABDRLfrDggNtyiyyh6WoJPK7BuhKDYh0Bd372Mrt2mm15ZSGaxHJLmbpv2wg==",
"dependencies": {
"@astrojs/internal-helpers": "0.2.0",
"@vercel/analytics": "^1.0.2",
"@vercel/nft": "^0.23.1",
"esbuild": "^0.19.2",
"fast-glob": "^3.3.1",
"set-cookie-parser": "^2.6.0",
"web-vitals": "^3.4.0"
},
"peerDependencies": {
"astro": "^3.1.1"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.22.13", "version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
@@ -881,25 +863,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mdx-js/mdx": { "node_modules/@mdx-js/mdx": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz",
@@ -1025,23 +988,6 @@
"win32" "win32"
] ]
}, },
"node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
"dependencies": {
"estree-walker": "^2.0.1",
"picomatch": "^2.2.2"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@types/acorn": { "node_modules/@types/acorn": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@@ -1175,45 +1121,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz",
"integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==" "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw=="
}, },
"node_modules/@vercel/analytics": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.0.2.tgz",
"integrity": "sha512-BZFxVrv24VbNNl5xMxqUojQIegEeXMI6rX3rg1uVLYUEXsuKNBSAEQf4BWEcjQDp/8aYJOj6m8V4PUA3x/cxgg=="
},
"node_modules/@vercel/nft": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.23.1.tgz",
"integrity": "sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.5",
"@rollup/pluginutils": "^4.0.0",
"acorn": "^8.6.0",
"async-sema": "^3.1.1",
"bindings": "^1.4.0",
"estree-walker": "2.0.2",
"glob": "^7.1.3",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.2",
"node-gyp-build": "^4.2.2",
"resolve-from": "^5.0.0"
},
"bin": {
"nft": "out/cli.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@vercel/nft/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.10.0", "version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
@@ -1233,17 +1140,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ansi-align": { "node_modules/ansi-align": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -1328,23 +1224,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/arg": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -1497,11 +1376,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/async-sema": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz",
"integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg=="
},
"node_modules/b4a": { "node_modules/b4a": {
"version": "1.6.4", "version": "1.6.4",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz",
@@ -1516,11 +1390,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1571,14 +1440,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": { "node_modules/bl": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
@@ -1647,15 +1508,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1943,14 +1795,6 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/color/node_modules/color-convert": { "node_modules/color/node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1981,16 +1825,6 @@
"resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz",
"integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==" "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@@ -2072,11 +1906,6 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/dequal": { "node_modules/dequal": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -2367,11 +2196,6 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2412,38 +2236,6 @@
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
}, },
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs-minipass/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2462,62 +2254,6 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gauge/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/gauge/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=="
},
"node_modules/gauge/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==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/gauge/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==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2547,25 +2283,6 @@
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -2643,11 +2360,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/hast-util-from-parse5": { "node_modules/hast-util-from-parse5": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
@@ -2856,18 +2568,6 @@
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
}, },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
@@ -2915,15 +2615,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -3347,28 +3038,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/markdown-extensions": { "node_modules/markdown-extensions": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@@ -4513,17 +4182,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -4532,53 +4190,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mkdirp-classic": { "node_modules/mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -4671,54 +4282,11 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-gyp-build": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
"integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.13", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
}, },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -4757,17 +4325,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/nth-check": { "node_modules/nth-check": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -4779,14 +4336,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1" "url": "https://github.com/fb55/nth-check?sponsor=1"
} }
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4964,14 +4513,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -5497,14 +5038,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"engines": {
"node": ">=8"
}
},
"node_modules/restore-cursor": { "node_modules/restore-cursor": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
@@ -5610,20 +5143,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.29.2", "version": "3.29.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.2.tgz",
@@ -5748,16 +5267,6 @@
"resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
"integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==" "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
},
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.32.5", "version": "0.32.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz",
@@ -6087,22 +5596,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar-fs": { "node_modules/tar-fs": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz",
@@ -6123,19 +5616,6 @@
"streamx": "^2.15.0" "streamx": "^2.15.0"
} }
}, },
"node_modules/tar/node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/to-fast-properties": { "node_modules/to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -6155,11 +5635,6 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/trim-lines": { "node_modules/trim-lines": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -6945,25 +6420,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/web-vitals": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.4.0.tgz",
"integrity": "sha512-n9fZ5/bG1oeDkyxLWyep0eahrNcPDF6bFqoyispt7xkW0xhDzpUBTgyDKqWDi1twT0MgH4HvvqzpUyh0ZxZV4A=="
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6998,51 +6454,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wide-align/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/wide-align/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=="
},
"node_modules/wide-align/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==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wide-align/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==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/widest-line": { "node_modules/widest-line": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",

View File

@@ -11,7 +11,6 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/starlight": "^0.10.0", "@astrojs/starlight": "^0.10.0",
"@astrojs/vercel": "^5.0.1",
"@fontsource/ibm-plex-serif": "^5.0.8", "@fontsource/ibm-plex-serif": "^5.0.8",
"astro": "^3.0.6", "astro": "^3.0.6",
"sharp": "^0.32.5" "sharp": "^0.32.5"

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -2,35 +2,39 @@
title: 🟠 Optimize Change Detection title: 🟠 Optimize Change Detection
description: Challenge 12 about optimizing the number of change detection cycle while scrolling description: Challenge 12 about optimizing the number of change detection cycle while scrolling
sidebar: sidebar:
order: 12 order: 107
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #12</div> <div class="chip">Challenge #12</div>
## Information ## Information
In this challenge, you will need to optimize the change detection cycles run by Angular. In Angular, there is a library called <b>Zone.js</b> that performs a lot of magic to simplify a developer's life. Zone.js monkey patches all DOM events so that it will recheck and rerender the view when something has changed inside the application. The developer doesn't have to manually trigger change detection.
Zone.js triggers a change detection cycle each time a scroll event is dispatched. However we only want to show or hide a button at a specific scroll position. Therefore, we only want to refresh our application once. However, sometimes Zone.js triggers a lot more change detection than needed. For example, when you are listening to a scroll event, each scroll event will dispatch a new change detection cycle.
> You can vizualise how many times CD is triggered by installing the [Angular chrome devTool](https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh) and starting a new recording on the profiler tab. In this challenge, we only need to refresh the view at a specific scroll position to display or hide a button. All other cycles are unnecessary.
The following video will explain what is the goal of this challenge. To have a better visualization of the problem, profile your application with Angular Dev Tools.
:::note
If you don't know how to use it, read [the performance introduction page](/challenges/angular-performance/) first and come back after.
:::
You can learn more details about zone pollution and how to resolve it [here](https://angular.io/guide/change-detection-zone-pollution).
The following video will explain more in-depth the issue of this application.
<video controls src="https://user-images.githubusercontent.com/30832608/209819211-58d9ddcf-e1ad-4a78-8a7a-2be9d729e3f1.mov"> <video controls src="https://user-images.githubusercontent.com/30832608/209819211-58d9ddcf-e1ad-4a78-8a7a-2be9d729e3f1.mov">
</video> </video>
## Statement ## Statement
Your goal for this challenge is to avoid all unnecessary change detection cycles and trigger a CD only when needed. Your goal for this challenge is to avoid all unnecessary change detection cycles and trigger a change detection only when needed.
## Constraint: ## Constraint:
You cannot opt-out of zone.js. If this code is part of a large project and you opt out of zone.js, you will break many things within your application. You cannot opt-out of Zone.js globally. If this code is part of a large project and you opt out of Zone.js, you will break your application without any doubt.
--- ---
@@ -49,7 +53,7 @@ Your PR title must start with <b>Answer:12</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A{challenge number}+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A{challenge number}+label%3A"answer+author"'
alt="Optimize Change Detection solution author"> alt="Optimize Change Detection solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,36 +2,40 @@
title: 🟢 Default vs OnPush title: 🟢 Default vs OnPush
description: Challenge 34 is about learning the difference between Default and OnPush Change Detection Strategy. description: Challenge 34 is about learning the difference between Default and OnPush Change Detection Strategy.
sidebar: sidebar:
order: 34 order: 7
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #34</div> <div class="chip">Challenge #34</div>
## Information ## Information
In this series of challenges, you will learn how to optimize and enhance the performance of your Angular Application. In this challenge, we will explore the differences and impacts of using `ChangeDetectionStrategy.Default` versus `ChangeDetectionStrategy.OnPush`.
The first step is to download the [Angular DevTools Chrome extention](https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh) if you haven't already done so. This extension allows you to profile your application and detect performance issues. You can read the [Angular documentation](https://angular.io/guide/change-detection-skipping-subtrees) to learn more about the differences between these strategies.
In this challenge, we will explore the differences and impacts of using `ChangeDetectionStrategy.Default` versus `ChangeDetectionStrategy.OnPush`. To provide a clearer demonstration, I have added color enlightment to each component and each row in our application. However, in real-world scenarios, you will not have such visualization. This is where the Angular DevTool profiler comes to the rescue. In this challenge, all components start with the `Default` strategy. When you type letters inside the input field, you will notice that all components are highlighted in orange.
Start by serving this application by running: `npx nx serve performance-default-onpush` inside your terminal. Then open Chrome DevTool by pressing **F12** and switch to the Angular Tab. From there you can select the Profiler tab as shown below. :::note
I added color highlighting to each component and each row to provide a better visualization of when a component is rerendered.
:::
![profiler tab](../../../../assets/34/profiler-tab.png 'Profiler tab') As you can see, each letter triggers a new change detection cycle, and all components are rerendered, causing performance issues.
Start profiling your application and type some letters inside the input field. You will notice that each element of your application will flash at each change detection cycle and the profiler will show you a bar for each change detection cycle. Let's use the <b>Angular DevTool</b> to profile our application and understand how this tool can help us understand what is happening inside our application.
If you click on one of the bars (indicated by the yellow arrow on the picture below), you can see that `PersonListComponent`, `RandomComponent` and all the `MatListItem` are impacted by the change detection cycle, even when we only interact with the input field. :::note
If you don't know how to use it, read [the performance introduction page](/challenges/angular-performance/) first and come back after.
:::
![profiler record](../../../../assets/34/profiler-record.png 'Profiler Record') Now, start profiling your application and type some letters inside the input field to trigger some change detection cycles.
If you click on one of the bars (indicated by the yellow arrow in the picture below), you can see that `PersonListComponent`, `RandomComponent`, and all the `MatListItem` are impacted by the change detection cycle, even when we only interact with the input field.
![profiler record](../../../../assets/angular-performance/34/profiler-record.png 'Profiler Record')
## Statement ## Statement
The goal of this challenge is to improve the clustering of change detection within the application. The goal of this challenge is to improve the clustering of change detection within the application using the `OnPush` change detection strategy, but not only...
## Hints: ## Hints:
@@ -66,7 +70,7 @@ Your PR title must start with <b>Answer:34</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A34+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A34+label%3A"answer+author"'
alt="Default vs OnPush solution author"> alt="Default vs OnPush solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -0,0 +1,70 @@
---
title: 🟢 Memoization
description: Challenge 35 is about learning
sidebar:
order: 8
---
<div class="chip">Challenge #35</div>
## Information
In Angular, <b>pure Pipes</b> are very powerful because the value is memoized, which means if the input value doesn't change, the `transform` function of the pipe is not recomputed, and the cached value is outputted.
You can learn more about pipes in the [Angular documentation](https://angular.io/guide/pipes) and inside this [deep dive article](https://medium.com/ngconf/deep-dive-into-angular-pipes-c040588cd15d).
In this challenge, we start with a button to load a list of people. Each person is associated with a number, and we will use the Fibonacci calculation to create a heavy computation that will slow down the application.
Once the list is loaded, try typing some letters inside the input field. You will notice that the application is very slow, even though you are only performing very basic typing.
:::note
We will not focus on the initial loading of the list in this challenge.
:::
Let's use the <b>Angular DevTool</b> to profile our application and understand how this tool can help us understand what is happening inside our application.
:::note
If you don't know how to use it, read [the performance introduction page](/challenges/angular-performance/) first and come back after.
:::
Now, start profiling your application and type some letters inside the input field. You will see some red bars showing up inside the profiler panel.
If you click on one of the bars (indicated by the yellow arrow in the picture below), you will see that the change detection cycle is taking more than 3s in `PersonListComponent`.
![profiler record](../../../../assets/angular-performance/35/memoize-profiler.png 'Profiler Record')
## Statement
The goal of this challenge is to understand what is causing this latency and to improve it.
## Hints:
<details>
<summary>Hint 1</summary>
Use `Pipes` to memoize the Fibonnaci computation.
</details>
---
:::note
Start the project by running: `npx nx serve performance-memoized`.
:::
:::tip[Reminder]
Your PR title must start with <b>Answer:35</b>.
:::
<div class="article-footer">
<a
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A35+label%3Aanswer"
alt="Memoization community solutions">
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A35+label%3A"answer+author"'
alt="Memoization solution author">
▶︎ Author Answer
</a>
</div>

View File

@@ -0,0 +1,54 @@
---
title: 🟢 NgFor Optimization
description: Challenge 36 is about ...
sidebar:
order: 13
badge: New
---
<div class="chip">Challenge #36</div>
## Information
In this application, we have a list of individuals that we can add, delete or update. If you open the developer Chrome panel by pressing **F12**, go to he <b>source</b> tab, and expand the element to see the list, you will notice that each time, you add, delete or update a list item, the entire DOM elements are destroyed and initialized again. (See video below).
<video controls src="https://github.com/tomalaforge/angular-challenges/assets/30832608/71b90307-3ee3-42c0-a532-b67ce4f20bf6">
</video>
We can also use the <b>Angular DevTool</b> to profile our application and understand what is happening inside our application. I will show you how to do it inside the following video.
<video controls src="https://github.com/tomalaforge/angular-challenges/assets/30832608/dd8108c6-1d89-4b05-9aa5-e760bd6f7f11">
</video>
:::note
If you don't know how to use it, read [the performance introduction page](/challenges/angular-performance/) first and come back after.
:::
If you need more information about `NgFor`, I invite you to read the [documentation](https://angular.io/api/common/NgFor) first.
## Statement
The goal of this challenge is to understand what is causing this DOM refresh and to solve it.
---
:::note
Start the project by running: `npx nx serve ngfor-optimize`.
:::
:::tip[Reminder]
Your PR title must start with <b>Answer:36</b>.
:::
<div class="article-footer">
<a
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A36+label%3Aanswer"
alt="NgFor Optimization community solutions">
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A36+label%3A"answer+author"'
alt="NgFor Optimization solution author">
▶︎ Author Answer
</a>
</div>

View File

@@ -0,0 +1,48 @@
---
title: Angular Performance
prev: false
next: false
description: Learn how to use the Angular Devtool chrome extension.
sidebar:
order: 1
---
import { LinkCard } from '@astrojs/starlight/components';
In this series of challenges about performance, you will learn how to optimize and enhance the performance of your Angular application.
Before starting to resolve any challenge, I invite you to download the [Angular DevTools Chrome extention](https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh) if you haven't already done so.
This extension allows you to profile your application and detect performance issues, which is very useful for understanding where performance issues can occur.
## How to use it
When you serve an Angular application, you can inspect a page by pressing <b>F12</b>, which will open the <b>Chrome developer tools</b>. Then navigate to the <b>Angular tab</b>. From there, you can select the <b>Profiler tab</b> as shown below.
![profiler tab](../../../../assets/angular-performance/profiler-tab.png 'Profiler tab')
You can now profile your application by clicking on the record button. You can play with your application and see when change detection is triggered and which components are rerendered.
:::tip[Learn more]
You can learn more on the [documentation page](https://angular.io/guide/devtools).
:::
Now that you know how to use the <b>Angular DevTool</b>, you can choose a challenge, profile it, and resolve it.
<LinkCard
title="🟠 Optimize Change Detection"
description="Learn how to remove zone pollution."
href="/challenges/angular-performance/12-scroll-cd/"
/>
<LinkCard
title="🟢 Default vs OnPush"
description="Learn the difference between Default and OnPush change detection strategies."
href="/challenges/angular-performance/34-default-onpush/"
/>
<LinkCard
title="🟢 Memoization"
description="Learn the power of pure pipes."
href="/challenges/angular-performance/35-memoize/"
/>

View File

@@ -56,7 +56,16 @@ Your PR title must start with <b>Answer:1</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Projection blog article"> alt="Projection blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
<a
href="https://www.youtube.com/watch?v=npyEyUZxoIw&ab_channel=ArthurLannelucq"
target="_blank"
rel="noopener noreferrer"
alt="Projection video by Arthur Lannelucq">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M23.5 6.2A3 3 0 0 0 21.4 4c-1.9-.5-9.4-.5-9.4-.5s-7.5 0-9.4.5A3 3 0 0 0 .5 6.3C0 8 0 12 0 12s0 4 .5 5.8A3 3 0 0 0 2.6 20c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2c.5-2 .5-5.9.5-5.9s0-4-.5-5.8zm-14 9.4V8.4l6.3 3.6-6.3 3.6z"></path></svg>
Video
<span class="flag">🇫🇷<span>
</a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🔴 Utility Wrapper Pipe title: 🔴 Utility Wrapper Pipe
description: Challenge 10 is about creating a pipe to wrap utilities description: Challenge 10 is about creating a pipe to wrap utilities
sidebar: sidebar:
order: 10 order: 202
--- ---
:::note :::note
@@ -40,7 +40,7 @@ Your PR title must start with <b>Answer:10</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A10+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A10+label%3A"answer+author"'
alt="Utility Wrapper Pipe solution author"> alt="Utility Wrapper Pipe solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Highly Customizable CSS title: 🟠 Highly Customizable CSS
description: Challenge 13 is about creating highly customizable CSS styles description: Challenge 13 is about creating highly customizable CSS styles
sidebar: sidebar:
order: 13 order: 104
--- ---
:::note :::note
@@ -42,7 +42,7 @@ Your PR title must start with <b>Answer:13</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A13+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A13+label%3A"answer+author"'
alt="Highly Customizable CSS solution author"> alt="Highly Customizable CSS solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🔴 Master Dependancy Injection title: 🔴 Master Dependancy Injection
description: Challenge 16 is about masjering how dependancy injection works description: Challenge 16 is about masjering how dependancy injection works
sidebar: sidebar:
order: 16 order: 203
--- ---
:::note :::note
@@ -45,7 +45,7 @@ Your PR title must start with <b>Answer:16</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A16+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A16+label%3A"answer+author"'
alt="Master Dependancy Injection solution author"> alt="Master Dependancy Injection solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 Anchor Navigation title: 🟢 Anchor Navigation
description: Challenge 21 is about navigating inside the page with anchor description: Challenge 21 is about navigating inside the page with anchor
sidebar: sidebar:
order: 21 order: 4
--- ---
:::note :::note
@@ -37,7 +37,7 @@ Your PR title must start with <b>Answer:21</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A21+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A21+label%3A"answer+author"'
alt="Anchor Navigation solution author"> alt="Anchor Navigation solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 @RouterInput() title: 🟢 @RouterInput()
description: Challenge 22 is about using the @Input decorator to retreive router params. description: Challenge 22 is about using the @Input decorator to retreive router params.
sidebar: sidebar:
order: 22 order: 5
--- ---
:::note :::note
@@ -32,7 +32,7 @@ Your PR title must start with <b>Answer:22</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A22+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A22+label%3A"answer+author"'
alt="@RouterInput() solution author"> alt="@RouterInput() solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -41,7 +41,7 @@ Your PR title must start with <b>Answer:22</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="@RouterInput() blog article"> alt="@RouterInput() blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🟠 Directive Enhancement title: 🟠 Directive Enhancement
description: Challenge 3 is about enhancing a built-in directive description: Challenge 3 is about enhancing a built-in directive
sidebar: sidebar:
order: 3 order: 101
--- ---
:::note :::note
@@ -60,7 +60,7 @@ Your PR title must start with <b>Answer:3</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A3+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A3+label%3A"answer+author"'
alt="Directive Enhancement solution author"> alt="Directive Enhancement solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -69,7 +69,7 @@ Your PR title must start with <b>Answer:3</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Directive Enhancement blog article"> alt="Directive Enhancement blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🔴 Interoperability Rxjs/Signal title: 🔴 Interoperability Rxjs/Signal
description: Challenge 30 is about learning how to mix signal with Rxjs description: Challenge 30 is about learning how to mix signal with Rxjs
sidebar: sidebar:
order: 30 order: 204
--- ---
:::note :::note
@@ -34,7 +34,7 @@ Your PR title must start with <b>Answer:30</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A30+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A30+label%3A"answer+author"'
alt="Interoperability Rxjs/Signal solution author"> alt="Interoperability Rxjs/Signal solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 Module to Standalone title: 🟢 Module to Standalone
description: Challenge 31 is about migrating a module based application to a standalone application. description: Challenge 31 is about migrating a module based application to a standalone application.
sidebar: sidebar:
order: 31 order: 6
--- ---
:::note :::note
@@ -42,7 +42,7 @@ Your PR title must start with <b>Answer:31</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A31+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A31+label%3A"answer+author"'
alt="Module to Standalone solution author"> alt="Module to Standalone solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Change Detection Bug title: 🟠 Change Detection Bug
description: Challenge 32 is about debugging an application that has issue when change detection is triggered description: Challenge 32 is about debugging an application that has issue when change detection is triggered
sidebar: sidebar:
order: 32 order: 105
--- ---
:::note :::note
@@ -11,11 +11,13 @@ WIP: The following documentation will be reviewed and improved. However, you can
<div class="chip">Challenge #32</div> <div class="chip">Challenge #32</div>
:::note
This challenge is inspired by a real-life example that I simplified to create this nice challenge. This challenge is inspired by a real-life example that I simplified to create this nice challenge.
:::
## Information ## Information
In this small application, we have a navigation menu to route our application to either `barComponent` or `FooComponent`. However our application is not loading and no errors are displayed inside the console. In this small application, we have a navigation menu to route our application to either `BarComponent` or `FooComponent`. However our application is not loading and no errors are displayed inside the console.
## Statement ## Statement
@@ -53,7 +55,7 @@ Your PR title must start with <b>Answer:32</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A32+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A32+label%3A"answer+author"'
alt="Change Detection Bug solution author"> alt="Change Detection Bug solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Decoupling Components title: 🟠 Decoupling Components
description: Challenge 33 is about decoupling two strongly coupled components using Injection Token description: Challenge 33 is about decoupling two strongly coupled components using Injection Token
sidebar: sidebar:
order: 33 order: 106
--- ---
:::note :::note
@@ -50,7 +50,7 @@ Your PR title must start with <b>Answer:33</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A33+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A33+label%3A"answer+author"'
alt="Decoupling Components solution author"> alt="Decoupling Components solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🔴 Typed ContextOutlet title: 🔴 Typed ContextOutlet
description: Challenge 4 is about strongly typing ngContextOutlet directives description: Challenge 4 is about strongly typing ngContextOutlet directives
sidebar: sidebar:
order: 4 order: 201
--- ---
:::note :::note
@@ -58,7 +58,7 @@ Your PR title must start with <b>Answer:4</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A4+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A4+label%3A"answer+author"'
alt="Typed ContextOutlet solution author"> alt="Typed ContextOutlet solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -67,7 +67,7 @@ Your PR title must start with <b>Answer:4</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Typed ContextOutlet blog article"> alt="Typed ContextOutlet blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🟢 Crud application title: 🟢 Crud application
description: Challenge 5 is about refactoring a crud application description: Challenge 5 is about refactoring a crud application
sidebar: sidebar:
order: 5 order: 2
--- ---
:::note :::note
@@ -67,12 +67,12 @@ Your PR title must start with <b>Answer:5</b>.
<div class="article-footer"> <div class="article-footer">
<a <a
href="https://jsonplaceholder.typicode.com/" href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A5+label%3Aanswer"
alt="Crud application community solutions"> alt="Crud application community solutions">
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A5+label%3Aanswer' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A5+label%3A"answer+author"'
alt="Crud application solution author"> alt="Crud application solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Structural Directive title: 🟠 Structural Directive
description: Challenge 6 is about creating a structural directive to handle permissions description: Challenge 6 is about creating a structural directive to handle permissions
sidebar: sidebar:
order: 6 order: 102
--- ---
:::note :::note
@@ -73,7 +73,7 @@ Your PR title must start with <b>Answer:6</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A6+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A6+label%3A"answer+author"'
alt="Structural Directive solution author"> alt="Structural Directive solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -82,7 +82,7 @@ Your PR title must start with <b>Answer:6</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Structural Directive blog article"> alt="Structural Directive blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🟢 Pure Pipe title: 🟢 Pure Pipe
description: Challenge 8 is about creating a pure pipe description: Challenge 8 is about creating a pure pipe
sidebar: sidebar:
order: 8 order: 3
--- ---
:::note :::note
@@ -40,7 +40,7 @@ Your PR title must start with <b>Answer:8</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A8+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A8+label%3A"answer+author"'
alt="Pure Pipe solution author"> alt="Pure Pipe solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -49,7 +49,7 @@ Your PR title must start with <b>Answer:8</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Pure Pipe blog article"> alt="Pure Pipe blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🟠 Wrap Function Pipe title: 🟠 Wrap Function Pipe
description: Challenge 9 is about creating a pipe to wrap component fonctions description: Challenge 9 is about creating a pipe to wrap component fonctions
sidebar: sidebar:
order: 9 order: 103
--- ---
:::note :::note
@@ -41,7 +41,7 @@ Your PR title must start with <b>Answer:9</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A9+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A9+label%3A"answer+author"'
alt="Wrap Function Pipe solution author"> alt="Wrap Function Pipe solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -50,7 +50,7 @@ Your PR title must start with <b>Answer:9</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Wrap Function Pipe blog article"> alt="Wrap Function Pipe blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🟠 Effect vs Selector title: 🟠 Effect vs Selector
description: Challenge 2 is about learning the difference between effects and selectors in NgRx description: Challenge 2 is about learning the difference between effects and selectors in NgRx
sidebar: sidebar:
order: 2 order: 113
--- ---
:::note :::note
@@ -48,7 +48,7 @@ Your PR title must start with <b>Answer:2</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A2+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A2+label%3A"answer+author"'
alt="Effect vs Selector solution author"> alt="Effect vs Selector solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -57,7 +57,7 @@ Your PR title must start with <b>Answer:2</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Effect vs Selector blog article"> alt="Effect vs Selector blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
title: 🔴 Power of Effect title: 🔴 Power of Effect
description: Challenge 7 is about creating an Ngrx effect with another Rxjs Hot observable description: Challenge 7 is about creating an Ngrx effect with another Rxjs Hot observable
sidebar: sidebar:
order: 7 order: 206
--- ---
:::note :::note
@@ -51,7 +51,7 @@ Your PR title must start with <b>Answer:7</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A7+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A7+label%3A"answer+author"'
alt="Power of Effect solution author"> alt="Power of Effect solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🔴 Extend Lib Generator title: 🔴 Extend Lib Generator
description: Challenge 25 is about creating a Nx generator to extend the built-in Library Generator description: Challenge 25 is about creating a Nx generator to extend the built-in Library Generator
sidebar: sidebar:
order: 25 order: 207
--- ---
:::note :::note
@@ -67,7 +67,7 @@ Your PR title must start with <b>Answer:25</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3A"answer+author"'
alt="Extend Lib Generator solution author"> alt="Extend Lib Generator solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Component Generator title: 🟠 Component Generator
description: Challenge 26 is about creating a Nx generator to create a custom component description: Challenge 26 is about creating a Nx generator to create a custom component
sidebar: sidebar:
order: 26 order: 116
--- ---
:::note :::note
@@ -155,7 +155,7 @@ Your PR title must start with <b>Answer:26</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3A"answer+author"'
alt="Component Generator solution author"> alt="Component Generator solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 Custom Eslint Rule title: 🟢 Custom Eslint Rule
description: Challenge 27 is about creating a custom Eslint Rule to forbid enums description: Challenge 27 is about creating a custom Eslint Rule to forbid enums
sidebar: sidebar:
order: 27 order: 12
--- ---
:::note :::note
@@ -36,7 +36,7 @@ Your PR title must start with <b>Answer:27</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3A"answer+author"'
alt="Custom Eslint Rule solution author"> alt="Custom Eslint Rule solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 High Order Operator Bug title: 🟠 High Order Operator Bug
description: Challenge 11 is about resolving a Rxjs bug because of high order operators description: Challenge 11 is about resolving a Rxjs bug because of high order operators
sidebar: sidebar:
order: 11 order: 114
--- ---
:::note :::note
@@ -48,7 +48,7 @@ Your PR title must start with <b>Answer:11</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A11+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A11+label%3A"answer+author"'
alt="High Order Operator Bug solution author"> alt="High Order Operator Bug solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 Race Condition title: 🟢 Race Condition
description: Challenge 14 is about race condition in Rxjs description: Challenge 14 is about race condition in Rxjs
sidebar: sidebar:
order: 14 order: 11
--- ---
:::note :::note
@@ -46,7 +46,7 @@ Your PR title must start with <b>Answer:14</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A14+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A14+label%3A"answer+author"'
alt="Race Condition solution author"> alt="Race Condition solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,34 +2,26 @@
title: 🟠 Router title: 🟠 Router
description: Challenge 17 is about testing the router description: Challenge 17 is about testing the router
sidebar: sidebar:
order: 17 order: 108
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #17</div> <div class="chip">Challenge #17</div>
## Information ## Information
Testing is a crucial step in building scalable, maintainable, and trustworthy applications. We have a functional application that lists available books for borrowing inside a library. If the book you searched for is available, you will be directed to the corresponding book(s), otherwise, you will end up on an error page.
Testing should never be avoided, even in the face of short deadlines or strong pressure from the product team.
Nowadays, there are numerous awesome tools available that make it easy to test your code and provide a great developer experience.
In this series of testing exercises, we will learn and master [Testing Library](https://testing-library.com/docs/) and [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/angular/overview) that simplifies DOM manipulation for testing any Angular component. The file named `app.component.spec.ts` will let you test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-router-outlet`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
## Statement:
We have a functional application that lists available books for borrowing inside a library. If the book you searched is available, you will be directed to the corresponding book(s), otherwise, you will end up on an error page.
The goal is to test this behavior with Testing library and Cypress
The file named `app.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-router-outlet`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
For testing cypress, you will execute your test inside the `app.component.cy.ts` and run `npx nx component-test testing-router-outlet` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode. For testing cypress, you will execute your test inside the `app.component.cy.ts` and run `npx nx component-test testing-router-outlet` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode.
I created some `it` blocks but feel free to add more test if you like to. # Statement
The goal is to test multiple behaviors of the application described in each test file using Testing library and Cypress Component Testing.
:::note
I have created some `it` blocks but feel free to add more tests if you want.
:::
--- ---
@@ -48,7 +40,7 @@ Your PR title must start with <b>Answer:17</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A17+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A17+label%3A"answer+author"'
alt="Router solution author"> alt="Router solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,28 +2,30 @@
title: 🟠 Nested Components title: 🟠 Nested Components
description: Challenge 18 is about testing nested components description: Challenge 18 is about testing nested components
sidebar: sidebar:
order: 18 order: 109
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #18</div> <div class="chip">Challenge #18</div>
## Statement: ## Information
We have a small application that sends a title, typed into an input to a fake backend.
If the title is correctly typed, you can send the request otherwise you receive an error and the request is not sent.
The application is created with <b>nested components</b>. `ChildComponent` is the container that includes four components: `ResultComponent`, `ButtonComponent`, `InputComponent` and `ErrorComponent`. However since we are testing our component as a black box, the architecture of our components doesn't change anything. You can create your test, change how the components are structured, and your tests should still pass. That's the goal of integration tests. <b>Never test internal implementation details!!!</b>.
We have a small application that send a title to a fake backend that you type inside a input.
If the title is correctly typed, you can send the request otherwise you get a nice error and the request is not sent.
You can play with it by running : `npx nx serve testing-nested`. You can play with it by running : `npx nx serve testing-nested`.
The goal is to test this behavior with Testing library and Cypress
The file named `child.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-nested`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks. The file named `child.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-nested`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
For testing cypress, you will execute your test inside the `child.component.cy.ts` and run `npx nx component-test testing-nested` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode. For testing cypress, you will execute your test inside the `child.component.cy.ts` and run `npx nx component-test testing-nested` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode.
I created some `it` blocks but feel free to add more test if you like to. # Statement
The goal is to test multiple behaviors of the application describe inside each test files using Testing library and Cypress Component Testing.
:::note
I have created some `it` blocks but feel free to add more tests if you want.
:::
--- ---
@@ -42,7 +44,7 @@ Your PR title must start with <b>Answer:18</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A18+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A18+label%3A"answer+author"'
alt="Nested Components solution author"> alt="Nested Components solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,27 +2,28 @@
title: 🟠 Input Output title: 🟠 Input Output
description: Challenge 19 is about testing inputs and ouputs description: Challenge 19 is about testing inputs and ouputs
sidebar: sidebar:
order: 19 order: 110
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #19</div> <div class="chip">Challenge #19</div>
## Statement: ## Information:
We have a small counter application that increments or decrements a number. The `CounterComponent` takes an initial value as an `@Input` and emits the result of the counter as an `@Output` when we click on the **Send** button. Since we are testing our component as a black box, we only have access to our inputs and listen to the output values. <b>We should not rely on any internal implementation details!!!</b>
We have a small counter application that increment or decrement a number.
You can play with it by running : `npx nx serve testing-input-output`. You can play with it by running : `npx nx serve testing-input-output`.
The goal is to test `CounterComponent` with Testing library and Cypress The file named `counter.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-input-output`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
The file named `counter.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-nested`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks. For testing cypress, you will execute your test inside the `child.component.cy.ts` and run `npx nx component-test testing-input-output` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode.
For testing cypress, you will execute your test inside the `counter.component.cy.ts` and run `npx nx component-test testing-nested` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode. # Statement
I created some `it` blocks but feel free to add more test if you like to. The goal is to test multiple behaviors of the application describe inside each test files using Testing library and Cypress Component Testing.
:::note
I have created some `it` blocks but feel free to add more tests if you want.
:::
--- ---
@@ -41,7 +42,7 @@ Your PR title must start with <b>Answer:19</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A19+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A19+label%3A"answer+author"'
alt="Input Output solution author"> alt="Input Output solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,30 +2,32 @@
title: 🟠 Modal title: 🟠 Modal
description: Challenge 20 is about testing modals description: Challenge 20 is about testing modals
sidebar: sidebar:
order: 20 order: 111
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #20</div> <div class="chip">Challenge #20</div>
## Statement: ## Information:
The goal of this challenge is to test dialogs inside your application. In this small application, you have an input prompting you to enter a name, and a **Confirm** button to submit your form.
Within this program, you will get an error modal if the user doesn't input a name, while a confirmation modal will appear in all other cases. If you enter a name, a confirmation modal will appear; otherwise an error modal will be displayed.
In the confirmation modal, if you click the "confirm" button, a message confirming the submission of the form will appear. Otherwise, if the user clicks on "Cancel", an error message will be displayed. In the confirmation modal, if you click the **Confirm** button, a message confirming the submission of the form will appear. If the user clicks on **Cancel**, an error message will be displayed.
The goal of this challenge is to test the dialogs inside your application. To do so, we will test the full application like an end-to-end test will do. This means, we will test the `AppComponent` as a black box and react to events on the page. <b>No internal details should be tested</b>. The difference between an e2e test and integration test is that we will mock all API calls. _(All http requests are faked inside this application, but this would not be the case in a real entreprice application.)_
You can play with it by running : `npx nx serve testing-modal`. You can play with it by running : `npx nx serve testing-modal`.
The goal is to test this behavior with Testing library and Cypress
The file named `app.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-modal`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks. The file named `app.component.spec.ts` will let test your application using Testing Library. To run the test suits, you need to run `npx nx test testing-modal`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
For testing cypress, you will execute your test inside the `app.component.cy.ts` and run `npx nx component-test testing-modal` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode. For testing cypress, you will execute your test inside the `app.component.cy.ts` and run `npx nx component-test testing-modal` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode.
I created some `it` blocks but feel free to add more test if you like to. # Statement
The goal is to test multiple behaviors of the application describe inside each test files using Testing library and Cypress Component Testing.
:::note
I have created some `it` blocks but feel free to add more tests if you want.
:::
--- ---
@@ -44,7 +46,7 @@ Your PR title must start with <b>Answer:20</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A20+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A20+label%3A"answer+author"'
alt="Modal solution author"> alt="Modal solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟢 Harness title: 🟢 Harness
description: Challenge 23 is about testing with component harnesses description: Challenge 23 is about testing with component harnesses
sidebar: sidebar:
order: 23 order: 9
--- ---
:::note :::note
@@ -39,7 +39,7 @@ Your PR title must start with <b>Answer:23</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A23+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A23+label%3A"answer+author"'
alt="Harness solution author"> alt="Harness solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,7 +2,7 @@
title: 🟠 Harness Creation title: 🟠 Harness Creation
description: Challenge 24 is about creating a component harness. description: Challenge 24 is about creating a component harness.
sidebar: sidebar:
order: 24 order: 112
--- ---
:::note :::note
@@ -63,7 +63,7 @@ Your PR title must start with <b>Answer:24</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A24+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A24+label%3A"answer+author"'
alt="Harness Creation solution author"> alt="Harness Creation solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,26 +2,26 @@
title: 🟢 Checkbox title: 🟢 Checkbox
description: Challenge 28 is about testing a simple checkbox description: Challenge 28 is about testing a simple checkbox
sidebar: sidebar:
order: 28 order: 10
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #28</div> <div class="chip">Challenge #28</div>
## Information ## Information
This is the perfect example to get started with `Testing Library`. This application is very simple. It consists of a checkbox that enables or disables a button. The primary goal of this application is to become familiar with the debug API of Testing Library. Knowing how to debug your tests is a crucial tool you need to have in your toolkit.
You will need to only check if the button gets enabled when clicking on the checkbox You can find the documentation about debugging in Testing Library [here](https://testing-library.com/docs/dom-testing-library/api-debugging#screenlogtestingplaygroundurl).
You can look into debug function to get full power of `Testing Library` like: The main functions to remember are as follows:
- logRoles(...); - `logRoles(myDOMElement)`: prints out all ARIA roles within the tree of the given DOM element. ARIA roles are the primary selectors you should reach for in the first place.
- screen.debug(); // you can pass a element as input - `screen.debug()` or `screen.debug(myDOMElement)`: prints the DOM inside the console.
- screen.logTestingPlaygroundURL(); // you can pass a element as input - `screen.logTestingPlaygroundURL()` or `screen.logTestingPlaygroundURL(myDOMElement)`: this function is very powerful. It will create a playground to expose all elements, and you can interact with it to see the selectors you should choose for a DOM element.
## Statement
The goal of this challenge is not to submit an answer, but you can if you want. It's more about using the debugging API to play around. These tools will be of great help for the upcoming testing challenges.
--- ---
@@ -40,7 +40,7 @@ Your PR title must start with <b>Answer:28</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A28+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A28+label%3A"answer+author"'
alt="Checkbox solution author"> alt="Checkbox solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -2,25 +2,34 @@
title: 🔴 Real-life Application title: 🔴 Real-life Application
description: Challenge 29 is about testing a real-life application description: Challenge 29 is about testing a real-life application
sidebar: sidebar:
order: 29 order: 205
--- ---
:::note
WIP: The following documentation will be reviewed and improved. However, you can still take on the challenge. If you don't understand a certain part, please feel free to reach out or create an issue.
:::
<div class="chip">Challenge #29</div> <div class="chip">Challenge #29</div>
## Statement: ## Information:
I built this more real life application to create more real life test cases. This application presents a greater challenge because it closely resembles a real-life application that you might encounter in your day-to-day activities as an Angular developer. What makes it more difficult is the need to handle asynchronous tasks and create appropriate mocks.
In this application, you can search for tickets, you can assign or finish them. You can also create new tickets.
This is a very simple application, but it will let you deal with asynchronous task and mocks The application is a typical todo list application. You can filter tickets, create new ones, assign each ticket, close others, and navigate to the details of each ticket.
The goal of this challenge is to write all test cases of `ticket.store` , `list.component` and `row.component` with Testing Library. In this challenge, you will write tests for the `ListComponent`, which represents the global view, and the `RowComponent`, which represents a specific ticket. Additionally, you will need to write unit tests for the `TicketStoreService` using Testing Library. _This library allows you to test services effectively._
You can also do it with cypress. Handling asynchronous tasks will be particularly challenging. It's important not to introduce any explicit <b>waits</b> in your tests, as this would introduce unnecessary delays. Instead, it's better to look for an element that needs to appear or disappear from the DOM. In this case, the test will naturally wait for the correct period of time, as the waits are already implemented within both libraries. Take advantage of these built-in functionalities to create efficient and reliable tests.
You can play with it by running : `npx nx serve testing-todos-list`.
To run Testing Library test suits, you need to run `npx nx test testing-input-output`. You can also install [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) to execute your test by clicking on the `Run` button above each `describe` or `it` blocks.
For testing cypress, you will execute your test inside the `child.component.cy.ts` and run `npx nx component-test testing-input-output` to execute your test suits. You can add the `--watch` flag to execute your test in watch mode.
# Statement
The goal is to test multiple behaviors of the application describe inside each test files using Testing library and Cypress Component Testing.
:::note
I have created some `it` blocks but feel free to add more tests if you want.
:::
--- ---
@@ -39,7 +48,7 @@ Your PR title must start with <b>Answer:29</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A29+label%3A%22answer+author%22' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A29+label%3A"answer+author"'
alt="Real-life Application solution author"> alt="Real-life Application solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>

View File

@@ -0,0 +1,70 @@
---
title: Testing
prev: false
next: false
description: Introduction to testing challenges.
sidebar:
order: 1
---
import { LinkCard } from '@astrojs/starlight/components';
Testing is a crucial step in building scalable, maintainable, and trustworthy applications.
Testing should never be avoided, even in the face of short deadlines or strong pressure from the product team.
Nowadays, there are numerous awesome tools available that make it easy to test your code and provide a great developer experience.
In this series of testing exercises, we will learn and master [Testing Library](https://testing-library.com/docs/) and [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/angular/overview) that simplifies DOM manipulation for testing any Angular component.
The benefits of using <b>Testing Library</b> or <b>Cypress Component Testing</b> are to test your component as a black box. You will only interact with what the user can do on the UI. However, the difference with end-to-end tests is that the backend is mocked, which makes the tests faster and more maintainable.
The goal is to mock as little as possible to test your component at a higher level than unit testing, which will make refactoring easier.
Within a real application, integration tests are the tests you will write the most. Learning how to write them will make your application more robust and more maintainable.
Here is a series of 8 challenges that you can take in any order.
<LinkCard
title="🟢 Harness"
description="Learn how to test using Angular CDK Component harnesses"
href="/challenges/testing/23-harness/"
/>
<LinkCard
title="🟢 Checkbox"
description="Learn how to debug your tests using Testing Library on a simple checkbox application"
href="/challenges/testing/28-checkbox/"
/>
<LinkCard
title="🟠 Router"
description="Learn how to test the routed components"
href="/challenges/testing/17-router/"
/>
<LinkCard
title="🟠 Nested Components"
description="Learn how to test nested components"
href="/challenges/testing/18-nested-comp/"
/>
<LinkCard
title="🟠 Input Output"
description="Learn how to test inputs and outputs"
href="/challenges/testing/19-input-output/"
/>
<LinkCard
title="🟠 Modal"
description="Learn how to test a modal component"
href="/challenges/testing/20-modal/"
/>
<LinkCard
title="🟠 Harness Creation"
description="Learn how to create harness on your own components"
href="/challenges/testing/24-harness-creation/"
/>
<LinkCard
title="🔴 Real-life Application"
description="Learn how to write a series of test for a real-life applications"
href="/challenges/testing/29-real-application/"
/>

View File

@@ -2,7 +2,7 @@
title: 🟠 Function Overload title: 🟠 Function Overload
description: Challenge 15 is about creating overload functions description: Challenge 15 is about creating overload functions
sidebar: sidebar:
order: 15 order: 115
--- ---
:::note :::note
@@ -42,7 +42,7 @@ Your PR title must start with <b>Answer:15</b>.
❖ Community Answers ❖ Community Answers
</a> </a>
<a <a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A15+label%3A' href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A15+label%3A"answer+author"'
alt="Function Overload solution author"> alt="Function Overload solution author">
▶︎ Author Answer ▶︎ Author Answer
</a> </a>
@@ -51,7 +51,7 @@ Your PR title must start with <b>Answer:15</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="Function Overload blog article"> alt="Function Overload blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>

View File

@@ -5,6 +5,16 @@ sidebar:
order: 4 order: 4
--- ---
:::note You can contribute to this repository in many ways:
WIP:
::: 🔥 Create a new challenge by following the intructions [here](/guides/create-challenge).
🔥 Answer challenges and submit the results. (guide [here](/guides/resolve-challenge)).
🔥 Comment on other's solutions by providing constructive and caring feedback.
🔥 Correct typos or English mistakes within the documentation.
🔥 File an issue to suggest new challenge ideas or report a bug.
🔥 Sponsor the project [here](https://github.com/sponsors/tomalaforge)

View File

@@ -23,8 +23,8 @@ hero:
import { Card, CardGrid } from '@astrojs/starlight/components'; import { Card, CardGrid } from '@astrojs/starlight/components';
<CardGrid> <CardGrid>
<Card title="35 Challenges"> <Card title="36 Challenges">
This repository gathers 35 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>. This repository gathers 36 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
These challenges resolve around real-life issues or specific features to elevate your skills. These challenges resolve around real-life issues or specific features to elevate your skills.
</Card> </Card>
@@ -56,7 +56,10 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
--- ---
<div class="article-footer"> <div class="article-footer">
<a href="https://github.com/sponsors/tomalaforge" alt="Sponsor link"> <a
href="https://github.com/sponsors/tomalaforge"
alt="Sponsor link"
class="primary action astro-yjy4zhro">
🤍 Sponsor the Project 🤍 Sponsor the Project
</a> </a>
</div> </div>

View File

@@ -2,18 +2,30 @@
--sl-color-black: #1b1b1d; --sl-color-black: #1b1b1d;
--sl-color-gray-6: #242526; --sl-color-gray-6: #242526;
--sl-color-accent-high: #f10023; --sl-color-accent-high: #f10023;
/* --sl-color-black: white; */
--sl-icon-color: #fff; --sl-icon-color: #fff;
--sl-color-text-invert: #fff; --sl-color-text-invert: #fff;
--primary-color: var(--sl-color-bg-nav) !important; --primary-color: var(--sl-color-bg-nav) !important;
/* --sl-color-text-invert: #212121; */
--cardBgColor: #242526; --cardBgColor: #242526;
/* --sl-font: 'IBM Plex Serif', serif; */
--sl-hue-purple: 41; --sl-hue-purple: 41;
--sl-color-purple-low: hsl(var(--sl-hue-orange), 39%, 22%); --sl-color-purple-low: hsl(var(--sl-hue-orange), 39%, 22%);
--sl-color-purple: hsl(var(--sl-hue-orange), 82%, 63%); --sl-color-purple: hsl(var(--sl-hue-orange), 82%, 63%);
--sl-color-purple-high: hsl(var(--sl-hue-orange), 82%, 87%); --sl-color-purple-high: hsl(var(--sl-hue-orange), 82%, 87%);
--color-btn: var(--sl-color-white);
--color-chip: rgb(35, 38, 47);
--color-chip-border: rgba(240, 246, 252, 0.1);
}
:root[data-theme='light'],
[data-theme='light'] {
--sl-color-accent: #f10023;
--sl-color-accent-high: #f10023;
--sl-icon-color: rgb(35, 38, 47);
--color-btn: white;
--color-chip: white;
--color-chip-border: rgb(33, 38, 45);
} }
.github-success-btn { .github-success-btn {
@@ -21,7 +33,7 @@
border-radius: 6px; border-radius: 6px;
padding: 2px 8px; padding: 2px 8px;
background-color: rgb(35, 134, 54); background-color: rgb(35, 134, 54);
color: var(--sl-color-white); color: var(-color-btn);
} }
.github-neutral-btn { .github-neutral-btn {
@@ -38,12 +50,13 @@
} }
.chip { .chip {
border: 1px solid rgba(240, 246, 252, 0.1); border-width: 1px;
border-style: solid;
border-color: var(--color-chip-border);
border-radius: 6px; border-radius: 6px;
padding: 2px 8px; padding: 2px 8px;
background-color: rgb(33, 38, 45); background-color: var(--color-chip);
color: rgb(201, 209, 217); color: var(--sl-color-text);
fill: rgb(201, 209, 217);
width: fit-content; width: fit-content;
} }
@@ -54,14 +67,14 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 1.5rem; gap: 1.5rem;
} }
.article-footer > a { .article-footer > a {
border: 1px solid var(--sl-color-accent-high); border: 1px solid var(--sl-color-accent-high);
border-radius: 999rem; border-radius: 999rem;
padding: 1rem; padding: 1rem;
text-decoration: none; text-decoration: none;
color: var(--sl-color-gray-2); color: var(--color-btn);
box-shadow: var(--sl-shadow-md); box-shadow: var(--sl-shadow-md);
color: var(--sl-color-white);
font-size: var(--sl-text-lg); font-size: var(--sl-text-lg);
line-height: var(--sl-line-height-headings); line-height: var(--sl-line-height-headings);
display: flex; display: flex;
@@ -72,14 +85,19 @@
background-color: var(--sl-color-accent-high); background-color: var(--sl-color-accent-high);
} }
a.primary { a.primary,
color: var(--sl-color-white) !important; a.primary > svg {
color: var(--color-btn) !important;
} }
b { b {
color: var(--sl-color-accent-high); color: var(--sl-color-accent-high);
} }
.starlight-aside--tip b {
color: var(--sl-color-asides-text-accent);
}
.main-page-footer { .main-page-footer {
margin-top: 2rem !important; margin-top: 2rem !important;
font-size: var(--sl-text-sm); font-size: var(--sl-text-sm);
@@ -96,3 +114,9 @@ b {
starlight-menu-button svg { starlight-menu-button svg {
color: #1c1a1d; color: #1c1a1d;
} }
.flag {
background-color: white;
padding: 1px 2px;
border-radius: 999px;
}

View File

@@ -1,45 +0,0 @@
<!-- TODO: add title of your exercice -->
<h1>{Title of your exercice}</h1>
<!-- TODO: Add your name -->
> Author: {Your name}
<!-- TODO: add Information/Statement/Rules/Constraint/Steps -->
## Information
## Statement
### Step 1
### Step 2
### Constraints:
### Submitting your work
<!-- To replace
{Project name} by your project name
{challenge number} by the following number (To be find inside the main readme) -->
1. Fork the project
2. clone it
3. npm ci
<!-- TODO: add you project app name directory -->
4. `npx nx serve {project app name}`
5. _...work on it_
6. Commit your work
<!-- TODO: add your challenge number -->
7. Submit a PR with a title beginning with **Answer:{challenge number}** that I will review and other dev can review.
<!-- TODO: add challenge number and project Name -->
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A{challenge number}+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="{Project name}"/></a>
<!-- TODO: uncomment when done late -->
<!-- <a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A{challenge number}+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="{Project name} solution author"/></a>
<a href="{Blog post url}" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/-Blog post explanation-blue" alt="{Project name} blog article"/></a> -->
<!-- TODO: you can add your twitter or anything else if you wish -->
<!-- _You can ask any question on_ <a href="https://twitter.com/laforge_toma" target="_blank" rel="noopener noreferrer"><img src="./../../logo/twitter.svg" height=20px alt="twitter"/></a> -->

View File

@@ -3,7 +3,7 @@ import { Component } from '@angular/core';
@Component({ @Component({
standalone: true, standalone: true,
imports: [], imports: [],
selector: 'lib-root', selector: 'app-root',
template: ``, template: ``,
styles: [''], styles: [''],
}) })

View File

@@ -2,7 +2,8 @@
title: <%= difficulty %> <%= title %> title: <%= difficulty %> <%= title %>
description: Challenge <%= challengeNumber %> is about ... description: Challenge <%= challengeNumber %> is about ...
sidebar: sidebar:
order: <%= challengeNumber %> order: <%= order %>
badge: New
--- ---
:::note :::note

View File

@@ -12,16 +12,44 @@ import {
updateJson, updateJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { Linter } from '@nx/linter'; import { Linter } from '@nx/linter';
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
import { getProjectDir } from '../../utils/normalize'; import { getProjectDir } from '../../utils/normalize';
import { Schema } from './schema'; import { Schema } from './schema';
function findPreviousChallengeFilePath(tree, path, number) {
if (tree.isFile(path) && path.startsWith(`${number}-`)) {
return path;
}
const matchingChild = tree
.children(path)
.find((child) => child.startsWith(`${number}-`));
if (matchingChild) {
const fullPath = path + '/' + matchingChild;
return fullPath;
}
for (const child of tree.children(path)) {
const childPath = path + '/' + child;
const result = findPreviousChallengeFilePath(tree, childPath, number);
if (result) {
return result;
}
}
return null;
}
export async function challengeGenerator(tree: Tree, options: Schema) { export async function challengeGenerator(tree: Tree, options: Schema) {
const { appDirectory } = getProjectDir(options.name, options.directory); const { appDirectory } = getProjectDir(options.name, options.directory);
const difficulty = options.challengeDifficulty;
// read json file with the total challanges and display order
const challengeNumberPath = 'challenge-number.json'; const challengeNumberPath = 'challenge-number.json';
const challengeNumber = readJsonFile(challengeNumberPath).total; const challangeNumberJson = readJsonFile(challengeNumberPath);
const challengeNumber = challangeNumberJson.total + 1;
const order = challangeNumberJson[difficulty] + 1;
await applicationGenerator(tree, { await applicationGenerator(tree, {
...options, ...options,
@@ -60,7 +88,8 @@ export async function challengeGenerator(tree: Tree, options: Schema) {
projectName: names(options.name).name, projectName: names(options.name).name,
title: options.title, title: options.title,
challengeNumber, challengeNumber,
difficulty: options.challengeDifficulty, difficulty,
order,
} }
); );
@@ -70,27 +99,40 @@ export async function challengeGenerator(tree: Tree, options: Schema) {
}); });
} }
const readme = await readFile('./README.md', { encoding: 'utf-8' }); const readme = tree.read('./README.md').toString();
const readmeRegex = new RegExp(`all ${challengeNumber} challenges`); const readmeRegex = new RegExp(`all ${challengeNumber - 1} challenges`);
const readmeReplace = readme.replace( const readmeReplace = readme.replace(
readmeRegex, readmeRegex,
`all ${challengeNumber + 1} challenges` `all ${challengeNumber} challenges`
); );
await writeFile('./README.md', readmeReplace, 'utf-8'); tree.write('./README.md', readmeReplace);
const docs = await readFile('./docs/src/content/docs/index.mdx', { const docs = tree.read('./docs/src/content/docs/index.mdx').toString();
encoding: 'utf-8',
});
const regex = new RegExp(`${challengeNumber} Challenges`, 'gi'); const regex = new RegExp(`${challengeNumber - 1} Challenges`, 'gi');
const replaced = docs.replace(regex, `${challengeNumber + 1} Challenges`); const replaced = docs.replace(regex, `${challengeNumber} Challenges`);
await writeFile('./docs/src/content/docs/index.mdx', replaced, 'utf-8'); tree.write('./docs/src/content/docs/index.mdx', replaced);
const previousChallengeFilePath = findPreviousChallengeFilePath(
tree,
`./docs/src/content/docs/challenges`,
String(challengeNumber - 1)
);
console.log(`restul`, previousChallengeFilePath);
const previousChallenge = tree.read(previousChallengeFilePath).toString();
tree.write(
previousChallengeFilePath,
previousChallenge.replace(`badge: New`, ``)
);
updateJson(tree, challengeNumberPath, (json) => { updateJson(tree, challengeNumberPath, (json) => {
json.total = json.total + 1; json.total += 1;
json[difficulty] += 1;
return json; return json;
}); });

View File

@@ -1,5 +1,4 @@
import { Tree, formatFiles } from '@nx/devkit'; import { Tree, formatFiles } from '@nx/devkit';
import { readFile, writeFile } from 'fs/promises';
const README_FILENAME = 'README.md'; const README_FILENAME = 'README.md';
const OMIT = ['memoized', 'projection', 'testing-table', 'testing-forms']; const OMIT = ['memoized', 'projection', 'testing-table', 'testing-forms'];
@@ -46,10 +45,10 @@ function findHref(href) {
async function rewriteFile(tree: Tree, file: string) { async function rewriteFile(tree: Tree, file: string) {
console.log('Current file', file); console.log('Current file', file);
const buffer = await readFile(file, { encoding: 'utf-8' }); const buffer = tree.read(file);
const regex = new RegExp(/Answer:(\d+)/); const regex = new RegExp(/Answer:(\d+)/);
const match = buffer.match(regex); const match = buffer.toString().match(regex);
if (!match) throw new Error('NO MATCH'); if (!match) throw new Error('NO MATCH');
@@ -69,19 +68,19 @@ async function rewriteFile(tree: Tree, file: string) {
-2 -2
)}/${pathElts.at(-1)}/`; )}/${pathElts.at(-1)}/`;
const doc = await readFile(docFile, { encoding: 'utf-8' }); const doc = tree.read(docFile);
const regexTitle = new RegExp(/title:\s(🟢|🟠|🔴)\s(.+?)\n/); const regexTitle = new RegExp(/title:\s(🟢|🟠|🔴)\s(.+?)\n/);
const matchTitle = doc.match(regexTitle); const matchTitle = doc.toString().match(regexTitle);
const title = matchTitle[2]; const title = matchTitle[2];
const regexCommand = new RegExp(/npx nx serve\s(.+?)`\s/); const regexCommand = new RegExp(/npx nx serve\s(.+?)`\s/);
const matchCommand = buffer.match(regexCommand); const matchCommand = buffer.toString().match(regexCommand);
let command = ''; let command = '';
if (!matchCommand) { if (!matchCommand) {
const regexOldCommand = new RegExp(/nx serve\s(.+?)\*/); const regexOldCommand = new RegExp(/nx serve\s(.+?)\*/);
command = buffer.match(regexOldCommand)[1]; command = buffer.toString().match(regexOldCommand)[1];
} else { } else {
command = matchCommand[1]; command = matchCommand[1];
} }
@@ -103,12 +102,12 @@ npx nx serve ${command}
Challenge documentation is [here](${link}). Challenge documentation is [here](${link}).
`; `;
await writeFile(file, finalText, { encoding: 'utf-8' }); tree.write(file, finalText);
///**** */ ///**** */
const regexHref = new RegExp(/<a href=("|')(.+?)("|')/, 'g'); const regexHref = new RegExp(/<a href=("|')(.+?)("|')/, 'g');
const href = buffer.match(regexHref).map(findHref); const href = buffer.toString().match(regexHref).map(findHref);
console.log('HREF', href); console.log('HREF', href);
@@ -139,7 +138,7 @@ Your PR title must start with <b>Answer:${number}</b>.
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
alt="${title} blog article"> alt="${title} blog article">
<svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg> <svg aria-hidden="true" class="astro-yzt5nm4y astro-lq7oo3uf" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"></path></svg>
Blog Post Blog Post
</a> </a>
</div> </div>
@@ -150,21 +149,21 @@ Your PR title must start with <b>Answer:${number}</b>.
} }
const regexHeader = new RegExp(/([\s\S]*?)\s:::note/); const regexHeader = new RegExp(/([\s\S]*?)\s:::note/);
const header = doc.match(regexHeader)[1]; const header = doc.toString().match(regexHeader)[1];
console.log('header', header); console.log('header', header);
const regexContent = new RegExp( const regexContent = new RegExp(
/Author: Thomas Laforge([\s\S]*?)### Submitting your work/ /Author: Thomas Laforge([\s\S]*?)### Submitting your work/
); );
const matchContent = buffer.match(regexContent); const matchContent = buffer.toString().match(regexContent);
let content = ''; let content = '';
if (!matchContent) { if (!matchContent) {
const regexOldContent = new RegExp( const regexOldContent = new RegExp(
/Author: Thomas Laforge([\s\S]*?)## Submitting your work/ /Author: Thomas Laforge([\s\S]*?)## Submitting your work/
); );
content = buffer.match(regexOldContent)[1]; content = buffer.toString().match(regexOldContent)[1];
} else { } else {
content = matchContent[1]; content = matchContent[1];
} }
@@ -184,7 +183,7 @@ ${content}
${footerText} ${footerText}
`; `;
await writeFile(docFile, fullDocText, { encoding: 'utf-8' }); tree.write(docFile, fullDocText);
} }
export async function readmeGenerator(tree: Tree) { export async function readmeGenerator(tree: Tree) {

View File

@@ -1 +1,2 @@
export * from './lib/cd-flashing.directive'; export * from './lib/cd-flashing.directive';
export { NgForTrackByModule } from './lib/track-by.directive';

View File

@@ -0,0 +1,51 @@
/* eslint-disable @angular-eslint/directive-selector */
import { NgFor, NgForOf } from '@angular/common';
import {
Directive,
Input,
NgIterable,
NgModule,
Provider,
inject,
} from '@angular/core';
@Directive({
selector: '[ngForTrackByProp]',
standalone: true,
})
export class NgForTrackByPropDirective<T> {
@Input() ngForOf!: NgIterable<T>;
@Input()
set ngForTrackByProp(ngForTrackBy: keyof T) {
// setter
this.ngFor.ngForTrackBy = (index: number, item: T) => item[ngForTrackBy];
}
private ngFor = inject(NgForOf<T>, { self: true });
}
@Directive({
selector: '[ngForTrackById]',
standalone: true,
})
export class NgForTrackByIdDirective<T extends { id: string | number }> {
@Input() ngForOf!: NgIterable<T>; // 2
private ngFor = inject(NgForOf<T>, { self: true }); // 3
constructor() {
this.ngFor.ngForTrackBy = (index: number, item: T) => item.id; // 4
}
}
export const NgForTrackByDirective: Provider[] = [
NgForTrackByIdDirective,
NgForTrackByPropDirective,
];
@NgModule({
imports: [NgFor, NgForTrackByDirective],
exports: [NgFor, NgForTrackByDirective],
})
export class NgForTrackByModule {}

View File

@@ -8,7 +8,8 @@
"start": "nx serve", "start": "nx serve",
"build": "nx build", "build": "nx build",
"test": "nx test", "test": "nx test",
"prepare": "husky install" "prepare": "husky install",
"doc:dev": "cd docs && npm run dev"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {