@prenta/admin 1.12.0 → 1.14.0

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/__tests__/lib/seo-service-errors.test.js +34 -1
  3. package/dist/__tests__/lib/seo-service-errors.test.js.map +1 -1
  4. package/dist/__tests__/lib/seo-service-proposals.test.d.ts +2 -0
  5. package/dist/__tests__/lib/seo-service-proposals.test.d.ts.map +1 -0
  6. package/dist/__tests__/lib/seo-service-proposals.test.js +71 -0
  7. package/dist/__tests__/lib/seo-service-proposals.test.js.map +1 -0
  8. package/dist/__tests__/router/seo-tab-for-path.test.js +1 -0
  9. package/dist/__tests__/router/seo-tab-for-path.test.js.map +1 -1
  10. package/dist/__tests__/views/seo-proposals-tab.render.test.d.ts +2 -0
  11. package/dist/__tests__/views/seo-proposals-tab.render.test.d.ts.map +1 -0
  12. package/dist/__tests__/views/seo-proposals-tab.render.test.js +292 -0
  13. package/dist/__tests__/views/seo-proposals-tab.render.test.js.map +1 -0
  14. package/dist/__tests__/views/seo-settings.render.test.js +73 -6
  15. package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
  16. package/dist/components/seo/ChangePreview.d.ts +6 -0
  17. package/dist/components/seo/ChangePreview.d.ts.map +1 -0
  18. package/dist/components/seo/ChangePreview.js +18 -0
  19. package/dist/components/seo/ChangePreview.js.map +1 -0
  20. package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
  21. package/dist/components/seo/SeoIssueFixPanel.js +1 -14
  22. package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
  23. package/dist/lib/seo-service.d.ts +106 -0
  24. package/dist/lib/seo-service.d.ts.map +1 -1
  25. package/dist/lib/seo-service.js +67 -0
  26. package/dist/lib/seo-service.js.map +1 -1
  27. package/dist/prenta-admin.css +1 -1
  28. package/dist/views/SEO.d.ts +5 -1
  29. package/dist/views/SEO.d.ts.map +1 -1
  30. package/dist/views/SEO.js +28 -9
  31. package/dist/views/SEO.js.map +1 -1
  32. package/dist/views/seo/ProposalsTab.d.ts +7 -0
  33. package/dist/views/seo/ProposalsTab.d.ts.map +1 -0
  34. package/dist/views/seo/ProposalsTab.js +298 -0
  35. package/dist/views/seo/ProposalsTab.js.map +1 -0
  36. package/dist/views/settings/SeoDigestCard.d.ts +9 -0
  37. package/dist/views/settings/SeoDigestCard.d.ts.map +1 -0
  38. package/dist/views/settings/SeoDigestCard.js +77 -0
  39. package/dist/views/settings/SeoDigestCard.js.map +1 -0
  40. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  41. package/dist/views/settings/SeoSettingsTab.js +2 -1
  42. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/__tests__/lib/seo-service-errors.test.ts +38 -0
  45. package/src/__tests__/lib/seo-service-proposals.test.ts +79 -0
  46. package/src/__tests__/router/seo-tab-for-path.test.ts +1 -0
  47. package/src/__tests__/views/seo-proposals-tab.render.test.tsx +370 -0
  48. package/src/__tests__/views/seo-settings.render.test.tsx +104 -5
  49. package/src/components/seo/ChangePreview.tsx +47 -0
  50. package/src/components/seo/SeoIssueFixPanel.tsx +1 -44
  51. package/src/lib/seo-service.ts +168 -0
  52. package/src/views/SEO.tsx +40 -7
  53. package/src/views/seo/ProposalsTab.tsx +681 -0
  54. package/src/views/settings/SeoDigestCard.tsx +164 -0
  55. package/src/views/settings/SeoSettingsTab.tsx +2 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @actuate-media/cms-admin
2
2
 
