@solidnumber/cli 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -5
- package/dist/__tests__/commands/auth.test.d.ts +6 -0
- package/dist/__tests__/commands/auth.test.d.ts.map +1 -0
- package/dist/__tests__/commands/auth.test.js +91 -0
- package/dist/__tests__/commands/auth.test.js.map +1 -0
- package/dist/__tests__/commands/billing.test.d.ts +6 -0
- package/dist/__tests__/commands/billing.test.d.ts.map +1 -0
- package/dist/__tests__/commands/billing.test.js +141 -0
- package/dist/__tests__/commands/billing.test.js.map +1 -0
- package/dist/__tests__/commands/company.test.d.ts +6 -0
- package/dist/__tests__/commands/company.test.d.ts.map +1 -0
- package/dist/__tests__/commands/company.test.js +129 -0
- package/dist/__tests__/commands/company.test.js.map +1 -0
- package/dist/__tests__/commands/import.test.d.ts +4 -0
- package/dist/__tests__/commands/import.test.d.ts.map +1 -0
- package/dist/__tests__/commands/import.test.js +71 -0
- package/dist/__tests__/commands/import.test.js.map +1 -0
- package/dist/__tests__/commands/pages.test.d.ts +5 -0
- package/dist/__tests__/commands/pages.test.d.ts.map +1 -0
- package/dist/__tests__/commands/pages.test.js +103 -0
- package/dist/__tests__/commands/pages.test.js.map +1 -0
- package/dist/__tests__/commands/push.test.d.ts +6 -0
- package/dist/__tests__/commands/push.test.d.ts.map +1 -0
- package/dist/__tests__/commands/push.test.js +113 -0
- package/dist/__tests__/commands/push.test.js.map +1 -0
- package/dist/__tests__/commands/sandbox.test.d.ts +5 -0
- package/dist/__tests__/commands/sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/commands/sandbox.test.js +132 -0
- package/dist/__tests__/commands/sandbox.test.js.map +1 -0
- package/dist/__tests__/commands/serve.test.d.ts +5 -0
- package/dist/__tests__/commands/serve.test.d.ts.map +1 -0
- package/dist/__tests__/commands/serve.test.js +115 -0
- package/dist/__tests__/commands/serve.test.js.map +1 -0
- package/dist/__tests__/commands/site.test.d.ts +5 -0
- package/dist/__tests__/commands/site.test.d.ts.map +1 -0
- package/dist/__tests__/commands/site.test.js +92 -0
- package/dist/__tests__/commands/site.test.js.map +1 -0
- package/dist/__tests__/integration/smoke.test.d.ts +7 -0
- package/dist/__tests__/integration/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/integration/smoke.test.js +135 -0
- package/dist/__tests__/integration/smoke.test.js.map +1 -0
- package/dist/__tests__/lib/api-client.test.d.ts +6 -0
- package/dist/__tests__/lib/api-client.test.d.ts.map +1 -0
- package/dist/__tests__/lib/api-client.test.js +74 -0
- package/dist/__tests__/lib/api-client.test.js.map +1 -0
- package/dist/__tests__/lib/company-isolation.test.d.ts +19 -0
- package/dist/__tests__/lib/company-isolation.test.d.ts.map +1 -0
- package/dist/__tests__/lib/company-isolation.test.js +96 -0
- package/dist/__tests__/lib/company-isolation.test.js.map +1 -0
- package/dist/__tests__/lib/config.test.d.ts +6 -0
- package/dist/__tests__/lib/config.test.d.ts.map +1 -0
- package/dist/__tests__/lib/config.test.js +131 -0
- package/dist/__tests__/lib/config.test.js.map +1 -0
- package/dist/__tests__/setup.d.ts +4 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +25 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/commands/company.js +93 -25
- package/dist/commands/company.js.map +1 -1
- package/dist/commands/diff.d.ts +14 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +286 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/import.d.ts +14 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +552 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/open.d.ts +15 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +179 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/sandbox.d.ts +15 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +321 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/serve.d.ts +19 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +449 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/watch.d.ts +15 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +252 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +9 -4
- package/dist/lib/api-client.js.map +1 -1
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -65,6 +65,30 @@ solid agent mission "Create a Valentine's campaign for VIP customers"
|
|
|
65
65
|
| `solid status` | Company dashboard |
|
|
66
66
|
| `solid pull` | Download pages, KB, settings as files |
|
|
67
67
|
| `solid push` | Push local changes to production |
|
|
68
|
+
| `solid diff` | Preview changes before pushing |
|
|
69
|
+
| `solid serve` | Local preview server (localhost:4000) |
|
|
70
|
+
| `solid open <page>` | Open page in web WYSIWYG builder |
|
|
71
|
+
| `solid watch` | Auto-push on file save |
|
|
72
|
+
|
|
73
|
+
### Import & Sandbox
|
|
74
|
+
| Command | Description |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| `solid import file.html --page "Title"` | Convert HTML/JSX to CMS blocks |
|
|
77
|
+
| `solid import --clipboard --page "Title"` | Import from clipboard |
|
|
78
|
+
| `solid sandbox create` | Fork site into isolated sandbox |
|
|
79
|
+
| `solid sandbox status` | Show sandbox changes |
|
|
80
|
+
| `solid sandbox diff` | Compare sandbox vs original |
|
|
81
|
+
| `solid sandbox push` | Promote sandbox to production |
|
|
82
|
+
| `solid sandbox reset` | Discard sandbox |
|
|
83
|
+
|
|
84
|
+
### Multi-Company & Droplets
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| `solid company create "Name"` | Create on shared platform |
|
|
88
|
+
| `solid company create "Name" --dedicated` | Provision dedicated droplet |
|
|
89
|
+
| `solid company create "Name" --dedicated --size medium` | With size (small/medium/large) |
|
|
90
|
+
| `solid switch <id>` | Switch active company |
|
|
91
|
+
| `solid droplet status <customer>` | Check droplet health |
|
|
68
92
|
|
|
69
93
|
### AI Training
|
|
70
94
|
| Command | Description |
|
|
@@ -152,11 +176,23 @@ solid agent mission "Create a Valentine's campaign for VIP customers"
|
|
|
152
176
|
## Workflow
|
|
153
177
|
|
|
154
178
|
```
|
|
155
|
-
1. solid pull
|
|
156
|
-
2. solid
|
|
157
|
-
3.
|
|
158
|
-
4. solid
|
|
159
|
-
5. solid
|
|
179
|
+
1. solid pull → Download pages, KB, services
|
|
180
|
+
2. solid serve --open → Preview locally at localhost:4000
|
|
181
|
+
3. solid context --claude → Give your AI full company knowledge
|
|
182
|
+
4. Edit files / solid import / vibe → Make changes any way you want
|
|
183
|
+
5. solid diff → Preview what will change
|
|
184
|
+
6. solid push → Deploy to production
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Agency Workflow (Sandbox Mode)
|
|
188
|
+
```
|
|
189
|
+
1. solid pull → Get the client's site
|
|
190
|
+
2. solid sandbox create → Fork into .sandbox/
|
|
191
|
+
3. solid serve --dir .sandbox → Preview sandbox locally
|
|
192
|
+
4. solid import promo.html --page "Ad" → Add ChatGPT landing page
|
|
193
|
+
5. solid sandbox diff → Review all changes
|
|
194
|
+
6. solid sandbox push → Promote to main files
|
|
195
|
+
7. solid push → Deploy to production
|
|
160
196
|
```
|
|
161
197
|
|
|
162
198
|
## File Formats
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/commands/auth.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auth command tests — login stores correct data, logout clears it.
|
|
4
|
+
* Mocks apiClient and config so no real API calls or filesystem access.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const config_1 = require("../../lib/config");
|
|
8
|
+
const api_client_1 = require("../../lib/api-client");
|
|
9
|
+
jest.mock('../../lib/api-client', () => ({
|
|
10
|
+
apiClient: {
|
|
11
|
+
login: jest.fn(),
|
|
12
|
+
authStatus: jest.fn(),
|
|
13
|
+
companiesList: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
handleApiError: jest.fn((e) => ({ message: e?.message || 'Error', status: 500 })),
|
|
16
|
+
}));
|
|
17
|
+
const mockApi = api_client_1.apiClient;
|
|
18
|
+
describe('auth logic', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
describe('login flow', () => {
|
|
23
|
+
it('stores token and company_id on successful login', async () => {
|
|
24
|
+
mockApi.login.mockResolvedValue({
|
|
25
|
+
data: {
|
|
26
|
+
access_token: 'new_token_abc',
|
|
27
|
+
refresh_token: 'new_refresh_xyz',
|
|
28
|
+
expires_at: '2026-12-31T00:00:00Z',
|
|
29
|
+
user: { id: 5, email: 'dev@agency.com', company_id: 42 },
|
|
30
|
+
},
|
|
31
|
+
status: 200,
|
|
32
|
+
success: true,
|
|
33
|
+
});
|
|
34
|
+
const result = await mockApi.login('dev@agency.com', 'password123');
|
|
35
|
+
// Verify the API was called with correct credentials
|
|
36
|
+
expect(mockApi.login).toHaveBeenCalledWith('dev@agency.com', 'password123');
|
|
37
|
+
// Verify response has all required fields
|
|
38
|
+
expect(result.data.access_token).toBe('new_token_abc');
|
|
39
|
+
expect(result.data.refresh_token).toBe('new_refresh_xyz');
|
|
40
|
+
expect(result.data.user.company_id).toBe(42);
|
|
41
|
+
expect(result.data.user.id).toBe(5);
|
|
42
|
+
});
|
|
43
|
+
it('login response includes company_id — never undefined', async () => {
|
|
44
|
+
mockApi.login.mockResolvedValue({
|
|
45
|
+
data: {
|
|
46
|
+
access_token: 'token',
|
|
47
|
+
refresh_token: 'refresh',
|
|
48
|
+
expires_at: '2026-12-31T00:00:00Z',
|
|
49
|
+
user: { id: 1, email: 'test@test.com', company_id: 99 },
|
|
50
|
+
},
|
|
51
|
+
status: 200,
|
|
52
|
+
success: true,
|
|
53
|
+
});
|
|
54
|
+
const result = await mockApi.login('test@test.com', 'pass');
|
|
55
|
+
expect(result.data.user.company_id).toBeDefined();
|
|
56
|
+
expect(typeof result.data.user.company_id).toBe('number');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('logout', () => {
|
|
60
|
+
it('config.logout clears auth state', () => {
|
|
61
|
+
config_1.config.logout();
|
|
62
|
+
expect(config_1.config.logout).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('auth status', () => {
|
|
66
|
+
it('isLoggedIn returns true when mocked as logged in', () => {
|
|
67
|
+
expect(config_1.config.isLoggedIn()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('multi-company', () => {
|
|
71
|
+
it('companiesList returns array of companies', async () => {
|
|
72
|
+
mockApi.companiesList.mockResolvedValue({
|
|
73
|
+
data: {
|
|
74
|
+
companies: [
|
|
75
|
+
{ id: 1, name: 'My Company', role: 'owner', is_active: true },
|
|
76
|
+
{ id: 42, name: 'Client Co', role: 'developer', is_active: true },
|
|
77
|
+
],
|
|
78
|
+
active_company_id: 1,
|
|
79
|
+
count: 2,
|
|
80
|
+
},
|
|
81
|
+
status: 200,
|
|
82
|
+
success: true,
|
|
83
|
+
});
|
|
84
|
+
const result = await mockApi.companiesList();
|
|
85
|
+
expect(result.data.companies).toHaveLength(2);
|
|
86
|
+
expect(result.data.companies[0].id).toBe(1);
|
|
87
|
+
expect(result.data.companies[1].role).toBe('developer');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../../src/__tests__/commands/auth.test.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,6CAA0C;AAC1C,qDAAiD;AAEjD,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE;QACT,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;QAChB,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE;QACrB,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;KACzB;IACD,cAAc,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;CACvF,CAAC,CAAC,CAAC;AAEJ,MAAM,OAAO,GAAG,sBAA0C,CAAC;AAE3D,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC;gBAC9B,IAAI,EAAE;oBACJ,YAAY,EAAE,eAAe;oBAC7B,aAAa,EAAE,iBAAiB;oBAChC,UAAU,EAAE,sBAAsB;oBAClC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,EAAE,EAAE;iBACzD;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;YAEpE,qDAAqD;YACrD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;YAE5E,0CAA0C;YAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC1D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC;gBAC9B,IAAI,EAAE;oBACJ,YAAY,EAAE,OAAO;oBACrB,aAAa,EAAE,SAAS;oBACxB,UAAU,EAAE,sBAAsB;oBAClC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,EAAE;iBACxD;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;YAC5D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;YAClD,MAAM,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,eAAM,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,CAAC,eAAM,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,CAAC,eAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACxD,OAAO,CAAC,aAAa,CAAC,iBAAiB,CAAC;gBACtC,IAAI,EAAE;oBACJ,SAAS,EAAE;wBACT,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE;wBAC7D,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE;qBAClE;oBACD,iBAAiB,EAAE,CAAC;oBACpB,KAAK,EAAE,CAAC;iBACT;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/commands/billing.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Billing command tests — checkout links and invoices.
|
|
4
|
+
* CRITICAL: wrong amount or wrong company_id = money goes to wrong place.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const api_client_1 = require("../../lib/api-client");
|
|
8
|
+
jest.mock('../../lib/api-client', () => ({
|
|
9
|
+
apiClient: {
|
|
10
|
+
post: jest.fn(),
|
|
11
|
+
get: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
handleApiError: jest.fn((e) => ({ message: e?.message || 'Error', status: 500 })),
|
|
14
|
+
}));
|
|
15
|
+
const mockApi = api_client_1.apiClient;
|
|
16
|
+
describe('billing commands', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
describe('checkout-link', () => {
|
|
21
|
+
it('sends correct company_id and tier', async () => {
|
|
22
|
+
mockApi.post.mockResolvedValue({
|
|
23
|
+
data: {
|
|
24
|
+
success: true,
|
|
25
|
+
url: 'https://checkout.stripe.com/session/test_123',
|
|
26
|
+
session_id: 'cs_test_123',
|
|
27
|
+
company_id: 42,
|
|
28
|
+
tier_slug: 'starter',
|
|
29
|
+
amount_cents: 8900,
|
|
30
|
+
},
|
|
31
|
+
status: 200,
|
|
32
|
+
success: true,
|
|
33
|
+
});
|
|
34
|
+
const result = await mockApi.post('/api/v1/billing/checkout-link', {
|
|
35
|
+
company_id: 42,
|
|
36
|
+
tier_slug: 'starter',
|
|
37
|
+
});
|
|
38
|
+
expect(mockApi.post).toHaveBeenCalledWith('/api/v1/billing/checkout-link', {
|
|
39
|
+
company_id: 42,
|
|
40
|
+
tier_slug: 'starter',
|
|
41
|
+
});
|
|
42
|
+
expect((result.data).url).toContain('checkout.stripe.com');
|
|
43
|
+
expect((result.data).amount_cents).toBe(8900);
|
|
44
|
+
expect((result.data).company_id).toBe(42);
|
|
45
|
+
});
|
|
46
|
+
it('returns amount matching tier pricing', async () => {
|
|
47
|
+
const TIER_PRICES = {
|
|
48
|
+
starter: 8900,
|
|
49
|
+
builder: 19900,
|
|
50
|
+
professional: 49900,
|
|
51
|
+
enterprise: 149900,
|
|
52
|
+
};
|
|
53
|
+
for (const [tier, price] of Object.entries(TIER_PRICES)) {
|
|
54
|
+
mockApi.post.mockResolvedValue({
|
|
55
|
+
data: { amount_cents: price, tier_slug: tier },
|
|
56
|
+
status: 200,
|
|
57
|
+
success: true,
|
|
58
|
+
});
|
|
59
|
+
const result = await mockApi.post('/api/v1/billing/checkout-link', {
|
|
60
|
+
company_id: 42,
|
|
61
|
+
tier_slug: tier,
|
|
62
|
+
});
|
|
63
|
+
expect((result.data).amount_cents).toBe(price);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('invoice — platform subscription', () => {
|
|
68
|
+
it('sends Stripe invoice for platform subscription', async () => {
|
|
69
|
+
mockApi.post.mockResolvedValue({
|
|
70
|
+
data: {
|
|
71
|
+
success: true,
|
|
72
|
+
invoice_id: 'in_test_abc',
|
|
73
|
+
hosted_url: 'https://invoice.stripe.com/i/test',
|
|
74
|
+
amount_cents: 8900,
|
|
75
|
+
recipient: 'client@business.com',
|
|
76
|
+
status: 'sent',
|
|
77
|
+
},
|
|
78
|
+
status: 200,
|
|
79
|
+
success: true,
|
|
80
|
+
});
|
|
81
|
+
const result = await mockApi.post('/api/v1/billing/invoice', {
|
|
82
|
+
company_id: 42,
|
|
83
|
+
amount_cents: 8900,
|
|
84
|
+
description: 'Starter Plan - Monthly',
|
|
85
|
+
invoice_type: 'platform_subscription',
|
|
86
|
+
});
|
|
87
|
+
expect(mockApi.post).toHaveBeenCalledWith('/api/v1/billing/invoice', expect.objectContaining({
|
|
88
|
+
company_id: 42,
|
|
89
|
+
amount_cents: 8900,
|
|
90
|
+
invoice_type: 'platform_subscription',
|
|
91
|
+
}));
|
|
92
|
+
expect((result.data).status).toBe('sent');
|
|
93
|
+
expect((result.data).hosted_url).toContain('stripe.com');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('invoice — agency service (document only)', () => {
|
|
97
|
+
it('creates document invoice WITHOUT Stripe charge', async () => {
|
|
98
|
+
mockApi.post.mockResolvedValue({
|
|
99
|
+
data: {
|
|
100
|
+
success: true,
|
|
101
|
+
invoice_id: 123,
|
|
102
|
+
public_token: 'abc123xyz',
|
|
103
|
+
amount_cents: 250000,
|
|
104
|
+
recipient: 'client@email.com',
|
|
105
|
+
status: 'open',
|
|
106
|
+
payment_method: 'external',
|
|
107
|
+
note: 'Agency service invoice — payment collected externally.',
|
|
108
|
+
},
|
|
109
|
+
status: 200,
|
|
110
|
+
success: true,
|
|
111
|
+
});
|
|
112
|
+
const result = await mockApi.post('/api/v1/billing/invoice', {
|
|
113
|
+
email: 'client@email.com',
|
|
114
|
+
amount_cents: 250000,
|
|
115
|
+
description: 'Website Build',
|
|
116
|
+
invoice_type: 'agency_service',
|
|
117
|
+
});
|
|
118
|
+
expect((result.data).payment_method).toBe('external');
|
|
119
|
+
expect((result.data).status).toBe('open');
|
|
120
|
+
// No Stripe URL — this is document-only
|
|
121
|
+
expect((result.data).hosted_url).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('amount validation', () => {
|
|
125
|
+
it('rejects zero amount', async () => {
|
|
126
|
+
// The backend rejects amount_cents <= 0
|
|
127
|
+
const payload = { company_id: 42, amount_cents: 0, description: 'test', invoice_type: 'platform_subscription' };
|
|
128
|
+
expect(payload.amount_cents).toBe(0);
|
|
129
|
+
// Backend would return 400
|
|
130
|
+
});
|
|
131
|
+
it('rejects negative amount', async () => {
|
|
132
|
+
const payload = { amount_cents: -100 };
|
|
133
|
+
expect(payload.amount_cents).toBeLessThan(0);
|
|
134
|
+
});
|
|
135
|
+
it('amount_cents is integer not float', () => {
|
|
136
|
+
const amount = 8900; // $89.00
|
|
137
|
+
expect(Number.isInteger(amount)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=billing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.test.js","sourceRoot":"","sources":["../../../src/__tests__/commands/billing.test.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,qDAAiD;AAEjD,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE;QACT,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;KACf;IACD,cAAc,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;CACvF,CAAC,CAAC,CAAC;AAEJ,MAAM,OAAO,GAAG,sBAA0C,CAAC;AAE3D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC;gBAC7B,IAAI,EAAE;oBACJ,OAAO,EAAE,IAAI;oBACb,GAAG,EAAE,8CAA8C;oBACnD,UAAU,EAAE,aAAa;oBACzB,UAAU,EAAE,EAAE;oBACd,SAAS,EAAE,SAAS;oBACpB,YAAY,EAAE,IAAI;iBACnB;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE;gBACjE,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,SAAS;aACrB,CAAC,CAAC;YAEH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,+BAA+B,EAAE;gBACzE,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,SAAS;aACrB,CAAC,CAAC;YACH,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;YACpE,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvD,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,WAAW,GAA2B;gBAC1C,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,KAAK;gBACd,YAAY,EAAE,KAAK;gBACnB,UAAU,EAAE,MAAM;aACnB,CAAC;YAEF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC;oBAC7B,IAAI,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE;oBAC9C,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,IAAI;iBACd,CAAC,CAAC;gBAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE;oBACjE,UAAU,EAAE,EAAE;oBACd,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;gBAEH,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;QAC/C,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YAC9D,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC;gBAC7B,IAAI,EAAE;oBACJ,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,aAAa;oBACzB,UAAU,EAAE,mCAAmC;oBAC/C,YAAY,EAAE,IAAI;oBAClB,SAAS,EAAE,qBAAqB;oBAChC,MAAM,EAAE,MAAM;iBACf;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBAC3D,UAAU,EAAE,EAAE;gBACd,YAAY,EAAE,IAAI;gBAClB,WAAW,EAAE,wBAAwB;gBACrC,YAAY,EAAE,uBAAuB;aACtC,CAAC,CAAC;YAEH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,yBAAyB,EAAE,MAAM,CAAC,gBAAgB,CAAC;gBAC3F,UAAU,EAAE,EAAE;gBACd,YAAY,EAAE,IAAI;gBAClB,YAAY,EAAE,uBAAuB;aACtC,CAAC,CAAC,CAAC;YACJ,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnD,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;QACxD,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YAC9D,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC;gBAC7B,IAAI,EAAE;oBACJ,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,GAAG;oBACf,YAAY,EAAE,WAAW;oBACzB,YAAY,EAAE,MAAM;oBACpB,SAAS,EAAE,kBAAkB;oBAC7B,MAAM,EAAE,MAAM;oBACd,cAAc,EAAE,UAAU;oBAC1B,IAAI,EAAE,wDAAwD;iBAC/D;gBACD,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBAC3D,KAAK,EAAE,kBAAkB;gBACzB,YAAY,EAAE,MAAM;gBACpB,WAAW,EAAE,eAAe;gBAC5B,YAAY,EAAE,gBAAgB;aAC/B,CAAC,CAAC;YAEH,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/D,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnD,wCAAwC;YACxC,MAAM,CAAE,CAAC,MAAM,CAAC,IAAI,CAAS,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;YACnC,wCAAwC;YACxC,MAAM,OAAO,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,uBAAuB,EAAE,CAAC;YAChH,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACrC,2BAA2B;QAC7B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;YACvC,MAAM,OAAO,GAAG,EAAE,YAAY,EAAE,CAAC,GAAG,EAAE,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,SAAS;YAC9B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"company.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/commands/company.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Company command tests — create sends correct payload, switch changes context.
|
|
4
|
+
* CRITICAL: company create is the most dangerous command — wrong payload = wrong company provisioned.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const api_client_1 = require("../../lib/api-client");
|
|
8
|
+
jest.mock('../../lib/api-client', () => ({
|
|
9
|
+
apiClient: {
|
|
10
|
+
companyCreate: jest.fn(),
|
|
11
|
+
companySwitch: jest.fn(),
|
|
12
|
+
companiesList: jest.fn(),
|
|
13
|
+
companyMembers: jest.fn(),
|
|
14
|
+
companyMemberRevoke: jest.fn(),
|
|
15
|
+
companyInvite: jest.fn(),
|
|
16
|
+
},
|
|
17
|
+
handleApiError: jest.fn((e) => ({ message: e?.message || 'Error', status: 500 })),
|
|
18
|
+
}));
|
|
19
|
+
const mockApi = api_client_1.apiClient;
|
|
20
|
+
describe('company commands', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
describe('company create', () => {
|
|
25
|
+
it('sends name and template to correct endpoint', async () => {
|
|
26
|
+
mockApi.companyCreate.mockResolvedValue({
|
|
27
|
+
data: {
|
|
28
|
+
status: 'created',
|
|
29
|
+
company: { id: 150, name: "Mike's Plumbing", slug: 'mikes-plumbing-a1b2' },
|
|
30
|
+
membership: { role: 'owner' },
|
|
31
|
+
},
|
|
32
|
+
status: 201,
|
|
33
|
+
success: true,
|
|
34
|
+
});
|
|
35
|
+
const result = await mockApi.companyCreate("Mike's Plumbing", 'plumber', 'plumbing');
|
|
36
|
+
expect(mockApi.companyCreate).toHaveBeenCalledWith("Mike's Plumbing", 'plumber', 'plumbing');
|
|
37
|
+
expect(result.data.company.id).toBe(150);
|
|
38
|
+
expect(result.data.membership.role).toBe('owner');
|
|
39
|
+
});
|
|
40
|
+
it('returns company_id that can be used for switch', async () => {
|
|
41
|
+
mockApi.companyCreate.mockResolvedValue({
|
|
42
|
+
data: {
|
|
43
|
+
status: 'created',
|
|
44
|
+
company: { id: 200, name: 'Test', slug: 'test-1234' },
|
|
45
|
+
membership: { role: 'owner' },
|
|
46
|
+
},
|
|
47
|
+
status: 201,
|
|
48
|
+
success: true,
|
|
49
|
+
});
|
|
50
|
+
const result = await mockApi.companyCreate('Test');
|
|
51
|
+
const newCompanyId = result.data.company.id;
|
|
52
|
+
expect(newCompanyId).toBeDefined();
|
|
53
|
+
expect(typeof newCompanyId).toBe('number');
|
|
54
|
+
expect(newCompanyId).toBeGreaterThan(0);
|
|
55
|
+
});
|
|
56
|
+
it('works without template (no industry)', async () => {
|
|
57
|
+
mockApi.companyCreate.mockResolvedValue({
|
|
58
|
+
data: {
|
|
59
|
+
status: 'created',
|
|
60
|
+
company: { id: 201, name: 'Generic Co', slug: 'generic-co-5678' },
|
|
61
|
+
membership: { role: 'owner' },
|
|
62
|
+
},
|
|
63
|
+
status: 201,
|
|
64
|
+
success: true,
|
|
65
|
+
});
|
|
66
|
+
await mockApi.companyCreate('Generic Co');
|
|
67
|
+
expect(mockApi.companyCreate).toHaveBeenCalledWith('Generic Co');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('company switch', () => {
|
|
71
|
+
it('switches to target company and returns new JWT', async () => {
|
|
72
|
+
mockApi.companySwitch.mockResolvedValue({
|
|
73
|
+
data: {
|
|
74
|
+
status: 'switched', role: 'owner', access_token: 'new_jwt_for_company_42',
|
|
75
|
+
refresh_token: 'new_refresh',
|
|
76
|
+
expires_in: 3600,
|
|
77
|
+
company: { id: 42, name: 'Client Co' },
|
|
78
|
+
},
|
|
79
|
+
status: 200,
|
|
80
|
+
success: true,
|
|
81
|
+
});
|
|
82
|
+
const result = await mockApi.companySwitch(42);
|
|
83
|
+
expect(mockApi.companySwitch).toHaveBeenCalledWith(42);
|
|
84
|
+
expect(result.data.access_token).toBe('new_jwt_for_company_42');
|
|
85
|
+
expect(result.data.company.id).toBe(42);
|
|
86
|
+
});
|
|
87
|
+
it('new token is scoped to target company only', async () => {
|
|
88
|
+
mockApi.companySwitch.mockResolvedValue({
|
|
89
|
+
data: {
|
|
90
|
+
status: 'switched', role: 'owner', access_token: 'jwt_company_99',
|
|
91
|
+
refresh_token: 'refresh_99',
|
|
92
|
+
expires_in: 3600,
|
|
93
|
+
company: { id: 99, name: 'Company 99' },
|
|
94
|
+
},
|
|
95
|
+
status: 200,
|
|
96
|
+
success: true,
|
|
97
|
+
});
|
|
98
|
+
const result = await mockApi.companySwitch(99);
|
|
99
|
+
// The new JWT should be for company 99, not the old company
|
|
100
|
+
expect(result.data.company.id).toBe(99);
|
|
101
|
+
expect(result.data.access_token).toContain('99');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('company isolation', () => {
|
|
105
|
+
it('cannot switch to company 1, 2, or 3 (reserved system companies)', () => {
|
|
106
|
+
// This is enforced on the backend, but we document the contract here
|
|
107
|
+
const RESERVED_IDS = [1, 2, 3];
|
|
108
|
+
for (const id of RESERVED_IDS) {
|
|
109
|
+
expect(id).toBeLessThanOrEqual(3);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
it('member list is scoped to requested company', async () => {
|
|
113
|
+
mockApi.companyMembers.mockResolvedValue({
|
|
114
|
+
data: {
|
|
115
|
+
company_id: 42, count: 2, members: [
|
|
116
|
+
{ user_id: 1, email: 'owner@test.com', role: 'owner' },
|
|
117
|
+
{ user_id: 2, email: 'dev@test.com', role: 'developer' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
status: 200,
|
|
121
|
+
success: true,
|
|
122
|
+
});
|
|
123
|
+
const result = await mockApi.companyMembers(42);
|
|
124
|
+
expect(mockApi.companyMembers).toHaveBeenCalledWith(42);
|
|
125
|
+
expect(result.data.members).toHaveLength(2);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=company.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"company.test.js","sourceRoot":"","sources":["../../../src/__tests__/commands/company.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/commands/import.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Import command tests — HTML parsing to layout_json blocks.
|
|
4
|
+
*/
|
|
5
|
+
// We test the parser directly by importing the module
|
|
6
|
+
// The actual command reads files/clipboard, but the core logic is parsing
|
|
7
|
+
describe('import: HTML-to-blocks parser', () => {
|
|
8
|
+
// We'll test via the command's internal parse logic
|
|
9
|
+
// Since the parser is not exported separately, we test the output files
|
|
10
|
+
describe('block detection', () => {
|
|
11
|
+
it('detects hero sections from H1 + button', () => {
|
|
12
|
+
const html = `<section><h1>Welcome to Our Business</h1><p>Best service in town</p><a href="/contact" class="btn">Get Started</a></section>`;
|
|
13
|
+
// Hero: has h1 + button/link
|
|
14
|
+
expect(html).toContain('<h1');
|
|
15
|
+
expect(html).toContain('btn');
|
|
16
|
+
});
|
|
17
|
+
it('detects pricing from dollar amounts', () => {
|
|
18
|
+
const html = `<div><h2>Pricing</h2><div><h3>Basic</h3><p>$29/mo</p></div><div><h3>Pro</h3><p>$99/mo</p></div></div>`;
|
|
19
|
+
const priceCount = (html.match(/\$\d/g) || []).length;
|
|
20
|
+
expect(priceCount).toBeGreaterThanOrEqual(2);
|
|
21
|
+
});
|
|
22
|
+
it('detects FAQ from question/answer patterns', () => {
|
|
23
|
+
const html = `<section><h2>FAQ</h2><div><h3>How does it work?</h3><p>Simple.</p></div></section>`;
|
|
24
|
+
expect(html.toLowerCase()).toContain('faq');
|
|
25
|
+
});
|
|
26
|
+
it('detects testimonials from quotes', () => {
|
|
27
|
+
const html = `<div><h2>Reviews</h2><blockquote><p>"Great service!"</p><cite>John</cite></blockquote></div>`;
|
|
28
|
+
expect(html.toLowerCase()).toContain('review');
|
|
29
|
+
});
|
|
30
|
+
it('detects contact forms', () => {
|
|
31
|
+
const html = `<section><h2>Contact Us</h2><form><input placeholder="Name"><textarea></textarea><button>Send</button></form></section>`;
|
|
32
|
+
expect(html.toLowerCase()).toContain('<form');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('slug generation', () => {
|
|
36
|
+
it('generates slug from title', () => {
|
|
37
|
+
const title = 'Summer Sale 2026!';
|
|
38
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
39
|
+
expect(slug).toBe('summer-sale-2026');
|
|
40
|
+
});
|
|
41
|
+
it('handles special characters', () => {
|
|
42
|
+
const title = "Mike's Plumbing & HVAC";
|
|
43
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
44
|
+
expect(slug).toBe('mike-s-plumbing-hvac');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('page JSON structure', () => {
|
|
48
|
+
it('creates valid page structure', () => {
|
|
49
|
+
const page = {
|
|
50
|
+
title: 'Test Page',
|
|
51
|
+
slug: 'test-page',
|
|
52
|
+
page_type: 'website',
|
|
53
|
+
is_published: false,
|
|
54
|
+
is_landing_page: false,
|
|
55
|
+
meta_title: 'Test Page',
|
|
56
|
+
meta_description: '',
|
|
57
|
+
layout_json: { sections: [] },
|
|
58
|
+
};
|
|
59
|
+
expect(page).toHaveProperty('title');
|
|
60
|
+
expect(page).toHaveProperty('slug');
|
|
61
|
+
expect(page).toHaveProperty('layout_json');
|
|
62
|
+
expect(page.layout_json).toHaveProperty('sections');
|
|
63
|
+
expect(Array.isArray(page.layout_json.sections)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('marks landing pages correctly', () => {
|
|
66
|
+
const page = { page_type: 'landing', is_landing_page: true };
|
|
67
|
+
expect(page.is_landing_page).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=import.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.test.js","sourceRoot":"","sources":["../../../src/__tests__/commands/import.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/commands/pages.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|