ngx-opalbytes-components 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +103 -0
  2. package/cli/cao-comp.cjs +186 -0
  3. package/fesm2022/ngx-opalbytes-components.mjs +331 -76
  4. package/fesm2022/ngx-opalbytes-components.mjs.map +1 -1
  5. package/package.json +4 -1
  6. package/src/lib/shared/components/autocomplete/autocomplete.component.html +126 -0
  7. package/src/lib/shared/components/autocomplete/autocomplete.component.scss +150 -0
  8. package/src/lib/shared/components/autocomplete/autocomplete.component.ts +552 -0
  9. package/src/lib/shared/components/base-button/base-button.component.html +33 -0
  10. package/src/lib/shared/components/base-button/base-button.component.scss +14 -0
  11. package/src/lib/shared/components/base-button/base-button.component.spec.ts +64 -0
  12. package/src/lib/shared/components/base-button/base-button.component.ts +48 -0
  13. package/src/lib/shared/components/base-icon/base-icon.css +0 -0
  14. package/src/lib/shared/components/base-icon/base-icon.html +11 -0
  15. package/src/lib/shared/components/base-icon/base-icon.spec.ts +23 -0
  16. package/src/lib/shared/components/base-icon/base-icon.ts +18 -0
  17. package/src/lib/shared/components/base-input/base-input.css +193 -0
  18. package/src/lib/shared/components/base-input/base-input.html +43 -0
  19. package/src/lib/shared/components/base-input/base-input.spec.ts +146 -0
  20. package/src/lib/shared/components/base-input/base-input.ts +133 -0
  21. package/src/lib/shared/components/base-time-range/base-time-range.css +27 -0
  22. package/src/lib/shared/components/base-time-range/base-time-range.html +23 -0
  23. package/src/lib/shared/components/base-time-range/base-time-range.spec.ts +27 -0
  24. package/src/lib/shared/components/base-time-range/base-time-range.ts +61 -0
  25. package/src/lib/shared/components/base-toggle/base-toggle.component.html +28 -0
  26. package/src/lib/shared/components/base-toggle/base-toggle.component.scss +45 -0
  27. package/src/lib/shared/components/base-toggle/base-toggle.component.spec.ts +22 -0
  28. package/src/lib/shared/components/base-toggle/base-toggle.component.ts +138 -0
  29. package/src/lib/shared/components/card/card.component.css +264 -0
  30. package/src/lib/shared/components/card/card.component.html +103 -0
  31. package/src/lib/shared/components/card/card.component.ts +63 -0
  32. package/src/lib/shared/components/date-picker/date-picker.html +82 -0
  33. package/src/lib/shared/components/date-picker/date-picker.scss +8 -0
  34. package/src/lib/shared/components/date-picker/date-picker.spec.ts +23 -0
  35. package/src/lib/shared/components/date-picker/date-picker.ts +165 -0
  36. package/src/lib/shared/components/drop-down/drop-down.css +61 -0
  37. package/src/lib/shared/components/drop-down/drop-down.html +96 -0
  38. package/src/lib/shared/components/drop-down/drop-down.spec.ts +335 -0
  39. package/src/lib/shared/components/drop-down/drop-down.ts +335 -0
  40. package/src/lib/shared/components/footer/footer.css +29 -0
  41. package/src/lib/shared/components/footer/footer.html +10 -0
  42. package/src/lib/shared/components/footer/footer.spec.ts +22 -0
  43. package/src/lib/shared/components/footer/footer.ts +15 -0
  44. package/src/lib/shared/components/index.ts +19 -0
  45. package/src/lib/shared/components/kpi/kpi.css +0 -0
  46. package/src/lib/shared/components/kpi/kpi.html +11 -0
  47. package/src/lib/shared/components/kpi/kpi.spec.ts +38 -0
  48. package/src/lib/shared/components/kpi/kpi.ts +25 -0
  49. package/src/lib/shared/components/links-button/links-button.css +70 -0
  50. package/src/lib/shared/components/links-button/links-button.html +23 -0
  51. package/src/lib/shared/components/links-button/links-button.spec.ts +139 -0
  52. package/src/lib/shared/components/links-button/links-button.ts +45 -0
  53. package/src/lib/shared/components/paginator/paginator.css +0 -0
  54. package/src/lib/shared/components/paginator/paginator.html +10 -0
  55. package/src/lib/shared/components/paginator/paginator.spec.ts +23 -0
  56. package/src/lib/shared/components/paginator/paginator.ts +25 -0
  57. package/src/lib/shared/components/stepper/step/step.css +8 -0
  58. package/src/lib/shared/components/stepper/step/step.html +1 -0
  59. package/src/lib/shared/components/stepper/step/step.spec.ts +131 -0
  60. package/src/lib/shared/components/stepper/step/step.ts +14 -0
  61. package/src/lib/shared/components/stepper/stepper.css +17 -0
  62. package/src/lib/shared/components/stepper/stepper.html +11 -0
  63. package/src/lib/shared/components/stepper/stepper.spec.ts +238 -0
  64. package/src/lib/shared/components/stepper/stepper.ts +57 -0
  65. package/src/lib/shared/components/tab-group/tab-group.component.html +84 -0
  66. package/src/lib/shared/components/tab-group/tab-group.component.scss +151 -0
  67. package/src/lib/shared/components/tab-group/tab-group.spec.ts +22 -0
  68. package/src/lib/shared/components/tab-group/tab-group.ts +218 -0
  69. package/src/lib/shared/components/tab-panel/tab-content.directive.ts +7 -0
  70. package/src/lib/shared/components/tab-panel/tab-label.directive.ts +7 -0
  71. package/src/lib/shared/components/tab-panel/tab-panel.css +0 -0
  72. package/src/lib/shared/components/tab-panel/tab-panel.spec.ts +22 -0
  73. package/src/lib/shared/components/tab-panel/tab-panel.ts +31 -0
  74. package/src/lib/shared/components/time-picker/time-picker.css +84 -0
  75. package/src/lib/shared/components/time-picker/time-picker.html +21 -0
  76. package/src/lib/shared/components/time-picker/time-picker.spec.ts +23 -0
  77. package/src/lib/shared/components/time-picker/time-picker.ts +90 -0
  78. package/types/ngx-opalbytes-components.d.ts +70 -13
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+
3
+ import { Footer } from "./footer";
4
+
5
+ describe("Footer", () => {
6
+ let component: Footer;
7
+ let fixture: ComponentFixture<Footer>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [Footer],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(Footer);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it("should create", () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,15 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import { Component, Input } from "@angular/core";
3
+
4
+ @Component({
5
+ selector: "cao-footer",
6
+ templateUrl: "./footer.html",
7
+ styleUrl: "./footer.css",
8
+ imports: [ CommonModule ]
9
+ })
10
+ export class Footer {
11
+ currentYear: number = new Date().getFullYear();
12
+ @Input() currentRoute?:string;
13
+ @Input() footerText?:string;
14
+ @Input() isFixed = true;
15
+ }
@@ -0,0 +1,19 @@
1
+ export * from './base-button/base-button.component'
2
+ export * from './base-input/base-input'
3
+ export * from './base-time-range/base-time-range'
4
+
5
+ export * from './autocomplete/autocomplete.component'
6
+ export * from './drop-down/drop-down'
7
+ export * from './links-button/links-button'
8
+ export * from './footer/footer'
9
+ export * from './paginator/paginator'
10
+ export * from './time-picker/time-picker'
11
+
12
+ export * from './stepper/stepper'
13
+ export * from './stepper/step/step'
14
+
15
+ export * from "./tab-group/tab-group"
16
+ export * from "./tab-panel/tab-panel"
17
+
18
+ export * from "./base-icon/base-icon"
19
+ export * from "./kpi/kpi"
File without changes
@@ -0,0 +1,11 @@
1
+ <div
2
+ class="flex flex-col py-2 pt-2.25 px-3.75 rounded-card border-b w-46.25 bg-(--color-white)"
3
+ [style.border-color]="kpiData().borderColor">
4
+ <div class="flex w-full justify-end">
5
+ <img class="w-6 h-6 mr-2" [src]="kpiData().icon.src" [alt]="kpiData().icon.alt" />
6
+ </div>
7
+ <div class="flex flex-col pl-1.5">
8
+ <p class="text-2xl" [style.color]="kpiData().textColor">{{ kpiData().value }}</p>
9
+ <h3 class="text-base" [style.color]="kpiData().textColor">{{ kpiData().title }}</h3>
10
+ </div>
11
+ </div>
@@ -0,0 +1,38 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+
3
+ import { CaoKpi, ICaoKPI } from "./kpi";
4
+
5
+ const mockKpiData: ICaoKPI = {
6
+ title: "Test",
7
+ value: "42",
8
+ textColor: "#000000",
9
+ borderColor: "#ffffff",
10
+ icon: { src: "icon.png", alt: "icon" },
11
+ };
12
+
13
+ describe("CaoKpi", () => {
14
+ let component: CaoKpi;
15
+ let fixture: ComponentFixture<CaoKpi>;
16
+
17
+ beforeEach(async () => {
18
+ await TestBed.configureTestingModule({
19
+ imports: [CaoKpi],
20
+ }).compileComponents();
21
+
22
+ fixture = TestBed.createComponent(CaoKpi);
23
+ component = fixture.componentInstance;
24
+ fixture.componentRef.setInput("kpiData", mockKpiData);
25
+ fixture.detectChanges();
26
+ await fixture.whenStable();
27
+ });
28
+
29
+ it("should create", () => {
30
+ expect(component).toBeTruthy();
31
+ });
32
+
33
+ it("should render the kpi data", () => {
34
+ const el: HTMLElement = fixture.nativeElement;
35
+ expect(el.textContent).toContain("42");
36
+ expect(el.textContent).toContain("Test");
37
+ });
38
+ });
@@ -0,0 +1,25 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import { Component, input } from "@angular/core";
3
+
4
+ export interface ICaoImage {
5
+ src: string;
6
+ alt: string;
7
+ }
8
+ export interface ICaoKPI {
9
+ title: string;
10
+ value: string;
11
+ textColor: string;
12
+ borderColor: string;
13
+ icon: ICaoImage;
14
+ }
15
+
16
+ @Component({
17
+ selector: "cao-kpi",
18
+ imports: [CommonModule],
19
+ templateUrl: "./kpi.html",
20
+ styleUrl: "./kpi.css",
21
+ standalone: true,
22
+ })
23
+ export class CaoKpi {
24
+ readonly kpiData = input.required<ICaoKPI>();
25
+ }
@@ -0,0 +1,70 @@
1
+ * {
2
+ text-decoration: none;
3
+ }
4
+
5
+ .links-info {
6
+ display: flex;
7
+ flex-direction: column;
8
+ width: 280px;
9
+ }
10
+
11
+ .links-info h2 {
12
+ font-size: 24px;
13
+ font-weight: 700;
14
+ text-align: center;
15
+ text-transform: uppercase;
16
+ color: var(--title-info);
17
+ margin-bottom: 12%;
18
+ text-wrap: wrap;
19
+ height: 64px;
20
+ }
21
+
22
+ .links-info .links {
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 46px;
27
+ }
28
+
29
+ .links-info .links .link {
30
+ width: 250px;
31
+ height: 100px;
32
+ text-transform: uppercase;
33
+ background-color: var(--primary);
34
+ color: var(--white);
35
+ font-weight: 700;
36
+ padding: 30px;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ text-align: center;
41
+ font-size: 16px;
42
+ border-radius: 20px;
43
+ box-shadow: 0 4px 4px 0 var(--shadow-black);
44
+ cursor: pointer;
45
+ }
46
+
47
+ .links-info .links .link.disabled {
48
+ background-color: var(--input-disabled-bg);
49
+ color: var(--black);
50
+ pointer-events: none;
51
+ cursor: default;
52
+ }
53
+
54
+ .links-info .links .link:active {
55
+ background-color: var(--active-success-btn);
56
+ }
57
+
58
+ .links-info .links .link:hover {
59
+ background: linear-gradient(125.78deg, var(--primary) 7.03%, var(--confirm-color) 96.73%);
60
+ }
61
+
62
+ @media (max-width: 768px) {
63
+ .links-info h2 {
64
+ height: 0;
65
+ }
66
+
67
+ .links-info .links {
68
+ padding-bottom: 20px;
69
+ }
70
+ }
@@ -0,0 +1,23 @@
1
+ <div class="links-info">
2
+ @if (navContent()) {
3
+ <h2>{{ navContent().title }}</h2>
4
+ <div class="links">
5
+ @for (link of navContent().links; track $index) {
6
+ <div
7
+ class="link"
8
+ role="button"
9
+ tabindex="0"
10
+ [tabIndex]="link.disabled ? -1 : 0"
11
+ [class.disabled]="link.disabled"
12
+ [attr.data-cy]="link.dataCy"
13
+ [attr.aria-disabled]="link.disabled"
14
+ (click)="isValidationRequired(link)"
15
+ (keydown.enter)="isValidationRequired(link)"
16
+ (keydown.space)="isValidationRequired(link); $event.preventDefault()"
17
+ >
18
+ {{ link.text }}
19
+ </div>
20
+ }
21
+ </div>
22
+ }
23
+ </div>
@@ -0,0 +1,139 @@
1
+ import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from "@angular/core/testing";
2
+ import { INavContent, INavLink, LinksButton } from "./links-button";
3
+ import { provideRouter, Routes } from "@angular/router";
4
+ import { Component, signal } from "@angular/core";
5
+ import { provideHttpClientTesting } from "@angular/common/http/testing";
6
+ import { provideHttpClient } from "@angular/common/http";
7
+ import { describe, it, expect, beforeEach, vi } from "vitest";
8
+
9
+ @Component({ template: '', standalone: true })
10
+ class EmptyComponent {}
11
+
12
+ const testRoutes: Routes = [
13
+ { path: '', component: EmptyComponent },
14
+ { path: 'reception', component: EmptyComponent },
15
+ { path: 'receptio', component: EmptyComponent },
16
+ ];
17
+
18
+ describe("LinksButton", () => {
19
+ let component: LinksButton;
20
+ let fixture: ComponentFixture<LinksButton>;
21
+
22
+ let modalServiceSpy: {
23
+ showModal: ReturnType<typeof vi.fn>;
24
+ closeAllModals: ReturnType<typeof vi.fn>;
25
+ };
26
+ let storageServiceSpy: {
27
+ setItem: ReturnType<typeof vi.fn>;
28
+ getItem: ReturnType<typeof vi.fn>;
29
+ };
30
+ let alertServiceSpy: {
31
+ show: ReturnType<typeof vi.fn>;
32
+ };
33
+
34
+ const mockLink: INavLink = {
35
+ text: "INICIAR ATENDIMENTO",
36
+ url: "reception",
37
+ };
38
+
39
+ const mockDisabledLink: INavLink = {
40
+ text: "ATENDIMENTO",
41
+ url: "receptio",
42
+ disabled: true,
43
+ };
44
+
45
+ const mockDirectRoute: INavLink = {
46
+ text: "ATENDIMENTO",
47
+ url: "receptio",
48
+ disabled: false,
49
+ directRoute: true,
50
+ };
51
+
52
+ beforeEach(async () => {
53
+ storageServiceSpy = {
54
+ setItem: vi.fn(),
55
+ getItem: vi.fn(),
56
+ };
57
+
58
+ modalServiceSpy = {
59
+ showModal: vi.fn(),
60
+ closeAllModals: vi.fn(),
61
+ };
62
+
63
+ alertServiceSpy = {
64
+ show: vi.fn(),
65
+ };
66
+
67
+ await TestBed.configureTestingModule({
68
+ imports: [LinksButton],
69
+ providers: [
70
+ provideRouter(testRoutes),
71
+ provideHttpClient(),
72
+ provideHttpClientTesting(),
73
+ {provide: ComponentFixtureAutoDetect, useValue: true}
74
+ ],
75
+ }).compileComponents();
76
+
77
+ fixture = TestBed.createComponent(LinksButton);
78
+ component = fixture.componentInstance;
79
+
80
+ const defaultData: INavContent = {
81
+ title: "Default",
82
+ links: [{ text: "Default", url: "/" }],
83
+ };
84
+ component.navContent = signal(defaultData);
85
+
86
+ fixture.detectChanges();
87
+ });
88
+
89
+ it("deveria criar", () => {
90
+ expect(component).toBeTruthy();
91
+ });
92
+
93
+ it("deveria ter informações", () => {
94
+ const expectedData: INavContent = {
95
+ title: "Atendimento",
96
+ links: [{ text: "cuidado", url: "route" }],
97
+ };
98
+ component.navContent = signal(expectedData);
99
+ expect(component.navContent().title).toBe(expectedData.title);
100
+ });
101
+
102
+ it("deve navegar para a rota", () => {
103
+ const expectedURL = "/";
104
+ component["navigateTo"](expectedURL);
105
+ expect(component.router.url).toBe(expectedURL);
106
+ });
107
+
108
+ describe("validação para navegação", () => {
109
+ it("deve navegar caso o link esteja disponivel", () => {
110
+ const navigateToSpy = vi.spyOn(component as any, "navigateTo");
111
+ component.isValidationRequired(mockLink);
112
+
113
+ expect(navigateToSpy).toHaveBeenCalledWith(mockLink.url);
114
+ });
115
+
116
+ it("não deve navegar caso o link esteja desabilitado", () => {
117
+ const navigateToSpy = vi.spyOn(component as any, "navigateTo");
118
+ component.isValidationRequired(mockDisabledLink);
119
+
120
+ expect(navigateToSpy).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it("deve navegar considerando a raiz do projeto", () => {
124
+ const directRouteSpy = vi.spyOn(component as any, "directRoute");
125
+ component.isValidationRequired(mockDirectRoute);
126
+
127
+ expect(directRouteSpy).toHaveBeenCalledWith(mockDirectRoute.url);
128
+ });
129
+ });
130
+
131
+ describe("directRoute", () => {
132
+ it("deve navegar a url raiz do projeto", () => {
133
+ vi.spyOn(component.router, "navigate");
134
+ component["directRoute"](mockLink.url);
135
+
136
+ expect(component.router.navigate).toHaveBeenCalledWith([mockLink.url]);
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,45 @@
1
+ import { Component, inject, Input, Signal } from "@angular/core";
2
+ import { ActivatedRoute, Router } from "@angular/router";
3
+
4
+ export interface INavContent {
5
+ title: string;
6
+ links: INavLink[];
7
+ permissions?: string[];
8
+ directRoute?: boolean;
9
+ }
10
+ export interface INavLink {
11
+ text: string;
12
+ url: string;
13
+ disabled?: boolean;
14
+ isValidationRequired?: boolean;
15
+ dataCy?: string;
16
+ directRoute?: boolean;
17
+ }
18
+
19
+ @Component({
20
+ standalone: true,
21
+ selector: "cao-links-button",
22
+ templateUrl: "./links-button.html",
23
+ styleUrl: "./links-button.css",
24
+ })
25
+ export class LinksButton {
26
+ @Input({ required: true }) navContent!: Signal<INavContent>;
27
+ router = inject(Router);
28
+ route = inject(ActivatedRoute);
29
+
30
+ isValidationRequired(link: INavLink) {
31
+ if (link.disabled) {
32
+ return;
33
+ }
34
+ link.directRoute ? this.directRoute(link.url) : this.navigateTo(link.url);
35
+ }
36
+
37
+ private navigateTo(url: string) {
38
+ this.router.navigate([url], { relativeTo: this.route });
39
+ }
40
+
41
+ private directRoute(url: string) {
42
+ this.router.navigate([url]);
43
+ }
44
+
45
+ }
@@ -0,0 +1,10 @@
1
+ <mat-paginator
2
+ #paginator
3
+ aria-label="Select page"
4
+ [length]="totalItems"
5
+ [pageSize]="pageSize"
6
+ [pageSizeOptions]="pageSizeOptions"
7
+ [pageIndex]="currentPage - 1"
8
+ [hidePageSize]="false"
9
+ [showFirstLastButtons]="showFirstLastButtons"
10
+ (page)="onPageEvent($event)" />
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { Paginator } from './paginator';
4
+
5
+ describe('Paginator', () => {
6
+ let component: Paginator;
7
+ let fixture: ComponentFixture<Paginator>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [Paginator]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(Paginator);
16
+ component = fixture.componentInstance;
17
+ await fixture.whenStable();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,25 @@
1
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
2
+ import { PageEvent } from '@angular/material/paginator';
3
+
4
+ import { MaterialModule } from '../../../material.module';
5
+
6
+ @Component({
7
+ selector: 'cao-paginator',
8
+ imports: [MaterialModule],
9
+ templateUrl: './paginator.html',
10
+ styleUrl: './paginator.css',
11
+ })
12
+ export class Paginator {
13
+ @Input() totalItems = 0;
14
+ @Input() pageSize = 10;
15
+ @Input() currentPage = 1;
16
+ @Input() pageSizeOptions: number[] = [5, 10, 20, 50];
17
+ @Input() customClass = "";
18
+ @Input() showFirstLastButtons = true;
19
+
20
+ @Output() readonly pageEvent = new EventEmitter<PageEvent>();
21
+
22
+ onPageEvent(event: PageEvent): void {
23
+ this.pageEvent.emit(event)
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ .step {
2
+ width: 44px;
3
+ height: 44px;
4
+ border-radius: 100%;
5
+ font-size: 18px;
6
+ font-weight: 700;
7
+ }
8
+
@@ -0,0 +1 @@
1
+ <div class="step d-flex aic jcc" [ngClass]="{'active': isActive}"> {{ title }} </div>
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
3
+
4
+ import { StepComponent } from "./step";
5
+ import { By } from "@angular/platform-browser";
6
+
7
+ describe("StepComponent", () => {
8
+ let component: StepComponent;
9
+ let fixture: ComponentFixture<StepComponent>;
10
+
11
+ beforeEach(async () => {
12
+ await TestBed.configureTestingModule({
13
+ imports: [StepComponent],
14
+ }).compileComponents();
15
+
16
+ fixture = TestBed.createComponent(StepComponent);
17
+ component = fixture.componentInstance;
18
+ });
19
+
20
+ it("deve criar o componente", () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+
24
+ it("deve exibir o título corretamente", async() => {
25
+ const testTitle = "Passo 1";
26
+ component.title = testTitle;
27
+ await fixture.whenStable() // Primeira detecção de mudanças
28
+
29
+ const element = fixture.debugElement.query(By.css(".step"));
30
+ expect(element.nativeElement.textContent.trim()).toBe(testTitle);
31
+ });
32
+
33
+ it("deve aplicar a classe active quando isActive for true", async() => {
34
+ component.isActive = true;
35
+ await fixture.whenStable() // Primeira detecção de mudanças
36
+
37
+ const element = fixture.debugElement.query(By.css(".step"));
38
+ expect(element.nativeElement.classList.contains("active")).toBe(true);
39
+ });
40
+
41
+ it("não deve aplicar a classe active quando isActive for false", async () => {
42
+ component.isActive = false;
43
+ await fixture.whenStable() // Primeira detecção de mudanças
44
+
45
+ const element = fixture.debugElement.query(By.css(".step"));
46
+ expect(element.nativeElement.classList.contains("active")).toBe(false);
47
+ });
48
+
49
+ it("deve atualizar o estado ativo quando a propriedade isActive mudar", async() => {
50
+ // Configuração inicial
51
+ component.isActive = false;
52
+
53
+ let element = fixture.debugElement.query(By.css(".step"));
54
+
55
+ expect(element.nativeElement.classList.contains("active")).toBe(false);
56
+
57
+ // Mudança e nova detecção
58
+ component.isActive = true;
59
+ await fixture.whenStable()
60
+ element = fixture.debugElement.query(By.css(".step"));
61
+
62
+ expect(element.nativeElement.classList.contains("active")).toBe(true);
63
+ });
64
+
65
+ // Testes adicionais que podem ser úteis
66
+ describe("Propriedades adicionais", () => {
67
+ it("deve ter title como string vazia por padrão", () => {
68
+ // Não chame fixture.detectChanges() aqui para evitar o erro
69
+ expect(component.title).toBe("");
70
+ });
71
+
72
+ it("deve ter isActive como false por padrão", () => {
73
+ // Não chame fixture.detectChanges() aqui para evitar o erro
74
+ expect(component.isActive).toBe(false);
75
+ });
76
+
77
+ it("deve manter o template renderizado corretamente", async() => {
78
+ await fixture.whenStable() // Apenas uma detecção
79
+ const element = fixture.debugElement.query(By.css(".step"));
80
+ expect(element).toBeTruthy();
81
+ expect(element.nativeElement).toBeInstanceOf(HTMLElement);
82
+ });
83
+ });
84
+
85
+ describe("Comportamento de DOM", () => {
86
+ it("deve lidar com título vazio corretamente", async() => {
87
+ component.title = "";
88
+ await fixture.whenStable() // Apenas uma detecção
89
+
90
+ const element = fixture.debugElement.query(By.css(".step"));
91
+ expect(element.nativeElement.textContent.trim()).toBe("");
92
+ });
93
+
94
+ it("deve renderizar corretamente com título longo", async() => {
95
+ const longTitle = "Este é um título muito longo para o passo do stepper";
96
+ component.title = longTitle;
97
+ await fixture.whenStable() // Apenas uma detecção
98
+
99
+ const element = fixture.debugElement.query(By.css(".step"));
100
+ expect(element.nativeElement.textContent.trim()).toBe(longTitle);
101
+ });
102
+ });
103
+
104
+ // Alternativa: Usar fixture.changeDetectorRef.detectChanges() com cuidado
105
+ describe("Solução alternativa para detecção de mudanças", () => {
106
+ it("deve evitar ExpressionChangedAfterItHasBeenCheckedError", () => {
107
+ // Solução 1: Usar async e await
108
+ const longTitle = "Este é um título muito longo para o passo do stepper";
109
+ component.title = longTitle;
110
+
111
+ // Detecção de mudanças com tratamento de erro
112
+ fixture.detectChanges(false); // Passar false para evitar throwOnError
113
+
114
+ const element = fixture.debugElement.query(By.css(".step"));
115
+ expect(element.nativeElement.textContent.trim()).toBe(longTitle);
116
+ });
117
+
118
+ it("deve usar tick e fakeAsync para testes assíncronos", async () => {
119
+ // Solução 2: Usar fakeAsync (requer importação)
120
+ const longTitle = "Este é um título muito longo para o passo do stepper";
121
+ component.title = longTitle;
122
+
123
+ // Aguardar ciclo de detecção
124
+ await fixture.whenStable();
125
+ await fixture.whenStable()
126
+
127
+ const element = fixture.debugElement.query(By.css(".step"));
128
+ expect(element.nativeElement.textContent.trim()).toBe(longTitle);
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,14 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import { Component, Input } from "@angular/core";
3
+
4
+ @Component({
5
+ selector: "cao-step",
6
+ standalone: true,
7
+ templateUrl: "./step.html",
8
+ styleUrl: "./step.css",
9
+ imports: [CommonModule],
10
+ })
11
+ export class StepComponent {
12
+ @Input() title = "";
13
+ @Input() isActive = false;
14
+ }
@@ -0,0 +1,17 @@
1
+ .step-container {
2
+ position: relative;
3
+ }
4
+
5
+ .steps {
6
+ z-index: 2;
7
+ gap: 24px;
8
+ }
9
+
10
+ .line {
11
+ width: 100%;
12
+ position: absolute;
13
+ top: 50%;
14
+ height: 1.5px;
15
+ background-color: var(--return-color);
16
+ z-index: 1;
17
+ }
@@ -0,0 +1,11 @@
1
+ @if(title){
2
+ <h3 class="stepper-title">{{ title }}</h3>
3
+ }
4
+ <div class="d-flex step-container aic jcsb">
5
+ @if (showLine) {
6
+ <div class="line"></div>
7
+ }
8
+ <div class="steps d-flex aic jcsb">
9
+ <ng-content />
10
+ </div>
11
+ </div>