ngx-opalbytes-components 1.22.0 → 1.24.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.
- package/README.md +98 -0
- package/fesm2022/ngx-opalbytes-components.mjs +174 -2
- package/fesm2022/ngx-opalbytes-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/shared/components/error/error.css +10 -0
- package/src/lib/shared/components/error/error.html +5 -0
- package/src/lib/shared/components/error/error.spec.ts +76 -0
- package/src/lib/shared/components/error/error.stories.ts +61 -0
- package/src/lib/shared/components/error/error.ts +33 -0
- package/src/lib/shared/components/form-field/error-state-matcher.ts +41 -0
- package/src/lib/shared/components/form-field/form-field-control.ts +10 -0
- package/src/lib/shared/components/form-field/form-field.css +154 -0
- package/src/lib/shared/components/form-field/form-field.html +51 -0
- package/src/lib/shared/components/form-field/form-field.spec.ts +114 -0
- package/src/lib/shared/components/form-field/form-field.stories.ts +177 -0
- package/src/lib/shared/components/form-field/form-field.ts +118 -0
- package/src/lib/shared/components/hint/hint.css +10 -0
- package/src/lib/shared/components/hint/hint.html +1 -0
- package/src/lib/shared/components/hint/hint.spec.ts +22 -0
- package/src/lib/shared/components/hint/hint.stories.ts +53 -0
- package/src/lib/shared/components/hint/hint.ts +9 -0
- package/src/lib/shared/components/index.ts +6 -0
- package/types/ngx-opalbytes-components.d.ts +70 -2
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
import { CaoFormField } from '../form-field/form-field';
|
|
6
|
+
import { CaoError } from './error';
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'cao-error-host',
|
|
10
|
+
imports: [CaoFormField, CaoError, ReactiveFormsModule],
|
|
11
|
+
template: `
|
|
12
|
+
<cao-form-field label="Email" [control]="control">
|
|
13
|
+
<input caoInput [formControl]="control" />
|
|
14
|
+
<cao-error>E-mail obrigatório</cao-error>
|
|
15
|
+
</cao-form-field>
|
|
16
|
+
`,
|
|
17
|
+
})
|
|
18
|
+
class CaoErrorHostComponent {
|
|
19
|
+
control = new FormControl('', [Validators.required]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('CaoError', () => {
|
|
23
|
+
let fixture: ComponentFixture<CaoError>;
|
|
24
|
+
let component: CaoError;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
await TestBed.configureTestingModule({
|
|
28
|
+
imports: [CaoError],
|
|
29
|
+
}).compileComponents();
|
|
30
|
+
|
|
31
|
+
fixture = TestBed.createComponent(CaoError);
|
|
32
|
+
component = fixture.componentInstance;
|
|
33
|
+
await fixture.whenStable();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should create', () => {
|
|
37
|
+
expect(component).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('não deve exibir erro quando não há controle', () => {
|
|
41
|
+
expect(component.hasErrorState).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('CaoError (dentro de CaoFormField)', () => {
|
|
46
|
+
let hostFixture: ComponentFixture<CaoErrorHostComponent>;
|
|
47
|
+
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
await TestBed.configureTestingModule({
|
|
50
|
+
imports: [CaoErrorHostComponent],
|
|
51
|
+
}).compileComponents();
|
|
52
|
+
|
|
53
|
+
hostFixture = TestBed.createComponent(CaoErrorHostComponent);
|
|
54
|
+
hostFixture.detectChanges();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('deve ocultar o erro quando o controle está válido', () => {
|
|
58
|
+
hostFixture.componentInstance.control.setValue('teste@exemplo.com');
|
|
59
|
+
hostFixture.detectChanges();
|
|
60
|
+
const error = hostFixture.nativeElement.querySelector('cao-error .cao-error');
|
|
61
|
+
expect(error).toBeFalsy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('deve exibir o erro quando o controle está inválido e tocado', () => {
|
|
65
|
+
hostFixture.componentInstance.control.markAsTouched();
|
|
66
|
+
hostFixture.detectChanges();
|
|
67
|
+
const error = hostFixture.nativeElement.querySelector('cao-error .cao-error');
|
|
68
|
+
expect(error).toBeTruthy();
|
|
69
|
+
expect(error.textContent.trim()).toBe('E-mail obrigatório');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('deve ocultar o erro quando está inválido mas não foi tocado', () => {
|
|
73
|
+
const error = hostFixture.nativeElement.querySelector('cao-error .cao-error');
|
|
74
|
+
expect(error).toBeFalsy();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { AbstractControl, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/angular-vite';
|
|
5
|
+
|
|
6
|
+
import { CaoError } from './error';
|
|
7
|
+
import { CaoBaseInput } from '../base-input/base-input';
|
|
8
|
+
import { CaoFormField } from '../form-field/form-field';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'cao-error-story',
|
|
12
|
+
imports: [ReactiveFormsModule, CaoFormField, CaoBaseInput, CaoError],
|
|
13
|
+
template: `
|
|
14
|
+
<div style="max-width: 420px; display: flex; flex-direction: column; gap: 24px;">
|
|
15
|
+
<cao-form-field [control]="requiredControl" label="E-mail">
|
|
16
|
+
<cao-input [control]="requiredControl" placeholder="Digite seu e-mail"></cao-input>
|
|
17
|
+
<cao-error>O campo é obrigatório</cao-error>
|
|
18
|
+
</cao-form-field>
|
|
19
|
+
<cao-form-field [control]="patternControl" label="CPF">
|
|
20
|
+
<cao-input [control]="patternControl" placeholder="000.000.000-00"></cao-input>
|
|
21
|
+
@if (patternControl.hasError('pattern')) {
|
|
22
|
+
<cao-error>Formato de CPF inválido</cao-error>
|
|
23
|
+
}
|
|
24
|
+
</cao-form-field>
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
class CaoErrorStoryComponent {
|
|
29
|
+
requiredControl: AbstractControl = new FormControl('', [Validators.required]);
|
|
30
|
+
patternControl: AbstractControl = new FormControl('', [
|
|
31
|
+
Validators.pattern(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.requiredControl.markAsTouched();
|
|
36
|
+
this.patternControl.markAsTouched();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const render = () => ({
|
|
41
|
+
component: CaoErrorStoryComponent,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const meta: Meta<CaoError> = {
|
|
45
|
+
title: 'Componentes/Error',
|
|
46
|
+
component: CaoError,
|
|
47
|
+
tags: ['autodocs'],
|
|
48
|
+
args: {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default meta;
|
|
52
|
+
type Story = StoryObj<CaoError>;
|
|
53
|
+
|
|
54
|
+
export const ErroObrigatorio: Story = {
|
|
55
|
+
render,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const ErroPorPadrao: Story = {
|
|
59
|
+
args: {},
|
|
60
|
+
render,
|
|
61
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component, inject, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { IErrorStateMatcher } from '../form-field/error-state-matcher';
|
|
4
|
+
import { CaoFormField } from '../form-field/form-field';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'cao-error',
|
|
8
|
+
imports: [],
|
|
9
|
+
templateUrl: './error.html',
|
|
10
|
+
styleUrl: './error.css',
|
|
11
|
+
})
|
|
12
|
+
export class CaoError {
|
|
13
|
+
@Input() errorStateMatcher?: IErrorStateMatcher;
|
|
14
|
+
|
|
15
|
+
private readonly formField = inject(CaoFormField, { optional: true }) as
|
|
16
|
+
| CaoFormField
|
|
17
|
+
| null;
|
|
18
|
+
|
|
19
|
+
get control() {
|
|
20
|
+
return this.formField?.control ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get hasErrorState(): boolean {
|
|
24
|
+
const control = this.control;
|
|
25
|
+
if (!control) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const matcher = this.errorStateMatcher ?? this.formField?.errorStateMatcher;
|
|
29
|
+
return matcher
|
|
30
|
+
? matcher.isErrorState(control, this.formField)
|
|
31
|
+
: !!(control.invalid && (control.touched || control.dirty));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AbstractControl } from '@angular/forms';
|
|
2
|
+
|
|
3
|
+
import { IFormFieldControl } from './form-field-control';
|
|
4
|
+
|
|
5
|
+
export interface IErrorStateMatcher {
|
|
6
|
+
isErrorState(
|
|
7
|
+
control: AbstractControl | null,
|
|
8
|
+
formFieldControl: IFormFieldControl | null
|
|
9
|
+
): boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ErrorStateMatcher implements IErrorStateMatcher {
|
|
13
|
+
isErrorState(
|
|
14
|
+
control: AbstractControl | null,
|
|
15
|
+
formFieldControl: IFormFieldControl | null
|
|
16
|
+
): boolean {
|
|
17
|
+
if (!control) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const isSubmitted = formFieldControl && (formFieldControl as any)._isSubmitted;
|
|
21
|
+
return !!(
|
|
22
|
+
control.invalid &&
|
|
23
|
+
(control.touched || control.dirty || isSubmitted)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ShowOnDirtyErrorStateMatcher implements IErrorStateMatcher {
|
|
29
|
+
isErrorState(
|
|
30
|
+
control: AbstractControl | null,
|
|
31
|
+
formFieldControl: IFormFieldControl | null
|
|
32
|
+
): boolean {
|
|
33
|
+
if (!control) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const isSubmitted = formFieldControl && (formFieldControl as any)._isSubmitted;
|
|
37
|
+
return !!(control.invalid && (control.dirty || isSubmitted));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_ERROR_STATE_MATCHER = new ErrorStateMatcher();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AbstractControl } from '@angular/forms';
|
|
2
|
+
|
|
3
|
+
export interface IFormFieldControl<T = any> {
|
|
4
|
+
readonly control: AbstractControl<T> | null | undefined;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly labelInside: boolean;
|
|
8
|
+
readonly disabled: boolean;
|
|
9
|
+
readonly required: boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--cao-form-field-bg-background: var(--input-background, #ffffff);
|
|
3
|
+
--cao-form-field-border-color: var(--line-color, #e5e7eb);
|
|
4
|
+
--cao-form-field-border-width: 1px;
|
|
5
|
+
--cao-form-field-border-radius: 8px;
|
|
6
|
+
--cao-form-field-color-error: var(--error, #dc2626);
|
|
7
|
+
--cao-form-field-color-label: var(--primary, #111827);
|
|
8
|
+
--cao-form-field-color-hint: var(--input-text, #9ca3af);
|
|
9
|
+
--cao-form-field-color-text: var(--text-color-input, #111827);
|
|
10
|
+
--cao-form-field-focus-border: var(--primary, #111827);
|
|
11
|
+
--cao-form-field-success-border: var(--success, #16a34a);
|
|
12
|
+
|
|
13
|
+
display: block;
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cao-form-field {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 4px;
|
|
21
|
+
width: 100%;
|
|
22
|
+
font-family: inherit;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.cao-form-field__label {
|
|
26
|
+
display: block;
|
|
27
|
+
font-size: 0.75rem;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
line-height: 1.25rem;
|
|
30
|
+
color: var(--cao-form-field-color-label);
|
|
31
|
+
text-transform: uppercase;
|
|
32
|
+
letter-spacing: 0.02em;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.cao-form-field__label--float {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 50%;
|
|
39
|
+
left: 12px;
|
|
40
|
+
transform: translateY(-50%);
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
text-transform: none;
|
|
43
|
+
letter-spacing: 0;
|
|
44
|
+
color: var(--cao-form-field-color-hint);
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
47
|
+
font-size 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
48
|
+
color 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
49
|
+
max-width: calc(100% - 24px);
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
text-overflow: ellipsis;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.cao-form-field__label--floated {
|
|
55
|
+
transform: translateY(calc(-50% - 14px)) scale(0.75);
|
|
56
|
+
transform-origin: left top;
|
|
57
|
+
color: var(--cao-form-field-color-label);
|
|
58
|
+
font-weight: 700;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.input-field {
|
|
62
|
+
position: relative;
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
width: 100%;
|
|
66
|
+
min-height: 44px;
|
|
67
|
+
background-color: var(--cao-form-field-bg-background);
|
|
68
|
+
border: var(--cao-form-field-border-width) solid var(--cao-form-field-border-color);
|
|
69
|
+
border-radius: var(--cao-form-field-border-radius);
|
|
70
|
+
transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
71
|
+
background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.input-field:focus-within {
|
|
75
|
+
border-color: var(--cao-form-field-focus-border);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.cao-form-field.error-state .input-field {
|
|
79
|
+
border-color: var(--cao-form-field-color-error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.cao-form-field.disabled .input-field {
|
|
83
|
+
opacity: 0.7;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.input-slot {
|
|
87
|
+
position: relative;
|
|
88
|
+
flex: 1;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
width: 100%;
|
|
92
|
+
height: 100%;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.input-slot ::ng-deep input,
|
|
96
|
+
.input-slot ::ng-deep textarea,
|
|
97
|
+
.input-slot ::ng-deep select {
|
|
98
|
+
width: 100%;
|
|
99
|
+
min-height: 40px;
|
|
100
|
+
border: none;
|
|
101
|
+
outline: none;
|
|
102
|
+
background: transparent;
|
|
103
|
+
color: var(--cao-form-field-color-text);
|
|
104
|
+
font-family: inherit;
|
|
105
|
+
font-size: 0.875rem;
|
|
106
|
+
padding: 6px 12px;
|
|
107
|
+
box-sizing: border-box;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.input-slot ::ng-deep cao-input,
|
|
111
|
+
.input-slot ::ng-deep .cao-input {
|
|
112
|
+
width: 100%;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.input-slot ::ng-deep cao-input .input-component,
|
|
116
|
+
.input-slot ::ng-deep cao-input .input-wrapper {
|
|
117
|
+
width: 100%;
|
|
118
|
+
background: transparent;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.input-slot ::ng-deep cao-input input,
|
|
122
|
+
.input-slot ::ng-deep cao-input textarea {
|
|
123
|
+
background: transparent;
|
|
124
|
+
border: none;
|
|
125
|
+
color: var(--cao-form-field-color-text);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.label-inside .input-slot ::ng-deep input,
|
|
129
|
+
.label-inside .input-slot ::ng-deep textarea,
|
|
130
|
+
.label-inside .input-slot ::ng-deep select {
|
|
131
|
+
padding-top: 16px;
|
|
132
|
+
padding-bottom: 8px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.subscript-wrapper {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 2px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.cao-form-field__hint {
|
|
142
|
+
font-size: 0.75rem;
|
|
143
|
+
line-height: 1.25rem;
|
|
144
|
+
color: var(--cao-form-field-color-hint);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.cao-form-field__hint + .subscript-wrapper {
|
|
148
|
+
margin-top: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.cao-form-field.required .cao-form-field__label::after {
|
|
152
|
+
content: " *";
|
|
153
|
+
color: var(--cao-form-field-color-error);
|
|
154
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="cao-form-field"
|
|
3
|
+
[class.label-inside]="labelInside"
|
|
4
|
+
[class.error-state]="errorStateActive"
|
|
5
|
+
[class.disabled]="disabled"
|
|
6
|
+
[class.required]="required"
|
|
7
|
+
[style]="formFieldStyles()">
|
|
8
|
+
@if (label && !labelInside) {
|
|
9
|
+
<label class="cao-form-field__label" [for]="forId">{{ label }}</label>
|
|
10
|
+
}
|
|
11
|
+
<div class="input-field" (focusin)="onFocusIn()" (focusout)="onFocusOut()">
|
|
12
|
+
@if (leadingIcon) {
|
|
13
|
+
<cao-base-icon
|
|
14
|
+
[isLucideIcon]="isLucideIcon"
|
|
15
|
+
[strokeWidthIcon]="strokeWidthIcon"
|
|
16
|
+
[imgSource]="leadingIcon"
|
|
17
|
+
[iconColor]="iconColor"
|
|
18
|
+
[size]="size" />
|
|
19
|
+
}
|
|
20
|
+
<div class="input-slot">
|
|
21
|
+
@if (label && labelInside) {
|
|
22
|
+
<label
|
|
23
|
+
class="cao-form-field__label cao-form-field__label--float"
|
|
24
|
+
[class.cao-form-field__label--floated]="floating"
|
|
25
|
+
[for]="forId">{{ label }}</label>
|
|
26
|
+
}
|
|
27
|
+
<ng-content select="[caoInput]" />
|
|
28
|
+
<ng-content select="cao-input" />
|
|
29
|
+
<ng-content select="input" />
|
|
30
|
+
<ng-content select="select" />
|
|
31
|
+
<ng-content select="textarea" />
|
|
32
|
+
<ng-content select="h2" />
|
|
33
|
+
<ng-content />
|
|
34
|
+
</div>
|
|
35
|
+
@if (trailingIcon) {
|
|
36
|
+
<cao-base-icon
|
|
37
|
+
[isLucideIcon]="isLucideIcon"
|
|
38
|
+
[iconColor]="iconColor"
|
|
39
|
+
[size]="size"
|
|
40
|
+
[imgSource]="trailingIcon"
|
|
41
|
+
[strokeWidthIcon]="strokeWidthIcon" />
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="subscript-wrapper">
|
|
45
|
+
@if (hintLabel) {
|
|
46
|
+
<small class="cao-form-field__hint">{{ hintLabel }}</small>
|
|
47
|
+
}
|
|
48
|
+
<ng-content select="cao-hint" />
|
|
49
|
+
<ng-content select="cao-error" />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { FormControl, Validators } from '@angular/forms';
|
|
3
|
+
|
|
4
|
+
import { CaoFormField } from './form-field';
|
|
5
|
+
|
|
6
|
+
describe('CaoFormField', () => {
|
|
7
|
+
let component: CaoFormField;
|
|
8
|
+
let fixture: ComponentFixture<CaoFormField>;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await TestBed.configureTestingModule({
|
|
12
|
+
imports: [CaoFormField],
|
|
13
|
+
}).compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(CaoFormField);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
await fixture.whenStable();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should create', () => {
|
|
22
|
+
expect(component).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('deve exibir label fora do input por padrão', () => {
|
|
26
|
+
fixture.componentRef.setInput('label', 'Nome');
|
|
27
|
+
fixture.detectChanges();
|
|
28
|
+
const label = fixture.nativeElement.querySelector('.cao-form-field__label');
|
|
29
|
+
expect(label).toBeTruthy();
|
|
30
|
+
expect(label.textContent.trim()).toBe('Nome');
|
|
31
|
+
expect(component.labelInside).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('deve usar label interna quando labelInside for true', () => {
|
|
35
|
+
fixture.componentRef.setInput('label', 'Nome');
|
|
36
|
+
fixture.componentRef.setInput('labelInside', true);
|
|
37
|
+
fixture.detectChanges();
|
|
38
|
+
const label = fixture.nativeElement.querySelector('.cao-form-field__label--float');
|
|
39
|
+
expect(label).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('deve flutuar o label quando o controle possui valor', () => {
|
|
43
|
+
const control = new FormControl('valor');
|
|
44
|
+
fixture.componentRef.setInput('label', 'Nome');
|
|
45
|
+
fixture.componentRef.setInput('labelInside', true);
|
|
46
|
+
fixture.componentRef.setInput('control', control);
|
|
47
|
+
fixture.detectChanges();
|
|
48
|
+
expect(component.floating).toBe(true);
|
|
49
|
+
const label = fixture.nativeElement.querySelector('.cao-form-field__label--floated');
|
|
50
|
+
expect(label).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('deve exibir hintLabel quando definido', () => {
|
|
54
|
+
fixture.componentRef.setInput('hintLabel', 'Digite um CEP válido');
|
|
55
|
+
fixture.detectChanges();
|
|
56
|
+
const hint = fixture.nativeElement.querySelector('.cao-form-field__hint');
|
|
57
|
+
expect(hint).toBeTruthy();
|
|
58
|
+
expect(hint.textContent.trim()).toBe('Digite um CEP válido');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('deve detectar estado de erro quando controle inválido e tocado', () => {
|
|
62
|
+
const control = new FormControl('', [Validators.required]);
|
|
63
|
+
control.markAsTouched();
|
|
64
|
+
fixture.componentRef.setInput('control', control);
|
|
65
|
+
fixture.detectChanges();
|
|
66
|
+
expect(component.errorStateActive).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('nao deve ativar estado de erro quando controle não for tocado', () => {
|
|
70
|
+
const control = new FormControl('', [Validators.required]);
|
|
71
|
+
fixture.componentRef.setInput('control', control);
|
|
72
|
+
fixture.detectChanges();
|
|
73
|
+
expect(component.errorStateActive).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('deve marcar como obrigatório quando o controle tem Validators.required', () => {
|
|
77
|
+
const control = new FormControl('', [Validators.required]);
|
|
78
|
+
fixture.componentRef.setInput('control', control);
|
|
79
|
+
fixture.detectChanges();
|
|
80
|
+
expect(component.required).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('deve reutilizar o id do forId no atributo for do label', () => {
|
|
84
|
+
fixture.componentRef.setInput('label', 'Email');
|
|
85
|
+
fixture.componentRef.setInput('forId', 'campo-email');
|
|
86
|
+
fixture.detectChanges();
|
|
87
|
+
const label = fixture.nativeElement.querySelector('.cao-form-field__label');
|
|
88
|
+
expect(label.getAttribute('for')).toBe('campo-email');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('deve detectar o controle vindo de um AbstractControl', () => {
|
|
92
|
+
const control = new FormControl('abc');
|
|
93
|
+
fixture.componentRef.setInput('control', control);
|
|
94
|
+
fixture.detectChanges();
|
|
95
|
+
expect(component.disabled).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('deve aplicar as cores customizadas como custom props no elemento interno', () => {
|
|
99
|
+
fixture.componentRef.setInput('inputBackground', '#f8fafc');
|
|
100
|
+
fixture.componentRef.setInput('errorColor', '#e11d48');
|
|
101
|
+
fixture.componentRef.setInput('borderRadius', '16px');
|
|
102
|
+
fixture.detectChanges();
|
|
103
|
+
const el = fixture.nativeElement.querySelector('.cao-form-field') as HTMLElement;
|
|
104
|
+
expect(el.style.getPropertyValue('--cao-form-field-bg-background')).toBe('#f8fafc');
|
|
105
|
+
expect(el.style.getPropertyValue('--cao-form-field-color-error')).toBe('#e11d48');
|
|
106
|
+
expect(el.style.getPropertyValue('--cao-form-field-border-radius')).toBe('16px');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('nao deve aplicar custom props de cor quando o input tem undefined', () => {
|
|
110
|
+
fixture.detectChanges();
|
|
111
|
+
const el = fixture.nativeElement.querySelector('.cao-form-field') as HTMLElement;
|
|
112
|
+
expect(el.style.getPropertyValue('--cao-form-field-bg-background')).toBe('');
|
|
113
|
+
});
|
|
114
|
+
});
|