@hailer/mcp 1.1.12 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -7
- package/{.claude → dist}/CLAUDE.md +2 -2
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +10 -1
- package/dist/bot/bot-config.js +64 -3
- package/dist/bot/bot-manager.d.ts +2 -0
- package/dist/bot/bot-manager.js +9 -2
- package/dist/bot/bot.d.ts +33 -0
- package/dist/bot/bot.js +461 -160
- package/dist/bot/services/message-classifier.js +17 -0
- package/dist/bot/services/permission-guard.d.ts +52 -0
- package/dist/bot/services/permission-guard.js +149 -0
- package/dist/bot/services/types.d.ts +5 -0
- package/dist/bot/services/typing-indicator.d.ts +6 -1
- package/dist/bot/services/typing-indicator.js +19 -3
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +51 -19
- package/dist/mcp/hailer-clients.d.ts +19 -1
- package/dist/mcp/hailer-clients.js +158 -24
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +2 -0
- package/dist/mcp/tool-registry.d.ts +17 -4
- package/dist/mcp/tool-registry.js +37 -7
- package/dist/mcp/tools/activity.js +99 -7
- package/dist/mcp/tools/app-scaffold.js +304 -336
- package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- package/dist/mcp/tools/company.d.ts +9 -0
- package/dist/mcp/tools/company.js +88 -0
- package/dist/mcp/tools/discussion.js +68 -0
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
- package/dist/mcp/tools/workflow-permissions.js +204 -0
- package/dist/mcp/tools/workflow.js +57 -18
- package/dist/mcp/utils/index.d.ts +2 -0
- package/dist/mcp/utils/index.js +12 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +43 -1
- package/dist/mcp/utils/types.js +14 -0
- package/dist/mcp/webhook-handler.d.ts +4 -0
- package/dist/mcp/webhook-handler.js +8 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -127
- package/dist/plugins/vipunen/client.d.ts +150 -0
- package/dist/plugins/vipunen/client.js +535 -0
- package/dist/plugins/vipunen/config/schema-config.json +19 -0
- package/dist/plugins/vipunen/config/schema-doc.json +22 -0
- package/dist/plugins/vipunen/index.d.ts +41 -0
- package/dist/plugins/vipunen/index.js +88 -0
- package/dist/plugins/vipunen/tools.d.ts +26 -0
- package/dist/plugins/vipunen/tools.js +501 -0
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +2 -1
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/commands/app-squad.md +0 -135
- package/.claude/commands/audit-squad.md +0 -158
- package/.claude/commands/autoplan.md +0 -563
- package/.claude/commands/cleanup-squad.md +0 -98
- package/.claude/commands/config-squad.md +0 -106
- package/.claude/commands/crud-squad.md +0 -87
- package/.claude/commands/data-squad.md +0 -97
- package/.claude/commands/debug-squad.md +0 -303
- package/.claude/commands/doc-squad.md +0 -65
- package/.claude/commands/handoff.md +0 -137
- package/.claude/commands/health.md +0 -49
- package/.claude/commands/help.md +0 -29
- package/.claude/commands/help:agents.md +0 -151
- package/.claude/commands/help:commands.md +0 -78
- package/.claude/commands/help:faq.md +0 -79
- package/.claude/commands/help:plugins.md +0 -50
- package/.claude/commands/help:skills.md +0 -93
- package/.claude/commands/help:tools.md +0 -75
- package/.claude/commands/hotfix-squad.md +0 -112
- package/.claude/commands/integration-squad.md +0 -82
- package/.claude/commands/janitor-squad.md +0 -167
- package/.claude/commands/learn-auto.md +0 -120
- package/.claude/commands/learn.md +0 -120
- package/.claude/commands/mcp-list.md +0 -27
- package/.claude/commands/onboard-squad.md +0 -140
- package/.claude/commands/plan-workspace.md +0 -732
- package/.claude/commands/prd.md +0 -130
- package/.claude/commands/project-status.md +0 -82
- package/.claude/commands/publish.md +0 -138
- package/.claude/commands/recap.md +0 -69
- package/.claude/commands/restore.md +0 -64
- package/.claude/commands/review-squad.md +0 -152
- package/.claude/commands/save.md +0 -24
- package/.claude/commands/stats.md +0 -19
- package/.claude/commands/swarm.md +0 -210
- package/.claude/commands/tool-builder.md +0 -39
- package/.claude/commands/ws-pull.md +0 -44
- package/.claude/hooks/_shared-memory.cjs +0 -305
- package/.claude/hooks/_utils.cjs +0 -108
- package/.claude/hooks/agent-failure-detector.cjs +0 -383
- package/.claude/hooks/agent-usage-logger.cjs +0 -204
- package/.claude/hooks/app-edit-guard.cjs +0 -494
- package/.claude/hooks/auto-learn.cjs +0 -304
- package/.claude/hooks/bash-guard.cjs +0 -272
- package/.claude/hooks/builder-mode-manager.cjs +0 -354
- package/.claude/hooks/bulk-activity-guard.cjs +0 -271
- package/.claude/hooks/context-watchdog.cjs +0 -230
- package/.claude/hooks/delegation-reminder.cjs +0 -465
- package/.claude/hooks/design-system-lint.cjs +0 -271
- package/.claude/hooks/post-scaffold-hook.cjs +0 -181
- package/.claude/hooks/prompt-guard.cjs +0 -354
- package/.claude/hooks/publish-template-guard.cjs +0 -147
- package/.claude/hooks/session-start.cjs +0 -35
- package/.claude/hooks/shared-memory-writer.cjs +0 -147
- package/.claude/hooks/skill-injector.cjs +0 -140
- package/.claude/hooks/skill-usage-logger.cjs +0 -258
- package/.claude/hooks/src-edit-guard.cjs +0 -240
- package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
- package/.claude/settings.json +0 -257
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
- package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
- package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-api-client/SKILL.md +0 -518
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
- package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
- package/.claude/skills/hailer-design-system/SKILL.md +0 -235
- package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
- package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/integration-patterns/SKILL.md +0 -421
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/lsp-setup/SKILL.md +0 -160
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
- package/.claude/skills/testing-patterns/SKILL.md +0 -630
- package/.claude/skills/tool-builder/SKILL.md +0 -250
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
- package/.mcp.json +0 -13
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -203
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
- package/.opencode/opencode.json +0 -28
- package/SESSION-HANDOFF.md +0 -68
- package/inbox/2026-03-04-bot-config-patterns.md +0 -24
- package/scripts/postinstall.cjs +0 -64
- package/scripts/test-hal-tools.ts +0 -154
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: testing-patterns
|
|
3
|
-
description: Testing patterns for Hailer projects - vitest for function fields, playwright for apps, build verification
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
triggers: Run tests, vitest, playwright, test failure, build error, coverage
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Testing Patterns
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
| Project Type | Test Framework | Location |
|
|
13
|
-
|--------------|---------------|----------|
|
|
14
|
-
| SDK (function fields) | Vitest | `workspace/[Workflow]/main.test.ts` |
|
|
15
|
-
| Apps (E2E) | Playwright | `apps/[app]/test/` or `test/` |
|
|
16
|
-
| Apps (unit) | Vitest | `apps/[app]/src/**/*.test.ts` |
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Vitest - Function Field Tests
|
|
21
|
-
|
|
22
|
-
### Running Tests
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# Run all tests
|
|
26
|
-
npm test
|
|
27
|
-
|
|
28
|
-
# Watch mode (re-run on changes)
|
|
29
|
-
npm run test:watch
|
|
30
|
-
|
|
31
|
-
# Interactive UI
|
|
32
|
-
npm run test:ui
|
|
33
|
-
|
|
34
|
-
# Specific file
|
|
35
|
-
npm test -- workspace/Orders_abc/main.test.ts
|
|
36
|
-
|
|
37
|
-
# Specific test by name
|
|
38
|
-
npm test -- -t "calculates total"
|
|
39
|
-
|
|
40
|
-
# With coverage
|
|
41
|
-
npm test -- --coverage
|
|
42
|
-
|
|
43
|
-
# Verbose output
|
|
44
|
-
npm test -- --reporter=verbose
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Test File Structure
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
// workspace/Orders_abc/main.test.ts
|
|
51
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
52
|
-
import { total_cost_abc } from './functions/total_cost_abc';
|
|
53
|
-
import { discount_def } from './functions/discount_def';
|
|
54
|
-
|
|
55
|
-
describe('total_cost_abc', () => {
|
|
56
|
-
describe('basic calculations', () => {
|
|
57
|
-
it('multiplies quantity by unit price', () => {
|
|
58
|
-
const result = total_cost_abc({
|
|
59
|
-
quantity: 10,
|
|
60
|
-
unitPrice: 25.50
|
|
61
|
-
});
|
|
62
|
-
expect(result).toBe(255);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('returns 0 for zero quantity', () => {
|
|
66
|
-
const result = total_cost_abc({
|
|
67
|
-
quantity: 0,
|
|
68
|
-
unitPrice: 100
|
|
69
|
-
});
|
|
70
|
-
expect(result).toBe(0);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('null handling', () => {
|
|
75
|
-
it('handles null quantity', () => {
|
|
76
|
-
const result = total_cost_abc({
|
|
77
|
-
quantity: null,
|
|
78
|
-
unitPrice: 50
|
|
79
|
-
});
|
|
80
|
-
expect(result).toBe(0);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('handles undefined unit price', () => {
|
|
84
|
-
const result = total_cost_abc({
|
|
85
|
-
quantity: 10,
|
|
86
|
-
unitPrice: undefined
|
|
87
|
-
});
|
|
88
|
-
expect(result).toBe(0);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('handles both null', () => {
|
|
92
|
-
const result = total_cost_abc({
|
|
93
|
-
quantity: null,
|
|
94
|
-
unitPrice: null
|
|
95
|
-
});
|
|
96
|
-
expect(result).toBe(0);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('edge cases', () => {
|
|
101
|
-
it('handles string numbers', () => {
|
|
102
|
-
const result = total_cost_abc({
|
|
103
|
-
quantity: '10',
|
|
104
|
-
unitPrice: '5'
|
|
105
|
-
} as any);
|
|
106
|
-
expect(result).toBe(50);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('handles negative values', () => {
|
|
110
|
-
const result = total_cost_abc({
|
|
111
|
-
quantity: -5,
|
|
112
|
-
unitPrice: 10
|
|
113
|
-
});
|
|
114
|
-
expect(result).toBe(-50);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Testing Backlink Aggregations
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
describe('total_from_invoices_abc', () => {
|
|
124
|
-
it('sums array of invoice amounts', () => {
|
|
125
|
-
const result = total_from_invoices_abc({
|
|
126
|
-
invoiceAmounts: [100, 200, 300]
|
|
127
|
-
});
|
|
128
|
-
expect(result).toBe(600);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('handles empty array', () => {
|
|
132
|
-
const result = total_from_invoices_abc({
|
|
133
|
-
invoiceAmounts: []
|
|
134
|
-
});
|
|
135
|
-
expect(result).toBe(0);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('handles null values in array', () => {
|
|
139
|
-
const result = total_from_invoices_abc({
|
|
140
|
-
invoiceAmounts: [100, null, 200, undefined, 300]
|
|
141
|
-
});
|
|
142
|
-
expect(result).toBe(600);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('handles undefined array', () => {
|
|
146
|
-
const result = total_from_invoices_abc({
|
|
147
|
-
invoiceAmounts: undefined
|
|
148
|
-
});
|
|
149
|
-
expect(result).toBe(0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Testing Parallel Arrays
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
describe('weighted_average_abc', () => {
|
|
158
|
-
it('calculates weighted average from parallel arrays', () => {
|
|
159
|
-
// Arrays from same source workflow are guaranteed same length
|
|
160
|
-
const result = weighted_average_abc({
|
|
161
|
-
values: [80, 90, 70],
|
|
162
|
-
weights: [1, 2, 1]
|
|
163
|
-
});
|
|
164
|
-
// (80*1 + 90*2 + 70*1) / (1+2+1) = 330/4 = 82.5
|
|
165
|
-
expect(result).toBe(82.5);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('handles null values at same index', () => {
|
|
169
|
-
const result = weighted_average_abc({
|
|
170
|
-
values: [80, null, 70],
|
|
171
|
-
weights: [1, 2, 1] // Weight at index 1 should be ignored
|
|
172
|
-
});
|
|
173
|
-
// Should skip index 1 entirely
|
|
174
|
-
expect(result).toBe(75); // (80*1 + 70*1) / 2
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Testing JSON Output Functions
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
describe('line_items_json_abc', () => {
|
|
183
|
-
it('returns valid JSON string', () => {
|
|
184
|
-
const result = line_items_json_abc({
|
|
185
|
-
names: ['Item A', 'Item B'],
|
|
186
|
-
quantities: [2, 3],
|
|
187
|
-
prices: [10, 20]
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const parsed = JSON.parse(result);
|
|
191
|
-
expect(parsed).toEqual([
|
|
192
|
-
{ name: 'Item A', quantity: 2, price: 10 },
|
|
193
|
-
{ name: 'Item B', quantity: 3, price: 20 }
|
|
194
|
-
]);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('returns empty array JSON for no items', () => {
|
|
198
|
-
const result = line_items_json_abc({
|
|
199
|
-
names: [],
|
|
200
|
-
quantities: [],
|
|
201
|
-
prices: []
|
|
202
|
-
});
|
|
203
|
-
expect(result).toBe('[]');
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Playwright - App E2E Tests
|
|
211
|
-
|
|
212
|
-
### Running Tests
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
# Run all tests
|
|
216
|
-
npm run test
|
|
217
|
-
|
|
218
|
-
# With UI
|
|
219
|
-
npm run test:open
|
|
220
|
-
|
|
221
|
-
# Headed (see browser)
|
|
222
|
-
npm run test -- --headed
|
|
223
|
-
|
|
224
|
-
# Specific file
|
|
225
|
-
npm run test -- test/suites/dashboard.spec.ts
|
|
226
|
-
|
|
227
|
-
# Specific test
|
|
228
|
-
npm run test -- -g "displays activity count"
|
|
229
|
-
|
|
230
|
-
# Debug mode
|
|
231
|
-
npm run test -- --debug
|
|
232
|
-
|
|
233
|
-
# Generate report
|
|
234
|
-
npm run test -- --reporter=html
|
|
235
|
-
npx playwright show-report
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Test File Structure
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
// test/suites/dashboard.spec.ts
|
|
242
|
-
import { test, expect } from '@playwright/test';
|
|
243
|
-
import { login, setupTestData } from '../helpers/setup';
|
|
244
|
-
|
|
245
|
-
test.describe('Dashboard', () => {
|
|
246
|
-
test.beforeEach(async ({ page }) => {
|
|
247
|
-
await login(page);
|
|
248
|
-
await page.goto('/');
|
|
249
|
-
await page.waitForSelector('[data-testid="dashboard"]');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('displays activity count', async ({ page }) => {
|
|
253
|
-
const count = page.locator('[data-testid="activity-count"]');
|
|
254
|
-
await expect(count).toBeVisible();
|
|
255
|
-
await expect(count).toHaveText(/\d+/);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('shows workflow list', async ({ page }) => {
|
|
259
|
-
const workflows = page.locator('[data-testid="workflow-item"]');
|
|
260
|
-
await expect(workflows).toHaveCount.greaterThan(0);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test('navigates to workflow on click', async ({ page }) => {
|
|
264
|
-
await page.click('[data-testid="workflow-item"]:first-child');
|
|
265
|
-
await expect(page).toHaveURL(/\/workflow\//);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Page Object Pattern
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
// test/locators/Dashboard.ts
|
|
274
|
-
import { Page, Locator } from '@playwright/test';
|
|
275
|
-
|
|
276
|
-
export class DashboardPage {
|
|
277
|
-
readonly page: Page;
|
|
278
|
-
readonly activityCount: Locator;
|
|
279
|
-
readonly workflowList: Locator;
|
|
280
|
-
readonly searchInput: Locator;
|
|
281
|
-
|
|
282
|
-
constructor(page: Page) {
|
|
283
|
-
this.page = page;
|
|
284
|
-
this.activityCount = page.locator('[data-testid="activity-count"]');
|
|
285
|
-
this.workflowList = page.locator('[data-testid="workflow-list"]');
|
|
286
|
-
this.searchInput = page.locator('[data-testid="search-input"]');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async goto() {
|
|
290
|
-
await this.page.goto('/');
|
|
291
|
-
await this.page.waitForSelector('[data-testid="dashboard"]');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async search(query: string) {
|
|
295
|
-
await this.searchInput.fill(query);
|
|
296
|
-
await this.searchInput.press('Enter');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async getWorkflowCount(): Promise<number> {
|
|
300
|
-
return this.workflowList.locator('[data-testid="workflow-item"]').count();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Usage in test
|
|
305
|
-
test('search filters workflows', async ({ page }) => {
|
|
306
|
-
const dashboard = new DashboardPage(page);
|
|
307
|
-
await dashboard.goto();
|
|
308
|
-
|
|
309
|
-
const initialCount = await dashboard.getWorkflowCount();
|
|
310
|
-
await dashboard.search('Orders');
|
|
311
|
-
|
|
312
|
-
const filteredCount = await dashboard.getWorkflowCount();
|
|
313
|
-
expect(filteredCount).toBeLessThanOrEqual(initialCount);
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Test Helpers
|
|
318
|
-
|
|
319
|
-
```typescript
|
|
320
|
-
// test/helpers/setup.ts
|
|
321
|
-
import { Page } from '@playwright/test';
|
|
322
|
-
|
|
323
|
-
export async function login(page: Page) {
|
|
324
|
-
await page.goto('/login');
|
|
325
|
-
await page.fill('[data-testid="email"]', process.env.TEST_EMAIL!);
|
|
326
|
-
await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD!);
|
|
327
|
-
await page.click('[data-testid="login-button"]');
|
|
328
|
-
await page.waitForURL('/');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export async function createTestActivity(page: Page, workflowId: string, data: any) {
|
|
332
|
-
// Create via API for faster setup
|
|
333
|
-
const response = await page.request.post('/api/activities', {
|
|
334
|
-
data: { workflowId, ...data }
|
|
335
|
-
});
|
|
336
|
-
return response.json();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export async function cleanupTestData(page: Page, activityIds: string[]) {
|
|
340
|
-
for (const id of activityIds) {
|
|
341
|
-
await page.request.delete(`/api/activities/${id}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
## Build Verification
|
|
349
|
-
|
|
350
|
-
### Commands
|
|
351
|
-
|
|
352
|
-
```bash
|
|
353
|
-
# TypeScript compilation
|
|
354
|
-
npm run build
|
|
355
|
-
|
|
356
|
-
# Type check only (no output)
|
|
357
|
-
npx tsc --noEmit
|
|
358
|
-
|
|
359
|
-
# ESLint
|
|
360
|
-
npm run lint
|
|
361
|
-
npm run lint -- --fix
|
|
362
|
-
|
|
363
|
-
# All checks
|
|
364
|
-
npm run build && npm run lint && npm test
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### Common Build Errors
|
|
368
|
-
|
|
369
|
-
**Cannot find module**
|
|
370
|
-
```
|
|
371
|
-
error TS2307: Cannot find module './utils' or its corresponding type declarations.
|
|
372
|
-
```
|
|
373
|
-
Fix: Check import path, ensure file exists, run `npm install`
|
|
374
|
-
|
|
375
|
-
**Type mismatch**
|
|
376
|
-
```
|
|
377
|
-
error TS2322: Type 'string' is not assignable to type 'number'.
|
|
378
|
-
```
|
|
379
|
-
Fix: Check variable types, add type assertion or fix logic
|
|
380
|
-
|
|
381
|
-
**Missing property**
|
|
382
|
-
```
|
|
383
|
-
error TS2339: Property 'value' does not exist on type 'Field'.
|
|
384
|
-
```
|
|
385
|
-
Fix: Add optional chaining (`?.`) or check type definition
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
## Common Test Failures & Fixes
|
|
390
|
-
|
|
391
|
-
### Vitest Failures
|
|
392
|
-
|
|
393
|
-
**TypeError: Cannot read property of undefined**
|
|
394
|
-
```typescript
|
|
395
|
-
// Problem
|
|
396
|
-
const total = dep.quantity * dep.price;
|
|
397
|
-
|
|
398
|
-
// Fix
|
|
399
|
-
const qty = Number(dep.quantity) || 0;
|
|
400
|
-
const price = Number(dep.price) || 0;
|
|
401
|
-
const total = qty * price;
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
**Expected X but received Y**
|
|
405
|
-
```typescript
|
|
406
|
-
// Check calculation logic
|
|
407
|
-
// Check return type (string vs number)
|
|
408
|
-
// Check floating point: use toBeCloseTo() for decimals
|
|
409
|
-
expect(result).toBeCloseTo(expected, 2);
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**Test timeout**
|
|
413
|
-
```typescript
|
|
414
|
-
// Increase timeout for slow tests
|
|
415
|
-
test('slow operation', async () => {
|
|
416
|
-
// ...
|
|
417
|
-
}, 30000); // 30 second timeout
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### Playwright Failures
|
|
421
|
-
|
|
422
|
-
**Timeout waiting for selector**
|
|
423
|
-
```typescript
|
|
424
|
-
// Problem
|
|
425
|
-
await page.click('[data-testid="submit"]');
|
|
426
|
-
|
|
427
|
-
// Fix - wait first
|
|
428
|
-
await page.waitForSelector('[data-testid="submit"]');
|
|
429
|
-
await page.click('[data-testid="submit"]');
|
|
430
|
-
|
|
431
|
-
// Or use auto-waiting locator
|
|
432
|
-
await page.locator('[data-testid="submit"]').click();
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**Element not visible**
|
|
436
|
-
```typescript
|
|
437
|
-
// Scroll into view
|
|
438
|
-
await page.locator('[data-testid="element"]').scrollIntoViewIfNeeded();
|
|
439
|
-
await page.click('[data-testid="element"]');
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
**Navigation timeout**
|
|
443
|
-
```typescript
|
|
444
|
-
// Increase navigation timeout
|
|
445
|
-
await page.goto('/', { timeout: 60000 });
|
|
446
|
-
|
|
447
|
-
// Or wait for specific element instead
|
|
448
|
-
await page.goto('/');
|
|
449
|
-
await page.waitForSelector('[data-testid="loaded"]', { timeout: 60000 });
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Output Parsing
|
|
455
|
-
|
|
456
|
-
### Vitest Output
|
|
457
|
-
|
|
458
|
-
```
|
|
459
|
-
✓ workspace/Orders_abc/main.test.ts (8)
|
|
460
|
-
✓ total_cost_abc (4)
|
|
461
|
-
✓ multiplies quantity by price
|
|
462
|
-
✓ handles null quantity
|
|
463
|
-
✓ handles undefined price
|
|
464
|
-
✓ handles string numbers
|
|
465
|
-
✗ discount_abc (4)
|
|
466
|
-
✓ applies percentage discount
|
|
467
|
-
✓ applies fixed discount
|
|
468
|
-
✗ handles negative discount
|
|
469
|
-
AssertionError: expected 100 to be 0
|
|
470
|
-
✓ caps at total amount
|
|
471
|
-
|
|
472
|
-
Test Files 1 passed (1)
|
|
473
|
-
Tests 7 passed | 1 failed (8)
|
|
474
|
-
Time 1.23s
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
**Extract:**
|
|
478
|
-
- Total: 8
|
|
479
|
-
- Passed: 7
|
|
480
|
-
- Failed: 1
|
|
481
|
-
- Failed test: "discount_abc > handles negative discount"
|
|
482
|
-
- Error: "expected 100 to be 0"
|
|
483
|
-
|
|
484
|
-
### Playwright Output
|
|
485
|
-
|
|
486
|
-
```
|
|
487
|
-
Running 12 tests using 4 workers
|
|
488
|
-
|
|
489
|
-
✓ 1 login.spec.ts:5:1 › Login › shows form (1.2s)
|
|
490
|
-
✓ 2 login.spec.ts:15:1 › Login › logs in (2.3s)
|
|
491
|
-
✗ 3 dashboard.spec.ts:8:1 › Dashboard › shows count (10.0s)
|
|
492
|
-
|
|
493
|
-
1 failed
|
|
494
|
-
dashboard.spec.ts:8:1 › Dashboard › shows count
|
|
495
|
-
Timeout of 10000ms exceeded.
|
|
496
|
-
waiting for locator('[data-testid="activity-count"]')
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
**Extract:**
|
|
500
|
-
- Total: 12
|
|
501
|
-
- Passed: 2 (shown)
|
|
502
|
-
- Failed: 1
|
|
503
|
-
- Failed: "Dashboard > shows count"
|
|
504
|
-
- Error: Timeout waiting for selector
|
|
505
|
-
|
|
506
|
-
---
|
|
507
|
-
|
|
508
|
-
## CI/CD Integration
|
|
509
|
-
|
|
510
|
-
### GitHub Actions Example
|
|
511
|
-
|
|
512
|
-
```yaml
|
|
513
|
-
name: Tests
|
|
514
|
-
on: [push, pull_request]
|
|
515
|
-
|
|
516
|
-
jobs:
|
|
517
|
-
test:
|
|
518
|
-
runs-on: ubuntu-latest
|
|
519
|
-
steps:
|
|
520
|
-
- uses: actions/checkout@v4
|
|
521
|
-
|
|
522
|
-
- name: Setup Node
|
|
523
|
-
uses: actions/setup-node@v4
|
|
524
|
-
with:
|
|
525
|
-
node-version: '20'
|
|
526
|
-
cache: 'npm'
|
|
527
|
-
|
|
528
|
-
- name: Install dependencies
|
|
529
|
-
run: npm ci
|
|
530
|
-
|
|
531
|
-
- name: Type check
|
|
532
|
-
run: npx tsc --noEmit
|
|
533
|
-
|
|
534
|
-
- name: Lint
|
|
535
|
-
run: npm run lint
|
|
536
|
-
|
|
537
|
-
- name: Unit tests
|
|
538
|
-
run: npm test -- --coverage
|
|
539
|
-
|
|
540
|
-
- name: Install Playwright
|
|
541
|
-
run: npx playwright install --with-deps
|
|
542
|
-
|
|
543
|
-
- name: E2E tests
|
|
544
|
-
run: npm run test:e2e
|
|
545
|
-
env:
|
|
546
|
-
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
|
547
|
-
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
|
|
548
|
-
|
|
549
|
-
- name: Upload coverage
|
|
550
|
-
uses: codecov/codecov-action@v3
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
---
|
|
554
|
-
|
|
555
|
-
## Best Practices
|
|
556
|
-
|
|
557
|
-
1. **Test null/undefined inputs** - Every function field test should cover these
|
|
558
|
-
2. **Use data-testid** - More stable than CSS selectors
|
|
559
|
-
3. **Page Objects** - Reduce duplication in E2E tests
|
|
560
|
-
4. **Parallel tests** - Playwright runs in parallel by default
|
|
561
|
-
5. **Test isolation** - Each test should be independent
|
|
562
|
-
6. **CI integration** - Run tests on every PR
|
|
563
|
-
7. **Coverage tracking** - Monitor test coverage over time
|
|
564
|
-
|
|
565
|
-
---
|
|
566
|
-
|
|
567
|
-
## Test Data Creation Order
|
|
568
|
-
|
|
569
|
-
**ActivityLink targets must exist before creating linking activities.**
|
|
570
|
-
|
|
571
|
-
When creating test data for workflows with relationships, follow this order:
|
|
572
|
-
|
|
573
|
-
```
|
|
574
|
-
1. Customers (no dependencies)
|
|
575
|
-
2. Contacts + Products (parallel - both link to Customers)
|
|
576
|
-
3. Deals (links to Customers)
|
|
577
|
-
4. Deal Lines (links to Deals + Products)
|
|
578
|
-
5. Activities with links (targets must exist first)
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
### Example Test Setup
|
|
582
|
-
|
|
583
|
-
```typescript
|
|
584
|
-
// test/helpers/test-data.ts
|
|
585
|
-
export async function createTestData(hailer: HailerClient) {
|
|
586
|
-
// 1. Create independent entities first
|
|
587
|
-
const customer = await hailer.activity.create({
|
|
588
|
-
workflow: WorkflowIds.customers,
|
|
589
|
-
fields: { name: 'Test Customer' }
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// 2. Create entities that link to customer (can be parallel)
|
|
593
|
-
const [contact, product] = await Promise.all([
|
|
594
|
-
hailer.activity.create({
|
|
595
|
-
workflow: WorkflowIds.contacts,
|
|
596
|
-
fields: {
|
|
597
|
-
name: 'Test Contact',
|
|
598
|
-
customer: customer._id // Link to existing customer
|
|
599
|
-
}
|
|
600
|
-
}),
|
|
601
|
-
hailer.activity.create({
|
|
602
|
-
workflow: WorkflowIds.products,
|
|
603
|
-
fields: { name: 'Test Product', price: 100 }
|
|
604
|
-
})
|
|
605
|
-
]);
|
|
606
|
-
|
|
607
|
-
// 3. Create deal (links to customer)
|
|
608
|
-
const deal = await hailer.activity.create({
|
|
609
|
-
workflow: WorkflowIds.deals,
|
|
610
|
-
fields: {
|
|
611
|
-
name: 'Test Deal',
|
|
612
|
-
customer: customer._id
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// 4. Create deal lines (links to deal + product)
|
|
617
|
-
const dealLine = await hailer.activity.create({
|
|
618
|
-
workflow: WorkflowIds.deal_lines,
|
|
619
|
-
fields: {
|
|
620
|
-
deal: deal._id,
|
|
621
|
-
product: product._id,
|
|
622
|
-
quantity: 5
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
return { customer, contact, product, deal, dealLine };
|
|
627
|
-
}
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
**Common mistake:** Creating a Deal Line before the Deal exists → ActivityLink validation fails.
|