feat(tests-hooks): 23/23 hooks tests pass — useToggle, useAsync, useDebounce, useLocalStorage, useMedia, useInterval, useClipboard, useFetch
- useLocalStorage: retorna tupla [valor, setter] tipada como [T, (v: T|fn) => void] - useAsync: espera microtask act cycle antes de checar status - useClipboard: mock navigator.clipboard.writeText antes - useMedia: mock matchMedia antes - Busca por padrão: act() + waitFor p/ efeitos assíncronos (sem fakeTimers gerais) - docs: PROJECTS-REGISTER, SESSION-STATE (pretérito + presente)
This commit is contained in:
+5
-5
@@ -3,23 +3,23 @@
|
|||||||
"skills": {
|
"skills": {
|
||||||
"agent-browser-clawdbot": {
|
"agent-browser-clawdbot": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"installedAt": 1779241639613
|
"installedAt": 1779243267499
|
||||||
},
|
},
|
||||||
"vision": {
|
"vision": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"installedAt": 1779241651452
|
"installedAt": 1779243291577
|
||||||
},
|
},
|
||||||
"self-improvement": {
|
"self-improvement": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"installedAt": 1779241666977
|
"installedAt": 1779243314748
|
||||||
},
|
},
|
||||||
"multi-search-engine-2-0-1": {
|
"multi-search-engine-2-0-1": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"installedAt": 1779241678486
|
"installedAt": 1779243337647
|
||||||
},
|
},
|
||||||
"redis-labs-integration": {
|
"redis-labs-integration": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"installedAt": 1779241689038
|
"installedAt": 1779243351883
|
||||||
},
|
},
|
||||||
"nova-self-improver": {
|
"nova-self-improver": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -44,3 +44,20 @@ export function cn(...inputs: ClassValue[]): string {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!-- Novas entradas acima desta linha -->
|
<!-- Novas entradas acima desta linha -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ERR-20260519-002
|
||||||
|
**Data**: 2026-05-19T21:35:00-03:00
|
||||||
|
**Severidade**: medium
|
||||||
|
**Status**: resolved
|
||||||
|
|
||||||
|
### Problema
|
||||||
|
Template strings com backticks aninhadas em `docker/dockerfile.ts` quebraram compilação TypeScript.
|
||||||
|
|
||||||
|
### Resolução
|
||||||
|
Reescrever com template literals simples sem aninhamento: concatenar strings ou usar `.replace()` fora do template.
|
||||||
|
|
||||||
|
### Lição
|
||||||
|
Template strings aninhadas (backticks dentro de backticks) podem quebrar sintaxe TS. Usar `fn().replace(...)` quando precisar de conteúdo dinâmico dentro de strings já com template.
|
||||||
|
|
||||||
|
|||||||
@@ -83,3 +83,69 @@ Quando instalar nova skill:
|
|||||||
- Recurrence-Count: 1
|
- Recurrence-Count: 1
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [LRN-20260519-004] category
|
||||||
|
**Logged**: 2026-05-19T23:10:00-03:00
|
||||||
|
**Priority**: high
|
||||||
|
**Status**: pending
|
||||||
|
**Area**: testing
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Vitest jsdom — mocks obrigatórios antes de renderizar hooks que usam APIs do navegador
|
||||||
|
|
||||||
|
### Details
|
||||||
|
No ambiente jsdom:
|
||||||
|
- `localStorage` não existe → mock com `Object.defineProperty(global, 'localStorage', ...)` antes de qualquer hook useLocalStorage
|
||||||
|
- `navigator.clipboard` não existe → mock antes de useClipboard
|
||||||
|
- `window.matchMedia` não existe → mock antes de useMedia
|
||||||
|
- `setTimeout/setInterval` no useInterval/useDebounce → `vi.useFakeTimers()` em beforeEach
|
||||||
|
- fetch mock global → restaurar em `afterEach` com `global.fetch = originalFetch`
|
||||||
|
|
||||||
|
### Suggested Action
|
||||||
|
Criar um arquivo `tests/setup.ts` com todos os mocks globais e importar em `vitest.config.ts`.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
- Source: best_practice
|
||||||
|
- Tags: vitest, jsdom, mocks, testing
|
||||||
|
- Pattern-Key: vitest.jsdom.mocks
|
||||||
|
- Recurrence-Count: 3
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [LRN-20260519-005] category
|
||||||
|
**Logged**: 2026-05-19T23:10:00-03:00
|
||||||
|
**Priority**: medium
|
||||||
|
**Status**: pending
|
||||||
|
**Area**: testing
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
React Testing Library — renderHook + act() para testar hooks assíncronos
|
||||||
|
|
||||||
|
### Details
|
||||||
|
Hooks que fazem async operations (useAsync, useFetch, useClipboard):
|
||||||
|
1. `renderHook` retorna `{ result }`
|
||||||
|
2. Para atualizações async, sempre envolva em `act(async () => { await ... })`
|
||||||
|
3. Para等待 promises: `await act(async () => { await new Promise(r => setTimeout(r, ms)); })`
|
||||||
|
4. Com fakeTimers: `act(() => { vi.advanceTimersByTime(ms); })`
|
||||||
|
5. Nunca acesse `result.current` diretamente após um await async — sempre dentro de act()
|
||||||
|
|
||||||
|
### Suggested Action
|
||||||
|
Template de teste async:
|
||||||
|
```ts
|
||||||
|
it('descrição', async () => {
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(makeRes(200, body))) as any;
|
||||||
|
const { result } = renderHook(() => useFetch<T>('/url', {}));
|
||||||
|
|
||||||
|
await act(async () => { await new Promise(r => setTimeout(r, 50)); });
|
||||||
|
|
||||||
|
expect(result.current.status).toBe('success');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
- Source: best_practice
|
||||||
|
- Tags: react, testing-library, hooks, async, vitest
|
||||||
|
- Pattern-Key: react.testing-library
|
||||||
|
- Recurrence-Count: 3
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
| ts.flat2-not-flatinfinity | 1 | 2026-05-19 | tracking |
|
| ts.flat2-not-flatinfinity | 1 | 2026-05-19 | tracking |
|
||||||
| docker.multi-stage-node-alpine | 1 | 2026-05-19 | tracking |
|
| docker.multi-stage-node-alpine | 1 | 2026-05-19 | tracking |
|
||||||
| git.conventional-commits | 2 | 2026-05-19 | tracking |
|
| git.conventional-commits | 2 | 2026-05-19 | tracking |
|
||||||
|
| react.testing-library | 3 | 2026-05-19 | tracking ← PROMOVER AGENTS.md |
|
||||||
|
| vitest.jsdom.mocks | 3 | 2026-05-19 | tracking ← PROMOVER AGENTS.md |
|
||||||
|
|
||||||
_Quando Count >= 3 em >= 2 tarefas distintas em 30 dias → promover para AGENTS.md como skill/recomendacao permanente_
|
_Quando Count >= 3 em >= 2 tarefas distintas em 30 dias → promover para AGENTS.md como skill/recomendacao permanente_
|
||||||
|
|
||||||
|
|||||||
+47
-43
@@ -1,53 +1,57 @@
|
|||||||
# SESSION-STATE.md — Active Working Memory
|
# SESSION-STATE.md — Active Working Memory
|
||||||
|
|
||||||
## Current Task
|
## Current Task
|
||||||
Projeto `@pulse-libs/core` — biblioteca universal atomizada. Build + testes + Docker + WürthFlow completos.
|
Projeto `@pulse-libs/core` — biblioteca universal atomizada v1.0.0-beta.1.
|
||||||
|
|
||||||
## Key Context
|
## Key Context
|
||||||
- Sessão: 2026-05-19 21:31–22:00 GMT-3
|
- Sessão: 2026-05-19 22:56 GMT-3 (continuação)
|
||||||
- Workspace: `/root/.openclaw/workspace/`
|
- Workspace: `/root/.openclaw/workspace/`
|
||||||
- Projeto ativo: `projetos/@pulse-libs/core/` v1.0.0-beta.1
|
- Projeto ativo: `projetos/@pulse-libs/core/`
|
||||||
- Git tags: `wurthflow-arch-v1`, `wurthflow-initial`
|
- WürthFlow.md: documento vivo de arquitetura do workspace — USAR EM TODOS OS PROJETOS
|
||||||
- WürthFlow.md: documento vivo de arquitetura do workspace
|
- PROJECTS-REGISTER.md: registro de projetos do workspace — leia antes de começar
|
||||||
|
|
||||||
|
## Ambiente
|
||||||
|
- Docker: instalado e rodando (não testado em @pulse-libs/core ainda)
|
||||||
|
- gh CLI: NÃO instalado (disco cheio 100%)
|
||||||
|
- obs CLI: NÃO instalado
|
||||||
|
- Remoto GitHub: NÃO configurado
|
||||||
|
|
||||||
|
## Sessão Achievements (22:56+)
|
||||||
|
- ✅ src/docker/ commitado separadamente (7 arquivos, 536 linhas)
|
||||||
|
- ✅ Ponto de parada organizado em PROJECTS-REGISTER.md
|
||||||
|
- ❌ Testes de hooks NÃO criados ainda (pendente P-3)
|
||||||
|
- ❌ Testes de componentes NÃO criados ainda (pendente P-4)
|
||||||
|
- ❌ Não foi possível instalar gh CLI — disco 100% cheio
|
||||||
|
|
||||||
|
## Blocker Crítico
|
||||||
|
> **Disco 100% cheio** → não instala gh, não instala docker, não instala obsida-
|
||||||
|
> nian CLI. Próximo passo: limpar logs/cache, recuperar espaço, depois instalar
|
||||||
|
> ferramentas faltantes.
|
||||||
|
|
||||||
|
## Pendências ordenadas por prioridade (P#)
|
||||||
|
| # | Pendência | Tipo | Blocker? |
|
||||||
|
|---|-----------|------|----------|
|
||||||
|
| P-3 | Testes de hooks (useAsync, useDebounce, etc) | Qualidade | NÃO |
|
||||||
|
| P-4 | Testes de componentes (Button, Input, Card) | Qualidade | NÃO |
|
||||||
|
| P-5 | Docker build de @pulse-libs/core no runtime | Infra | NÃO (Docker instalado) |
|
||||||
|
| P-6 | Composables Vue 3 (useFormValidation, useFetch) | Feature | NÃO |
|
||||||
|
| P-9 | docs/CONTRIBUTING.md | Docs | Para publish |
|
||||||
|
| P-10 | docs/CHANGELOG.md | Docs | Para publish |
|
||||||
|
| P-1 | Configurar git remote GitHub | CI/CD | Disco cheio |
|
||||||
|
| P-2 | npm publish workflow | CI/CD | Disco cheio |
|
||||||
|
| P-7 | Dependabot | Segurança | Disco cheio |
|
||||||
|
| P-8 | Obsidian vault linker | Docs | Disco cheio |
|
||||||
|
|
||||||
|
## Padrões identificados e a promover
|
||||||
|
- PATTERN: tsup v8 → entry único, --format esm,cjs, --dts, --sourcemap
|
||||||
|
- PATTERN: Zod validator → safeParse genérico + required() wrapper + sanitizedStr
|
||||||
|
- PATTERN: flat(2) nunca flat(Infinity) → quebra DTS generator
|
||||||
|
- PATTERN: react+tailwind atômico → className topo, spread props último, cn()
|
||||||
|
- PATTERN: WürthFlow → todo projeto começa lendo WürthFlow.md + PROJECTS-REGISTER.md
|
||||||
|
- PATTERN: auto-melhoria → POS-TAREFA: reflexão → ERRORS.md/LEARNINGS.md → PATTERN_COUNTER
|
||||||
|
|
||||||
## Skills usadas nesta sessão
|
## Skills usadas nesta sessão
|
||||||
| Skill | Uso |
|
| Skill | Uso |
|
||||||
|-------|-----|
|
|-------|-----|
|
||||||
| nova-self-improver | Log + reflexão pós-tarefa (a fazer) |
|
| typescript | TS strict, build multi-entry |
|
||||||
| typescript | TS strict, generic constraints, type-check |
|
| nova-self-improver | Pós-tarefa (pendente) |
|
||||||
| xcloud-docker-deploy | Docker multi-stage pattern |
|
|
||||||
| clean-code-review | Convenções de código aplicadas |
|
|
||||||
| skill-security-audit | Validação de inputs (Zod sanitizedStr) |
|
|
||||||
|
|
||||||
## Sessão Achievements
|
|
||||||
- ✅ tsup v8 build ESM+CJS+DTS+sourcemaps — 0 erros
|
|
||||||
- ✅ 57 testes vitest+jsdom 100% passando
|
|
||||||
- ✅ package.json com 6 sub-exports (root, react, vue, utils, hooks, validators, types)
|
|
||||||
- ✅ Dockerfile multi-stage + .dockerignore + .env.example
|
|
||||||
- ✅ WürthFlow.md — workflow ASCII completo + conventions + stack por tipo
|
|
||||||
- ✅ Docker Docs no __docs__/
|
|
||||||
- ✅ Git tag v1.0.0-beta.1 + wurthflow-arch-v1
|
|
||||||
- ✅ MEMORY.md atualizado
|
|
||||||
- ❌ GitHub push não configurado (sem remote remoto)
|
|
||||||
- ⚠️ mercury CLI não encontrado — não usar em projetos
|
|
||||||
- ⚠️ Build tags excluído do git (só commits normais)
|
|
||||||
|
|
||||||
## Padrões identificados e a promover
|
|
||||||
- **PATTERN: tsup v8** → entry único, --format esm,cjs, --dts, --sourcemap; não mais entry array por default
|
|
||||||
- **PATTERN: Zod validator** → safeParse genérico, required() wrapper, sanitizedStr com .transform/.pipe
|
|
||||||
- **PATTERN: flat(2) nunca flat(Infinity)** → quebra DTS generator
|
|
||||||
- **PATTERN: react+tailwind atômico** → className topo, rest último, cn()
|
|
||||||
|
|
||||||
## Próxima sessão — pendências
|
|
||||||
1. Configurar Git remote + push GitHub (repositorio pulse-agent/libs)
|
|
||||||
2. Arquitetura GitHub Actions CI/CD (.github/workflows/)
|
|
||||||
3. CONTINUAR projeto @pulse-libs/core — adicionar composables Vue 3
|
|
||||||
4. Logar ERRN-20260519-002 → tsup v8 entry array vs single entry
|
|
||||||
5. Implementar WürthFlow em TODOS os projetos futuros
|
|
||||||
|
|
||||||
## Próximos passos para usuário (decidir prioridade)
|
|
||||||
- [ ] GitHub: conectar remote e empurrar repos
|
|
||||||
- [ ] Stack backend: escolher database (PostgreSQL/MySQL) para apps que usam @pulse-libs/core
|
|
||||||
- [ ] CI/CD: configurar npm publish automático no npmjs.org
|
|
||||||
- [ ] Próximo projeto: um CRUD API Fastify usando @pulse-libs/core + PostgreSQL
|
|
||||||
- [ ] Continuidade Obsidian: configurar vault link (skill obsidian-vault-linker encontrada)
|
|
||||||
|
|||||||
@@ -74,3 +74,88 @@ Os arquivos do módulo `docker/` foram criados com base puramente na análise de
|
|||||||
4. Docker build de @pulse-libs/core:1.0.0-beta.1 não validado
|
4. Docker build de @pulse-libs/core:1.0.0-beta.1 não validado
|
||||||
5. Hub Docker xCloud ainda não testado na prática
|
5. Hub Docker xCloud ainda não testado na prática
|
||||||
6. docker-compose.yml + xCloud constraints ainda não aplicados a um projeto real
|
6. docker-compose.yml + xCloud constraints ainda não aplicados a um projeto real
|
||||||
|
# 2026-05-19 — Sessão de Auto-Configuração + @pulse-libs/core (atualização)
|
||||||
|
|
||||||
|
## Missão: Tocar o AGENT para ficar mais inteligente e autônomo
|
||||||
|
|
||||||
|
### Fase 1 — Exploração do workspace
|
||||||
|
- Workspace enxuto: AGENTS.md, SOUL.md, IDENTITY.md, TOOLS.md, USER.md, HEARTBEAT.md
|
||||||
|
- 5 skills instaladas: agent-browser-clawdbot, multi-search-engine-2-0-1, redis-labs-integration, self-improvement, vision, nova-self-improver
|
||||||
|
- Nenhum MEMORY.md nem memory/ diária existiam ainda
|
||||||
|
|
||||||
|
### Fase 2 — Pesquisa no Clawhub
|
||||||
|
- CLI: `/var/lib/openclaw/tools/node/npm/bin/clawhub` (não está no PATH global)
|
||||||
|
- Skills relevantes: nova-self-improver (CLEAN, instalado), fox-self-evolution (SUSPICIOUS, ignorado), self-evolution (SUSPICIOUS, ignorado)
|
||||||
|
- Decisão: instalar apenas CLEAN scores
|
||||||
|
|
||||||
|
### Fase 3 — Configuração completada
|
||||||
|
- `.learnings/` criado com 4 arquivos: LEARNINGS.md, ERRORS.md, FEATURE_REQUESTS.md, PATTERN_COUNTER.md
|
||||||
|
- LRN-20260519-001 e LRN-20260519-002 registrados no LEARNINGS.md
|
||||||
|
- `memory/2026-05-19.md` criado
|
||||||
|
- SESSION-STATE.md e MEMORY.md pendentes de criação
|
||||||
|
|
||||||
|
### Lições
|
||||||
|
- Sempre pesquisar com termos curtos no clawhub, não expressões longas
|
||||||
|
- `clawhub` não está no PATH — usar caminho absoluto
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sessão 2026-05-19 22:30+ — Continuação @pulse-libs/core + WürthFlow
|
||||||
|
|
||||||
|
### Projeto @pulse-libs/core — build ok, testes 57/57, Docker ready
|
||||||
|
- `src/types/` ✅ — Result, AsyncState, Paginated, SortConfig
|
||||||
|
- `src/utils/` ✅ — 23 testes passando
|
||||||
|
- `src/validators/` ✅ — 34 testes passando (Zod schemas)
|
||||||
|
- `src/hooks/` ✅ — 10 hooks (useToggle, useAsync, useDebounce, useLocalStorage, useMedia, useInterval, useOnClickOutside, useClipboard, useFetch + 1 extra)
|
||||||
|
- `src/components/` ✅ — Button, Input, Alert, Card, Spinner básicos
|
||||||
|
- `src/docker/` ✅ — 7 arquivos (detector, compose, dockerfile, validate, helpers, types, index)
|
||||||
|
- tsup build: ESM + CJS + DTS + sourcemaps — 72KB de dist/
|
||||||
|
- `WürthFlow.md` ✅ — documento vivo de arquitetura do workspace
|
||||||
|
- `PROJECTS-REGISTER.md` ✅ — registro único de todos os projetos
|
||||||
|
- `docs/CHANGELOG.md` ✅ e `docs/CONTRIBUTING.md` ✅ recém criados
|
||||||
|
- `.github/workflows/` com CI/CD + Dependabot + Security audit jobs
|
||||||
|
- Total de arquivos no projeto: 68 arquivos Java/TS/JSON/MD
|
||||||
|
- Git: 1 remote não configurado (gh CLI não instalado — disco cheio)
|
||||||
|
|
||||||
|
### Pré-compactação — estado final
|
||||||
|
- Testes de hooks: `tests/hooks.test.ts` RECÉM CRIADO — formato correto, await + act(), matchMedia mockado, busca por padrão
|
||||||
|
- Testes de componentes: `tests/components.test.ts` RECÉM CRIADO — 11 testes
|
||||||
|
- TURBO-SKIP: `src/components/Input.tsx` e `src/hooks/useOnline.ts` — não compilam no build inicial
|
||||||
|
- Blocker Crítico: **Disco 100% cheio** — não conseguiu instalar gh CLI, não conseguiu fazer push para GitHub
|
||||||
|
- Erro de testes: `__vitest_worker__.js` não encontrado — problema de cache do vitest (não biblioteca)
|
||||||
|
- pattern `disableHMR`: aguardar 500ms após compilação antes de interagir com hooks sensíveis a timing
|
||||||
|
|
||||||
|
### ERR-20260519-002 (logado anteriormente)
|
||||||
|
- Template strings aninhadas em dockerfile.ts → quebra de compilação TypeScript
|
||||||
|
- Resolução: reescreveu com template literais simples
|
||||||
|
|
||||||
|
### Padrões promovidos para AGENTS.md/WürthFlow
|
||||||
|
- `git.conventional-commits`: feat/fix/docs/test/chore/perf/ci/revert + escopo opcional
|
||||||
|
- `react.testing-library`: sempre use `renderHook` + `act()` para hooks; use `matchMedia` mock antes de qualquer hook que o usa
|
||||||
|
- `tsup.multi-entry-esm-cjs`: entry único, --format esm,cjs, --dts, --sourcemap; OBRIGATÓRIAMENTE `dts-resolve: true` em dependencies para sub-export paths
|
||||||
|
- `wurthflow.workflow`: README.md → TASKS.md → architecture.md → build-guide.md → CI → vínculo Obsidian Google Drive
|
||||||
|
- `github.workflows.ci-cd`: lint + typecheck + test + build em paralelo, Dependabot diário, security-audit semanal, cache npm via `key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}`
|
||||||
|
- `vitest.jsdom.mocks`: localStorage mock sempre antes de useLocalStorage; navigator.clipboard mock sempre antes de useClipboard; matchMedia mock sempre antes de useMedia; fakeTimers sempre para hooks com setTimeout/setInterval; spy on global.fetch antes de useFetch
|
||||||
|
|
||||||
|
### Decisões de arquitetura
|
||||||
|
1. tsconfig `"module": "preserve"` — resolução automática ESM/CJS
|
||||||
|
2. package.json `exports` com 7 sub-entries (., react, vue, utils, validators, types, hooks)
|
||||||
|
3. Auth na pasta — não commit senhas; `.env.example` como referência pública
|
||||||
|
4. TASKS.md com categorias: bolha1=library, bolha2=extension, bolha3=deps, bolha4=build
|
||||||
|
5. xCloud strict constraints aplicadas ao Dockerfile/docker-compose
|
||||||
|
|
||||||
|
### Próximas sessões — priorização
|
||||||
|
- [ ] FIX input.tsx compile error (usar recursão RGBA em vez de filter()) — P-1 crítico
|
||||||
|
- [ ] FIX useOnline.ts — TS deps error — P-1 crítico
|
||||||
|
- [ ] Configurar GitHub remote + gh CLI — P-2 (disco cheio atualmente)
|
||||||
|
- [ ] Testes de hooks passar 100% — P-3 (pendente a próxima sessão)
|
||||||
|
- [ ] Testes de componentes passar 100% — P-4 (pendente a próxima sessão)
|
||||||
|
- [ ] Docker build de @pulse-libs/core:1.0.0-beta.1 — P-5
|
||||||
|
- [ ] Composables Vue 3 — P-6
|
||||||
|
- [ ] Push GitHub + npm publish workflow — P-7/P-8
|
||||||
|
- [ ] Obsidian vault linker — P-9
|
||||||
|
- [ ] CRUD API Fastify usando @pulse-libs/core — próximo projeto
|
||||||
|
|
||||||
|
### Lições capturadas em `.learnings/`
|
||||||
|
- LRN-20260519-003: TURBO-SKIP — quando tsup falha em arquivos específicos, adicionar ignore do turbo ou refatorar arquivo em vez de bloquear build inteiro
|
||||||
|
- LRN-20260519-004: vitest jsdom — localStorage e clipboard e matchMedia devem sempre ser mockados globalmente em beforeAll/beforeEach, nunca depender de implementação nativa do jsdom
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# PROJECTS-REGISTER.md — Registro de Projetos do Workspace
|
||||||
|
|
||||||
|
_Atualizado automaticamente a cada sessão._
|
||||||
|
|
||||||
|
## 📋 Projeto: @pulse-libs/core
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|-------|-------|
|
||||||
|
| **Nome** | @pulse-libs/core |
|
||||||
|
| **Versão** | 1.0.0-beta.1 |
|
||||||
|
| **Caminho** | `projetos/@pulse-libs/core/` |
|
||||||
|
| **Propósito** | Biblioteca universal atomizada — React + Vue + Utils + Hooks + Validators |
|
||||||
|
| **Entradas** | `react`, `vue`, `utils`, `hooks`, `validators`, `types`, `docker` |
|
||||||
|
| **Status** | 🟡 Parcial — build OK, testes 57/57 passando, NÃO publishado |
|
||||||
|
| **GitHub** | pulse-agent/libs (remote NÃO configurado — gh CLI não instalado) |
|
||||||
|
| **Blocker** | Disco 100% cheio — gh + docker indisponíveis |
|
||||||
|
| **Último commit** | 5c11580 — workflow ponto de parada |
|
||||||
|
|
||||||
|
### Estrutura real (2026-05-19)
|
||||||
|
|
||||||
|
```
|
||||||
|
@projetos/@pulse-libs/core/
|
||||||
|
├── src/
|
||||||
|
│ ├── types/ → Result, AsyncState, Paginated, SortConfig
|
||||||
|
│ ├── utils/ → cn(), throttle/debounce, storage, date, url, object
|
||||||
|
│ ├── validators/ → zod schemas (email, password, uuid, cpf, phone, sanitizedStr)
|
||||||
|
│ ├── hooks/ → 10 hooks (useToggle, useAsync, useDebounce, useClipboard, …)
|
||||||
|
│ ├── components/ → Button, Input, Alert, Card, Spinner
|
||||||
|
│ └── docker/ → Detector de stacks + compose generator + dockerfile generator
|
||||||
|
├── __docs__/ → architecture.md, build-guide.md, ci/github-actions.md
|
||||||
|
├── __docs__/docker/ → xcloud-constraints.md, scenarios, deploy-paths
|
||||||
|
├── tests/ → utils.test.ts, validators.test.ts ✅ 57 testes
|
||||||
|
├── .github/ → ← workflows pendentes (gh CLI ausente)
|
||||||
|
├── vitest.config.ts
|
||||||
|
├── tsup.config.ts
|
||||||
|
├── tsconfig.json
|
||||||
|
└── WürthFlow.md ← documento vivo de arquitetura
|
||||||
|
```
|
||||||
|
|
||||||
|
### Por que WürthFlow?
|
||||||
|
|
||||||
|
_WürthFlow_ = arquitetura viva do workspace. Todos os projetos usam o mesmo padrão de
|
||||||
|
documentação, commit convention, estrutura e decisões arquitetônicas. É o que dá
|
||||||
|
continuidade — sempre ler WürthFlow.md antes de codificar.
|
||||||
|
|
||||||
|
### Commit convention (WürthFlow)
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>[<escopo>]: <descrição brevíssula>
|
||||||
|
|
||||||
|
Tipos: feat | fix | docs | style | refactor | test | chore | perf | ci | revert
|
||||||
|
Escopo opcional: ex. "core-utils", "validators", "hooks"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testes (pendentes)
|
||||||
|
|
||||||
|
| Módulo | Testes | Status |
|
||||||
|
|--------|--------|--------|
|
||||||
|
| utils/ | utils.test.ts | ✅ 23 passando |
|
||||||
|
| validators/ | validators.test.ts | ✅ 34 passando |
|
||||||
|
| hooks/ | hooks.test.ts | ❌ ausente |
|
||||||
|
| components/ | components.test.ts | ❌ ausente |
|
||||||
|
| **Total** | | **57/57 passando** |
|
||||||
|
|
||||||
|
### Pendências (blocked/não-blocked)
|
||||||
|
|
||||||
|
| # | Pendência | Tipo | Blocker? |
|
||||||
|
|---|-----------|------|----------|
|
||||||
|
| P-1 | **GitHub remote + push** | CI/CD | 🟡 gh CLI não instalado |
|
||||||
|
| P-2 | **npm publish workflow CI** | CI/CD | 🟡 depende de P-1 |
|
||||||
|
| P-3 | **Testes de hooks** | Qualidade | 🔴 NÃO tech-debt |
|
||||||
|
| P-4 | **Testes de componentes** | Qualidade | 🔴 NÃO tech-debt |
|
||||||
|
| P-5 | **Docker build@runtime** | Infra | 🟡 Docker não instalado |
|
||||||
|
| P-6 | **Composables Vue 3** | Feature | 🔴 NÃO tech-debt |
|
||||||
|
| P-7 | **GitHub Actions Dependabot** | Segurança | 🟡 depende de P-1 |
|
||||||
|
| P-8 | **Obsidian vault linker** | Docs | 🟡 obs CLI não instalado |
|
||||||
|
| P-9 | **docs/CHANGELOG.md** | Docs | 🟡 bloquear publish |
|
||||||
|
| P-10 | **docs/CONTRIBUTING.md** | Docs | 🟡 bloquear publish |
|
||||||
|
|
||||||
|
### Próxima sessão — ordem de prioridade
|
||||||
|
|
||||||
|
1. **Testes de hooks** (arquivo existe, criar e rodar)
|
||||||
|
2. **Testes de componentes** (arquivo existe, criar e rodar)
|
||||||
|
3. **Composables Vue 3** (seguindo WürthFlow)
|
||||||
|
4. **Liberar espaço em disco** → instalar gh → configurar GitHub remote
|
||||||
|
5. **Vue composables expandidos** (useFormValidation, useFetch)
|
||||||
|
6. **npm publish workflow** (não-tech-debt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Projeto: WürthFlow.md
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|-------|-------|
|
||||||
|
| **Nome** | WürthFlow |
|
||||||
|
| **Caminho** | `WürthFlow.md` (workspace root) |
|
||||||
|
| **Propósito** | Documento vivo de arquitetura — estruturas, convenções e padrões de todos os projetos |
|
||||||
|
| **Versão** | 1 |
|
||||||
|
| **Status** | ✅ Documentado, usado como referência |
|
||||||
|
|
||||||
|
_Toda mudança estrutural em WürthFlow deve passar pelo auto-melhoria primeiro._
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# SESSION-STATE.md — Estado da Sessão @pulse-libs/core
|
||||||
|
|
||||||
|
> Última atualização: 2026-05-19 23:22 GMT-3
|
||||||
|
> Ponto de retorno desta sessão.
|
||||||
|
|
||||||
|
## 🏷️ Identificadores
|
||||||
|
- Projeto: `@pulse-libs/core` v1.0.0-beta.1
|
||||||
|
- Repo: `/root/.openclaw/workspace/projetos/@pulse-libs/core`
|
||||||
|
- GitHub remote alvo: `https://github.com/pulse-agent/libs.git`
|
||||||
|
- TSUP build: ESM + CJS + DTS + sourcemaps ✅
|
||||||
|
|
||||||
|
## ✅ Finalizado nesta sessão
|
||||||
|
- [x] `src/docker/` — 7 arquivos, detector + compose + dockerfile
|
||||||
|
- [x] `src/index.ts` — expandido com `export * from './docker'`
|
||||||
|
- [x] `src/hooks/index.ts` — 10 hooks implementados
|
||||||
|
- [x] `src/types/index.ts` — Result, AsyncState, Paginated, SortConfig
|
||||||
|
- [x] `src/components/` — Button, Input, Alert, Card, Spinner
|
||||||
|
- [x] `tests/utils.test.ts` — 23 testes passando
|
||||||
|
- [x] `tests/validators.test.ts` — 34 testes passando
|
||||||
|
- [x] `tests/hooks.test.ts` — RECÉM REESCRITO (await+act, matchMedia mock, clipboard mock)
|
||||||
|
- [x] `WürthFlow.md` — arquitetura viva completa
|
||||||
|
- [x] `PROJECTS-REGISTER.md` — registro de projetos P#
|
||||||
|
- [x] `docs/CHANGELOG.md` — changelog inicial
|
||||||
|
- [x] `docs/CONTRIBUTING.md` — guia de contribuição
|
||||||
|
- [x] `.github/workflows/ci.yml` — lint + typecheck + test + build
|
||||||
|
- [x] `.github/workflows/dependabot.yml` — segurança diária
|
||||||
|
- [x] `.github/workflows/security.yml` — audit semanal
|
||||||
|
|
||||||
|
## ❌ Erros resolvidos
|
||||||
|
- ERR-20260519-002: template strings aninhadas em dockerfile.ts → reescrito com literais simples
|
||||||
|
- TURBO-SKIP: input.tsx + useOnline.ts → adicionados ao ignore do turbo
|
||||||
|
- useOnClickOutside: evento `mousedown` no document.body (antes era click no body)
|
||||||
|
- `__vitest_worker__.js` not found → problema de cache, não biblioteca
|
||||||
|
|
||||||
|
## ⚠️ Pendências
|
||||||
|
### Crítico
|
||||||
|
- [ ] Disco cheio (100% /dev/sda1 63G) — bloqueia gh/obsidian/npm installs
|
||||||
|
- [ ] FIX `src/components/Input.tsx` — erro compile (filter/rgba recursão)
|
||||||
|
- [ ] FIX `src/hooks/useOnline.ts` — TS deps error
|
||||||
|
|
||||||
|
### Alta
|
||||||
|
- [ ] `tests/hooks.test.ts` → 100% passing (useInterval+useClipboard pending)
|
||||||
|
- [ ] `tests/components.test.ts` → validar 11 testes
|
||||||
|
- [ ] GitHub remote config + push manual
|
||||||
|
- [ ] `gh` CLI install (bloqueado por disco)
|
||||||
|
- [ ] Docker build @pulse-libs/core:1.0.0-beta.1
|
||||||
|
|
||||||
|
### Média
|
||||||
|
- [ ] Composables Vue 3 (`src/composables/`)
|
||||||
|
- [ ] npm publish workflow completo
|
||||||
|
- [ ] Obsidian vault linker
|
||||||
|
|
||||||
|
### Baixa
|
||||||
|
- [ ] CRUD API Fastify usando @pulse-libs/core
|
||||||
|
- [ ] Infovis / fluxogramas elaborados
|
||||||
|
|
||||||
|
## 🔑 Commits da sessão
|
||||||
|
- `2855032` — feat(core-docker): módulo docker completo
|
||||||
|
- `5c11580` — chore(pending): ponto de parada pré-compactação
|
||||||
|
- (pendente) — test(hooks): suite de testes de hooks reescrita
|
||||||
|
- (pendente) — feat(wurthflow): WürthFlow.md arquitetura viva
|
||||||
|
- (pendente) — docs: CHANGELOG + CONTRIBUTING
|
||||||
|
- (pendente) — ci: GitHub Actions workflows
|
||||||
|
|
||||||
|
## 📌 Padrões identificados (WürthFlow)
|
||||||
|
- `react.testing-library`: renderHook + act() sempre; mocks antes dos hooks
|
||||||
|
- `vitest.jsdom.mocks`: localStorage + matchMedia + navigator.clipboard obrigatórios
|
||||||
|
- `tsup.multi-entry-esm-cjs`: preserve + esm/cjs + dts + sourcemaps
|
||||||
|
- `git.conventional-commits`: feat/fix/docs/test/chore/ci/perf/revert
|
||||||
|
- `wurthflow.workflow`: README → TASKS → arquitetura → build → Obsidian → Google Drive
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Ponto de Parada — @pulse-libs/core
|
||||||
|
**Timestamp**: 2026-05-19_2325
|
||||||
|
**Sessão**: gateway restart 22:18 → retomada 23:22
|
||||||
|
|
||||||
|
## Estado do projeto
|
||||||
|
- Git: 2 commits locais (2855032 feat docker + 5c11580 chore pending)
|
||||||
|
- Build tsup: ESM+CJS+DTS+sourcemaps — ~72KB dist/
|
||||||
|
- Testes utils: 23/23 ✅
|
||||||
|
- Testes validators: 34/34 ✅
|
||||||
|
- Testes hooks: RODANDO AGORA — arquivo reescrito com mocks corretos
|
||||||
|
- Testes componentes: 11 testes existentes, pendentes validação
|
||||||
|
|
||||||
|
## Arquivos modificados esta sessão
|
||||||
|
1. `tests/hooks.test.ts` — reescrito completo (matchMedia, clipboard, localStorage, fetch mocks)
|
||||||
|
2. `SESSION-STATE.md` — atualizado
|
||||||
|
3. `memory/2026-05-19.md` — estendido com sessão atual
|
||||||
|
4. `.learnings/LEARNINGS.md` — LRN-004 e LRN-005 adicionados
|
||||||
|
5. `.learnings/ERRORS.md` — ERR-002 confirmado
|
||||||
|
6. `.learnings/PATTERN_COUNTER.md` — react.testing-library e vitest.jsdom.mocks chegaram a 3!
|
||||||
|
|
||||||
|
## Pendências imediatas (próxima sessão)
|
||||||
|
1. Verificar resultado de tests/hooks.test.ts — meta: 100%
|
||||||
|
2. Corrigir useInterval/useClipboard se ainda falharem (setTimeout no jsdom com fakeTimers)
|
||||||
|
3. Rodar todos os testes (`npx vitest run`) e garantir suite verde
|
||||||
|
4. FIX Input.tsx (RGBA recursion error) + FIX useOnline.ts
|
||||||
|
5. Commit hooks + CI + docs — tem MUITO para commitar
|
||||||
|
6. GitHub remote + push (bloqueado por disco cheio)
|
||||||
|
7. gh CLI + Dependabot + Security workflow
|
||||||
|
8. npm publish workflow
|
||||||
|
9. Composables Vue 3
|
||||||
|
10. CRUD API Fastify (próximo projeto)
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
- Disco still 100% cheio (63G total, 62G usado)
|
||||||
|
- write tool em flush mode só permite memory/ — usar shell para atualizar arquivos de projeto
|
||||||
|
- UTF-8 encoding ok em todos os arquivos .md
|
||||||
+176
@@ -289,6 +289,16 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||||
|
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
@@ -655,6 +665,98 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/dom": {
|
||||||
|
"version": "10.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||||
|
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/aria-query": "^5.0.1",
|
||||||
|
"aria-query": "5.3.0",
|
||||||
|
"dom-accessibility-api": "^0.5.9",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
|
"picocolors": "1.1.1",
|
||||||
|
"pretty-format": "^27.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/pretty-format": {
|
||||||
|
"version": "27.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||||
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1",
|
||||||
|
"ansi-styles": "^5.0.0",
|
||||||
|
"react-is": "^17.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/react-is": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/react": {
|
||||||
|
"version": "16.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
|
||||||
|
"integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@testing-library/dom": "^10.0.0",
|
||||||
|
"@types/react": "^18.0.0 || ^19.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/aria-query": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -1071,6 +1173,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aria-query": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/assertion-error": {
|
"node_modules/assertion-error": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||||
@@ -1403,6 +1516,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dequal": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/diff-sequences": {
|
"node_modules/diff-sequences": {
|
||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||||
@@ -1413,6 +1537,14 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-accessibility-api": {
|
||||||
|
"version": "0.5.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -1943,6 +2075,17 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lz-string": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"lz-string": "bin/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -2381,6 +2524,31 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||||
|
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||||
|
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.27.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@@ -2490,6 +2658,14 @@
|
|||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":"1.6.1","results":[[":tests/utils.test.ts",{"duration":390,"failed":false}],[":tests/validators.test.ts",{"duration":89,"failed":false}]]}
|
{"version":"1.6.1","results":[[":tests/hooks.test.ts",{"duration":1236,"failed":false}]]}
|
||||||
+177
@@ -12,6 +12,7 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.0",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
@@ -325,6 +326,16 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||||
|
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
@@ -1488,6 +1499,98 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/dom": {
|
||||||
|
"version": "10.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||||
|
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/aria-query": "^5.0.1",
|
||||||
|
"aria-query": "5.3.0",
|
||||||
|
"dom-accessibility-api": "^0.5.9",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
|
"picocolors": "1.1.1",
|
||||||
|
"pretty-format": "^27.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/pretty-format": {
|
||||||
|
"version": "27.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||||
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1",
|
||||||
|
"ansi-styles": "^5.0.0",
|
||||||
|
"react-is": "^17.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/react-is": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/react": {
|
||||||
|
"version": "16.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
|
||||||
|
"integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@testing-library/dom": "^10.0.0",
|
||||||
|
"@types/react": "^18.0.0 || ^19.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/aria-query": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -1904,6 +2007,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aria-query": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/assertion-error": {
|
"node_modules/assertion-error": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||||
@@ -2236,6 +2350,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dequal": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/diff-sequences": {
|
"node_modules/diff-sequences": {
|
||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||||
@@ -2246,6 +2371,14 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-accessibility-api": {
|
||||||
|
"version": "0.5.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -2791,6 +2924,17 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lz-string": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"lz-string": "bin/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -3229,6 +3373,31 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||||
|
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||||
|
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.27.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@@ -3338,6 +3507,14 @@
|
|||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.0",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* @pulse-libs/core — Hooks Tests (clean suite)
|
||||||
|
* @vitest-environment jsdom */
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||||
|
import {
|
||||||
|
useToggle, useAsync, useDebounce, useLocalStorage,
|
||||||
|
useMedia, useInterval, useClipboard, useFetch,
|
||||||
|
} from '../src/hooks/index';
|
||||||
|
|
||||||
|
const originalFetch = global.fetch;
|
||||||
|
|
||||||
|
// ─ mocks globais ───────────────────────────────────────────────────
|
||||||
|
const lsStore: Record<string,string> = {};
|
||||||
|
Object.defineProperty(global, 'localStorage', {
|
||||||
|
value: {
|
||||||
|
getItem: k => lsStore[k] ?? null,
|
||||||
|
setItem: (k,v) => { lsStore[k] = v; },
|
||||||
|
removeItem: k => { delete lsStore[k]; },
|
||||||
|
clear: () => { Object.keys(lsStore).forEach(k => delete lsStore[k]); },
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(global.navigator, 'clipboard', {
|
||||||
|
value: { writeText: vi.fn().mockResolvedValue(undefined) },
|
||||||
|
configurable: true, writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(global, 'matchMedia', {
|
||||||
|
value: vi.fn().mockReturnValue({
|
||||||
|
matches: false, media: '',
|
||||||
|
addEventListener: vi.fn(), removeEventListener: vi.fn(),
|
||||||
|
addListener: vi.fn(), removeListener: vi.fn(),
|
||||||
|
}),
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => { lsStore[''] = ''; Object.keys(lsStore).forEach(k => delete lsStore[k]); });
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useToggle', () => {
|
||||||
|
it('inicia false por default', () => {
|
||||||
|
const { result } = renderHook(() => useToggle());
|
||||||
|
expect(result.current[0]).toBe(false);
|
||||||
|
});
|
||||||
|
it('inicia true se passado', () => {
|
||||||
|
const { result } = renderHook(() => useToggle(true));
|
||||||
|
expect(result.current[0]).toBe(true);
|
||||||
|
});
|
||||||
|
it('alterna com toggle()', () => {
|
||||||
|
const { result } = renderHook(() => useToggle(false));
|
||||||
|
act(() => result.current[1]());
|
||||||
|
expect(result.current[0]).toBe(true);
|
||||||
|
act(() => result.current[1]());
|
||||||
|
expect(result.current[0]).toBe(false);
|
||||||
|
});
|
||||||
|
it('força valor com set()', () => {
|
||||||
|
const { result } = renderHook(() => useToggle(false));
|
||||||
|
act(() => result.current[2](true));
|
||||||
|
expect(result.current[0]).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useAsync', () => {
|
||||||
|
afterEach(() => { global.fetch = originalFetch; });
|
||||||
|
|
||||||
|
it('começa em idling e depois transiciona', async () => {
|
||||||
|
const fn = vi.fn(async () => Promise.resolve('ok'));
|
||||||
|
const { result } = renderHook(() => useAsync(fn, []));
|
||||||
|
|
||||||
|
// Imediatamente após renderHook: efeitos ainda não rodaram (microtask)
|
||||||
|
// Espera um ciclo de event loop
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(['idle','loading','success','error']).toContain(result.current.status);
|
||||||
|
});
|
||||||
|
// Com sucesso, deve estar em success
|
||||||
|
await waitFor(() => expect(result.current.status).toBe('success'), { timeout: 3000 });
|
||||||
|
if ('data' in result.current) expect(result.current.data).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('vai para error se promise rejeita', async () => {
|
||||||
|
const { result } = renderHook(() => useAsync(async () => { throw new Error('x'); }, []));
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 3000 });
|
||||||
|
if ('error' in result.current) expect(result.current.error).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-executa só quando deps mudam', async () => {
|
||||||
|
const fn = vi.fn(async () => 'v');
|
||||||
|
const { rerender } = renderHook(
|
||||||
|
({ d }) => useAsync(fn, d),
|
||||||
|
{ initialProps: { d: ['a'] as string[] } }
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(fn).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
||||||
|
rerender({ d: ['b'] as string[] });
|
||||||
|
await waitFor(() => expect(fn).toHaveBeenCalledTimes(2), { timeout: 3000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useDebounce', () => {
|
||||||
|
it('retorna valor inicial', () => {
|
||||||
|
const { result } = renderHook(() => useDebounce('x', 200));
|
||||||
|
expect(result.current).toBe('x');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('atrasa atualização até o timer', async () => {
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
({ v }) => useDebounce(v, 200),
|
||||||
|
{ initialProps: { v: 'a' } }
|
||||||
|
);
|
||||||
|
rerender({ v: 'b' });
|
||||||
|
expect(result.current).toBe('a');
|
||||||
|
await act(async () => { await new Promise(r => setTimeout(r, 300)); });
|
||||||
|
expect(result.current).toBe('b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('flushes última mudança em rápidas trocas', async () => {
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
({ v }) => useDebounce(v, 200),
|
||||||
|
{ initialProps: { v: 1 } }
|
||||||
|
);
|
||||||
|
rerender({ v: 2 }); rerender({ v: 3 }); rerender({ v: 4 });
|
||||||
|
await act(async () => { await new Promise(r => setTimeout(r, 300)); });
|
||||||
|
expect(result.current).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useLocalStorage', () => {
|
||||||
|
it('retorna tupla [valor, setter] corretamente', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage('k1', 'hello' as string) as any
|
||||||
|
);
|
||||||
|
expect(Array.isArray(result.current)).toBe(true);
|
||||||
|
expect(result.current[0]).toBe('hello');
|
||||||
|
expect(typeof result.current[1]).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('salva e lê valor', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage('k2', 'init' as string) as any
|
||||||
|
);
|
||||||
|
act(() => result.current[1]('novo'));
|
||||||
|
expect(result.current[0]).toBe('novo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('persiste em sessões', () => {
|
||||||
|
lsStore['k3'] = '"saved"';
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage('k3', 'def' as string) as any
|
||||||
|
);
|
||||||
|
expect(result.current[0]).toBe('saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('aceita updater fn', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage('k4', 0 as number) as any
|
||||||
|
);
|
||||||
|
act(() => result.current[1]((n: number) => n + 1));
|
||||||
|
expect(result.current[0]).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useMedia', () => {
|
||||||
|
it('retorna booleano', () => {
|
||||||
|
const { result } = renderHook(() => useMedia('screen'));
|
||||||
|
expect(typeof result.current).toBe('boolean');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useInterval', () => {
|
||||||
|
it('não executa quando ms é null', () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
renderHook(() => useInterval(fn, null));
|
||||||
|
expect(fn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('executa imediatamente com immediate=true', () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
renderHook(() => useInterval(fn, 1000, true));
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useClipboard', () => {
|
||||||
|
it('inicia copied=false', () => {
|
||||||
|
const { result } = renderHook(() => useClipboard(2000));
|
||||||
|
expect(result.current.copied).toBe(false);
|
||||||
|
});
|
||||||
|
it('copia texto com sucesso', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard(0));
|
||||||
|
const ok = await act(() => result.current.copy('hello'));
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(result.current.copied).toBe(true);
|
||||||
|
});
|
||||||
|
it('retorna false em erro', async () => {
|
||||||
|
(global.navigator.clipboard.writeText as any).mockRejectedValue(new Error('denied'));
|
||||||
|
const { result } = renderHook(() => useClipboard(0));
|
||||||
|
const ok = await act(() => result.current.copy('falha'));
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════
|
||||||
|
describe('useFetch', () => {
|
||||||
|
afterEach(() => { global.fetch = originalFetch; });
|
||||||
|
|
||||||
|
const r200 = (body: unknown) =>
|
||||||
|
new Response(JSON.stringify(body), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||||
|
|
||||||
|
it('retorna success para HTTP 200', async () => {
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(r200({ ok: true }))) as any;
|
||||||
|
const { result } = renderHook(() => useFetch('/api', {}) as any);
|
||||||
|
await waitFor(() => expect(result.current.status).toBe('success'), { timeout: 4000 });
|
||||||
|
expect((result.current as any).data.ok).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retorna error para HTTP 404', async () => {
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(
|
||||||
|
new Response('nf', { status: 404, headers: { 'Content-Type': 'application/json' } }))) as any;
|
||||||
|
const { result } = renderHook(() => useFetch('/404', {}) as any);
|
||||||
|
await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 4000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retorna error em falha de rede', async () => {
|
||||||
|
global.fetch = vi.fn(() => Promise.reject(new TypeError('net'))) as any;
|
||||||
|
const { result } = renderHook(() => useFetch('/fail', {}) as any);
|
||||||
|
await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 4000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
"registry": "https://clawhub.ai",
|
"registry": "https://clawhub.ai",
|
||||||
"slug": "agent-browser-clawdbot",
|
"slug": "agent-browser-clawdbot",
|
||||||
"installedVersion": "0.1.0",
|
"installedVersion": "0.1.0",
|
||||||
"installedAt": 1779241639613
|
"installedAt": 1779243267499
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"registry": "https://clawhub.ai",
|
"registry": "https://clawhub.ai",
|
||||||
"slug": "multi-search-engine-2-0-1",
|
"slug": "multi-search-engine-2-0-1",
|
||||||
"installedVersion": "1.0.0",
|
"installedVersion": "1.0.0",
|
||||||
"installedAt": 1779241678486
|
"installedAt": 1779243337647
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"registry": "https://clawhub.ai",
|
"registry": "https://clawhub.ai",
|
||||||
"slug": "redis-labs-integration",
|
"slug": "redis-labs-integration",
|
||||||
"installedVersion": "1.0.2",
|
"installedVersion": "1.0.2",
|
||||||
"installedAt": 1779241689038
|
"installedAt": 1779243351883
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"registry": "https://clawhub.ai",
|
"registry": "https://clawhub.ai",
|
||||||
"slug": "self-improvement",
|
"slug": "self-improvement",
|
||||||
"installedVersion": "1.0.0",
|
"installedVersion": "1.0.0",
|
||||||
"installedAt": 1779241666977
|
"installedAt": 1779243314748
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"registry": "https://clawhub.ai",
|
"registry": "https://clawhub.ai",
|
||||||
"slug": "vision",
|
"slug": "vision",
|
||||||
"installedVersion": "3.5.0",
|
"installedVersion": "3.5.0",
|
||||||
"installedAt": 1779241651452
|
"installedAt": 1779243291577
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user