# Temas e Cores — Estratégia de Testes

> **Slug do módulo:** `temas-e-cores` (tabela `modulos`, id 2)

## 1. Estratégia

| Camada | Ferramenta sugerida | Cobertura |
|---|---|---|
| Feature (CMS) | PHPUnit + `RefreshDatabase` + acting-as com papel Sentinel | Rotas de leitura/gravação, permissões, validação de `layout`. |
| Feature (público) | Feature test com rota `/` e `/getColors` | Resolução do template, presença das variáveis CSS, resposta JSON. |
| Unit | PHPUnit puro | `HexColor`, `PortalLayout::newsQuantity()`, `ThemeResolver`. |
| Integração | `Dusk` ou Playwright | Grade de templates, colorpicker, reordenação drag-and-drop do modelo 5. |
| Regressão visual | Screenshots do portal com cada um dos 6 modelos | Garante que a troca de template não quebra a home. |

No legado **não existe nenhum teste** para este módulo — a cobertura é manual.

## 2. Fixtures necessárias

```php
// exemplo ilustrativo
class ThemeFactory extends Factory
{
    protected $model = Theme::class;

    public function definition(): array
    {
        return [
            'slug'                  => 'default',
            'layout'                => 'modelo1',
            'primary_color'         => '#1075ad',
            'title_color'           => '#66b367',
            'button_color'          => '#fece50',
            'text_background_color' => '#fece50',
            'cms_skin'              => 'black',
            'background_path'       => null,
            'background_fixed'      => true,
            'background_repeat'     => true,
        ];
    }
}
```

```php
// exemplo ilustrativo
class LayoutBlockFactory extends Factory
{
    protected $model = LayoutBlock::class;

    public function definition(): array
    {
        return [
            'key'     => 'logo',
            'section' => 'header',
            'type'    => 'element',
            'value'   => null,
            'order'   => 1,
            'style'   => null,
            'status'  => true,
            'path'    => 'portal.layouts.modelo5.layout.header.nav-header',
            'title'   => null,
            'columns' => null,
        ];
    }
}
```

| Fixture | Origem no legado |
|---|---|
| Usuário com papel `super-admin` | `Sentinel::findUserByCredentials` + `addRole`. |
| Usuário com permissão `tema-do-portal.edit` | `Sentinel::findById` + `addAccess`. |
| `configurations` com as 9 chaves de `temas` | `v6/database/seeds/ConfigurationsTableSeeder.php` (bloco "Temas e cores"). |
| `layouts` com seções/elementos do modelo 5 | 41 linhas de produção — usar um subset mínimo: `preview`, `tags`, `header`, `logo`, `ultimas-noticias`, `more-popular`, `transparencia-fiscal`, `transparencia-fiscal-grid`. |

## 3. Casos de teste — CMS

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| T01 | Abre a tela de temas | usuário com `tema-do-portal.edit` | GET `cms/configuracoes/editar/temas` | 200; view `prefecture.configurations.temas.edit`; `$templatesPortal` com 6 itens; `$temas_lte` com 11 itens |
| T02 | Sem permissão | usuário sem `tema-do-portal.edit` | GET `cms/configuracoes/editar/temas` | redirect `dashboard.index` + flash de aviso |
| T03 | Salva cores válidas | sessão válida | POST `cms/update/configuration` com `cor_1=#123456`, `cor_2=#654321`, `cor_3=#abcdef`, `cor_4=#fedcba`, `category_configurations_id=3` | 302 para `edit.configuracoes.by.category` com slug `temas`; banco atualizado; log de atividade criado |
| T04 | Cor vazia | sessão válida | POST com `cor_1=''` | 302 de volta; erro "O campo Cor Principal é obrigatório."; valor no banco inalterado |
| T05 | Cor acima de 50 caracteres | sessão válida | POST com `cor_1` de 51 chars | erro de validação `max` |
| T06 | `layout` inválido | sessão válida | POST com `layout=modelo9` em primeiro campo | flash "Por favor, selecione um layout entre as opções exibidas."; banco inalterado |
| T07 | `layout` válido | sessão válida | POST com `layout=modelo5` | banco passa a `modelo5` |
| T08 | `layout` não é o primeiro campo | sessão válida | POST com `cor_1` antes de `layout=modelo9` | **bug conhecido**: o valor inválido é gravado (documentar como falha esperada até a correção) |
| T09 | `cor_cms` inválida | sessão válida | POST com `cor_cms=inexistente` | **bug conhecido**: valor é aceito e salvo |
| T10 | Upload do plano de fundo | sessão válida, `UploadedFile::fake()->image('fundo.jpg', 1920, 1080)` | POST com `background` | arquivo em `storage/app/public/configuracoes/`; `configurations.value` = `configuracoes/<nome>`; imagem **não** redimensionada |
| T11 | `background_fixed` / `background_repeat` | sessão válida | POST com `background_fixed=1`, `background_repeat=0` | valores gravados como `'1'`/`'0'` |
| T12 | Remoção do arquivo | sessão válida | POST `cms/ajax/remove-arquivo-configuracao` | `configurations.value` do `background` fica vazio; arquivo removido do storage |
| T13 | Usuário sem `configuracoes.edit` grava | usuário só com `tema-do-portal.edit` | POST `cms/update/configuration` | **bug conhecido**: a gravação é permitida (permissão assimétrica) |

