Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,114 @@ jobs:
- run: npm run build:storage:lite
- run: npm run test:sync
- run: npm run test:sync:schematics

test-visual:
name: Test visual regression

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: 24

# Detecta o nome da branch atual
- name: Get branch name
id: branch
env:
HEAD_REF: ${{ github.head_ref }}
REF_NAME: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
BRANCH_NAME="$HEAD_REF"
else
BRANCH_NAME="$REF_NAME"
fi
echo "name=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "Branch name: $BRANCH_NAME"

# Verifica se existe uma branch de mesmo nome no po-style
- name: Check for matching po-style branch
id: po_style
env:
BRANCH_NAME: ${{ steps.branch.outputs.name }}
run: |
ENCODED_BRANCH=$(echo "$BRANCH_NAME" | sed 's|/|%2F|g')
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"https://api.github.com/repos/po-ui/po-style/branches/$ENCODED_BRANCH")
if [ "$HTTP_STATUS" = "200" ]; then
echo "found=true" >> $GITHUB_OUTPUT
echo "Branch '$BRANCH_NAME' encontrada no po-style"
else
echo "found=false" >> $GITHUB_OUTPUT
echo "Branch '$BRANCH_NAME' nao encontrada no po-style (HTTP $HTTP_STATUS), usando @po-ui/style do npm"
fi

# Se encontrou a branch, clona po-style, builda e gera o tgz
- name: Build po-style from matching branch
if: steps.po_style.outputs.found == 'true'
env:
BRANCH_NAME: ${{ steps.branch.outputs.name }}
run: |
git clone --branch "$BRANCH_NAME" --depth 1 https://github.com/po-ui/po-style.git /tmp/po-style
cd /tmp/po-style
npm ci
npm run build
npm run pack:style
echo "TGZ gerado:"
ls -la dist/style/*.tgz

# Instala dependencias do po-angular
- run: npm i

# Se tiver o tgz do po-style, instala por cima da versao do npm
- name: Install po-style from tgz
if: steps.po_style.outputs.found == 'true'
run: |
TGZ_PATH=$(ls /tmp/po-style/dist/style/*.tgz)
echo "Instalando @po-ui/style a partir de: $TGZ_PATH"
npm install "$TGZ_PATH"

- run: npm run build:ui:lite
- run: npx playwright install --with-deps chromium

# Restaura baselines do cache (geradas em execucoes anteriores no CI)
- name: Restore visual regression baselines from cache
id: baselines-cache
uses: actions/cache@v4
with:
path: e2e/visual/__snapshots__
key: visual-baselines-${{ runner.os }}-${{ hashFiles('e2e/visual/**/*.visual.spec.ts', 'e2e/visual/**/*.component.html') }}
restore-keys: |
visual-baselines-${{ runner.os }}-

# Se nao encontrou baselines no cache, gera novas para o ambiente CI
- name: Generate baselines for CI environment
if: steps.baselines-cache.outputs.cache-hit != 'true'
run: npx playwright test --update-snapshots --reporter=list

# Executa os testes visuais comparando com as baselines
- name: Run visual regression tests
run: npx playwright test
Comment on lines +253 to +259

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CI visual regression tests always pass on cache miss due to self-comparison

When the baselines cache key doesn't match exactly (which happens whenever any *.visual.spec.ts or *.component.html file in e2e/visual/ changes), the CI workflow generates fresh baselines with --update-snapshots (line 255) and then immediately runs the tests comparing against those same just-generated baselines (line 259). Since the screenshots are compared against themselves, the tests always pass unconditionally. This means any PR that adds or modifies visual test files — including this very PR — will never detect a visual regression, even if the actual UI components have visual defects. The 186 committed baseline PNGs in __snapshots__/ are also overwritten in every CI run (either by cache restore or by --update-snapshots), so they are never used as a reference in CI.

Prompt for agents
In .github/workflows/ci.yml, the 'Generate baselines for CI environment' step (lines 253-255) regenerates ALL baselines and then the 'Run visual regression tests' step (lines 258-259) compares against these just-generated baselines, which always passes. To make the visual regression tests effective:

1. Remove the 'Generate baselines for CI environment' step entirely.
2. Remove the 'Restore visual regression baselines from cache' step and its cache configuration.
3. Rely on the committed baseline PNGs in e2e/visual/__snapshots__ as the source of truth for comparison.
4. When a developer makes an intentional visual change, they should regenerate baselines locally (npx playwright test --update-snapshots) and commit the updated PNGs.