3
+ ## 1.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8b6b25c: AI Proposals Inbox (SEO AI roadmap item 4).
8
+
9
+ **@prenta/core**
10
+ - New routes: `GET /seo/proposals` (unified list of cached issue fixes + open 404 redirect suggestions with staleness, brand-alignment, confidence, and per-feature `allowBulkApply` flags), `POST /seo/proposals/generate` (confirm-gated batch generation, 10 issues per call, 3 concurrent, 20 s budget, halts on `rate_limited`), `POST /seo/proposals/bulk-apply` (max 50; >1 items require `allowBulkApply` for every represented feature → 403 `governance_blocked`; same-document items are applied sequentially against a batch-start snapshot; unreached items return `pending`), `POST /seo/issues/:id/dismiss-fix` (sticky dismissal, audited as `seo_fix_dismissed`).
11
+ - `SeoIssue.metadata.pendingFix` now stores `brandAlignment` (minimum across text fields) and `generatedAt`; `dismissedFingerprint` / `generateSkippedFingerprint` markers keep batch generation from recreating rejected or non-cacheable proposals until the document changes.
12
+ - `SuggestIssueFixResult` gains optional `aiError` (code + message) when the AI path refused or failed.
13
+ - `applyIssueFix` (`seo/issue-fix-apply.ts`) and `acceptRedirectSuggestion` (`redirects/suggestion-accept.ts`) are extracted services; the existing routes keep identical HTTP behaviour.
14
+ - `GET /seo/overview` gains `pendingProposals: { issueFixes, redirects }`.
15
+ - New governance helper `assertBulkApplyAllowed`.
16
+
17
+ **@prenta/admin**
18
+ - New SEO → Proposals tab: multi-select, batch approve (disabled with a Settings link when governance forbids bulk apply), batch dismiss, capped "Generate proposals" loop with Stop, stale-row flagging, "Apply remaining" after a time-budget cut-off. `ChangePreview` is now a shared component.
19
+
20
+ **@prenta/mcp-server**
21
+ - New tools: `list_seo_proposals`, `generate_seo_proposals` (confirm-gated), `bulk_apply_seo_proposals` (confirm-gated), `dismiss_seo_proposal`.
22
+ - `PrentaClientError` now carries the server's machine-readable `code` (e.g. `plan_required`, `governance_blocked`) alongside `status`, and tool error responses append it to the error text as `(code: <code>)` so the calling agent can branch on it.
23
+
24
+ ## 1.13.0
25
+
26
+ ### Minor Changes
27
+
28
+ - 9b3a11c: The weekly SEO cron (`/api/cms/cron/seo-scan`, Mondays) now runs the full
29
+ persisting audit pipeline — the same `SeoAuditRun` + `SeoIssue` artifacts as
30
+ the admin's "Run audit" button, with run-over-run score deltas and 404
31
+ redirect-recovery seeding — instead of the legacy compute-and-discard scan
32
+ (the admin's manual `POST /seo/scan` quick scan is unchanged). Stale RUNNING
33
+ runs older than 15 minutes are swept to FAILED at the start of each tick.
34
+
35
+ New opt-in **weekly SEO digest email** (Settings → SEO → Weekly digest): site
36
+ score + delta, 7-day search clicks/impressions with week-over-week change,
37
+ impression-weighted quick wins, new issues since the previous audit, and
38
+ pending proposal counts, with deep links into the admin. Off by default;
39
+ sends only when enabled with at least one recipient (max 10, one email per
40
+ recipient). A failed or stale audit is called out at the top of the email
41
+ instead of silently reporting old numbers. Includes
42
+ `GET/PUT /seo/digest-settings` and a rate-limited
43
+ `POST /seo/digest-settings/test-send` that emails the current user. No new
44
+ cron entry is needed — existing deployments get all of this by upgrading.
45
+
3
46
  ## 1.12.0
4
47
 
5
48
  ### Minor Changes
@@ -3,7 +3,7 @@ const cmsApi = vi.fn();
3
3
  vi.mock('../../lib/api.js', () => ({
4
4
  cmsApi: (...args) => cmsApi(...args),
5
5
  }));
6
- const { applySeoInternalLink, explainSeoIssue, fetchSeoOverview, fetchSeoContentRecords, fetchSeoIssues, fetchRedirectRules, generate404RecoverySuggestions, generateSeoField, generateStructuredData, isInlineSeoFixIssue, runSeoAudit, summarizeSeoAudit, updateSeoFields, } = await import('../../lib/seo-service.js');
6
+ const { applySeoInternalLink, explainSeoIssue, fetchDigestSettings, fetchSeoOverview, fetchSeoContentRecords, fetchSeoIssues, fetchRedirectRules, generate404RecoverySuggestions, generateSeoField, generateStructuredData, isInlineSeoFixIssue, runSeoAudit, summarizeSeoAudit, updateSeoFields, } = await import('../../lib/seo-service.js');
7
7
  afterEach(() => {
8
8
  cmsApi.mockReset();
9
9
  });
@@ -122,6 +122,39 @@ describe('generate404RecoverySuggestions governance passthrough', () => {
122
122
  await expect(generate404RecoverySuggestions()).resolves.toEqual({ autoApplied: 1 });
123
123
  });
124
124
  });
