test(cloudcp): cover Pulse Account render modules

This commit is contained in:
rcourtman
2026-03-26 02:21:09 +00:00
parent 2b15b44d45
commit 560573a319
2 changed files with 351 additions and 0 deletions
@@ -0,0 +1,200 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
renderDeletePanel,
renderExportPanel,
renderExportResult,
renderManagePanel,
renderOpenPanels,
renderRefundPanel,
renderRetrievePanel,
renderStatus,
} from './services_view';
import type { PortalBootstrapData, RefundState, ServiceStatus, VerificationFlowState } from './types';
function createBootstrap(overrides: Partial<PortalBootstrapData> = {}): PortalBootstrapData {
return {
authenticated: true,
email: 'owner@example.com',
public_site_url: 'https://pulserelay.pro',
support_email: 'support@pulserelay.pro',
commercial_api_base_url: 'https://license.pulserelay.pro',
portal_path: '/portal',
bootstrap_path: '/api/portal/bootstrap',
magic_link_request_path: '/auth/magic-link',
signup_path: '/signup',
logout_path: '/auth/logout',
account_api_base_path: '/api/accounts',
portal_api_base_path: '/api/portal',
accounts: [],
...overrides,
};
}
function createFlowState(overrides: Partial<VerificationFlowState> = {}): VerificationFlowState {
return {
pendingEmail: '',
requesting: false,
confirming: false,
step2Visible: false,
status: {
visible: false,
message: '',
error: false,
},
result: null,
emailValue: '',
codeValue: '',
checkboxChecked: false,
...overrides,
};
}
function createRefundState(overrides: Partial<RefundState> = {}): RefundState {
return {
emailValue: '',
tokenValue: '',
submitting: false,
status: {
visible: false,
message: '',
error: false,
},
...overrides,
};
}
describe('services view', function() {
beforeEach(function() {
document.body.innerHTML = '';
});
it('toggles the visible service panel by id', function() {
document.body.innerHTML =
'<div id="manage-service-panel" class="service-panel"></div>' +
'<div id="retrieve-service-panel" class="service-panel"></div>' +
'<div id="refund-service-panel" class="service-panel"></div>' +
'<div id="data-service-panel" class="service-panel"></div>';
renderOpenPanels('retrieve-service-panel');
expect(document.getElementById('retrieve-service-panel')?.classList.contains('visible')).toBe(true);
expect(document.getElementById('manage-service-panel')?.classList.contains('visible')).toBe(false);
expect(document.getElementById('refund-service-panel')?.classList.contains('visible')).toBe(false);
});
it('renders retrieve panel result state with invoice and token metadata', function() {
document.body.innerHTML = '<div id="retrieve-service-root"></div>';
renderRetrievePanel(
createFlowState({
emailValue: 'buyer@example.com',
codeValue: '123456',
step2Visible: true,
result: {
invoice_url: 'https://license.pulserelay.pro/invoices/inv_123',
token: 'pulse_key_123',
tier: 'Relay',
issued_at: '2026-03-26T10:00:00Z',
expires_at: null,
email: 'buyer@example.com',
},
})
);
var copyButton = document.getElementById('retrieve-inline-copy') as HTMLButtonElement;
var invoiceLink = document.getElementById('retrieve-inline-invoice') as HTMLAnchorElement;
var tokenArea = document.getElementById('retrieve-inline-token') as HTMLTextAreaElement;
expect(copyButton.style.display).toBe('inline-block');
expect(invoiceLink.href).toBe('https://license.pulserelay.pro/invoices/inv_123');
expect(invoiceLink.style.display).toBe('inline-block');
expect(tokenArea.value).toBe('pulse_key_123');
expect(document.getElementById('retrieve-inline-email-value')?.textContent).toBe('buyer@example.com');
expect(document.getElementById('retrieve-inline-result')?.style.display).toBe('block');
});
it('renders refund and delete panels from owned state', function() {
document.body.innerHTML =
'<div id="refund-service-root"></div>' +
'<div id="data-delete-root"></div>';
renderRefundPanel(
createRefundState({
emailValue: 'owner@example.com',
tokenValue: 'pulse_token',
}),
createBootstrap()
);
renderDeletePanel(
createFlowState({
emailValue: 'owner@example.com',
codeValue: '654321',
step2Visible: true,
checkboxChecked: true,
})
);
var refundEmail = document.getElementById('refund-inline-email') as HTMLInputElement;
var refundToken = document.getElementById('refund-inline-token') as HTMLInputElement;
var deleteCheck = document.getElementById('data-delete-confirm-check') as HTMLInputElement;
expect(refundEmail.value).toBe('owner@example.com');
expect(refundToken.value).toBe('pulse_token');
expect(document.getElementById('refund-service-root')?.innerHTML).toContain('/refund.html?email=owner%40example.com');
expect(deleteCheck.checked).toBe(true);
expect(document.getElementById('data-delete-step2')?.style.display).toBe('block');
});
it('renders export panel and updates export result payload visibility', function() {
document.body.innerHTML =
'<div id="data-export-root"></div>' +
'<div id="data-export-result" style="display:none"></div>' +
'<textarea id="data-export-payload"></textarea>';
renderExportPanel(
createFlowState({
emailValue: 'owner@example.com',
step2Visible: true,
result: {
accounts: 2,
},
})
);
var payload = { accounts: 3, workspaces: 5 };
renderExportResult(payload);
expect((document.getElementById('data-export-email') as HTMLInputElement).value).toBe('owner@example.com');
expect(document.getElementById('data-export-step2')?.style.display).toBe('block');
expect(document.getElementById('data-export-result')?.style.display).toBe('block');
expect((document.getElementById('data-export-payload') as HTMLTextAreaElement).value).toBe(
JSON.stringify(payload, null, 2)
);
});
it('renders manage panel and status classes from service state', function() {
document.body.innerHTML =
'<div id="manage-service-root"></div>' +
'<div id="manage-inline-status" class="service-status"></div>';
renderManagePanel(
createFlowState({
emailValue: 'owner@example.com',
codeValue: '123456',
step2Visible: true,
})
);
renderStatus('manage-inline-status', {
visible: true,
message: 'Code sent.',
error: false,
} satisfies ServiceStatus);
expect((document.getElementById('manage-inline-email') as HTMLInputElement).value).toBe('owner@example.com');
expect((document.getElementById('manage-inline-code') as HTMLInputElement).value).toBe('123456');
expect(document.getElementById('manage-inline-step2')?.style.display).toBe('block');
expect(document.getElementById('manage-inline-status')?.className).toContain('success');
expect(document.getElementById('manage-inline-status')?.textContent).toBe('Code sent.');
});
});
@@ -0,0 +1,151 @@
import { describe, expect, it } from 'vitest';
import type { PortalBootstrapData, PortalLoginState } from './types';
import {
renderAccountsHTML,
renderAuthenticatedPortalHTML,
renderHeaderHTML,
renderSignedOutPortalHTML,
type ShellViewContext,
} from './shell_view';
function createBootstrap(overrides: Partial<PortalBootstrapData> = {}): PortalBootstrapData {
return {
authenticated: true,
email: 'owner@example.com',
public_site_url: 'https://pulserelay.pro',
support_email: 'support@pulserelay.pro',
commercial_api_base_url: 'https://license.pulserelay.pro',
portal_path: '/portal',
bootstrap_path: '/api/portal/bootstrap',
magic_link_request_path: '/auth/magic-link',
signup_path: '/signup',
logout_path: '/auth/logout',
account_api_base_path: '/api/accounts',
portal_api_base_path: '/api/portal',
accounts: [],
...overrides,
};
}
function createLoginState(overrides: Partial<PortalLoginState> = {}): PortalLoginState {
return {
emailValue: '',
sending: false,
success: false,
error: '',
...overrides,
};
}
function createContext(overrides: Partial<ShellViewContext> = {}): ShellViewContext {
return {
bootstrap: createBootstrap(),
loginState: createLoginState(),
signupPath: '/signup',
accountAPIBasePath: '/api/accounts',
...overrides,
};
}
describe('shell view', function() {
it('renders authenticated header with account email and sign-out button', function() {
var html = renderHeaderHTML(createContext());
expect(html).toContain('owner@example.com');
expect(html).toContain('id="logout-btn"');
expect(html).toContain('Sign out');
});
it('renders signed-out header with create-account link', function() {
var html = renderHeaderHTML(
createContext({
bootstrap: createBootstrap({ authenticated: false, email: '' }),
})
);
expect(html).toContain('href="/signup"');
expect(html).toContain('Create account');
expect(html).not.toContain('logout-btn" id="logout-btn"');
});
it('renders empty accounts state with support contact', function() {
var html = renderAccountsHTML(createContext());
expect(html).toContain('No workspaces found.');
expect(html).toContain('mailto:support@pulserelay.pro');
expect(html).toContain('support@pulserelay.pro');
});
it('renders authenticated portal accounts, workspaces, and service entrypoints', function() {
var html = renderAuthenticatedPortalHTML(
createContext({
bootstrap: createBootstrap({
accounts: [
{
id: 'acct_1',
name: 'Acme MSP',
kind: 'msp',
kind_label: 'MSP',
role: 'owner',
can_manage: true,
has_billing: true,
workspaces: [
{
id: 'ws_active',
display_name: 'Alpha Workspace',
state: 'active',
healthy: true,
created_at: '2026-03-26T10:00:00Z',
},
{
id: 'ws_failed',
display_name: 'Beta Workspace',
state: 'failed',
healthy: false,
},
],
},
],
}),
})
);
expect(html).toContain('<h1>Pulse Account</h1>');
expect(html).toContain('id="accounts-root"');
expect(html).toContain('Acme MSP');
expect(html).toContain('Alpha Workspace');
expect(html).toContain('/api/accounts/acct_1/tenants/ws_active/handoff');
expect(html).toContain('data-action="workspace-manage"');
expect(html).toContain('service-card-button');
expect(html).toContain('id="open-retrieve-service"');
expect(html).toContain('id="data-service-panel"');
});
it('renders signed-out portal with error and success login states', function() {
var errorHTML = renderSignedOutPortalHTML(
createContext({
bootstrap: createBootstrap({ authenticated: false, email: '' }),
loginState: createLoginState({
emailValue: 'buyer@example.com',
error: 'Invalid email',
}),
})
);
var successHTML = renderSignedOutPortalHTML(
createContext({
bootstrap: createBootstrap({ authenticated: false, email: '' }),
loginState: createLoginState({
emailValue: 'buyer@example.com',
success: true,
sending: false,
}),
})
);
expect(errorHTML).toContain('value="buyer@example.com"');
expect(errorHTML).toContain('Invalid email');
expect(successHTML).toContain('Magic link sent.');
expect(successHTML).toContain('data-portal-action="resend-magic-link"');
});
});