Alternatively, if CI-specific baselines are needed (due to rendering differences between developer machines and CI), consider:
- Only generating baselines on the default branch (master/development), NOT on PRs.
- For PRs, always compare against previously cached baselines from the default branch.
- Use a cache key like 'visual-baselines-${{ runner.os }}-${{ github.event.pull_request.base.sha }}' to tie the baseline to the base branch state.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esse trade-off é intencional e está documentado na descrição da PR. O motivo: as baselines commitadas no repo são geradas em ambiente local e possuem dimensões diferentes do CI (ex: <textarea> renderiza com 7px de diferença na altura entre Ubuntu local e GitHub Actions Ubuntu 24.04). Usar as baselines commitadas diretamente no CI sempre falharia por mismatch de dimensão.

O fluxo atual:

  1. Cache miss (primeira execução ou mudança nos specs/HTML): gera baselines no ambiente CI → testes passam (self-comparison)
  2. Cache hit (execuções subsequentes sem mudança nos specs): compara contra baselines cacheadas → detecta regressões causadas por mudanças em componentes/CSS

Para detecção cross-versão mais robusta (comparando PRs contra baselines da branch base), a sugestão de usar visual-baselines-${{ runner.os }}-${{ github.event.pull_request.base.sha }} é boa e pode ser implementada numa iteração futura.


# Disponibiliza o relatorio HTML do Playwright como artefato
- name: Upload visual regression report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: visual-regression-report
path: e2e/visual/playwright-report/
retention-days: 30

# Disponibiliza os resultados de teste (diffs, screenshots) como artefato
- name: Upload visual regression test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: visual-regression-test-results
path: e2e/visual/test-results/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ testem.log
.scannerwork
.env

# Playwright
/e2e/visual/test-results/
/e2e/visual/playwright-report/

# System Files
.DS_Store
Thumbs.db
Expand Down
40 changes: 40 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,46 @@
}
}
},
"visual-app": {
"projectType": "application",
"schematics": {},
"root": "e2e/visual",
"sourceRoot": "e2e/visual",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": {
"base": "dist/visual-app"
},
"index": "e2e/visual/app/index.html",
"polyfills": ["e2e/visual/app/polyfills.ts"],
"tsConfig": "e2e/visual/tsconfig.visual.json",
"assets": [
{
"glob": "**/*",
"input": "node_modules/@po-ui/style/images",
"output": "/assets/images"
}
],
"styles": ["./node_modules/@po-ui/style/css/po-theme-default.min.css"],
"scripts": [],
"extractLicenses": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"browser": "e2e/visual/app/main.ts"
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "visual-app:build"
}
}
}
},
"portal": {
"projectType": "application",
"schematics": {},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions e2e/visual/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>',
standalone: false
})
export class AppComponent {}
135 changes: 135 additions & 0 deletions e2e/visual/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { PoModule } from '@po-ui/ng-components';

import { AppComponent } from './app.component';

// Sample components - Buttons & Controls
import { SamplePoButtonBasicComponent } from '../../../projects/ui/src/lib/components/po-button/samples/sample-po-button-basic/sample-po-button-basic.component';
import { SamplePoButtonGroupBasicComponent } from '../../../projects/ui/src/lib/components/po-button-group/samples/sample-po-button-group-basic/sample-po-button-group-basic.component';