125
+ /**
126
+ * Regression tests for the digest-settings shape crash: a response body that
127
+ * isn't the expected settings object (e.g. a catch-all `{}` mock, or a future
128
+ * server drift) must resolve to null — which SeoDigestCard renders as its
129
+ * error state — instead of leaking a malformed object that crashes on
130
+ * `recipients.join`.
131
+ */
132
+ describe('fetchDigestSettings response validation', () => {
133
+ it('returns null when the response body is not a digest settings object', async () => {
134
+ cmsApi.mockResolvedValue({ data: {}, status: 200 });
135
+ await expect(fetchDigestSettings()).resolves.toBeNull();
136
+ });
137
+ it('returns null when recipients is missing', async () => {
138
+ cmsApi.mockResolvedValue({ data: { enabled: true }, status: 200 });
139
+ await expect(fetchDigestSettings()).resolves.toBeNull();
140
+ });
141
+ it('returns null on request failure', async () => {
142
+ cmsApi.mockResolvedValue({ error: 'Request failed (500)', status: 500 });
143
+ await expect(fetchDigestSettings()).resolves.toBeNull();
144
+ });
145
+ it('normalizes a valid payload, defaulting absent nullable fields to null', async () => {
146
+ cmsApi.mockResolvedValue({
147
+ data: { enabled: true, recipients: ['owner@example.com'] },
148
+ status: 200,
149
+ });
150
+ await expect(fetchDigestSettings()).resolves.toEqual({
151
+ enabled: true,
152
+ recipients: ['owner@example.com'],
153
+ lastSentAt: null,
154
+ lastError: null,
155
+ });
156
+ });
157
+ });
125
158
  describe('inline SEO fix issue types', () => {
126
159
  it('treats orphan-page as an inline Approve issue', () => {
127
160
  expect(isInlineSeoFixIssue('orphan-page')).toBe(true);
@@ -1 +1 @@
1
- {"version":3,"file":"seo-service-errors.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-errors.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seo-service-errors.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-errors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=seo-service-proposals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-service-proposals.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-proposals.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const cmsApi = vi.fn();
3
+ vi.mock('../../lib/api.js', () => ({ cmsApi: (...args) => cmsApi(...args) }));
4
+ const svc = await import('../../lib/seo-service.js');
5
+ beforeEach(() => {
6
+ // Block body on purpose: `mockReset()` returns the mock, and vitest would
7
+ // run a returned function as after-test cleanup.
8
+ cmsApi.mockReset();
9
+ });
10
+ describe('proposal service helpers', () => {
11
+ it('fetchSeoProposals GETs /seo/proposals and throws on error', async () => {
12
+ cmsApi.mockResolvedValueOnce({ status: 500, error: 'boom' });
13
+ await expect(svc.fetchSeoProposals()).rejects.toThrow('boom');
14
+ cmsApi.mockResolvedValueOnce({
15
+ status: 200,
16
+ data: {
17
+ proposals: [],
18
+ counts: { issueFixes: 0, redirects: 0, stale: 0, eligibleForGeneration: 0 },
19
+ governance: { issueFixBulk: false, redirectBulk: false },
20
+ },
21
+ });
22
+ await expect(svc.fetchSeoProposals()).resolves.toMatchObject({ proposals: [] });
23
+ expect(cmsApi).toHaveBeenLastCalledWith('/seo/proposals');
24
+ });
25
+ it('generateSeoProposals POSTs confirm: true and returns the result or error', async () => {
26
+ cmsApi.mockResolvedValueOnce({
27
+ status: 200,
28
+ data: { generated: 3, skipped: 1, failed: 0, remaining: 2, haltedReason: null },
29
+ });
30
+ await expect(svc.generateSeoProposals()).resolves.toEqual({
31
+ result: { generated: 3, skipped: 1, failed: 0, remaining: 2, haltedReason: null },
32
+ });
33
+ expect(cmsApi).toHaveBeenCalledWith('/seo/proposals/generate', {
34
+ method: 'POST',
35
+ body: JSON.stringify({ confirm: true }),
36
+ });
37
+ cmsApi.mockResolvedValueOnce({ status: 429, error: 'AI rate limit reached.' });
38
+ await expect(svc.generateSeoProposals()).resolves.toEqual({ error: 'AI rate limit reached.' });
39
+ });
40
+ it('bulkApplySeoProposals flags governance_blocked distinctly', async () => {
41
+ cmsApi.mockResolvedValueOnce({
42
+ status: 403,
43
+ error: 'Enable "Allow bulk apply"',
44
+ code: 'governance_blocked',
45
+ });
46
+ await expect(svc.bulkApplySeoProposals([{ kind: 'issue-fix', id: 'i1', fingerprint: 'f' }])).resolves.toEqual({ error: 'Enable "Allow bulk apply"', governanceBlocked: true });
47
+ cmsApi.mockResolvedValueOnce({
48
+ status: 200,
49
+ data: {
50
+ results: [{ kind: 'issue-fix', id: 'i1', status: 'applied' }],
51
+ applied: 1,
52
+ failed: 0,
53
+ pending: 0,
54
+ },
55
+ });
56
+ await expect(svc.bulkApplySeoProposals([{ kind: 'issue-fix', id: 'i1' }])).resolves.toEqual({
57
+ result: {
58
+ results: [{ kind: 'issue-fix', id: 'i1', status: 'applied' }],
59
+ applied: 1,
60
+ failed: 0,
61
+ pending: 0,
62
+ },
63
+ });
64
+ });
65
+ it('dismissSeoIssueFix POSTs to the encoded issue path', async () => {
66
+ cmsApi.mockResolvedValueOnce({ status: 200, data: { dismissed: true } });
67
+ await expect(svc.dismissSeoIssueFix('a/b')).resolves.toEqual({});
68
+ expect(cmsApi).toHaveBeenCalledWith('/seo/issues/a%2Fb/dismiss-fix', { method: 'POST' });
69
+ });
70
+ });
71
+ //# sourceMappingURL=seo-service-proposals.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-service-proposals.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-proposals.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7D,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;AACtB,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AAExF,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAA;AAEpD,UAAU,CAAC,GAAG,EAAE;IACd,0EAA0E;IAC1E,iDAAiD;IACjD,MAAM,CAAC,SAAS,EAAE,CAAA;AACpB,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5D,MAAM,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAC7D,MAAM,CAAC,qBAAqB,CAAC;YAC3B,MAAM,EAAE,GAAG;YACX,IAAI,EAAE;gBACJ,SAAS,EAAE,EAAE;gBACb,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE;gBAC3E,UAAU,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;aACzD;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAA;QAC/E,MAAM,CAAC,MAAM,CAAC,CAAC,wBAAwB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,CAAC,qBAAqB,CAAC;YAC3B,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE;SAChF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YACxD,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE;SAClF,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,yBAAyB,EAAE;YAC7D,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC,CAAA;QACF,MAAM,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAA;QAC9E,MAAM,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAA;IAChG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,CAAC,qBAAqB,CAAC;YAC3B,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,2BAA2B;YAClC,IAAI,EAAE,oBAAoB;SAC3B,CAAC,CAAA;QACF,MAAM,MAAM,CACV,GAAG,CAAC,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,CAC/E,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAA;QACnF,MAAM,CAAC,qBAAqB,CAAC;YAC3B,MAAM,EAAE,GAAG;YACX,IAAI,EAAE;gBACJ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC7D,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC;aACX;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC1F,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC7D,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC;aACX;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;QACxE,MAAM,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1F,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -15,6 +15,7 @@ describe('seoTabForPath', () => {
15
15
  expect(seoTabForPath('/seo/links')).toBe('technical');
16
16
  expect(seoTabForPath('/seo/redirects')).toBe('redirects');
17
17
  expect(seoTabForPath('/seo/audit')).toBe('audit');
18
+ expect(seoTabForPath('/seo/proposals')).toBe('proposals');
18
19
  });
19
20
  it('returns null for non-SEO paths so they fall through', () => {
20
21
  expect(seoTabForPath('/dashboard')).toBeNull();
@@ -1 +1 @@
1
- {"version":3,"file":"seo-tab-for-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/router/seo-tab-for-path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC1C,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3C,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,2EAA2E;QAC3E,wEAAwE;QACxE,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACtC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"seo-tab-for-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/router/seo-tab-for-path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACjD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC1C,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3C,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,2EAA2E;QAC3E,wEAAwE;QACxE,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACtC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=seo-proposals-tab.render.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-proposals-tab.render.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/views/seo-proposals-tab.render.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,292 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
5
+ function proposal(over = {}) {
6
+ return {
7
+ id: 'issue:i1',
8
+ kind: 'issue-fix',
9
+ title: 'Duplicate meta title',
10
+ entity: { type: 'page', id: 'd1', title: 'Pricing', url: '/pricing' },
11
+ changes: [{ field: 'metaTitle', label: 'Meta title', before: 'Dup', after: 'Unique Pricing' }],
12
+ justification: 'AI drafted a unique title.',
13
+ source: 'ai',
14
+ confidence: null,
15
+ brandAlignment: 88,
16
+ generatedAt: new Date(Date.now() - 3600_000).toISOString(),
17
+ fingerprint: 'fp-1',
18
+ stale: false,
19
+ issueType: 'duplicate-meta-title',
20
+ severity: 'warning',
21
+ issueId: 'i1',
22
+ suggestionId: null,
23
+ ...over,
24
+ };
25
+ }
26
+ function payload(over = {}) {
27
+ return {
28
+ proposals: [
29
+ proposal(),
30
+ proposal({
31
+ id: 'issue:i2',
32
+ issueId: 'i2',
33
+ fingerprint: 'fp-2',
34
+ title: 'Missing description',
35
+ brandAlignment: 60,
36
+ }),
37
+ proposal({
38
+ id: 'redirect:s1',
39
+ kind: 'redirect',
40
+ title: 'Redirect /old → /new',
41
+ source: 'ai-404-recovery',
42
+ confidence: 0.82,
43
+ brandAlignment: null,
44
+ fingerprint: null,
45
+ issueId: null,
46
+ suggestionId: 's1',
47
+ severity: null,
48
+ changes: [{ field: 'redirect', label: 'Redirect', before: '/old', after: '/new' }],
49
+ }),
50
+ ],
51
+ counts: { issueFixes: 2, redirects: 1, stale: 0, eligibleForGeneration: 5 },
52
+ governance: { issueFixBulk: true, redirectBulk: true },
53
+ ...over,
54
+ };
55
+ }
56
+ const fetchSeoProposals = vi.fn(async () => payload());
57
+ const generateSeoProposals = vi.fn(async () => ({
58
+ result: { generated: 2, skipped: 0, failed: 0, remaining: 0, haltedReason: null },
59
+ }));
60
+ const bulkApplySeoProposals = vi.fn(async (_items) => ({
61
+ result: { results: [], applied: 0, failed: 0, pending: 0 },
62
+ }));
63
+ const dismissSeoIssueFix = vi.fn(async (_id) => ({}));
64
+ const dismissRedirectSuggestion = vi.fn(async (_id) => ({}));
65
+ vi.mock('../../lib/seo-service.js', () => ({
66
+ fetchSeoProposals: () => fetchSeoProposals(),
67
+ generateSeoProposals: () => generateSeoProposals(),
68
+ bulkApplySeoProposals: (items) => bulkApplySeoProposals(items),
69
+ dismissSeoIssueFix: (id) => dismissSeoIssueFix(id),
70
+ dismissRedirectSuggestion: (id) => dismissRedirectSuggestion(id),
71
+ }));
72
+ vi.mock('sonner', () => ({
73
+ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), loading: vi.fn() },
74
+ }));
75
+ const { ProposalsTab } = await import('../../views/seo/ProposalsTab.js');
76
+ const { toast } = await import('sonner');
77
+ function deferred() {
78
+ let resolve;
79
+ let reject;
80
+ const promise = new Promise((res, rej) => {
81
+ resolve = res;
82
+ reject = rej;
83
+ });
84
+ return { promise, resolve, reject };
85
+ }
86
+ /** Let any already-settled promise continuations run. */
87
+ async function flushMicrotasks() {
88
+ await new Promise((r) => setTimeout(r, 0));
89
+ }
90
+ beforeEach(() => {
91
+ fetchSeoProposals.mockClear();
92
+ fetchSeoProposals.mockImplementation(async () => payload());
93
+ generateSeoProposals.mockClear();
94
+ bulkApplySeoProposals.mockClear();
95
+ dismissSeoIssueFix.mockClear();
96
+ dismissRedirectSuggestion.mockClear();
97
+ vi.mocked(toast.success).mockClear();
98
+ vi.mocked(toast.error).mockClear();
99
+ });
100
+ describe('ProposalsTab', () => {
101
+ it('renders rows with kind, badges, and expandable change preview', async () => {
102
+ render(_jsx(ProposalsTab, { planInfo: null }));
103
+ expect(await screen.findByText('Duplicate meta title')).toBeTruthy();
104
+ expect(screen.getByText('82%')).toBeTruthy();
105
+ expect(screen.getByLabelText('Brand voice score 88 out of 100')).toBeTruthy();
106
+ fireEvent.click(screen.getByRole('button', { name: 'Show changes for Duplicate meta title' }));
107
+ expect(screen.getByRole('table', { name: 'Proposed SEO changes' })).toBeTruthy();
108
+ expect(screen.getByText('Unique Pricing')).toBeTruthy();
109
+ });
110
+ it('select-all selects only live rows and shows the bulk bar', async () => {
111
+ fetchSeoProposals.mockImplementationOnce(async () => payload({
112
+ proposals: [
113
+ proposal(),
114
+ proposal({ id: 'issue:stale', issueId: 'stale', stale: true, title: 'Stale one' }),
115
+ ],
116
+ counts: { issueFixes: 1, redirects: 0, stale: 1, eligibleForGeneration: 1 },
117
+ }));
118
+ render(_jsx(ProposalsTab, { planInfo: null }));
119
+ await screen.findByText('Stale one');
120
+ expect(screen.getByText('Stale — regenerate')).toBeTruthy();
121
+ expect(screen.queryByLabelText('Select proposal Stale one')).toBeNull();
122
+ fireEvent.click(screen.getByLabelText('Select all proposals'));
123
+ expect(screen.getByRole('region', { name: 'Bulk actions' }).textContent).toContain('1 selected');
124
+ });
125
+ it('disables batch approve with a settings callout when governance forbids bulk apply', async () => {
126
+ fetchSeoProposals.mockImplementationOnce(async () => payload({ governance: { issueFixBulk: false, redirectBulk: true } }));
127
+ const onNavigate = vi.fn();
128
+ render(_jsx(ProposalsTab, { planInfo: null, onNavigate: onNavigate }));
129
+ await screen.findByText('Duplicate meta title');
130
+ fireEvent.click(screen.getByLabelText('Select proposal Duplicate meta title'));
131
+ fireEvent.click(screen.getByLabelText('Select proposal Missing description'));
132
+ const approve = screen.getByRole('button', { name: 'Approve selected' });
133
+ expect(approve).toHaveProperty('disabled', true);
134
+ expect(screen.getByText(/Allow bulk apply/)).toBeTruthy();
135
+ expect(screen.queryByRole('link')).toBeNull();
136
+ fireEvent.click(screen.getByRole('button', { name: 'Open Settings → AI' }));
137
+ expect(onNavigate).toHaveBeenCalledWith('/settings?tab=ai');
138
+ });
139
+ it('renders the governance callout without a settings control when onNavigate is absent', async () => {
140
+ fetchSeoProposals.mockImplementationOnce(async () => payload({ governance: { issueFixBulk: false, redirectBulk: true } }));
141
+ render(_jsx(ProposalsTab, { planInfo: null }));
142
+ await screen.findByText('Duplicate meta title');
143
+ fireEvent.click(screen.getByLabelText('Select all proposals'));
144
+ expect(screen.getByText(/Allow bulk apply/)).toBeTruthy();
145
+ expect(screen.queryByRole('button', { name: 'Open Settings → AI' })).toBeNull();
146
+ expect(screen.queryByRole('link')).toBeNull();
147
+ });
148
+ it('issues a single bulk-apply request when approve is triggered twice while in flight', async () => {
149
+ const inflight = deferred();
150
+ bulkApplySeoProposals.mockImplementationOnce(() => inflight.promise);
151
+ render(_jsx(ProposalsTab, { planInfo: null }));
152
+ await screen.findByText('Duplicate meta title');
153
+ const approve = screen.getByRole('button', { name: 'Approve Duplicate meta title' });
154
+ fireEvent.click(approve);
155
+ fireEvent.click(approve);
156
+ fireEvent.click(screen.getByRole('button', { name: 'Approve Missing description' }));
157
+ await flushMicrotasks();
158
+ expect(bulkApplySeoProposals).toHaveBeenCalledTimes(1);
159
+ expect(approve).toHaveProperty('disabled', true);
160
+ inflight.resolve({ result: { results: [], applied: 0, failed: 0, pending: 0 } });
161
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Approve Duplicate meta title' })).toHaveProperty('disabled', false));
162
+ });
163
+ it('disables every other mutation control while an apply is in flight', async () => {
164
+ const inflight = deferred();
165
+ bulkApplySeoProposals.mockImplementationOnce(() => inflight.promise);
166
+ render(_jsx(ProposalsTab, { planInfo: null }));
167
+ await screen.findByText('Duplicate meta title');
168
+ fireEvent.click(screen.getByLabelText('Select all proposals'));
169
+ fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
170
+ fireEvent.click(await screen.findByRole('button', { name: 'Apply 3 proposals' }));
171
+ await waitFor(() => expect(bulkApplySeoProposals).toHaveBeenCalledTimes(1));
172
+ expect(screen.getByRole('button', { name: 'Dismiss selected' })).toHaveProperty('disabled', true);
173
+ expect(screen.getByRole('button', { name: 'Approve selected' })).toHaveProperty('disabled', true);
174
+ expect(screen.getByRole('button', { name: 'Dismiss Duplicate meta title' })).toHaveProperty('disabled', true);
175
+ expect(screen.getByRole('button', { name: 'Generate proposals (5 eligible)' })).toHaveProperty('disabled', true);
176
+ fireEvent.click(screen.getByRole('button', { name: 'Dismiss selected' }));
177
+ expect(dismissSeoIssueFix).not.toHaveBeenCalled();
178
+ inflight.resolve({ result: { results: [], applied: 0, failed: 0, pending: 0 } });
179
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Generate proposals (5 eligible)' })).toHaveProperty('disabled', false));
180
+ });
181
+ it('re-enables controls and toasts when the apply request throws', async () => {
182
+ bulkApplySeoProposals.mockRejectedValueOnce(new Error('network down'));
183
+ render(_jsx(ProposalsTab, { planInfo: null }));
184
+ await screen.findByText('Duplicate meta title');
185
+ fireEvent.click(screen.getByRole('button', { name: 'Approve Duplicate meta title' }));
186
+ await waitFor(() => expect(toast.error).toHaveBeenCalledWith('network down'));
187
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Approve Duplicate meta title' })).toHaveProperty('disabled', false));
188
+ expect(screen.getByRole('button', { name: 'Dismiss Duplicate meta title' })).toHaveProperty('disabled', false);
189
+ });
190
+ it('stops the generate loop and stays silent after unmount', async () => {
191
+ const firstPass = deferred();
192
+ generateSeoProposals.mockImplementationOnce(() => firstPass.promise);
193
+ const { unmount } = render(_jsx(ProposalsTab, { planInfo: null }));
194
+ fireEvent.click(await screen.findByRole('button', { name: 'Generate proposals (5 eligible)' }));
195
+ fireEvent.click(await screen.findByRole('button', { name: 'Generate' }));
196
+ await waitFor(() => expect(generateSeoProposals).toHaveBeenCalledTimes(1));
197
+ unmount();
198
+ firstPass.resolve({
199
+ result: { generated: 1, skipped: 0, failed: 0, remaining: 1, haltedReason: null },
200
+ });
201
+ await flushMicrotasks();
202
+ await flushMicrotasks();
203
+ expect(generateSeoProposals).toHaveBeenCalledTimes(1);
204
+ expect(toast.success).not.toHaveBeenCalled();
205
+ expect(toast.error).not.toHaveBeenCalled();
206
+ });
207
+ it('confirming batch approve calls bulkApplySeoProposals with selected items and reports partial results', async () => {
208
+ bulkApplySeoProposals.mockResolvedValueOnce({
209
+ result: {
210
+ results: [
211
+ { kind: 'issue-fix', id: 'i1', status: 'applied' },
212
+ { kind: 'issue-fix', id: 'i2', status: 'stale', reason: 'stale' },
213
+ ],
214
+ applied: 1,
215
+ failed: 1,
216
+ pending: 0,
217
+ },
218
+ });
219
+ const { toast } = await import('sonner');
220
+ render(_jsx(ProposalsTab, { planInfo: null }));
221
+ await screen.findByText('Duplicate meta title');
222
+ fireEvent.click(screen.getByLabelText('Select proposal Duplicate meta title'));
223
+ fireEvent.click(screen.getByLabelText('Select proposal Missing description'));
224
+ fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
225
+ fireEvent.click(await screen.findByRole('button', { name: 'Apply 2 proposals' }));
226
+ await waitFor(() => expect(bulkApplySeoProposals).toHaveBeenCalledWith([
227
+ { kind: 'issue-fix', id: 'i1', fingerprint: 'fp-1' },
228
+ { kind: 'issue-fix', id: 'i2', fingerprint: 'fp-2' },
229
+ ]));
230
+ await waitFor(() => expect(toast.success).toHaveBeenCalledWith('1 proposal applied.'));
231
+ expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('1 could not be applied'));
232
+ });
233
+ it('offers "Apply remaining" when the server reports pending items', async () => {
234
+ bulkApplySeoProposals.mockResolvedValueOnce({
235
+ result: {
236
+ results: [
237
+ { kind: 'issue-fix', id: 'i1', status: 'applied' },
238
+ { kind: 'issue-fix', id: 'i2', status: 'pending' },
239
+ ],
240
+ applied: 1,
241
+ failed: 0,
242
+ pending: 1,
243
+ },
244
+ });
245
+ render(_jsx(ProposalsTab, { planInfo: null }));
246
+ await screen.findByText('Duplicate meta title');
247
+ fireEvent.click(screen.getByLabelText('Select all proposals'));
248
+ fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
249
+ fireEvent.click(await screen.findByRole('button', { name: 'Apply 3 proposals' }));
250
+ expect(await screen.findByRole('button', { name: 'Apply remaining (1)' })).toBeTruthy();
251
+ });
252
+ it('dismiss selected fans out to the per-kind dismiss helpers', async () => {
253
+ render(_jsx(ProposalsTab, { planInfo: null }));
254
+ await screen.findByText('Duplicate meta title');
255
+ fireEvent.click(screen.getByLabelText('Select all proposals'));
256
+ fireEvent.click(screen.getByRole('button', { name: 'Dismiss selected' }));
257
+ await waitFor(() => expect(dismissSeoIssueFix).toHaveBeenCalledTimes(2));
258
+ expect(dismissRedirectSuggestion).toHaveBeenCalledWith('s1');
259
+ });
260
+ it('generate loops while remaining > 0 and shows the eligible count', async () => {
261
+ generateSeoProposals
262
+ .mockResolvedValueOnce({
263
+ result: { generated: 10, skipped: 0, failed: 0, remaining: 3, haltedReason: null },
264
+ })
265
+ .mockResolvedValueOnce({
266
+ result: { generated: 3, skipped: 0, failed: 0, remaining: 0, haltedReason: null },
267
+ });
268
+ const { toast } = await import('sonner');
269
+ render(_jsx(ProposalsTab, { planInfo: null }));
270
+ const button = await screen.findByRole('button', { name: 'Generate proposals (5 eligible)' });
271
+ fireEvent.click(button);
272
+ fireEvent.click(await screen.findByRole('button', { name: 'Generate' }));
273
+ await waitFor(() => expect(generateSeoProposals).toHaveBeenCalledTimes(2));
274
+ await waitFor(() => expect(toast.success).toHaveBeenCalledWith('13 proposals generated.'));
275
+ });
276
+ it('disables Generate when nothing is eligible and shows the empty state', async () => {
277
+ fetchSeoProposals.mockImplementationOnce(async () => payload({
278
+ proposals: [],
279
+ counts: { issueFixes: 0, redirects: 0, stale: 0, eligibleForGeneration: 0 },
280
+ }));
281
+ render(_jsx(ProposalsTab, { planInfo: null }));
282
+ expect(await screen.findByText('No proposals yet')).toBeTruthy();
283
+ expect(screen.getByRole('button', { name: 'Generate proposals (0 eligible)' })).toHaveProperty('disabled', true);
284
+ });
285
+ it('hides Generate behind the plan callout when seo.inlineApply is off', async () => {
286
+ render(_jsx(ProposalsTab, { planInfo: { tier: 'starter', features: [], upgradeUrl: null } }));
287
+ await screen.findByText('Duplicate meta title');
288
+ expect(screen.queryByRole('button', { name: /Generate proposals/ })).toBeNull();
289
+ expect(screen.getByText(/Upgrade to unlock SEO inline apply/)).toBeTruthy();
290
+ });
291
+ });
292
+ //# sourceMappingURL=seo-proposals-tab.render.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-proposals-tab.render.test.js","sourceRoot":"","sources":["../../../src/__tests__/views/seo-proposals-tab.render.test.tsx"],"names":[],"mappings":"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"}