## 4. Casos de teste — modelo 5

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| T14 | Abre o editor do modelo 5 | usuário autenticado | GET `cms/editar/modelo-personalizavel` | 200; view `prefecture.configurations.modelo5.edit`; `$configLayout` com `preview`/`tags`; `$configStyle` só com linhas de `type=element` e `style` não nulo |
| T15 | Salva o HTML do preview | blocos `preview`/`tags` existentes | POST `cms/update/modelo-personalizavel` com `html` e `htmlTags` | `layouts.value` atualizado nas duas linhas; flash de sucesso |
| T16 | Salva título de bloco | bloco `ultimas-noticias` | POST com `input-title-ultimas-noticias=Últimas Notícias` | `layouts.title` atualizado |
| T17 | Salva colunas do grid | bloco `transparencia-fiscal` | POST com `input-columnsTransparenciaFiscal=4` | `layouts.columns` = `4` |
| T18 | Reordena blocos | blocos `logo` e `menu` | POST `cms/order/modelo-personalizavel` com `{"header": ["logo-preview", "menu-preview"]}` | `order` 0/1; `section='header'`; `status=1` |
| T19 | Reordena o grupo `tags` | bloco `tags` | POST com `{"tags": ["tags-preview"]}` | `status = 0` |
| T20 | `updateOrInsert` do módulo ativo | — | POST `cms/update-module-status` com `module=transparencia`, `subId=3` | linha `layouts` com `key='secretarias-grid'`, `value='transparencia:3'`, `status=0`; JSON `{"success": true}` |
| T21 | `legacyMap` | linha `secretarias-grid` com `value='1'` | GET `cms/editar/modelo-personalizavel` | `$activeModule = 'secretaria'` |
| T22 | Upload de CSS | — | POST `cms/upload/modelo-personalizavel` com `file_css` | arquivo `modelo5.<ext>` criado em `storage/app/css/` |
| T23 | Download de CSS | arquivo presente | GET `cms/download/modelo-personalizavel` | download do `modelo5.css`; se ausente, do `modelo5-example.css` |

## 5. Casos de teste — portal

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| T24 | Endpoint público de cores | 4 chaves `cor_1..cor_4` | GET `/getColors` | JSON com 4 itens, `key`/`value`, ordenados por `id` |
| T25 | Home usa o template gravado | `layout=modelo3` | GET `/` | view `portal.layouts.modelo3.home.index` |
| T26 | Template inexistente | `layout=modelo99` | GET `/` | 404 e `configurations.pagina_em_manutencao` = `1` |
| T27 | Variáveis CSS no `<head>` | `cor_1=#111111` | GET `/` | resposta contém `--cor-1: #111111` |
| T28 | Plano de fundo no `<body>` | `background=configuracoes/x.jpg`, `background_fixed='1'`, `background_repeat='0'` | GET `/` | resposta contém `background-attachment: fixed` e `background-repeat: no-repeat` |
| T29 | Quantidade de notícias por modelo | `layout=modelo2` | GET `/` | 7 notícias no bloco de notícias |
| T30 | Slides da home | `layout=modelo6` | GET `/` | bloco de slides presente |
| T31 | Skin do CMS | `cor_cms=black` | GET `/cms/dashboard` | `<link>` para `skin-black.min.css`; logo `logo-cms.png` |
| T32 | Skin do CMS (outra) | `cor_cms=blue` | GET `/cms/dashboard` | `<link>` para `skin-blue.min.css`; logo `logo-cms-branco.png` |

## 6. Como rodar

```bash
# suíte completa
php artisan test

# apenas o módulo
php artisan test --filter=ThemeTest
php artisan test --filter=LayoutBlockTest

# uma classe isolada
php artisan test tests/Feature/Admin/ThemeTest.php

# unit
phpunit tests/Unit/PortalLayoutTest.php
```

Comandos do projeto legado (Laravel 5.5):

```bash
# v6
cd v6 && ./vendor/bin/phpunit
```

> O `v6/composer.json` declara `phpunit/phpunit: ~6.0` (linha 50) — versão compatível com
> PHP 7.2, e **não** com PHP 8.x. Na v7 o PHPUnit deve ser elevado (11.x) junto com o
> framework.

## 7. Checklist de regressão manual (aceite pelo negócio)

1. Trocar para cada um dos 6 modelos e conferir a home, a listagem de notícias e a página de busca.
2. Alterar cada uma das 4 cores e conferir botões, títulos e fundos de data.
3. Subir um plano de fundo, marcar "Fixar" e "Repetir", conferir em página com scroll longo.
4. Trocar a skin do CMS e conferir todas as telas do painel.
5. No modelo 5: reordenar blocos, ocultar, renomear títulos, alterar nº de colunas, upar CSS.
6. Gerar um PDF de conteúdo e confirmar que `cor_1` foi aplicada.