// Sample components - Form Fields
import { SamplePoInputBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-basic/sample-po-input-basic.component';
import { SamplePoCheckboxBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-basic/sample-po-checkbox-basic.component';
import { SamplePoSwitchBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-basic/sample-po-switch-basic.component';
import { SamplePoSelectBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-basic/sample-po-select-basic.component';

// Sample components - Data Display
import { SamplePoTableBasicComponent } from '../../../projects/ui/src/lib/components/po-table/samples/sample-po-table-basic/sample-po-table-basic.component';
import { SamplePoTagBasicComponent } from '../../../projects/ui/src/lib/components/po-tag/samples/sample-po-tag-basic/sample-po-tag-basic.component';

// Sample components - Layout & Navigation
import { SamplePoAccordionBasicComponent } from '../../../projects/ui/src/lib/components/po-accordion/samples/sample-po-accordion-basic/sample-po-accordion-basic.component';
import { SamplePoDividerBasicComponent } from '../../../projects/ui/src/lib/components/po-divider/samples/sample-po-divider-basic/sample-po-divider-basic.component';
import { SamplePoProgressBasicComponent } from '../../../projects/ui/src/lib/components/po-progress/samples/sample-po-progress-basic/sample-po-progress-basic.component';

// Visual test - po-input states (legacy route, kept for backward compatibility)
import { VisualTestPoInputStatesComponent } from '../po-input/po-input-states.component';

// Visual test - Field state components (co-located in e2e/visual/fields/)
import { VisualTestPoInputFieldStatesComponent } from '../fields/po-input/po-input-states.component';
import { VisualTestPoDecimalStatesComponent } from '../fields/po-decimal/po-decimal-states.component';
import { VisualTestPoEmailStatesComponent } from '../fields/po-email/po-email-states.component';
import { VisualTestPoNumberStatesComponent } from '../fields/po-number/po-number-states.component';
import { VisualTestPoPasswordStatesComponent } from '../fields/po-password/po-password-states.component';
import { VisualTestPoUrlStatesComponent } from '../fields/po-url/po-url-states.component';
import { VisualTestPoLoginStatesComponent } from '../fields/po-login/po-login-states.component';
import { VisualTestPoComboStatesComponent } from '../fields/po-combo/po-combo-states.component';
import { VisualTestPoDatepickerStatesComponent } from '../fields/po-datepicker/po-datepicker-states.component';
import { VisualTestPoDatepickerRangeStatesComponent } from '../fields/po-datepicker-range/po-datepicker-range-states.component';
import { VisualTestPoLookupStatesComponent } from '../fields/po-lookup/po-lookup-states.component';
import { VisualTestPoMultiselectStatesComponent } from '../fields/po-multiselect/po-multiselect-states.component';
import { VisualTestPoSelectStatesComponent } from '../fields/po-select/po-select-states.component';
import { VisualTestPoTextareaStatesComponent } from '../fields/po-textarea/po-textarea-states.component';
import { VisualTestPoRichTextStatesComponent } from '../fields/po-rich-text/po-rich-text-states.component';
import { VisualTestPoUploadStatesComponent } from '../fields/po-upload/po-upload-states.component';
import { VisualTestPoCheckboxStatesComponent } from '../fields/po-checkbox/po-checkbox-states.component';
import { VisualTestPoCheckboxGroupStatesComponent } from '../fields/po-checkbox-group/po-checkbox-group-states.component';
import { VisualTestPoSwitchStatesComponent } from '../fields/po-switch/po-switch-states.component';
import { VisualTestPoRadioGroupStatesComponent } from '../fields/po-radio-group/po-radio-group-states.component';

const visualRoutes: Routes = [
// Basic sample routes
{ path: 'visual/po-button-basic', component: SamplePoButtonBasicComponent },
{ path: 'visual/po-button-group-basic', component: SamplePoButtonGroupBasicComponent },
{ path: 'visual/po-input-basic', component: SamplePoInputBasicComponent },
{ path: 'visual/po-checkbox-basic', component: SamplePoCheckboxBasicComponent },
{ path: 'visual/po-switch-basic', component: SamplePoSwitchBasicComponent },
{ path: 'visual/po-select-basic', component: SamplePoSelectBasicComponent },
{ path: 'visual/po-table-basic', component: SamplePoTableBasicComponent },
{ path: 'visual/po-tag-basic', component: SamplePoTagBasicComponent },
{ path: 'visual/po-accordion-basic', component: SamplePoAccordionBasicComponent },
{ path: 'visual/po-divider-basic', component: SamplePoDividerBasicComponent },
{ path: 'visual/po-progress-basic', component: SamplePoProgressBasicComponent },
{ path: 'visual/po-input-states', component: VisualTestPoInputStatesComponent },

// Field state combination routes (e2e/visual/fields/)
{ path: 'visual/fields/po-input-states', component: VisualTestPoInputFieldStatesComponent },
{ path: 'visual/fields/po-decimal-states', component: VisualTestPoDecimalStatesComponent },
{ path: 'visual/fields/po-email-states', component: VisualTestPoEmailStatesComponent },
{ path: 'visual/fields/po-number-states', component: VisualTestPoNumberStatesComponent },
{ path: 'visual/fields/po-password-states', component: VisualTestPoPasswordStatesComponent },
{ path: 'visual/fields/po-url-states', component: VisualTestPoUrlStatesComponent },
{ path: 'visual/fields/po-login-states', component: VisualTestPoLoginStatesComponent },
{ path: 'visual/fields/po-combo-states', component: VisualTestPoComboStatesComponent },
{ path: 'visual/fields/po-datepicker-states', component: VisualTestPoDatepickerStatesComponent },
{ path: 'visual/fields/po-datepicker-range-states', component: VisualTestPoDatepickerRangeStatesComponent },
{ path: 'visual/fields/po-lookup-states', component: VisualTestPoLookupStatesComponent },
{ path: 'visual/fields/po-multiselect-states', component: VisualTestPoMultiselectStatesComponent },
{ path: 'visual/fields/po-select-states', component: VisualTestPoSelectStatesComponent },
{ path: 'visual/fields/po-textarea-states', component: VisualTestPoTextareaStatesComponent },
{ path: 'visual/fields/po-rich-text-states', component: VisualTestPoRichTextStatesComponent },
{ path: 'visual/fields/po-upload-states', component: VisualTestPoUploadStatesComponent },
{ path: 'visual/fields/po-checkbox-states', component: VisualTestPoCheckboxStatesComponent },
{ path: 'visual/fields/po-checkbox-group-states', component: VisualTestPoCheckboxGroupStatesComponent },
{ path: 'visual/fields/po-switch-states', component: VisualTestPoSwitchStatesComponent },
{ path: 'visual/fields/po-radio-group-states', component: VisualTestPoRadioGroupStatesComponent }
];

@NgModule({
declarations: [
AppComponent,
// Basic samples
SamplePoButtonBasicComponent,
SamplePoButtonGroupBasicComponent,
SamplePoInputBasicComponent,
SamplePoCheckboxBasicComponent,
SamplePoSwitchBasicComponent,
SamplePoSelectBasicComponent,
SamplePoTableBasicComponent,
SamplePoTagBasicComponent,
SamplePoAccordionBasicComponent,
SamplePoDividerBasicComponent,
SamplePoProgressBasicComponent,
VisualTestPoInputStatesComponent,
// Field state components
VisualTestPoInputFieldStatesComponent,
VisualTestPoDecimalStatesComponent,
VisualTestPoEmailStatesComponent,
VisualTestPoNumberStatesComponent,
VisualTestPoPasswordStatesComponent,
VisualTestPoUrlStatesComponent,
VisualTestPoLoginStatesComponent,
VisualTestPoComboStatesComponent,
VisualTestPoDatepickerStatesComponent,
VisualTestPoDatepickerRangeStatesComponent,
VisualTestPoLookupStatesComponent,
VisualTestPoMultiselectStatesComponent,
VisualTestPoSelectStatesComponent,
VisualTestPoTextareaStatesComponent,
VisualTestPoRichTextStatesComponent,
VisualTestPoUploadStatesComponent,
VisualTestPoCheckboxStatesComponent,
VisualTestPoCheckboxGroupStatesComponent,
VisualTestPoSwitchStatesComponent,
VisualTestPoRadioGroupStatesComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule, FormsModule, RouterModule.forRoot(visualRoutes, {}), PoModule],
providers: [provideHttpClient(withInterceptorsFromDi())]
})
export class VisualAppModule {}
12 changes: 12 additions & 0 deletions e2e/visual/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PO UI Visual Regression Tests</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
8 changes: 8 additions & 0 deletions e2e/visual/app/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { enableProdMode, provideZoneChangeDetection } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { VisualAppModule } from './app.module';

platformBrowserDynamic()
.bootstrapModule(VisualAppModule, { applicationProviders: [provideZoneChangeDetection()] })
.catch(err => console.error('Error in visual-app bootstrap:', err));
1 change: 1 addition & 0 deletions e2e/visual/app/polyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'zone.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<div style="padding: 16px; display: flex; flex-direction: column; gap: 24px">
<!-- State: basic -->
<div data-testid="state-basic">
<po-checkbox-group name="basic" p-label="Grupo basico" [p-options]="options"></po-checkbox-group>
</div>

<!-- State: label + helper -->
<div data-testid="state-label-helper">
<po-checkbox-group
name="label-helper"
p-label="Grupo com helper"
p-helper="Texto de ajuda do helper"
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: label + help-text -->
<div data-testid="state-label-help">
<po-checkbox-group
name="label-help"
p-label="Grupo com help"
p-help="Texto de apoio do campo"
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: disabled -->
<div data-testid="state-disabled">
<po-checkbox-group
name="disabled"
p-label="Grupo desabilitado"
p-disabled
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: required -->
<div data-testid="state-required">
<po-checkbox-group name="required" p-label="Grupo obrigatorio" p-required [p-options]="options"></po-checkbox-group>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-visual-test-po-checkbox-group-states',
templateUrl: './po-checkbox-group-states.component.html',
standalone: false
})
export class VisualTestPoCheckboxGroupStatesComponent {
options = [
{ label: 'Opcao 1', value: '1' },
{ label: 'Opcao 2', value: '2' },
{ label: 'Opcao 3', value: '3' }
];
}
Loading
Loading