160 lines
9.6 KiB
JavaScript
160 lines
9.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const pagePath = fileURLToPath(new URL("../src/features/scheduling/SchedulingPage.tsx", import.meta.url));
|
|
const publicPagePath = fileURLToPath(new URL("../src/features/scheduling/SchedulingPublicPage.tsx", import.meta.url));
|
|
const apiPath = fileURLToPath(new URL("../src/api/scheduling.ts", import.meta.url));
|
|
const modulePath = fileURLToPath(new URL("../src/module.ts", import.meta.url));
|
|
const page = readFileSync(pagePath, "utf8");
|
|
const publicPage = readFileSync(publicPagePath, "utf8");
|
|
const api = readFileSync(apiPath, "utf8");
|
|
const moduleSource = readFileSync(modulePath, "utf8");
|
|
|
|
assert.match(page, /usePlatformUiCapability<CalendarPickerUiCapability>\("calendar\.picker"\)/);
|
|
assert.match(page, /hasScope\(auth, "calendar:calendar:read"\)/);
|
|
assert.match(page, /Boolean\(calendarPickerCapability\) && canReadCalendars && canReadAvailability && canWriteCalendarEvent/);
|
|
assert.doesNotMatch(page, /@govoplan\/calendar-webui|govoplan-calendar\/webui/);
|
|
assert.match(page, /Card,[\s\S]*DataGrid,[\s\S]*DataGridRowActions,[\s\S]*FormField,[\s\S]*MetricCard,[\s\S]*PasswordField,[\s\S]*PeoplePicker,[\s\S]*SelectionList,[\s\S]*ToggleSwitch,[\s\S]*from "@govoplan\/core-webui"/);
|
|
assert.doesNotMatch(page, /@govoplan\/core-webui\/src\//);
|
|
|
|
assert.match(page, /className="scheduling-workspace-layout"/);
|
|
assert.match(page, /<aside className="scheduling-request-sidebar">/);
|
|
assert.match(page, /<section className="scheduling-main-panel">/);
|
|
assert.match(page, /title=\{I18N\.requests\}[\s\S]*<Plus aria-hidden="true" size=\{16\} \/> \{I18N\.add\}/);
|
|
assert.match(page, /title=\{I18N\.myRequests\}/);
|
|
assert.match(page, /title=\{I18N\.invitedRequests\}/);
|
|
assert.ok(page.indexOf("title={I18N.myRequests}") < page.indexOf("title={I18N.invitedRequests}"));
|
|
assert.match(page, /<SelectionList label=\{title\} className="scheduling-request-list">/);
|
|
assert.match(page, /<SelectionListItem[\s\S]*selected=\{selectedId === request\.id\}[\s\S]*className="scheduling-list-item"/);
|
|
assert.match(page, /className="scheduling-list-item"[\s\S]{0,100}disabled=\{disabled\}/);
|
|
|
|
assert.match(page, /editorMode \? \(/);
|
|
assert.match(page, /id="scheduling-editor-form"/);
|
|
assert.doesNotMatch(page, /ModuleSubnav|scheduling-create-subnav|scheduling-full-editor/);
|
|
const editorStart = page.indexOf('<div className="scheduling-editor-surface">');
|
|
const editorEnd = page.indexOf('</form>', editorStart);
|
|
const editor = page.slice(editorStart, editorEnd);
|
|
assert.ok(editor.indexOf("I18N.discard") < editor.indexOf("I18N.save"));
|
|
assert.match(editor, /form="scheduling-editor-form"/);
|
|
assert.match(editor, /<Card title=\{I18N\.basicInformation\}>/);
|
|
assert.match(editor, /<Card title=\{I18N\.calendarIntegration\}>/);
|
|
assert.match(page, /<Card title=\{I18N\.candidateSlots\}>/);
|
|
assert.match(page, /<Card title=\{I18N\.participants\}>/);
|
|
assert.match(page, /<Card title=\{I18N\.generalSettings\}>/);
|
|
assert.match(page, /<Card title=\{I18N\.participantPrivacy\}>/);
|
|
assert.match(editor, /<FormField label=\{I18N\.title\}>/);
|
|
assert.match(page, /useUnsavedDraftGuard\(/);
|
|
assert.match(page, /requestDiscard\(exitEditor\)/);
|
|
assert.match(page, /dirty: responseDirty,[\s\S]*onSave: persistAvailability,[\s\S]*onDiscard: resetResponseDraft/);
|
|
assert.doesNotMatch(page, /window\.(?:alert|confirm)\(/);
|
|
|
|
for (const setting of [
|
|
"participantRosterVisible",
|
|
"notifyOnAnswers",
|
|
"singleChoice",
|
|
"participantLimitEnabled",
|
|
"allowMaybe",
|
|
"allowComments",
|
|
"participantEmailRequired",
|
|
"anonymousPasswordProtectionEnabled"
|
|
]) {
|
|
assert.match(page, new RegExp(`checked=\\{${setting}\\}`));
|
|
}
|
|
assert.match(page, /<PasswordField[\s\S]*minLength=\{8\}/);
|
|
assert.match(page, /type="number"[\s\S]*min=\{1\}/);
|
|
assert.match(page, /min=\{addLocalMinutes\(slot\.start_at, 1\)\}/);
|
|
assert.match(page, /allow_external_participants: allowExternalParticipants/);
|
|
assert.doesNotMatch(page, /usesGatewayPolicy|updateSchedulingCandidateSlot/);
|
|
assert.match(page, /public_participation_policy_enforcement_available/);
|
|
assert.match(page, /const canCreateOrWrite = canWrite \|\| canAdminister/);
|
|
assert.match(page, /policyLocked=\{participationPolicyLocked\}/);
|
|
|
|
assert.match(page, /id="scheduling-create-candidate-slots-grid"/);
|
|
assert.match(page, /id="scheduling-participant-picker"/);
|
|
assert.match(page, /id="scheduling-candidate-slots-grid"/);
|
|
assert.match(page, /id="scheduling-participants-grid"/);
|
|
assert.match(page, /<DataGridRowActions/);
|
|
assert.match(page, /disabled=\{!canCreateOrWrite\}[\s\S]{0,80}reorderable/);
|
|
assert.doesNotMatch(page, /reorderable=\{editorMode === "create"\}/);
|
|
assert.doesNotMatch(page, /EmailAddressInput|MailboxAddress|addressSuggestions|addressLookupQuery/);
|
|
assert.doesNotMatch(page, /<input[\s\S]{0,220}aria-label=\{I18N\.participantEmail\}/);
|
|
assert.match(page, /allowManualExternal=\{allowExternalParticipants\}/);
|
|
assert.match(page, /search=\{participantSearch\}/);
|
|
assert.doesNotMatch(page, /<table|scheduling-table|scheduling-card(?:\s|"|`)/);
|
|
assert.match(page, /<TableActionGroup[\s\S]*disabled: saving \|\| !decisionEnabled/);
|
|
assert.match(page, /showDecisionAction=\{canManageSelected\}/);
|
|
assert.match(page, /<IconButton[\s\S]*label=\{I18N\.refresh\}/);
|
|
assert.doesNotMatch(page, /AdminIconButton/);
|
|
|
|
const participantGridStart = page.indexOf("function ParticipantsGrid(");
|
|
const participantGridEnd = page.indexOf("function invitationActionDisabledReason", participantGridStart);
|
|
const participantGrid = page.slice(participantGridStart, participantGridEnd);
|
|
assert.match(participantGrid, /\.\.\.\(canManage \? \[\{/);
|
|
assert.match(participantGrid, /minimumSlots=\{3\}/);
|
|
assert.ok(participantGrid.indexOf('id: "copy-invitation"') < participantGrid.indexOf('id: "send-invitation"'));
|
|
assert.ok(participantGrid.indexOf('id: "send-invitation"') < participantGrid.indexOf('id: "revoke-invitation"'));
|
|
assert.match(participantGrid, /schedulingInvitationActionBlocks\(request, participant, now\)/);
|
|
assert.match(participantGrid, /disabledReason: copyDisabledReason/);
|
|
assert.match(participantGrid, /disabledReason: deliveryDisabledReason/);
|
|
assert.match(participantGrid, /disabledReason: revokeDisabledReason/);
|
|
assert.match(page, /<ConfirmDialog[\s\S]*title=\{I18N\.revokeInvitationLabel\}[\s\S]*tone="danger"/);
|
|
assert.match(page, /navigator\.clipboard\.writeText\(value\)/);
|
|
assert.match(page, /navigator\.clipboard\.write\(\[new ClipboardItem/);
|
|
assert.match(page, /schedulingPublicInvitationUrl\(response\.action_url, window\.location\.origin\)/);
|
|
assert.match(page, /\["failed", "skipped"\]\.includes\(result\.status\)/);
|
|
assert.match(page, /isApiError\(err, 409\)/);
|
|
assert.match(page, /scheduleExpiryRefresh/);
|
|
assert.doesNotMatch(page, /(?:localStorage|sessionStorage).*action_url|action_url.*(?:localStorage|sessionStorage)/);
|
|
|
|
assert.match(page, /submitSchedulingAvailability\(settings, selected\.id/);
|
|
assert.match(page, /option_revision: slot\.revision/);
|
|
assert.match(page, /getSchedulingAvailabilityResponse\(settings, selected\.id\)/);
|
|
assert.match(page, /const answers = Object\.fromEntries\(response\.answers\.map/);
|
|
assert.match(page, /setSavedAvailability\(answers\)/);
|
|
assert.match(page, /const comment = response\.comment \?\? ""/);
|
|
assert.match(page, /setSavedAvailabilityComment\(comment\)/);
|
|
assert.match(page, /<ParticipationStats/);
|
|
assert.match(page, /selected\.effective_participant_visibility === "names_and_statuses"/);
|
|
assert.doesNotMatch(page, /<p>\{selected\.calendar_id\}<\/p>/);
|
|
assert.match(page, /className="scheduling-selected-calendar"/);
|
|
|
|
for (const field of [
|
|
"notify_on_answers",
|
|
"single_choice",
|
|
"max_participants_per_option",
|
|
"allow_maybe",
|
|
"allow_comments",
|
|
"participant_email_required",
|
|
"anonymous_password_protection_enabled",
|
|
"public_participation_policy_enforcement_available",
|
|
"participant_invitation_delivery_available"
|
|
]) {
|
|
assert.match(api, new RegExp(`${field}:`));
|
|
}
|
|
assert.match(api, /method: "PATCH"/);
|
|
assert.match(api, /\/api\/v1\/scheduling\/people\?/);
|
|
assert.doesNotMatch(api, /address-lookup/);
|
|
assert.match(page, /slots: slots\.map\(\(slot\) => \(\{/);
|
|
assert.match(page, /participants: participants[\s\S]*create_participant_invitations: true/);
|
|
assert.match(api, /\/api\/v1\/scheduling\/requests\/\$\{requestId\}\/responses/);
|
|
assert.match(api, /\/api\/v1\/scheduling\/requests\/\$\{requestId\}\/responses\/me/);
|
|
assert.match(api, /issueSchedulingParticipantInvitation\([\s\S]*json\(\{ action, participant_revision: participantRevision \}\)/);
|
|
assert.match(api, /revokeSchedulingParticipantInvitation\([\s\S]*method: "DELETE"[\s\S]*participant_revision: participantRevision/);
|
|
assert.match(api, /participants\/\$\{encodeURIComponent\(participantId\)\}\/invitation/);
|
|
assert.match(api, /\/api\/v1\/scheduling\/public\/\$\{encodeURIComponent\(requestId\)\}\/\$\{encodeURIComponent\(token\)\}/);
|
|
assert.match(page, /useSearchParams\(\)/);
|
|
assert.match(page, /Promise\.allSettled/);
|
|
|
|
assert.match(moduleSource, /publicRoutes:[\s\S]*path: "\/scheduling\/public\/:requestId\/:token"/);
|
|
assert.match(moduleSource, /SchedulingPublicPage/);
|
|
assert.match(publicPage, /Card,[\s\S]*DismissibleAlert,[\s\S]*FormField,[\s\S]*LoadingFrame,[\s\S]*from "@govoplan\/core-webui"/);
|
|
assert.match(publicPage, /getPublicSchedulingParticipation\(settings, requestId, token, \{\}\)/);
|
|
assert.match(publicPage, /applySchedulingAvailabilityChoice\(/);
|
|
assert.match(publicPage, /option_revision: slot\.revision/);
|
|
assert.match(publicPage, /idempotency_key: newIdempotencyKey\(\)/);
|
|
assert.doesNotMatch(publicPage, /window\.(?:alert|confirm)\(/);
|
|
assert.doesNotMatch(publicPage, /(?:localStorage|sessionStorage).*token|token.*(?:localStorage|sessionStorage)/);
|
|
|
|
console.log("Scheduling pages satisfy the two-pane editor, public response, and policy contracts.");
|