{"id":21155513,"url":"https://github.com/ortegavan/ecommerce","last_synced_at":"2025-07-09T11:31:43.239Z","repository":{"id":224809138,"uuid":"764275862","full_name":"ortegavan/ecommerce","owner":"ortegavan","description":"Este repositório contém os exercícios/aprendizados da Mentoria Angular Pro de Paolo Almeida e Andrew Rosário","archived":false,"fork":false,"pushed_at":"2024-08-11T09:01:55.000Z","size":616,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-08-11T22:45:43.787Z","etag":null,"topics":["angular","nx","rxjs","typescript"],"latest_commit_sha":null,"homepage":"https://mentoria-angular.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ortegavan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-27T19:41:24.000Z","updated_at":"2024-08-11T09:01:58.000Z","dependencies_parsed_at":"2024-03-26T00:29:30.000Z","dependency_job_id":"5f9d51b6-082f-482e-b576-6f1d07a96198","html_url":"https://github.com/ortegavan/ecommerce","commit_stats":null,"previous_names":["ortegavan/ecommerce"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ortegavan%2Fecommerce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ortegavan%2Fecommerce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ortegavan%2Fecommerce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ortegavan%2Fecommerce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ortegavan","download_url":"https://codeload.github.com/ortegavan/ecommerce/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225538118,"owners_count":17485140,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","nx","rxjs","typescript"],"created_at":"2024-11-20T11:21:44.597Z","updated_at":"2024-11-20T11:21:45.294Z","avatar_url":"https://github.com/ortegavan.png","language":"TypeScript","readme":"# Projeto criado na Mentoria Angular Pro\n\n![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ortegavan/ecommerce/ci.yml) ![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/ortegavan_ecommerce?server=https%3A%2F%2Fsonarcloud.io) ![GitHub last commit](https://img.shields.io/github/last-commit/ortegavan/ecommerce) ![GitHub Tag](https://img.shields.io/github/v/tag/ortegavan/ecommerce) ![Static Badge](https://img.shields.io/badge/code_style-prettier-ff69b4)\n\nEste documento contém os exercícios feitos em aula + minhas notas pessoais sobre a Mentoria Angular Pro de Paolo Almeida e Andrew Rosário.\n\nEm aula, os mentores utilizam `scss`. Eu optei por usar `css` por ter mais prática com ele. Os scripts do Nx que criam libs com `scss`, na mentoria, foram alterados aqui para `css`.\n\nSe este documento for útil para você, considere deixar uma :star: no repositório.\n\n## ✨ Aula 1\n\nExplicação do escopo do projeto: trata-se de um e-commerce com as funcionalidades de cadastro/login, uma home e um catálogo de produtos. O projeto contempla o front-end desenvolvido em Angular com Nx e o uso de uma API fake disponível no [https://mockapi.io](https://mockapi.io).\n\n## ✨ Aula 2\n\nCriação do projeto utilizando o comando:\n\n```bash\nnpx create-nx-workspace@latest ecommerce --preset=angular-standalone\n```\n\nExecução do projeto com o comando:\n\n```bash\nnx serve\n```\n\n## ✨ Aula 3\n\nCriação da biblioteca de layout com o comando:\n\n```bash\nnx g @nx/angular:library --name=layout --directory=modules/feature/layout --projectNameAndRootFormat=as-provided --standalone=false --style=css\n```\n\nVisualização do gráfico de dependências do projeto com o comando:\n\n```bash\nnx graph\n```\n\nCriado componente header usando Nx Console a partir da pasta `modules/feature/layout/src/lib` passando as opções `export=true` e `standalone=false`. O comando gerado a partir do Nx Console foi:\n\n```bash\nnpx nx generate @nx/angular:component --name=header --directory=header --export=true --standalone=false --nameAndDirectoryFormat=as-provided --no-interactive\n```\n\nConsumo da biblioteca de layout no projeto principal:\n\n1. Importado o módulo `LayoutModule` em `app.component.ts`;\n2. Adicionado o componente `ecommerce-header` em `app.component.html`;\n3. Além disso, foi excluído o teste que não passava (fazendo referência a um título que não existe).\n\nFeito isso, `nx lint` e `nx test` foram executados para garantir que o projeto está funcionando corretamente.\n\n## ✨ Aula 4\n\nPrimeiro, foi estilizado o componente `header`. Em seguida, foram escritos testes unitários para o componente `header`:\n\n```typescript\nit(`should contain title`, () =\u003e {\n    const header: HTMLHeadElement =\n        fixture.nativeElement.querySelector('header');\n    expect(header.textContent).toBe('Ecommerce');\n});\n```\n\nE para o componente `app`:\n\n```typescript\nit(`should contain header`, () =\u003e {\n    const header: HTMLElement = fixture.nativeElement.querySelector('header');\n    expect(header).toBeTruthy();\n});\n```\n\n## ✨ Aula 5\n\nInstalado `husky` + `lint-staged` para rodar `nx lint` e `nx test` antes de cada commit. Seguem comandos:\n\n```bash\nnpx husky-init \u0026\u0026 npm install\nnpm install lint-staged\n```\n\nPara configurar:\n\n1. Substituir a instrução `npm test` por `npx lint-staged` no arquivo `.husky/pre-commit`;\n2. Criar um arquivo `.lintstagedrc` na raiz da aplicação com o seguinte conteúdo:\n\n```json\n{\n    \"{src,modules}/**/*.{js,ts,jsx,tsx,json,html,css,scss}\": [\n        \"nx affected:lint --fix --uncommitted\",\n        \"nx affected:test\",\n        \"nx format:write --uncommited\"\n    ]\n}\n```\n\n3. Adicionar a regra abaixo na seção `rules` do arquivo `eslintrc.base.json` para permitir o uso de `console.warn` e `console.error` no código mas não permitir `console.log`:\n\n```json\n\"no-console\": [\n    \"error\", {\n        \"allow\": [\"warn\", \"error\"]\n    }\n],\n```\n\n4. Testar o commit.\n\n## ✨ Aula 6\n\nCriado o módulo `product-data-access` com o comando:\n\n```bash\nnpx nx g @nx/angular:library --name=product-data-access --directory=modules/data-access/product --projectNameAndRootFormat=as-provided\n```\n\nO componente `product-data-access` foi excluído e removido da `index.ts`.\n\n**Notas**\n\nPara que o commit funcionasse, precisei alterar na configuração do `lint-staged`:\n\n1. Removi `js` e `css` do `nx lint`;\n2. Acrescentei `--passWithNoTests` no `nx test`.\n\nCriada a model `Product` em `modules/data-access/product/src/lib/models/product.ts`:\n\n```typescript\nexport type Product = {\n    createdAt: string;\n    name: string;\n    price: string;\n    description: string;\n    image: string;\n    id: string;\n    quantity: number;\n};\n```\n\nCriado o serviço para busca de produtos com o comando:\n\n```bash\nnpx nx g @schematics/angular:service --name=product-search --project=product-data-access --flat=false\n```\n\nEm `src/app/app.config.ts` foi importado o `HttpClient` via `provideHttpClient()`.\n\nPor último, foi implementado o teste através do `HttpClientTestingModule` e `HttpTestingController` para o serviço `ProductSearchService`.\n\n## 🍺 Pausa para meus ajustes\n\nAlterei meu `.prettierrc` para usar 4 espaços em vez de 2 nas formatações:\n\n```json\n{\n    \"singleQuote\": true,\n    \"useTabs\": false,\n    \"tabWidth\": 4\n}\n```\n\nRodei o comando abaixo para reformatar todos os arquivos do projeto com o novo espaçamento:\n\n```bash\nnx format:write --all\n```\n\nReestilizei alguns componentes e apliquei uma nova fonte ao projeto:\n\n1. Adicionei a fonte [Montserrat](https://fonts.google.com/specimen/Montserrat) na `index.html`;\n2. Alterei `modules/feature/layout/src/lib/header/header.component.css`;\n3. Alterei `styles.css`.\n\n🌸 Floreei 🌸 este README.md.\n\n## ✨ Aula 7\n\nFoi instalado/adicionado ao projeto o Angular Material com os comandos abaixo:\n\n```bash\nnpm install @angular/material\nnpx nx g @angular/material:ng-add --project=ecommerce\n```\n\nEm seguida, foi criado o módulo Product Search com o comando:\n\n```bash\nnpx nx g @nx/angular:library --name=product-search --directory=modules/feature/product/search --projectNameAndRootFormat=as-provided --style=css\n```\n\nOs dados do Data Access foram exportados via `modules/data-access/product/src/lib/index.ts`:\n\n```typescript\nexport * from './lib/mocks/product.mock';\nexport * from './lib/product-search/product-search.service';\n```\n\nO componente `product-search` foi implementado usando o componente [Autocomplete](https://material.angular.io/components/autocomplete/overview#automatically-highlighting-the-first-option) do Angular Material;\n\nO padrão de composição foi aplicado no componente `header`:\n\n```html\n\u003cheader class=\"header\"\u003e\n    \u003ch1 class=\"logo\"\u003e{{ title }}\u003c/h1\u003e\n    \u003cng-content\u003e\u003c/ng-content\u003e\n    \u003cng-content select=\"[right]\"\u003e\u003c/ng-content\u003e\n\u003c/header\u003e\n```\n\nE o componente foi então consumido no `app.component.html`:\n\n```html\n\u003cecommerce-header title=\"e-Commerce\"\u003e\n    \u003cecommerce-product-search\u003e\u003c/ecommerce-product-search\u003e\n    \u003cp right\u003eLogin\u003c/p\u003e\n\u003c/ecommerce-header\u003e\n\u003crouter-outlet\u003e\u003c/router-outlet\u003e\n```\n\nPor fim, para os testes rodarem corretamente, foram desabilitadas as animações do Angular Material no `product.search.component.spec.ts`:\n\n```typescript\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\n```\n\n## 🍺 Pausa para meus ajustes\n\n-   Setei a propriedade `subscriptSizing` do campo de busca para `dynamic` para alinhar o componente verticalmente;\n-   Removi a fonte Roboto da `index.html` porque já havia configurado a Montserrat;\n-   Temporariamente, coloquei um ícone no lugar do texto \"Login\" no `app.component.html` até definirmos o próximo componente.\n\n## ✨ Aula 8\n\nFoi implementada a busca de produtos no componente `product-search` com o uso do `FormControl` e operadores do RxJS para evitar requisições desnecessárias e foi utilizado pipe async para subscrever o observable no template.\n\n```typescript\nthis.products$ = this.control.valueChanges.pipe(\n    debounceTime(333),\n    distinctUntilChanged(),\n    filter((text) =\u003e text.length \u003e 1),\n    switchMap((text) =\u003e this.productSearchService.searchByName(text))\n);\n```\n\n## ✨ Aula 9\n\nForam implementados testes para o componente `product-search` e para o serviço `ProductSearchService`. Utilizamos `FakeAsync` + `tick` para simular o tempo de espera da requisição e usamos spy para verificar se o método `searchByName` foi chamado.\n\n## ✨ Aula 10\n\nNesta aula, criamos o módulo `home` com o comando:\n\n```bash\nnx g @nx/angular:library --name=home --directory=modules/feature/home --lazy=true --routing=true --projectNameAndRootFormat=as-provided --style=css\n```\n\nDiscutimos as estratégias de preloading disponíveis no Angular e implementamos o lazy loading do módulo `home`. Aprendi que o lazy loading pode ser configurado usando `loadChildren` apontando para o módulo de rotas:\n\n```typescript\nexport const appRoutes: Route[] = [\n    { path: '', redirectTo: 'home', pathMatch: 'full' },\n    {\n        path: 'home',\n        loadChildren: () =\u003e import('@ecommerce/home').then((r) =\u003e r.homeRoutes),\n    },\n];\n```\n\nVimos o lazy loading em ação ao inspecionar a aplicação no navegador:\n\n![Lazy loading](src/assets/readme/001.png)\n\nMais sobre as estratégias de preloading pode ser visto [neste post](https://dev.to/this-is-angular/optimize-your-angular-apps-user-experience-with-preloading-strategies-3ie7).\n\n## ✨ Aula 11\n\nImplementamos a seção de produtos recomendados, por enquanto com mock de dados. Conversamos sobre HTML semântico e a importância de usar tags apropriadas para melhorar a acessibilidade e SEO. Famos sobre o padrão BEM para nomenclatura de classes do CSS.\n\nMais sobre padrões de acessibilidade pode ser visto [neste link](https://www.w3.org/WAI/ARIA/apg/patterns/).\n\n## ✨ Aula 12\n\nCriamos o serviço de produtos recomendados agora buscando da API (antes usávamos mock) e refatoramos a home para separar o código responsável pelo card de produto.\n\n## ✨ Aula 13\n\nCriamos a lib para exibir os detalhes de um produto com o comando:\n\n```bash\nnpx nx g @nx/angular:library --name=product-detail --directory=modules/feature/product/detail --lazy=true --routing=true --projectNameAndRootFormat=as-provided --style=css\n```\n\nHabilitamos a captura de parâmetros através de input no componente adicionando a função `withComponentInputBinding` no `app.config.ts`:\n\n```typescript\nexport const appConfig: ApplicationConfig = {\n    providers: [\n        provideRouter(appRoutes, withComponentInputBinding()),\n        provideHttpClient(),\n        provideAnimationsAsync(),\n    ],\n};\n```\n\nPara os testes passarem, foi necessário adicionar o `RouterTestingModule` em `product-detail.component.spec.ts`.\n\n## ✨ Aula 14\n\nImplementamos _module boundaries_ para garantir as regras:\n\n-   `type:data-access` deve ser capaz de importar de `type:data-access`;\n-   `type:feature` deve ser capaz de importar de `type:feature`, `type:ui` e `type:data-access`;\n-   `type:ui` deve ser capaz de importar de `type:ui` e `type:data-access`\n\nPara aplicar as regras, precisamos dos dois passos:\n\n1. Atribuir um identificador às nossas libs. Isso é feito adicionando-se tags no arquivo `project.json` (exemplo abaixo para a lib `product-data-access`):\n\n```json\n\"tags\": [\"type:data-access\"]\n```\n\n2. Definir as regras de importação no arquivo `.eslintrc.base.json` no array `depConstraints` do plugin `@nx/enforce-module-boundaries`:\n\n```json\n{\n    \"sourceTag\": \"type:data-access\",\n    \"onlyDependOnLibsWithTags\": [\"type:data-access\"]\n},\n{\n    \"sourceTag\": \"type:feature\",\n    \"onlyDependOnLibsWithTags\": [\n        \"type:feature\",\n        \"type:ui\",\n        \"type:data-access\"\n    ]\n},\n{\n    \"sourceTag\": \"type:ui\",\n    \"onlyDependOnLibsWithTags\": [\n        \"type:ui\",\n        \"type:data-access\"\n    ]\n}\n```\n\nMais sobre _module boundaries_ pode ser visto [neste link](https://nx.dev/features/enforce-module-boundaries) e [neste link](https://andrewrosario.medium.com/definindo-limites-de-módulos-no-nx-com-module-boundaries-4088f758957f).\n\n## ✨ Aula 15\n\nCriamos um pipe customizado e vimos a diferença entre um pipe puro e um pipe impuro. O pipe customizado foi criado com o comando:\n\n```bash\nnx g @nx/angular:pipe --name=quantity-description --directory=modules/feature/product/detail/src/lib/pipes/quantity-description --nameAndDirectoryFormat=as-provided\n```\n\nVimos a importância de utilizar o pipe em vez de chamar funções diretamente no template pois o change detection do Angular é mais eficiente. Mais sobre pipes pode ser visto [neste link](https://andrewrosario.medium.com/angular-pipes-uma-visão-mais-profunda-69e2413c34d8).\n\n## ✨ Aula 16\n\nCriamos um interceptor para tratar erros nas requisições HTTP. O interceptor foi criado com o comando:\n\n```bash\nnx g @schematics/angular:interceptor --name=http-errors --project=ecommerce --flat=false --path=src/app/interceptors\n```\n\nUtilizamos o snack bar do Angular Material para exibir mensagens de erro ao usuário. O código final do interceptor ficou assim:\n\n```typescript\nexport const httpErrorsInterceptor: HttpInterceptorFn = (req, next) =\u003e {\n    const snackBar = inject(MatSnackBar);\n\n    return next(req).pipe(\n        catchError((err) =\u003e {\n            snackBar.open('Ops, ocorreu um erro', 'Fechar', {\n                duration: 5000,\n            });\n\n            return throwError(() =\u003e err);\n        })\n    );\n};\n```\n\nNo teste do interceptor, forçamos um erro na requisição e verificamos se o snack bar foi chamado:\n\n```typescript\nit('should open notification on http error', () =\u003e {\n    jest.spyOn(snackBar, 'open');\n    httpClient.get('/test').subscribe();\n\n    const request = httpMock.expectOne('/test');\n    request.error(new ProgressEvent('error'));\n\n    expect(snackBar.open).toHaveBeenCalled();\n});\n```\n\n## ✨ Aula 17\n\nDiscutimos sobre gerenciamento de estado e falamos sobre as libs disponíveis no mercado para isso no Angular. No entanto, concordamos que na maioria das vezes é possível gerenciar o estado apenas com RxJS ou Signals, fugindo da complexidade que estas libs trazem.\n\nCriamos uma service para gerenciar o estado do carrinho de compras com o comando:\n\n```bash\nnx g @schematics/angular:service --name=cart --project=product-data-access --flat=false --path=modules/data-access/product/src/lib/state\n```\n\nE, nesta service, implementamos o gerenciamento de estado a) primeiro com RxJS:\n\n```typescript\nprivate cartSubject$ = new BehaviorSubject\u003cProduct[]\u003e([]);\ncart$ = this.cartSubject$.asObservable();\nquantity$ = this.cart$.pipe(map((products) =\u003e products.length));\n\naddToCart(product: Product) {\n    const cart = this.cartSubject$.getValue();\n    this.cartSubject$.next([...cart, product]);\n}\n```\n\nb) e depois com Signals:\n\n```typescript\nprivate cartSignal = signal\u003cProduct[]\u003e([]);\ncart = this.cartSignal.asReadonly();\nquantity = computed(() =\u003e this.cart().length);\n\naddToCart(product: Product) {\n    this.cartSignal.update((cart) =\u003e [...cart, product]);\n}\n```\n\n## ✨ Aula 18\n\nCriamos o componente `CartComponent` para exibir a quantidade de itens no carrinho usando o Badge do Angular Material. Para termos acesso ao estado do carrinho, utilizamos o serviço `CartService` com o signal já implementado anteriormente. O componente foi, então, consumido em `app.component.html`.\n\n## ✨ Aula 19\n\nCriado primeiro script de integração contínua disponível em `.github/workflows/ci.yml` que executa formatação, lint e testes no projeto a cada push na `main` ou novo pull request.\n\n## ✨ Aula 20\n\nConfiguramos uma conta na Vercel e importamos o projeto do GitHub para a plataforma. A partir de agora, o deploy será feito automaticamente a cada push na branch `main`.\n\nTambém criamos a nova lib que será usada para construir o formulário para autenticação do usuário:\n\n```bash\nnpx nx g @nx/angular:library --name=auth-form --directory=modules/feature/auth/form --lazy=true --routing=true --projectNameAndRootFormat=as-provided --style=css --tags=type:feature\n```\n\nE adicionamos nova rota no `appRoutes` para o módulo de autenticação:\n\n```typescript\n{\n    path: 'auth',\n    loadChildren: () =\u003e\n        import('@ecommerce/auth-form').then((r) =\u003e r.authFormRoutes),\n},\n```\n\n## ✨ Aula 21\n\nComeçamos a construir um formulário reativo em etapas utilizando um componente como orquestrador tendo seu próprio `router-outlet`. Cada etapa do formulário é um componente filho separado com rotas configuradas no arquivo `lib.routes.ts` da lib de autenticação.\n\nOs componentes filhos conseguem acessar o componente pai via injeção de dependência (sim, de componentes!) e, assim, compartilhar informações entre si. Para saber mais, acesse [este link](https://angular.dev/guide/di/hierarchical-dependency-injection).\n\nPara cenários mais genéricos, é possível criar uma classe abstrata que os orquestradores implementam para serem injetadas nos componentes filhos. Para saber mais, acesse [este link](https://netbasal.com/create-a-multi-step-form-in-angular-44cdc5b75cdc).\n\nFormulários complexos também podem ser divididos com [ControlContainer](https://andrewrosario.medium.com/dividindo-formulários-complexos-no-angular-com-controlcontainer-1b107d59c8be) e com [ControlValueAccessor](https://andrewrosario.medium.com/form-controls-customizados-no-angular-com-controlvalueaccessor-367e773e3fec).\n\n## ✨ Aula 22\n\nFinalizamos o formulário iniciado na aula anterior e implementamos os testes. Foi necessário fornecer rotas com `provideRouter` e importar o `NoopAnimationsModule` para os testes passarem. Para testarmos a interação com o HTML do form, substituímos a manipulação do `FormControl` como mostrado a seguir:\n\n```typescript\nit('should display email error message', () =\u003e {\n    // component.control.setValue('teste');\n    const input: HTMLInputElement =\n        fixture.nativeElement.querySelector('input');\n    input.value = 'teste';\n    input.dispatchEvent(new Event('input'));\n\n    component.control.markAllAsTouched();\n    fixture.detectChanges();\n    const error = fixture.nativeElement.querySelector(\n        '[data-testid=\"error-email\"]'\n    );\n\n    expect(error).toBeTruthy();\n});\n```\n\nOnde `data-testid` é um atributo customizado que usamos para identificar elementos no HTML.\n\n## ✨ Aula 23\n\nCriamos uma nova lib para gerenciar autenticação com o comando:\n\n```bash\nnx g @nx/angular:library --name=auth-data-access --directory=modules/data-access/auth --projectNameAndRootFormat=as-provided --standalone=false --tags=type:data-access\n```\n\nCriamos uma nova service para armazenar o estado de autenticação com o comando:\n\n```bash\nnpx nx g @schematics/angular:service --name=auth --project=auth-data-access --flat=false\n```\n\nCriamos a função `authGuard` para ser utilizada como guarda da rota de login que redireciona para a home caso o usuário já esteja autenticado ou retorna `true` no `canActivate` caso contrário, permitindo o acesso à tela de autenticação.\n\n## ✨ Aula 24\n\nCriamos, implementamos alguns casos de uso e escrevemos testes para a diretiva `Log`. Mais sobre diretivas pode ser visto [neste link](https://andrewrosario.medium.com/desmistificando-as-poderosas-diretivas-no-angular-ad2c3840a712) e [neste link](https://andrewrosario.medium.com/injetando-componentes-no-angular-através-de-diretivas-cae90992e83).\n\n## ✨ Aula 25\n\nImplementamos alguns testes E2E com Cypress. Rodamos o Cypress em modo headless com o comando:\n\n```bash\nnx e2e e2e\n```\n\nRodamos Cypress via interface gráfica com o comando:\n\n```bash\nnpx cypress open --project ./e2e\n```\n\nObs. 1: o parâmetro `--watch` foi descontinuado.\n\nObs. 2: precisei reconfigurar o `tsconfig.json` do Cypress para que o TypeScript reconhecesse os tipos do Cypress.\n\nAlteramos o workflow de CI para executar os testes E2E adicionando o comando a seguir:\n\n```yml\n- run: npx nx affected -t e2e --parallel=3 --configuration=ci\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fortegavan%2Fecommerce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fortegavan%2Fecommerce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fortegavan%2Fecommerce/lists"}