@brandlab-digital/conformance 3.13.0 → 3.15.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.
- package/README.md +68 -2
- package/dist/analytics-e2e.d.ts +10 -16
- package/dist/analytics-e2e.d.ts.map +1 -1
- package/dist/analytics-e2e.js +61 -3
- package/dist/analytics-e2e.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +18 -2
- package/dist/engineering.js.map +1 -1
- package/dist/estate-snapshot.d.ts +54 -0
- package/dist/estate-snapshot.d.ts.map +1 -0
- package/dist/estate-snapshot.js +164 -0
- package/dist/estate-snapshot.js.map +1 -0
- package/dist/estate.d.ts +38 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +71 -28
- package/dist/estate.js.map +1 -1
- package/dist/media-snapshot.d.ts +53 -0
- package/dist/media-snapshot.d.ts.map +1 -0
- package/dist/media-snapshot.js +66 -0
- package/dist/media-snapshot.js.map +1 -0
- package/dist/project.d.ts +20 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +92 -2
- package/dist/project.js.map +1 -1
- package/dist/ready-cli.d.ts.map +1 -1
- package/dist/ready-cli.js +8 -3
- package/dist/ready-cli.js.map +1 -1
- package/dist/rule-registry.d.ts +5 -0
- package/dist/rule-registry.d.ts.map +1 -1
- package/dist/rule-registry.js +1 -0
- package/dist/rule-registry.js.map +1 -1
- package/dist/site-system/checks.d.ts +28 -3
- package/dist/site-system/checks.d.ts.map +1 -1
- package/dist/site-system/checks.js +5 -2
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +13 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +30 -1
- package/dist/site-system/config.js.map +1 -1
- package/dist/site-system/index.d.ts +1 -1
- package/dist/site-system/index.d.ts.map +1 -1
- package/dist/site-system/index.js +1 -1
- package/dist/site-system/index.js.map +1 -1
- package/dist/site-system/journeys/conversion.d.ts.map +1 -1
- package/dist/site-system/journeys/conversion.js +15 -2
- package/dist/site-system/journeys/conversion.js.map +1 -1
- package/dist/site-system/journeys/menu.d.ts.map +1 -1
- package/dist/site-system/journeys/menu.js +24 -1
- package/dist/site-system/journeys/menu.js.map +1 -1
- package/dist/site-system/journeys/page-helpers.d.ts.map +1 -1
- package/dist/site-system/journeys/page-helpers.js +1 -1
- package/dist/site-system/journeys/page-helpers.js.map +1 -1
- package/dist/site-system/journeys/plan.d.ts +13 -3
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +12 -2
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/shell.d.ts +1 -1
- package/dist/site-system/journeys/shell.js +2 -2
- package/dist/site-system/journeys/shell.js.map +1 -1
- package/dist/site-system/report.d.ts.map +1 -1
- package/dist/site-system/report.js +42 -8
- package/dist/site-system/report.js.map +1 -1
- package/dist/site-system/rule-modules.d.ts.map +1 -1
- package/dist/site-system/rule-modules.js +5 -0
- package/dist/site-system/rule-modules.js.map +1 -1
- package/dist/site-system/rules/delivery/h13-inbox.d.ts +23 -0
- package/dist/site-system/rules/delivery/h13-inbox.d.ts.map +1 -0
- package/dist/site-system/rules/delivery/h13-inbox.js +208 -0
- package/dist/site-system/rules/delivery/h13-inbox.js.map +1 -0
- package/dist/site-system/rules/delivery/index.d.ts +12 -0
- package/dist/site-system/rules/delivery/index.d.ts.map +1 -0
- package/dist/site-system/rules/delivery/index.js +7 -0
- package/dist/site-system/rules/delivery/index.js.map +1 -0
- package/dist/site-system/rules/form/c7-thank-you.d.ts +7 -1
- package/dist/site-system/rules/form/c7-thank-you.d.ts.map +1 -1
- package/dist/site-system/rules/form/c7-thank-you.js +26 -3
- package/dist/site-system/rules/form/c7-thank-you.js.map +1 -1
- package/dist/site-system/rules/form/index.d.ts.map +1 -1
- package/dist/site-system/rules/form/index.js +11 -1
- package/dist/site-system/rules/form/index.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +2 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -1
- package/dist/site-system/rules/full/common.js.map +1 -1
- package/dist/site-system/rules/full/e1-pattern.d.ts +4 -0
- package/dist/site-system/rules/full/e1-pattern.d.ts.map +1 -0
- package/dist/site-system/rules/full/e1-pattern.js +47 -0
- package/dist/site-system/rules/full/e1-pattern.js.map +1 -0
- package/dist/site-system/rules/full/estate.d.ts +26 -3
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +58 -9
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/geography.d.ts +15 -2
- package/dist/site-system/rules/full/geography.d.ts.map +1 -1
- package/dist/site-system/rules/full/geography.js +15 -2
- package/dist/site-system/rules/full/geography.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +4 -3
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +20 -5
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/k12-slots.d.ts +4 -0
- package/dist/site-system/rules/full/k12-slots.d.ts.map +1 -0
- package/dist/site-system/rules/full/k12-slots.js +40 -0
- package/dist/site-system/rules/full/k12-slots.js.map +1 -0
- package/dist/site-system/rules/full/l10-location-copy.d.ts +1 -1
- package/dist/site-system/rules/full/l10-location-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l10-location-copy.js +5 -2
- package/dist/site-system/rules/full/l10-location-copy.js.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.d.ts +3 -2
- package/dist/site-system/rules/full/l3-neighbours.d.ts.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.js +4 -1
- package/dist/site-system/rules/full/l3-neighbours.js.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts +2 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.js +8 -3
- package/dist/site-system/rules/full/l4-local-copy.js.map +1 -1
- package/dist/site-system/rules/full/l9-data-contract.js +2 -2
- package/dist/site-system/rules/full/l9-data-contract.js.map +1 -1
- package/dist/site-system/rules/full/links.d.ts +3 -1
- package/dist/site-system/rules/full/links.d.ts.map +1 -1
- package/dist/site-system/rules/full/links.js +15 -5
- package/dist/site-system/rules/full/links.js.map +1 -1
- package/dist/site-system/rules/full/loc-index.d.ts +5 -0
- package/dist/site-system/rules/full/loc-index.d.ts.map +1 -1
- package/dist/site-system/rules/full/loc-index.js +43 -2
- package/dist/site-system/rules/full/loc-index.js.map +1 -1
- package/dist/site-system/rules/full/route-kinds.d.ts +15 -0
- package/dist/site-system/rules/full/route-kinds.d.ts.map +1 -0
- package/dist/site-system/rules/full/route-kinds.js +52 -0
- package/dist/site-system/rules/full/route-kinds.js.map +1 -0
- package/dist/site-system/rules/full/strategy.d.ts +6 -0
- package/dist/site-system/rules/full/strategy.d.ts.map +1 -1
- package/dist/site-system/rules/full/strategy.js +98 -10
- package/dist/site-system/rules/full/strategy.js.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +2 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.js +13 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.d.ts.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.js +69 -5
- package/dist/site-system/rules/sections/t6-media-entries.js.map +1 -1
- package/dist/site-system/runners.js +1 -1
- package/dist/site-system/runners.js.map +1 -1
- package/dist/site-system-e2e.d.ts +1 -1
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +17 -4
- package/dist/site-system-e2e.js.map +1 -1
- package/package.json +10 -2
|
@@ -15,7 +15,20 @@ export interface SiteSystemSettings {
|
|
|
15
15
|
readonly phones: readonly string[];
|
|
16
16
|
/** Estate families allowed to refresh more often than every 7 days, with the reason and decision (W3, 3.13.0); absent when none. */
|
|
17
17
|
readonly refreshExceptions?: readonly SiteSystemRefreshException[];
|
|
18
|
+
/** What the thank-you page promises about a reply (C7, 3.15.0); absent when not recorded (a specific time is required). */
|
|
19
|
+
readonly replyTime?: SiteSystemReplyTime;
|
|
18
20
|
}
|
|
21
|
+
/** site-core `BrandlabReplyTimeRecord`: a specific reply time, or a recorded decision to promise none and the wording used instead. */
|
|
22
|
+
export type SiteSystemReplyTime = {
|
|
23
|
+
readonly promise: true;
|
|
24
|
+
} | {
|
|
25
|
+
readonly promise: false;
|
|
26
|
+
readonly wording: string;
|
|
27
|
+
readonly decidedBy: "owner" | "romie";
|
|
28
|
+
readonly decidedOn: string;
|
|
29
|
+
};
|
|
30
|
+
/** Wording that still promises a time (site-core `BRANDLAB_REPLY_TIME_PROMISE`): a no-promise record's wording must not match. */
|
|
31
|
+
export declare const SITE_SYSTEM_REPLY_TIME_PROMISE: RegExp;
|
|
19
32
|
/** The estate families check W3 covers (site-core `BRANDLAB_ESTATE_REFRESH_FAMILIES`). */
|
|
20
33
|
export declare const SITE_SYSTEM_ESTATE_FAMILIES: readonly ["suburb", "service-in-suburb", "region", "council"];
|
|
21
34
|
/** A recorded W3 exception: `family` may refresh as often as every `revalidateSeconds`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"AAQA,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,oIAAoI;IACpI,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,10 +7,12 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import ts from "typescript";
|
|
9
9
|
import { SITE_SYSTEM_TIERS } from "./checks.js";
|
|
10
|
+
/** Wording that still promises a time (site-core `BRANDLAB_REPLY_TIME_PROMISE`): a no-promise record's wording must not match. */
|
|
11
|
+
export const SITE_SYSTEM_REPLY_TIME_PROMISE = /\d|\b(?:minutes?|mins?|hours?|hrs?|today|tomorrow|overnight)\b|\b(?:same|next)[-\s]+(?:business[-\s]+|working[-\s]+)?day\b|\b(?:business|working)[-\s]+days?\b|\bend\s+of\s+(?:the\s+)?(?:business\s+)?day\b/i;
|
|
10
12
|
/** The estate families check W3 covers (site-core `BRANDLAB_ESTATE_REFRESH_FAMILIES`). */
|
|
11
13
|
export const SITE_SYSTEM_ESTATE_FAMILIES = ["suburb", "service-in-suburb", "region", "council"];
|
|
12
14
|
const CONFIG_FILE = "brandlab.config.ts";
|
|
13
|
-
const SITE_SYSTEM_KEYS = new Set(["tier", "newBuild", "samples", "labels", "freeQuotes", "thirdParties", "phones", "refreshExceptions"]);
|
|
15
|
+
const SITE_SYSTEM_KEYS = new Set(["tier", "newBuild", "samples", "labels", "freeQuotes", "thirdParties", "phones", "refreshExceptions", "replyTime"]);
|
|
14
16
|
const HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
15
17
|
const TEL_HREF_PATTERN = /^tel:\+?\d{6,15}$/;
|
|
16
18
|
const NOT_LITERAL = Symbol("not literal");
|
|
@@ -124,8 +126,35 @@ function readSiteSystem(value) {
|
|
|
124
126
|
thirdParties: stringList(value.thirdParties, "thirdParties", "hostnames such as www.youtube.com", (host) => HOSTNAME_PATTERN.test(host)),
|
|
125
127
|
phones: stringList(value.phones, "phones", "tel: links such as tel:+61290001234", (href) => TEL_HREF_PATTERN.test(href)),
|
|
126
128
|
...(value.refreshExceptions !== undefined ? { refreshExceptions: refreshExceptions(value.refreshExceptions) } : {}),
|
|
129
|
+
...(value.replyTime !== undefined ? { replyTime: replyTime(value.replyTime) } : {}),
|
|
127
130
|
};
|
|
128
131
|
}
|
|
132
|
+
/** The C7 reply-time record, as defineBrandlabProject validates it; anything else is a configuration error. */
|
|
133
|
+
function replyTime(value) {
|
|
134
|
+
const expected = `siteSystem.replyTime in ${CONFIG_FILE} must be { promise: true } or { promise: false, wording, decidedBy: "owner" | "romie", decidedOn: "YYYY-MM-DD" }, with wording that promises no time`;
|
|
135
|
+
if (!isRecord(value))
|
|
136
|
+
throw new Error(`${expected}.`);
|
|
137
|
+
if (value.promise === true && Object.keys(value).length === 1)
|
|
138
|
+
return { promise: true };
|
|
139
|
+
const { promise, wording, decidedBy, decidedOn } = value;
|
|
140
|
+
if (promise !== false ||
|
|
141
|
+
Object.keys(value).some((key) => !["promise", "wording", "decidedBy", "decidedOn"].includes(key)) ||
|
|
142
|
+
typeof wording !== "string" ||
|
|
143
|
+
!wording.trim() ||
|
|
144
|
+
SITE_SYSTEM_REPLY_TIME_PROMISE.test(wording) ||
|
|
145
|
+
(decidedBy !== "owner" && decidedBy !== "romie") ||
|
|
146
|
+
!isIsoDay(decidedOn)) {
|
|
147
|
+
throw new Error(`${expected}.`);
|
|
148
|
+
}
|
|
149
|
+
return { promise: false, wording, decidedBy, decidedOn: decidedOn };
|
|
150
|
+
}
|
|
151
|
+
/** A real YYYY-MM-DD calendar day. */
|
|
152
|
+
function isIsoDay(value) {
|
|
153
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
154
|
+
return false;
|
|
155
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
156
|
+
return new Date(Date.UTC(year, month - 1, day)).toISOString().slice(0, 10) === value;
|
|
157
|
+
}
|
|
129
158
|
/** The W3 exceptions, as defineBrandlabProject validates them; anything else is a configuration error. */
|
|
130
159
|
function refreshExceptions(value) {
|
|
131
160
|
const expected = `siteSystem.refreshExceptions in ${CONFIG_FILE} must be a literal list of { family, revalidateSeconds, reason, decisionId }`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the ready gate reads from brandlab.config.ts: the Site System tier, the exceptions record and the\n * representative routes. The file is parsed, never executed, so these fields must be written literally (as\n * `defineBrandlabProject` requires anyway).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ts from \"typescript\";\nimport { SITE_SYSTEM_TIERS, type SiteSystemTier } from \"./checks.js\";\n\nexport interface SiteSystemSettings {\n readonly tier: SiteSystemTier;\n /** A new build or redesign: D4 blocks whatever the tier. */\n readonly newBuild: boolean;\n /** Extra pages to sample for the rendered checks. */\n readonly samples: readonly string[];\n /** Quote and call-to-action labels recorded for this site, beyond the closed list (C5). */\n readonly labels: readonly string[];\n /** Whether the business offers free quotes; null when not recorded. */\n readonly freeQuotes: boolean | null;\n /** Third-party hostnames the site may load (B1). */\n readonly thirdParties: readonly string[];\n /** Extra tel: links the site legitimately shows (C10). */\n readonly phones: readonly string[];\n /** Estate families allowed to refresh more often than every 7 days, with the reason and decision (W3, 3.13.0); absent when none. */\n readonly refreshExceptions?: readonly SiteSystemRefreshException[];\n}\n\n/** The estate families check W3 covers (site-core `BRANDLAB_ESTATE_REFRESH_FAMILIES`). */\nexport const SITE_SYSTEM_ESTATE_FAMILIES = [\"suburb\", \"service-in-suburb\", \"region\", \"council\"] as const;\n\n/** A recorded W3 exception: `family` may refresh as often as every `revalidateSeconds`. */\nexport interface SiteSystemRefreshException {\n readonly family: (typeof SITE_SYSTEM_ESTATE_FAMILIES)[number];\n readonly revalidateSeconds: number;\n readonly reason: string;\n readonly decisionId: string;\n}\n\n/** A Romie-owned holdout; `ruleId` names the check it covers. */\nexport interface SiteSystemException {\n readonly ruleId: string;\n readonly rationale: string;\n readonly evidence: string;\n readonly expiresAt?: string;\n readonly reviewTrigger: string;\n}\n\nexport interface SiteSystemProjectConfig {\n readonly projectId: string | null;\n /** Null when the site has not adopted Site System v2: nothing in the gate blocks. */\n readonly siteSystem: SiteSystemSettings | null;\n readonly exceptions: readonly SiteSystemException[];\n /** `searchCertification.representativeRoutes` (home, contact, service, location, blog). */\n readonly representativeRoutes: Readonly<Record<string, string>>;\n /** `hosting` (3.13.0): absent, written literally (its value) or computed (unreadable). Optional for older callers. */\n readonly hosting?: SiteSystemHostingRecord;\n /** False when defineBrandlabProject({ ... }) spreads or computes a member, so a tier could be hidden. */\n readonly complete: boolean;\n /** Fields that could not be read; the gate reports them and carries on without them. */\n readonly problems: readonly string[];\n}\n\n/** What the gate read of `hosting` in brandlab.config.ts. */\nexport type SiteSystemHostingRecord =\n | { readonly state: \"absent\" }\n | { readonly state: \"literal\"; readonly value: unknown }\n | { readonly state: \"computed\" };\n\nconst CONFIG_FILE = \"brandlab.config.ts\";\nconst SITE_SYSTEM_KEYS = new Set([\"tier\", \"newBuild\", \"samples\", \"labels\", \"freeQuotes\", \"thirdParties\", \"phones\", \"refreshExceptions\"]);\nconst HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$/;\nconst TEL_HREF_PATTERN = /^tel:\\+?\\d{6,15}$/;\nconst NOT_LITERAL = Symbol(\"not literal\");\n\nexport function readSiteSystemProjectConfig(root: string): SiteSystemProjectConfig {\n const path = join(root, CONFIG_FILE);\n if (!existsSync(path)) throw new Error(`${CONFIG_FILE} is missing, so the ready gate cannot tell which tier applies.`);\n return parseSiteSystemProjectConfig(readFileSync(path, \"utf8\"));\n}\n\n/** Throws when siteSystem is present but unreadable or invalid: a declared tier must never be skipped silently. */\nexport function parseSiteSystemProjectConfig(text: string): SiteSystemProjectConfig {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n if (!config) {\n throw new Error(`${CONFIG_FILE} has no literal defineBrandlabProject({ ... }) call.`);\n }\n const problems: string[] = [];\n const complete = config.properties.every(isPlainProperty);\n if (!complete) {\n problems.push(`defineBrandlabProject({ ... }) in ${CONFIG_FILE} spreads or computes a member, so the ready gate cannot read it in full; write every field literally.`);\n }\n\n const projectId = literal(property(config, \"projectId\"));\n\n const siteSystemNode = property(config, \"siteSystem\");\n const siteSystem = siteSystemNode === undefined ? null : readSiteSystem(literal(siteSystemNode));\n\n const exceptionsNode = property(config, \"exceptions\");\n let exceptions: SiteSystemException[] = [];\n if (exceptionsNode !== undefined) {\n const value = literal(exceptionsNode);\n if (!Array.isArray(value)) {\n problems.push(`The exceptions in ${CONFIG_FILE} are not written as a literal list, so none were applied.`);\n } else {\n exceptions = value.flatMap((entry, index) => {\n const exception = readException(entry);\n if (!exception) problems.push(`exceptions[${index}] in ${CONFIG_FILE} has no literal ruleId, rationale, evidence and reviewTrigger, so it was not applied.`);\n return exception ? [exception] : [];\n });\n }\n }\n\n const searchCertification = literal(property(config, \"searchCertification\"));\n const routes = isRecord(searchCertification) ? searchCertification.representativeRoutes : undefined;\n const representativeRoutes: Record<string, string> = {};\n if (isRecord(routes)) {\n for (const [name, path] of Object.entries(routes)) {\n if (typeof path === \"string\" && path.startsWith(\"/\")) representativeRoutes[name] = path;\n }\n } else if (property(config, \"searchCertification\") !== undefined) {\n problems.push(`searchCertification.representativeRoutes in ${CONFIG_FILE} is not literal, so only the home page was sampled from it.`);\n }\n\n return {\n projectId: typeof projectId === \"string\" ? projectId : null,\n siteSystem,\n exceptions,\n representativeRoutes,\n hosting: hostingRecord(config),\n complete,\n problems,\n };\n}\n\n/**\n * `hosting` from brandlab.config.ts text, without throwing: absent when there is no literal defineBrandlabProject\n * call or no hosting member. Engineering checks and the ready gate (W5, W10) read it this way.\n */\nexport function parseProjectHosting(text: string): SiteSystemHostingRecord {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n return config ? hostingRecord(config) : { state: \"absent\" };\n}\n\nfunction hostingRecord(config: ts.ObjectLiteralExpression): SiteSystemHostingRecord {\n const node = property(config, \"hosting\");\n if (node === undefined) return { state: \"absent\" };\n const value = literal(node);\n return value === NOT_LITERAL ? { state: \"computed\" } : { state: \"literal\", value };\n}\n\n/** `key: value` or `key` with an identifier or string key; spreads, computed keys and methods are not. */\nfunction isPlainProperty(member: ts.ObjectLiteralElementLike): boolean {\n if (ts.isShorthandPropertyAssignment(member)) return !member.objectAssignmentInitializer;\n return ts.isPropertyAssignment(member) && (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name));\n}\n\nfunction readSiteSystem(value: unknown): SiteSystemSettings {\n if (value === NOT_LITERAL || !isRecord(value)) {\n throw new Error(`siteSystem in ${CONFIG_FILE} must be written as a literal object, for example siteSystem: { tier: 'shell' }.`);\n }\n const unknown = Object.keys(value).filter((key) => !SITE_SYSTEM_KEYS.has(key));\n if (unknown.length > 0) throw new Error(`siteSystem in ${CONFIG_FILE} contains unsupported keys: ${unknown.join(\", \")}.`);\n if (!SITE_SYSTEM_TIERS.includes(value.tier as SiteSystemTier)) {\n throw new Error(`siteSystem.tier in ${CONFIG_FILE} must be ${SITE_SYSTEM_TIERS.join(\", \")}.`);\n }\n if (value.newBuild !== undefined && typeof value.newBuild !== \"boolean\") {\n throw new Error(`siteSystem.newBuild in ${CONFIG_FILE} must be true or false.`);\n }\n if (\n value.samples !== undefined &&\n (!Array.isArray(value.samples) || value.samples.some((path) => typeof path !== \"string\" || !path.startsWith(\"/\")))\n ) {\n throw new Error(`siteSystem.samples in ${CONFIG_FILE} must be a literal list of root-relative paths.`);\n }\n if (value.freeQuotes !== undefined && typeof value.freeQuotes !== \"boolean\") {\n throw new Error(`siteSystem.freeQuotes in ${CONFIG_FILE} must be true or false.`);\n }\n return {\n tier: value.tier as SiteSystemTier,\n newBuild: value.newBuild === true,\n samples: (value.samples as string[] | undefined) ?? [],\n labels: stringList(value.labels, \"labels\", \"non-empty labels\", (label) => label.trim().length > 0),\n freeQuotes: (value.freeQuotes as boolean | undefined) ?? null,\n thirdParties: stringList(value.thirdParties, \"thirdParties\", \"hostnames such as www.youtube.com\", (host) => HOSTNAME_PATTERN.test(host)),\n phones: stringList(value.phones, \"phones\", \"tel: links such as tel:+61290001234\", (href) => TEL_HREF_PATTERN.test(href)),\n ...(value.refreshExceptions !== undefined ? { refreshExceptions: refreshExceptions(value.refreshExceptions) } : {}),\n };\n}\n\n/** The W3 exceptions, as defineBrandlabProject validates them; anything else is a configuration error. */\nfunction refreshExceptions(value: unknown): SiteSystemRefreshException[] {\n const expected = `siteSystem.refreshExceptions in ${CONFIG_FILE} must be a literal list of { family, revalidateSeconds, reason, decisionId }`;\n if (!Array.isArray(value)) throw new Error(`${expected}.`);\n const families = new Set<string>();\n return value.map((entry, index) => {\n if (!isRecord(entry) || Object.keys(entry).some((key) => ![\"family\", \"revalidateSeconds\", \"reason\", \"decisionId\"].includes(key))) {\n throw new Error(`${expected}; entry ${index} is not.`);\n }\n const { family, revalidateSeconds, reason, decisionId } = entry;\n if (\n !(SITE_SYSTEM_ESTATE_FAMILIES as readonly unknown[]).includes(family) ||\n !Number.isSafeInteger(revalidateSeconds) ||\n (revalidateSeconds as number) < 60 ||\n (revalidateSeconds as number) >= 604_800 ||\n typeof reason !== \"string\" ||\n !reason.trim() ||\n typeof decisionId !== \"string\" ||\n !decisionId.trim() ||\n families.has(family as string)\n ) {\n throw new Error(`${expected}; entry ${index} needs one of ${SITE_SYSTEM_ESTATE_FAMILIES.join(\", \")} (once each), whole seconds from 60 to 604799, a reason and a decision id.`);\n }\n families.add(family as string);\n return { family: family as SiteSystemRefreshException[\"family\"], revalidateSeconds: revalidateSeconds as number, reason, decisionId };\n });\n}\n\n/** A literal list of unique strings that each pass `valid`, or [] when absent. */\nfunction stringList(value: unknown, key: string, expected: string, valid: (item: string) => boolean): string[] {\n if (value === undefined) return [];\n if (\n !Array.isArray(value) ||\n value.some((item) => typeof item !== \"string\" || !valid(item)) ||\n new Set(value).size !== value.length\n ) {\n throw new Error(`siteSystem.${key} in ${CONFIG_FILE} must be a literal list of unique ${expected}.`);\n }\n return value as string[];\n}\n\nfunction readException(value: unknown): SiteSystemException | null {\n if (!isRecord(value)) return null;\n const { ruleId, rationale, evidence, expiresAt, reviewTrigger } = value;\n if (\n typeof ruleId !== \"string\" || !ruleId.trim() ||\n typeof rationale !== \"string\" ||\n typeof evidence !== \"string\" ||\n typeof reviewTrigger !== \"string\" ||\n (expiresAt !== undefined && typeof expiresAt !== \"string\")\n ) {\n return null;\n }\n return { ruleId, rationale, evidence, reviewTrigger, ...(expiresAt !== undefined ? { expiresAt } : {}) };\n}\n\nfunction findProjectObject(source: ts.SourceFile): ts.ObjectLiteralExpression | undefined {\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node) => {\n if (found) return;\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : null;\n const argument = node.arguments[0] ? unwrap(node.arguments[0]) : undefined;\n if (name === \"defineBrandlabProject\" && argument && ts.isObjectLiteralExpression(argument)) {\n found = argument;\n return;\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n}\n\nfunction property(object: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined {\n for (const member of object.properties) {\n if (ts.isShorthandPropertyAssignment(member) && member.name.text === name) return member.name;\n if (!ts.isPropertyAssignment(member)) continue;\n const key = member.name;\n if ((ts.isIdentifier(key) || ts.isStringLiteralLike(key)) && key.text === name) return member.initializer;\n }\n return undefined;\n}\n\nfunction unwrap(node: ts.Expression): ts.Expression {\n let current = node;\n while (\n ts.isParenthesizedExpression(current) ||\n ts.isAsExpression(current) ||\n ts.isSatisfiesExpression(current) ||\n ts.isNonNullExpression(current) ||\n ts.isTypeAssertionExpression(current)\n ) {\n current = current.expression;\n }\n return current;\n}\n\n/** The JSON value of a literal expression, or NOT_LITERAL when any part is computed. */\nfunction literal(node: ts.Expression | undefined): unknown {\n if (!node) return undefined;\n const value = unwrap(node);\n if (ts.isStringLiteralLike(value)) return value.text;\n if (ts.isNumericLiteral(value)) return Number(value.text);\n if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(value.operand)) {\n return -Number(value.operand.text);\n }\n if (value.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (value.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (value.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isArrayLiteralExpression(value)) {\n const items = value.elements.map((element) => (ts.isSpreadElement(element) ? NOT_LITERAL : literal(element)));\n return items.includes(NOT_LITERAL) ? NOT_LITERAL : items;\n }\n if (ts.isObjectLiteralExpression(value)) {\n const object: Record<string, unknown> = {};\n for (const member of value.properties) {\n if (!ts.isPropertyAssignment(member)) return NOT_LITERAL;\n const key = member.name;\n if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) return NOT_LITERAL;\n const item = literal(member.initializer);\n if (item === NOT_LITERAL) return NOT_LITERAL;\n object[key.text] = item;\n }\n return object;\n }\n return NOT_LITERAL;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the ready gate reads from brandlab.config.ts: the Site System tier, the exceptions record and the\n * representative routes. The file is parsed, never executed, so these fields must be written literally (as\n * `defineBrandlabProject` requires anyway).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ts from \"typescript\";\nimport { SITE_SYSTEM_TIERS, type SiteSystemTier } from \"./checks.js\";\n\nexport interface SiteSystemSettings {\n readonly tier: SiteSystemTier;\n /** A new build or redesign: D4 blocks whatever the tier. */\n readonly newBuild: boolean;\n /** Extra pages to sample for the rendered checks. */\n readonly samples: readonly string[];\n /** Quote and call-to-action labels recorded for this site, beyond the closed list (C5). */\n readonly labels: readonly string[];\n /** Whether the business offers free quotes; null when not recorded. */\n readonly freeQuotes: boolean | null;\n /** Third-party hostnames the site may load (B1). */\n readonly thirdParties: readonly string[];\n /** Extra tel: links the site legitimately shows (C10). */\n readonly phones: readonly string[];\n /** Estate families allowed to refresh more often than every 7 days, with the reason and decision (W3, 3.13.0); absent when none. */\n readonly refreshExceptions?: readonly SiteSystemRefreshException[];\n /** What the thank-you page promises about a reply (C7, 3.15.0); absent when not recorded (a specific time is required). */\n readonly replyTime?: SiteSystemReplyTime;\n}\n\n/** site-core `BrandlabReplyTimeRecord`: a specific reply time, or a recorded decision to promise none and the wording used instead. */\nexport type SiteSystemReplyTime =\n | { readonly promise: true }\n | { readonly promise: false; readonly wording: string; readonly decidedBy: \"owner\" | \"romie\"; readonly decidedOn: string };\n\n/** Wording that still promises a time (site-core `BRANDLAB_REPLY_TIME_PROMISE`): a no-promise record's wording must not match. */\nexport const SITE_SYSTEM_REPLY_TIME_PROMISE =\n /\\d|\\b(?:minutes?|mins?|hours?|hrs?|today|tomorrow|overnight)\\b|\\b(?:same|next)[-\\s]+(?:business[-\\s]+|working[-\\s]+)?day\\b|\\b(?:business|working)[-\\s]+days?\\b|\\bend\\s+of\\s+(?:the\\s+)?(?:business\\s+)?day\\b/i;\n\n/** The estate families check W3 covers (site-core `BRANDLAB_ESTATE_REFRESH_FAMILIES`). */\nexport const SITE_SYSTEM_ESTATE_FAMILIES = [\"suburb\", \"service-in-suburb\", \"region\", \"council\"] as const;\n\n/** A recorded W3 exception: `family` may refresh as often as every `revalidateSeconds`. */\nexport interface SiteSystemRefreshException {\n readonly family: (typeof SITE_SYSTEM_ESTATE_FAMILIES)[number];\n readonly revalidateSeconds: number;\n readonly reason: string;\n readonly decisionId: string;\n}\n\n/** A Romie-owned holdout; `ruleId` names the check it covers. */\nexport interface SiteSystemException {\n readonly ruleId: string;\n readonly rationale: string;\n readonly evidence: string;\n readonly expiresAt?: string;\n readonly reviewTrigger: string;\n}\n\nexport interface SiteSystemProjectConfig {\n readonly projectId: string | null;\n /** Null when the site has not adopted Site System v2: nothing in the gate blocks. */\n readonly siteSystem: SiteSystemSettings | null;\n readonly exceptions: readonly SiteSystemException[];\n /** `searchCertification.representativeRoutes` (home, contact, service, location, blog). */\n readonly representativeRoutes: Readonly<Record<string, string>>;\n /** `hosting` (3.13.0): absent, written literally (its value) or computed (unreadable). Optional for older callers. */\n readonly hosting?: SiteSystemHostingRecord;\n /** False when defineBrandlabProject({ ... }) spreads or computes a member, so a tier could be hidden. */\n readonly complete: boolean;\n /** Fields that could not be read; the gate reports them and carries on without them. */\n readonly problems: readonly string[];\n}\n\n/** What the gate read of `hosting` in brandlab.config.ts. */\nexport type SiteSystemHostingRecord =\n | { readonly state: \"absent\" }\n | { readonly state: \"literal\"; readonly value: unknown }\n | { readonly state: \"computed\" };\n\nconst CONFIG_FILE = \"brandlab.config.ts\";\nconst SITE_SYSTEM_KEYS = new Set([\"tier\", \"newBuild\", \"samples\", \"labels\", \"freeQuotes\", \"thirdParties\", \"phones\", \"refreshExceptions\", \"replyTime\"]);\nconst HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$/;\nconst TEL_HREF_PATTERN = /^tel:\\+?\\d{6,15}$/;\nconst NOT_LITERAL = Symbol(\"not literal\");\n\nexport function readSiteSystemProjectConfig(root: string): SiteSystemProjectConfig {\n const path = join(root, CONFIG_FILE);\n if (!existsSync(path)) throw new Error(`${CONFIG_FILE} is missing, so the ready gate cannot tell which tier applies.`);\n return parseSiteSystemProjectConfig(readFileSync(path, \"utf8\"));\n}\n\n/** Throws when siteSystem is present but unreadable or invalid: a declared tier must never be skipped silently. */\nexport function parseSiteSystemProjectConfig(text: string): SiteSystemProjectConfig {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n if (!config) {\n throw new Error(`${CONFIG_FILE} has no literal defineBrandlabProject({ ... }) call.`);\n }\n const problems: string[] = [];\n const complete = config.properties.every(isPlainProperty);\n if (!complete) {\n problems.push(`defineBrandlabProject({ ... }) in ${CONFIG_FILE} spreads or computes a member, so the ready gate cannot read it in full; write every field literally.`);\n }\n\n const projectId = literal(property(config, \"projectId\"));\n\n const siteSystemNode = property(config, \"siteSystem\");\n const siteSystem = siteSystemNode === undefined ? null : readSiteSystem(literal(siteSystemNode));\n\n const exceptionsNode = property(config, \"exceptions\");\n let exceptions: SiteSystemException[] = [];\n if (exceptionsNode !== undefined) {\n const value = literal(exceptionsNode);\n if (!Array.isArray(value)) {\n problems.push(`The exceptions in ${CONFIG_FILE} are not written as a literal list, so none were applied.`);\n } else {\n exceptions = value.flatMap((entry, index) => {\n const exception = readException(entry);\n if (!exception) problems.push(`exceptions[${index}] in ${CONFIG_FILE} has no literal ruleId, rationale, evidence and reviewTrigger, so it was not applied.`);\n return exception ? [exception] : [];\n });\n }\n }\n\n const searchCertification = literal(property(config, \"searchCertification\"));\n const routes = isRecord(searchCertification) ? searchCertification.representativeRoutes : undefined;\n const representativeRoutes: Record<string, string> = {};\n if (isRecord(routes)) {\n for (const [name, path] of Object.entries(routes)) {\n if (typeof path === \"string\" && path.startsWith(\"/\")) representativeRoutes[name] = path;\n }\n } else if (property(config, \"searchCertification\") !== undefined) {\n problems.push(`searchCertification.representativeRoutes in ${CONFIG_FILE} is not literal, so only the home page was sampled from it.`);\n }\n\n return {\n projectId: typeof projectId === \"string\" ? projectId : null,\n siteSystem,\n exceptions,\n representativeRoutes,\n hosting: hostingRecord(config),\n complete,\n problems,\n };\n}\n\n/**\n * `hosting` from brandlab.config.ts text, without throwing: absent when there is no literal defineBrandlabProject\n * call or no hosting member. Engineering checks and the ready gate (W5, W10) read it this way.\n */\nexport function parseProjectHosting(text: string): SiteSystemHostingRecord {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n return config ? hostingRecord(config) : { state: \"absent\" };\n}\n\nfunction hostingRecord(config: ts.ObjectLiteralExpression): SiteSystemHostingRecord {\n const node = property(config, \"hosting\");\n if (node === undefined) return { state: \"absent\" };\n const value = literal(node);\n return value === NOT_LITERAL ? { state: \"computed\" } : { state: \"literal\", value };\n}\n\n/** `key: value` or `key` with an identifier or string key; spreads, computed keys and methods are not. */\nfunction isPlainProperty(member: ts.ObjectLiteralElementLike): boolean {\n if (ts.isShorthandPropertyAssignment(member)) return !member.objectAssignmentInitializer;\n return ts.isPropertyAssignment(member) && (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name));\n}\n\nfunction readSiteSystem(value: unknown): SiteSystemSettings {\n if (value === NOT_LITERAL || !isRecord(value)) {\n throw new Error(`siteSystem in ${CONFIG_FILE} must be written as a literal object, for example siteSystem: { tier: 'shell' }.`);\n }\n const unknown = Object.keys(value).filter((key) => !SITE_SYSTEM_KEYS.has(key));\n if (unknown.length > 0) throw new Error(`siteSystem in ${CONFIG_FILE} contains unsupported keys: ${unknown.join(\", \")}.`);\n if (!SITE_SYSTEM_TIERS.includes(value.tier as SiteSystemTier)) {\n throw new Error(`siteSystem.tier in ${CONFIG_FILE} must be ${SITE_SYSTEM_TIERS.join(\", \")}.`);\n }\n if (value.newBuild !== undefined && typeof value.newBuild !== \"boolean\") {\n throw new Error(`siteSystem.newBuild in ${CONFIG_FILE} must be true or false.`);\n }\n if (\n value.samples !== undefined &&\n (!Array.isArray(value.samples) || value.samples.some((path) => typeof path !== \"string\" || !path.startsWith(\"/\")))\n ) {\n throw new Error(`siteSystem.samples in ${CONFIG_FILE} must be a literal list of root-relative paths.`);\n }\n if (value.freeQuotes !== undefined && typeof value.freeQuotes !== \"boolean\") {\n throw new Error(`siteSystem.freeQuotes in ${CONFIG_FILE} must be true or false.`);\n }\n return {\n tier: value.tier as SiteSystemTier,\n newBuild: value.newBuild === true,\n samples: (value.samples as string[] | undefined) ?? [],\n labels: stringList(value.labels, \"labels\", \"non-empty labels\", (label) => label.trim().length > 0),\n freeQuotes: (value.freeQuotes as boolean | undefined) ?? null,\n thirdParties: stringList(value.thirdParties, \"thirdParties\", \"hostnames such as www.youtube.com\", (host) => HOSTNAME_PATTERN.test(host)),\n phones: stringList(value.phones, \"phones\", \"tel: links such as tel:+61290001234\", (href) => TEL_HREF_PATTERN.test(href)),\n ...(value.refreshExceptions !== undefined ? { refreshExceptions: refreshExceptions(value.refreshExceptions) } : {}),\n ...(value.replyTime !== undefined ? { replyTime: replyTime(value.replyTime) } : {}),\n };\n}\n\n/** The C7 reply-time record, as defineBrandlabProject validates it; anything else is a configuration error. */\nfunction replyTime(value: unknown): SiteSystemReplyTime {\n const expected = `siteSystem.replyTime in ${CONFIG_FILE} must be { promise: true } or { promise: false, wording, decidedBy: \"owner\" | \"romie\", decidedOn: \"YYYY-MM-DD\" }, with wording that promises no time`;\n if (!isRecord(value)) throw new Error(`${expected}.`);\n if (value.promise === true && Object.keys(value).length === 1) return { promise: true };\n const { promise, wording, decidedBy, decidedOn } = value;\n if (\n promise !== false ||\n Object.keys(value).some((key) => ![\"promise\", \"wording\", \"decidedBy\", \"decidedOn\"].includes(key)) ||\n typeof wording !== \"string\" ||\n !wording.trim() ||\n SITE_SYSTEM_REPLY_TIME_PROMISE.test(wording) ||\n (decidedBy !== \"owner\" && decidedBy !== \"romie\") ||\n !isIsoDay(decidedOn)\n ) {\n throw new Error(`${expected}.`);\n }\n return { promise: false, wording, decidedBy, decidedOn: decidedOn as string };\n}\n\n/** A real YYYY-MM-DD calendar day. */\nfunction isIsoDay(value: unknown): boolean {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return false;\n const [year, month, day] = value.split(\"-\").map(Number) as [number, number, number];\n return new Date(Date.UTC(year, month - 1, day)).toISOString().slice(0, 10) === value;\n}\n\n/** The W3 exceptions, as defineBrandlabProject validates them; anything else is a configuration error. */\nfunction refreshExceptions(value: unknown): SiteSystemRefreshException[] {\n const expected = `siteSystem.refreshExceptions in ${CONFIG_FILE} must be a literal list of { family, revalidateSeconds, reason, decisionId }`;\n if (!Array.isArray(value)) throw new Error(`${expected}.`);\n const families = new Set<string>();\n return value.map((entry, index) => {\n if (!isRecord(entry) || Object.keys(entry).some((key) => ![\"family\", \"revalidateSeconds\", \"reason\", \"decisionId\"].includes(key))) {\n throw new Error(`${expected}; entry ${index} is not.`);\n }\n const { family, revalidateSeconds, reason, decisionId } = entry;\n if (\n !(SITE_SYSTEM_ESTATE_FAMILIES as readonly unknown[]).includes(family) ||\n !Number.isSafeInteger(revalidateSeconds) ||\n (revalidateSeconds as number) < 60 ||\n (revalidateSeconds as number) >= 604_800 ||\n typeof reason !== \"string\" ||\n !reason.trim() ||\n typeof decisionId !== \"string\" ||\n !decisionId.trim() ||\n families.has(family as string)\n ) {\n throw new Error(`${expected}; entry ${index} needs one of ${SITE_SYSTEM_ESTATE_FAMILIES.join(\", \")} (once each), whole seconds from 60 to 604799, a reason and a decision id.`);\n }\n families.add(family as string);\n return { family: family as SiteSystemRefreshException[\"family\"], revalidateSeconds: revalidateSeconds as number, reason, decisionId };\n });\n}\n\n/** A literal list of unique strings that each pass `valid`, or [] when absent. */\nfunction stringList(value: unknown, key: string, expected: string, valid: (item: string) => boolean): string[] {\n if (value === undefined) return [];\n if (\n !Array.isArray(value) ||\n value.some((item) => typeof item !== \"string\" || !valid(item)) ||\n new Set(value).size !== value.length\n ) {\n throw new Error(`siteSystem.${key} in ${CONFIG_FILE} must be a literal list of unique ${expected}.`);\n }\n return value as string[];\n}\n\nfunction readException(value: unknown): SiteSystemException | null {\n if (!isRecord(value)) return null;\n const { ruleId, rationale, evidence, expiresAt, reviewTrigger } = value;\n if (\n typeof ruleId !== \"string\" || !ruleId.trim() ||\n typeof rationale !== \"string\" ||\n typeof evidence !== \"string\" ||\n typeof reviewTrigger !== \"string\" ||\n (expiresAt !== undefined && typeof expiresAt !== \"string\")\n ) {\n return null;\n }\n return { ruleId, rationale, evidence, reviewTrigger, ...(expiresAt !== undefined ? { expiresAt } : {}) };\n}\n\nfunction findProjectObject(source: ts.SourceFile): ts.ObjectLiteralExpression | undefined {\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node) => {\n if (found) return;\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : null;\n const argument = node.arguments[0] ? unwrap(node.arguments[0]) : undefined;\n if (name === \"defineBrandlabProject\" && argument && ts.isObjectLiteralExpression(argument)) {\n found = argument;\n return;\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n}\n\nfunction property(object: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined {\n for (const member of object.properties) {\n if (ts.isShorthandPropertyAssignment(member) && member.name.text === name) return member.name;\n if (!ts.isPropertyAssignment(member)) continue;\n const key = member.name;\n if ((ts.isIdentifier(key) || ts.isStringLiteralLike(key)) && key.text === name) return member.initializer;\n }\n return undefined;\n}\n\nfunction unwrap(node: ts.Expression): ts.Expression {\n let current = node;\n while (\n ts.isParenthesizedExpression(current) ||\n ts.isAsExpression(current) ||\n ts.isSatisfiesExpression(current) ||\n ts.isNonNullExpression(current) ||\n ts.isTypeAssertionExpression(current)\n ) {\n current = current.expression;\n }\n return current;\n}\n\n/** The JSON value of a literal expression, or NOT_LITERAL when any part is computed. */\nfunction literal(node: ts.Expression | undefined): unknown {\n if (!node) return undefined;\n const value = unwrap(node);\n if (ts.isStringLiteralLike(value)) return value.text;\n if (ts.isNumericLiteral(value)) return Number(value.text);\n if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(value.operand)) {\n return -Number(value.operand.text);\n }\n if (value.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (value.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (value.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isArrayLiteralExpression(value)) {\n const items = value.elements.map((element) => (ts.isSpreadElement(element) ? NOT_LITERAL : literal(element)));\n return items.includes(NOT_LITERAL) ? NOT_LITERAL : items;\n }\n if (ts.isObjectLiteralExpression(value)) {\n const object: Record<string, unknown> = {};\n for (const member of value.properties) {\n if (!ts.isPropertyAssignment(member)) return NOT_LITERAL;\n const key = member.name;\n if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) return NOT_LITERAL;\n const item = literal(member.initializer);\n if (item === NOT_LITERAL) return NOT_LITERAL;\n object[key.text] = item;\n }\n return object;\n }\n return NOT_LITERAL;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* records and the ready report.
|
|
4
4
|
*/
|
|
5
5
|
export { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemCheckBlocks, type SiteSystemCheck, type SiteSystemCheckId, type SiteSystemCheckKind, type SiteSystemCheckScope, type SiteSystemFinding, type SiteSystemTier, } from "./checks.js";
|
|
6
|
-
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, type SiteSystemException, type SiteSystemHostingRecord, type SiteSystemProjectConfig, type SiteSystemSettings, } from "./config.js";
|
|
6
|
+
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, type SiteSystemException, type SiteSystemHostingRecord, SITE_SYSTEM_REPLY_TIME_PROMISE, type SiteSystemProjectConfig, type SiteSystemReplyTime, type SiteSystemSettings, } from "./config.js";
|
|
7
7
|
export { readSiteSystemJourneyReport, siteSystemCheckIdsInTitle, type SiteSystemJourneyResults, } from "./playwright-report.js";
|
|
8
8
|
export { appendSiteSystemRecord, latestSiteSystemRecords, readSiteSystemRecords, SITE_SYSTEM_RECORD_PATHS, SITE_SYSTEM_RECORDS_SCHEMA, type SiteSystemRecord, type SiteSystemRecordsDocument, } from "./records.js";
|
|
9
9
|
export { buildSiteSystemReadyReport, isExceptionExpired, renderSiteSystemReadyMarkdown, SITE_SYSTEM_READY_REPORT_PATHS, SITE_SYSTEM_READY_SCHEMA, type BuildSiteSystemReadyReportInput, type SiteSystemReadyCheck, type SiteSystemReadyException, type SiteSystemReadyInputs, type SiteSystemReadyReport, type SiteSystemReadyStatus, type SiteSystemReadyStep, type SiteSystemReadyVerdict, type SiteSystemVerifyResult, } from "./report.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,8BAA8B,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* records and the ready report.
|
|
4
4
|
*/
|
|
5
5
|
export { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemCheckBlocks, } from "./checks.js";
|
|
6
|
-
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, } from "./config.js";
|
|
6
|
+
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, SITE_SYSTEM_REPLY_TIME_PROMISE, } from "./config.js";
|
|
7
7
|
export { readSiteSystemJourneyReport, siteSystemCheckIdsInTitle, } from "./playwright-report.js";
|
|
8
8
|
export { appendSiteSystemRecord, latestSiteSystemRecords, readSiteSystemRecords, SITE_SYSTEM_RECORD_PATHS, SITE_SYSTEM_RECORDS_SCHEMA, } from "./records.js";
|
|
9
9
|
export { buildSiteSystemReadyReport, isExceptionExpired, renderSiteSystemReadyMarkdown, SITE_SYSTEM_READY_REPORT_PATHS, SITE_SYSTEM_READY_SCHEMA, } from "./report.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GAOtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GAOtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAG3B,8BAA8B,GAI/B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAE1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,GAUzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,GAG7B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,GAIpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,GAKrB,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The Site System v2 ready gate: the check registry, the rule-module interface `brandlab-ready` runs, the step\n * records and the ready report.\n */\nexport {\n SITE_SYSTEM_CHECKS,\n SITE_SYSTEM_TIERS,\n siteSystemCheckBlocks,\n type SiteSystemCheck,\n type SiteSystemCheckId,\n type SiteSystemCheckKind,\n type SiteSystemCheckScope,\n type SiteSystemFinding,\n type SiteSystemTier,\n} from \"./checks.js\";\nexport {\n parseProjectHosting,\n parseSiteSystemProjectConfig,\n readSiteSystemProjectConfig,\n type SiteSystemException,\n type SiteSystemHostingRecord,\n SITE_SYSTEM_REPLY_TIME_PROMISE,\n type SiteSystemProjectConfig,\n type SiteSystemReplyTime,\n type SiteSystemSettings,\n} from \"./config.js\";\nexport {\n readSiteSystemJourneyReport,\n siteSystemCheckIdsInTitle,\n type SiteSystemJourneyResults,\n} from \"./playwright-report.js\";\nexport {\n appendSiteSystemRecord,\n latestSiteSystemRecords,\n readSiteSystemRecords,\n SITE_SYSTEM_RECORD_PATHS,\n SITE_SYSTEM_RECORDS_SCHEMA,\n type SiteSystemRecord,\n type SiteSystemRecordsDocument,\n} from \"./records.js\";\nexport {\n buildSiteSystemReadyReport,\n isExceptionExpired,\n renderSiteSystemReadyMarkdown,\n SITE_SYSTEM_READY_REPORT_PATHS,\n SITE_SYSTEM_READY_SCHEMA,\n type BuildSiteSystemReadyReportInput,\n type SiteSystemReadyCheck,\n type SiteSystemReadyException,\n type SiteSystemReadyInputs,\n type SiteSystemReadyReport,\n type SiteSystemReadyStatus,\n type SiteSystemReadyStep,\n type SiteSystemReadyVerdict,\n type SiteSystemVerifyResult,\n} from \"./report.js\";\nexport { SITE_SYSTEM_RENDERED_RUNNERS } from \"./rule-modules.js\";\nexport {\n isSiteSystemFinding,\n runSiteSystemRenderedRunners,\n type SiteSystemRenderedInput,\n type SiteSystemRenderedRunner,\n} from \"./runners.js\";\nexport {\n builtPageFile,\n loadSiteSystemPages,\n planSiteSystemSamplePaths,\n resolveStreamedHtml,\n type SiteSystemFetch,\n type SiteSystemPage,\n type SiteSystemPageLoad,\n} from \"./samples.js\";\nexport {\n SITE_SYSTEM_HUMAN_STEPS,\n SITE_SYSTEM_LAUNCH_STEPS,\n SITE_SYSTEM_RECORDED_CHECK_IDS,\n siteSystemRecordFile,\n type SiteSystemHumanStepId,\n type SiteSystemLaunchStepId,\n type SiteSystemRecordFile,\n type SiteSystemStep,\n} from \"./steps.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversion.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/conversion.ts"],"names":[],"mappings":"AAWA,OAAO,EAA6F,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAMlJ,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"conversion.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/conversion.ts"],"names":[],"mappings":"AAWA,OAAO,EAA6F,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAMlJ,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAsIrG"}
|
|
@@ -88,14 +88,23 @@ export function defineConversionJourneys(plan, runs) {
|
|
|
88
88
|
let analytics;
|
|
89
89
|
let taps = 0;
|
|
90
90
|
let declared = 0;
|
|
91
|
+
const skipped = [];
|
|
91
92
|
const tap = async (mark, link, where) => {
|
|
92
93
|
declared += 1;
|
|
93
94
|
if (link.invalid)
|
|
94
95
|
problems.push(`${where}: ${link.description} declares data-phone-location "${link.invalid}", which is not an allowed placement (${SITE_SYSTEM_PHONE_PLACEMENTS.join(', ')}); its taps are reported as ${link.placement}`);
|
|
95
96
|
if (!analytics)
|
|
96
97
|
return;
|
|
98
|
+
// An earlier tap can hide a link that showed when the page was read (a footer tap scrolls the footer in and
|
|
99
|
+
// the call bar hides over it): a link nobody can tap now is left out, not waited on.
|
|
100
|
+
const target = page.locator(`[data-brandlab-journey~="${mark}"]`).first();
|
|
101
|
+
const tappable = () => target.evaluate((element) => window.__brandlabSiteSystem.isRendered(element) && !element.closest('[inert]')).catch(() => false);
|
|
102
|
+
if (!(await waitUntil(tappable, 1_000))) {
|
|
103
|
+
skipped.push(`${where}: ${link.description}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
97
106
|
const before = analyticsEvents(commands, 'phone_link_click').length;
|
|
98
|
-
await
|
|
107
|
+
await target.click();
|
|
99
108
|
await waitUntil(async () => analyticsEvents(commands, 'phone_link_click').length > before, 2_000);
|
|
100
109
|
const sent = analyticsEvents(commands, 'phone_link_click').slice(before);
|
|
101
110
|
taps += 1;
|
|
@@ -116,7 +125,9 @@ export function defineConversionJourneys(plan, runs) {
|
|
|
116
125
|
const where = `${viewport.width} px`;
|
|
117
126
|
const links = await page.evaluate(() => {
|
|
118
127
|
const helpers = window.__brandlabSiteSystem;
|
|
119
|
-
|
|
128
|
+
// Below 1024 px the call bar's links are tapped in the call-bar step, which scrolls until the bar shows.
|
|
129
|
+
const bar = window.innerWidth < 1024 ? helpers.findCallBar() : null;
|
|
130
|
+
return Array.from(document.querySelectorAll('a[href^="tel:"]')).filter((link) => helpers.isRendered(link) && !bar?.contains(link)).map((link, index) => {
|
|
120
131
|
helpers.markElement(link, `tel-${index}`);
|
|
121
132
|
return { mark: `tel-${index}`, description: helpers.describeElement(link), ...helpers.phonePlacement(link) };
|
|
122
133
|
});
|
|
@@ -144,6 +155,8 @@ export function defineConversionJourneys(plan, runs) {
|
|
|
144
155
|
note(testInfo, `${path}: the build sends no analytics (no Google tag ran), so the declared placements were checked without tapping; build the e2e target with a test NEXT_PUBLIC_GA_MEASUREMENT_ID to check the events`);
|
|
145
156
|
else
|
|
146
157
|
note(testInfo, `${path}: ${taps} phone tap(s) checked against their phone_link_click events`);
|
|
158
|
+
if (skipped.length > 0)
|
|
159
|
+
note(testInfo, `${path}: not tapped, hidden by the time their turn came (their declared placements were checked): ${skipped.join('; ')}`);
|
|
147
160
|
expect(problems).toEqual([]);
|
|
148
161
|
});
|
|
149
162
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversion.js","sourceRoot":"","sources":["../../../src/site-system/journeys/conversion.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,sBAAsB,IAAI,KAAK,EAA8B,MAAM,WAAW,CAAC;AAClJ,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,2GAA2G;AAC3G,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,MAAM,UAAU,wBAAwB,CAAC,IAA2B,EAAE,IAAyB;IAC7F,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAE7E,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;YAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,2BAA2B,CAAC,EAAE,GAAG,EAAE;oBAClD,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAChC,CAAC,CAAC,CAAC;YACL,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,6GAA6G,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;oBACzJ,MAAM,QAAQ,GAAa,EAAE,CAAC;oBAC9B,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,0BAA0B,EAAE,CAAC,CAAC;oBAC9D,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;wBACzC,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC;wBAC5C,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;wBACjD,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,EAAE;4BAClC,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;4BAC7C,OAAO,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;wBAC7G,CAAC,CAAC;wBACF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;wBACrF,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;wBACxE,OAAO;4BACL,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,MAAM,CAAC,WAAW;4BAC3G,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;4BACvH,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;yBAC9D,CAAC;oBACJ,CAAC,EAAE,KAAK,CAAC,CAAC;oBACV,IAAI,CAAC,KAAK,CAAC,OAAO;wBAAE,QAAQ,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;oBAC1F,IAAI,CAAC,KAAK,CAAC,IAAI;wBAAE,QAAQ,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;oBAChF,IAAI,CAAC,KAAK,CAAC,KAAK;wBAAE,QAAQ,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;oBAClF,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,4BAA4B,EAAE,CAAC,CAAC;oBAChE,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;wBACpC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;wBACjD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC,OAAO,CAAC;+BAC1H,OAAO,CAAC,aAAa,CAAC,kDAAkD,CAAC,KAAK,IAAI,CAAC,CAAC;wBACzF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;wBACzG,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,4BAA4B,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;6BAC9G,MAAM,CAAC,CAAC,OAAO,EAAsB,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;wBAC5F,OAAO;4BACL,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC;4BAC3F,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;yBAC5F,CAAC;oBACJ,CAAC,CAAC,CAAC;oBACH,IAAI,CAAC,IAAI,CAAC,QAAQ;wBAAE,QAAQ,CAAC,IAAI,CAAC,wFAAwF,CAAC,CAAC;yBACvH,IAAI,CAAC,IAAI,CAAC,cAAc;wBAAE,QAAQ,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;oBACnH,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC/B,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO;IAE5B,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QACrC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,iBAAiB,IAAI,4BAA4B,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE;gBAChG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChC,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC;gBACrD,kEAAkE;gBAClE,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBACxE,IAAK,KAAK,CAAC,MAAyB,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC;wBAAE,KAAK,CAAC,cAAc,EAAE,CAAC;gBAC7F,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;gBACV,MAAM,QAAQ,GAAa,EAAE,CAAC;gBAC9B,IAAI,SAA8B,CAAC;gBACnC,IAAI,IAAI,GAAG,CAAC,CAAC;gBACb,IAAI,QAAQ,GAAG,CAAC,CAAC;gBACjB,MAAM,GAAG,GAAG,KAAK,EAAE,IAAY,EAAE,IAAwE,EAAE,KAAa,EAAE,EAAE;oBAC1H,QAAQ,IAAI,CAAC,CAAC;oBACd,IAAI,IAAI,CAAC,OAAO;wBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,WAAW,kCAAkC,IAAI,CAAC,OAAO,yCAAyC,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;oBAC5O,IAAI,CAAC,SAAS;wBAAE,OAAO;oBACvB,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,MAAM,CAAC;oBACpE,MAAM,IAAI,CAAC,OAAO,CAAC,4BAA4B,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC;oBACzE,MAAM,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC;oBAClG,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACzE,IAAI,IAAI,CAAC,CAAC;oBACV,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,cAAc,IAAI,CAAC,WAAW,SAAS,IAAI,CAAC,MAAM,4BAA4B,CAAC,CAAC;oBAC7H,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;oBAC1D,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,SAAS,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,cAAc,IAAI,CAAC,WAAW,wBAAwB,SAAS,yBAAyB,CAAC,CAAC;yBACrL,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,KAAK,IAAI,CAAC,SAAS;wBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,cAAc,IAAI,CAAC,WAAW,wBAAwB,SAAS,wBAAwB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;oBAC1L,MAAM,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;gBAC9C,CAAC,CAAC;gBACF,KAAK,MAAM,QAAQ,IAAI,CAAC,4BAA4B,EAAE,0BAA0B,CAAC,EAAE,CAAC;oBAClF,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;oBAC5C,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;oBACvB,SAAS,KAAK,MAAM,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;oBAClG,MAAM,KAAK,GAAG,GAAG,QAAQ,CAAC,KAAK,KAAK,CAAC;oBACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;wBACrC,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC;wBAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;4BAC7G,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,KAAK,EAAE,CAAC,CAAC;4BAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC/G,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;oBACH,KAAK,MAAM,IAAI,IAAI,KAAK;wBAAE,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC5D,IAAI,QAAQ,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC;wBAC1B,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;wBACrC,IAAI,GAAG,EAAE,CAAC;4BACR,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;gCACrC,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC;gCAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,qCAAqC,CAAC,CAAC;gCAC3E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;oCAChH,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,KAAK,EAAE,CAAC,CAAC;oCAC9C,OAAO,WAAW,KAAK,EAAE,CAAC;gCAC5B,CAAC,CAAC,CAAC;4BACL,CAAC,CAAC,CAAC;4BACH,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE;gCAAE,MAAM,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAE,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC;wBACjG,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,MAAM,CAAC,QAAQ,EAAE,GAAG,IAAI,sBAAsB,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;gBACnE,IAAI,CAAC,SAAS;oBAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,iMAAiM,CAAC,CAAC;;oBACpO,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,KAAK,IAAI,6DAA6D,CAAC,CAAC;gBACnG,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * C1 and C4. C1 (enquiry tier and above): on each money page the H1, a call action and a quote action show in the\n * first 390 x 844 screen, and the enquiry form shows in the first 1440 x 900 screen. C4: every phone tap reports an\n * allowed placement. Where the build renders the Google tag (a test measurement ID), each visible phone link is\n * tapped and its `phone_link_click` must carry the placement the link declares; without the tag the declared\n * placements are checked and the run says so (the analytics journey fails a build without its tag).\n */\nimport { expect, test } from '@playwright/test';\nimport { analyticsEvents, recordAnalyticsCommands } from '../../analytics-e2e.js';\nimport { note, openSitePage, raiseTimeout, siteHeader, waitUntil } from './browser.js';\nimport { SITE_SYSTEM_PHONE_PLACEMENTS } from './page-helpers.js';\nimport { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT, siteSystemJourneyTitle as title, type SiteSystemJourneyPlan } from './plan.js';\nimport { callBarLinks } from './shell.js';\n\n/** The shared listener ignores a second tap on the same number within 1.2 s; taps wait this long apart. */\nconst PHONE_TAP_GAP_MS = 1_500;\n\nexport function defineConversionJourneys(plan: SiteSystemJourneyPlan, runs: ReadonlySet<string>): void {\n const quote = { source: plan.quoteName.source, flags: plan.quoteName.flags };\n\n if (runs.has('C1')) {\n test.describe('money pages', () => {\n if (plan.problems.C1) {\n const problem = plan.problems.C1;\n test(title('C1', 'the money pages are named'), () => {\n expect([problem]).toEqual([]);\n });\n }\n for (const path of plan.money) {\n test(title('C1', `${path} shows the H1, a call and a quote action in the first phone screen and the form in the first desktop screen`), async ({ page }) => {\n const problems: string[] = [];\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await openSitePage(page, path, plan);\n const phone = await page.evaluate((name) => {\n const helpers = window.__brandlabSiteSystem;\n window.scrollTo({ top: 0, behavior: 'instant' });\n const inView = (element: Element) => {\n const rect = element.getBoundingClientRect();\n return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;\n };\n const heading = Array.from(document.querySelectorAll('h1')).find(helpers.isRendered);\n const headingTop = heading ? heading.getBoundingClientRect().top : null;\n return {\n heading: heading !== undefined && headingTop !== null && headingTop >= 0 && headingTop < window.innerHeight,\n call: Array.from(document.querySelectorAll('a[href^=\"tel:\"]')).some((link) => helpers.isRendered(link) && inView(link)),\n quote: helpers.quoteActions(document.body, name).some(inView),\n };\n }, quote);\n if (!phone.heading) problems.push('390 x 844: the H1 does not start in the first screen');\n if (!phone.call) problems.push('390 x 844: no call action in the first screen');\n if (!phone.quote) problems.push('390 x 844: no quote action in the first screen');\n await page.setViewportSize({ ...SITE_SYSTEM_DESKTOP_VIEWPORT });\n await openSitePage(page, path, plan);\n const form = await page.evaluate(() => {\n window.scrollTo({ top: 0, behavior: 'instant' });\n const forms = Array.from(document.querySelectorAll('form')).filter((element) => window.__brandlabSiteSystem.isRendered(element)\n && element.querySelector('input[type=\"tel\"], input[type=\"email\"], textarea') !== null);\n const heading = Array.from(document.querySelectorAll('h1')).find(window.__brandlabSiteSystem.isRendered);\n const first = [heading?.closest('section, [data-bl-section]'), document.querySelector('main')?.firstElementChild]\n .filter((element): element is Element => Boolean(element) && element!.tagName !== 'MAIN');\n return {\n inScreen: forms.some((element) => element.getBoundingClientRect().top < window.innerHeight),\n inFirstSection: forms.some((element) => first.some((section) => section.contains(element))),\n };\n });\n if (!form.inScreen) problems.push('1440 x 900: no enquiry form (with a phone, email or message field) in the first screen');\n else if (!form.inFirstSection) problems.push('the enquiry form is not in the hero or the first section of <main>');\n expect(problems).toEqual([]);\n });\n }\n });\n }\n\n if (!runs.has('C4')) return;\n\n test.describe('phone placements', () => {\n for (const path of plan.sampled) {\n test(title('C4', `phone taps on ${path} report allowed placements`), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n const commands = await recordAnalyticsCommands(page);\n // Keep the page in place; the shared listener still sees the tap.\n await page.addInitScript(() => window.addEventListener('click', (event) => {\n if ((event.target as Element | null)?.closest?.('a[href^=\"tel:\"]')) event.preventDefault();\n }, true));\n const problems: string[] = [];\n let analytics: boolean | undefined;\n let taps = 0;\n let declared = 0;\n const tap = async (mark: string, link: { description: string; placement: string; invalid: string | null }, where: string) => {\n declared += 1;\n if (link.invalid) problems.push(`${where}: ${link.description} declares data-phone-location \"${link.invalid}\", which is not an allowed placement (${SITE_SYSTEM_PHONE_PLACEMENTS.join(', ')}); its taps are reported as ${link.placement}`);\n if (!analytics) return;\n const before = analyticsEvents(commands, 'phone_link_click').length;\n await page.locator(`[data-brandlab-journey~=\"${mark}\"]`).first().click();\n await waitUntil(async () => analyticsEvents(commands, 'phone_link_click').length > before, 2_000);\n const sent = analyticsEvents(commands, 'phone_link_click').slice(before);\n taps += 1;\n if (sent.length !== 1) problems.push(`${where}: a tap on ${link.description} sent ${sent.length} phone_link_click event(s)`);\n const placement = String(sent[0]?.params.placement ?? '');\n if (sent.length > 0 && !SITE_SYSTEM_PHONE_PLACEMENTS.includes(placement)) problems.push(`${where}: a tap on ${link.description} reported placement \"${placement}\", which is not allowed`);\n else if (sent.length > 0 && placement !== link.placement) problems.push(`${where}: a tap on ${link.description} reported placement \"${placement}\" (the link declares ${link.placement})`);\n await page.waitForTimeout(PHONE_TAP_GAP_MS);\n };\n for (const viewport of [SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT]) {\n await page.setViewportSize({ ...viewport });\n await openSitePage(page, path, plan);\n await siteHeader(page);\n analytics ??= await waitUntil(async () => commands.some((command) => command[0] === 'js'), 3_000);\n const where = `${viewport.width} px`;\n const links = await page.evaluate(() => {\n const helpers = window.__brandlabSiteSystem;\n return Array.from(document.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered).map((link, index) => {\n helpers.markElement(link, `tel-${index}`);\n return { mark: `tel-${index}`, description: helpers.describeElement(link), ...helpers.phonePlacement(link) };\n });\n });\n for (const link of links) await tap(link.mark, link, where);\n if (viewport.width < 1024) {\n const bar = await callBarLinks(page);\n if (bar) {\n const marks = await page.evaluate(() => {\n const helpers = window.__brandlabSiteSystem;\n const root = document.querySelector('[data-brandlab-journey~=\"call-bar\"]');\n return Array.from(root?.querySelectorAll('a[href^=\"tel:\"]') ?? []).filter(helpers.isRendered).map((link, index) => {\n helpers.markElement(link, `bar-tel-${index}`);\n return `bar-tel-${index}`;\n });\n });\n for (const [index, mark] of marks.entries()) await tap(mark, bar[index]!, `${where} call bar`);\n }\n }\n }\n expect(declared, `${path} shows no phone link`).toBeGreaterThan(0);\n if (!analytics) note(testInfo, `${path}: the build sends no analytics (no Google tag ran), so the declared placements were checked without tapping; build the e2e target with a test NEXT_PUBLIC_GA_MEASUREMENT_ID to check the events`);\n else note(testInfo, `${path}: ${taps} phone tap(s) checked against their phone_link_click events`);\n expect(problems).toEqual([]);\n });\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conversion.js","sourceRoot":"","sources":["../../../src/site-system/journeys/conversion.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * C1 and C4. C1 (enquiry tier and above): on each money page the H1, a call action and a quote action show in the\n * first 390 x 844 screen, and the enquiry form shows in the first 1440 x 900 screen. C4: every phone tap reports an\n * allowed placement. Where the build renders the Google tag (a test measurement ID), each visible phone link is\n * tapped and its `phone_link_click` must carry the placement the link declares; without the tag the declared\n * placements are checked and the run says so (the analytics journey fails a build without its tag).\n */\nimport { expect, test } from '@playwright/test';\nimport { analyticsEvents, recordAnalyticsCommands } from '../../analytics-e2e.js';\nimport { note, openSitePage, raiseTimeout, siteHeader, waitUntil } from './browser.js';\nimport { SITE_SYSTEM_PHONE_PLACEMENTS } from './page-helpers.js';\nimport { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT, siteSystemJourneyTitle as title, type SiteSystemJourneyPlan } from './plan.js';\nimport { callBarLinks } from './shell.js';\n\n/** The shared listener ignores a second tap on the same number within 1.2 s; taps wait this long apart. */\nconst PHONE_TAP_GAP_MS = 1_500;\n\nexport function defineConversionJourneys(plan: SiteSystemJourneyPlan, runs: ReadonlySet<string>): void {\n const quote = { source: plan.quoteName.source, flags: plan.quoteName.flags };\n\n if (runs.has('C1')) {\n test.describe('money pages', () => {\n if (plan.problems.C1) {\n const problem = plan.problems.C1;\n test(title('C1', 'the money pages are named'), () => {\n expect([problem]).toEqual([]);\n });\n }\n for (const path of plan.money) {\n test(title('C1', `${path} shows the H1, a call and a quote action in the first phone screen and the form in the first desktop screen`), async ({ page }) => {\n const problems: string[] = [];\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await openSitePage(page, path, plan);\n const phone = await page.evaluate((name) => {\n const helpers = window.__brandlabSiteSystem;\n window.scrollTo({ top: 0, behavior: 'instant' });\n const inView = (element: Element) => {\n const rect = element.getBoundingClientRect();\n return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;\n };\n const heading = Array.from(document.querySelectorAll('h1')).find(helpers.isRendered);\n const headingTop = heading ? heading.getBoundingClientRect().top : null;\n return {\n heading: heading !== undefined && headingTop !== null && headingTop >= 0 && headingTop < window.innerHeight,\n call: Array.from(document.querySelectorAll('a[href^=\"tel:\"]')).some((link) => helpers.isRendered(link) && inView(link)),\n quote: helpers.quoteActions(document.body, name).some(inView),\n };\n }, quote);\n if (!phone.heading) problems.push('390 x 844: the H1 does not start in the first screen');\n if (!phone.call) problems.push('390 x 844: no call action in the first screen');\n if (!phone.quote) problems.push('390 x 844: no quote action in the first screen');\n await page.setViewportSize({ ...SITE_SYSTEM_DESKTOP_VIEWPORT });\n await openSitePage(page, path, plan);\n const form = await page.evaluate(() => {\n window.scrollTo({ top: 0, behavior: 'instant' });\n const forms = Array.from(document.querySelectorAll('form')).filter((element) => window.__brandlabSiteSystem.isRendered(element)\n && element.querySelector('input[type=\"tel\"], input[type=\"email\"], textarea') !== null);\n const heading = Array.from(document.querySelectorAll('h1')).find(window.__brandlabSiteSystem.isRendered);\n const first = [heading?.closest('section, [data-bl-section]'), document.querySelector('main')?.firstElementChild]\n .filter((element): element is Element => Boolean(element) && element!.tagName !== 'MAIN');\n return {\n inScreen: forms.some((element) => element.getBoundingClientRect().top < window.innerHeight),\n inFirstSection: forms.some((element) => first.some((section) => section.contains(element))),\n };\n });\n if (!form.inScreen) problems.push('1440 x 900: no enquiry form (with a phone, email or message field) in the first screen');\n else if (!form.inFirstSection) problems.push('the enquiry form is not in the hero or the first section of <main>');\n expect(problems).toEqual([]);\n });\n }\n });\n }\n\n if (!runs.has('C4')) return;\n\n test.describe('phone placements', () => {\n for (const path of plan.sampled) {\n test(title('C4', `phone taps on ${path} report allowed placements`), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n const commands = await recordAnalyticsCommands(page);\n // Keep the page in place; the shared listener still sees the tap.\n await page.addInitScript(() => window.addEventListener('click', (event) => {\n if ((event.target as Element | null)?.closest?.('a[href^=\"tel:\"]')) event.preventDefault();\n }, true));\n const problems: string[] = [];\n let analytics: boolean | undefined;\n let taps = 0;\n let declared = 0;\n const skipped: string[] = [];\n const tap = async (mark: string, link: { description: string; placement: string; invalid: string | null }, where: string) => {\n declared += 1;\n if (link.invalid) problems.push(`${where}: ${link.description} declares data-phone-location \"${link.invalid}\", which is not an allowed placement (${SITE_SYSTEM_PHONE_PLACEMENTS.join(', ')}); its taps are reported as ${link.placement}`);\n if (!analytics) return;\n // An earlier tap can hide a link that showed when the page was read (a footer tap scrolls the footer in and\n // the call bar hides over it): a link nobody can tap now is left out, not waited on.\n const target = page.locator(`[data-brandlab-journey~=\"${mark}\"]`).first();\n const tappable = () => target.evaluate((element) => window.__brandlabSiteSystem.isRendered(element) && !element.closest('[inert]')).catch(() => false);\n if (!(await waitUntil(tappable, 1_000))) {\n skipped.push(`${where}: ${link.description}`);\n return;\n }\n const before = analyticsEvents(commands, 'phone_link_click').length;\n await target.click();\n await waitUntil(async () => analyticsEvents(commands, 'phone_link_click').length > before, 2_000);\n const sent = analyticsEvents(commands, 'phone_link_click').slice(before);\n taps += 1;\n if (sent.length !== 1) problems.push(`${where}: a tap on ${link.description} sent ${sent.length} phone_link_click event(s)`);\n const placement = String(sent[0]?.params.placement ?? '');\n if (sent.length > 0 && !SITE_SYSTEM_PHONE_PLACEMENTS.includes(placement)) problems.push(`${where}: a tap on ${link.description} reported placement \"${placement}\", which is not allowed`);\n else if (sent.length > 0 && placement !== link.placement) problems.push(`${where}: a tap on ${link.description} reported placement \"${placement}\" (the link declares ${link.placement})`);\n await page.waitForTimeout(PHONE_TAP_GAP_MS);\n };\n for (const viewport of [SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT]) {\n await page.setViewportSize({ ...viewport });\n await openSitePage(page, path, plan);\n await siteHeader(page);\n analytics ??= await waitUntil(async () => commands.some((command) => command[0] === 'js'), 3_000);\n const where = `${viewport.width} px`;\n const links = await page.evaluate(() => {\n const helpers = window.__brandlabSiteSystem;\n // Below 1024 px the call bar's links are tapped in the call-bar step, which scrolls until the bar shows.\n const bar = window.innerWidth < 1024 ? helpers.findCallBar() : null;\n return Array.from(document.querySelectorAll('a[href^=\"tel:\"]')).filter((link) => helpers.isRendered(link) && !bar?.contains(link)).map((link, index) => {\n helpers.markElement(link, `tel-${index}`);\n return { mark: `tel-${index}`, description: helpers.describeElement(link), ...helpers.phonePlacement(link) };\n });\n });\n for (const link of links) await tap(link.mark, link, where);\n if (viewport.width < 1024) {\n const bar = await callBarLinks(page);\n if (bar) {\n const marks = await page.evaluate(() => {\n const helpers = window.__brandlabSiteSystem;\n const root = document.querySelector('[data-brandlab-journey~=\"call-bar\"]');\n return Array.from(root?.querySelectorAll('a[href^=\"tel:\"]') ?? []).filter(helpers.isRendered).map((link, index) => {\n helpers.markElement(link, `bar-tel-${index}`);\n return `bar-tel-${index}`;\n });\n });\n for (const [index, mark] of marks.entries()) await tap(mark, bar[index]!, `${where} call bar`);\n }\n }\n }\n expect(declared, `${path} shows no phone link`).toBeGreaterThan(0);\n if (!analytics) note(testInfo, `${path}: the build sends no analytics (no Google tag ran), so the declared placements were checked without tapping; build the e2e target with a test NEXT_PUBLIC_GA_MEASUREMENT_ID to check the events`);\n else note(testInfo, `${path}: ${taps} phone tap(s) checked against their phone_link_click events`);\n if (skipped.length > 0) note(testInfo, `${path}: not tapped, hidden by the time their turn came (their declared placements were checked): ${skipped.join('; ')}`);\n expect(problems).toEqual([]);\n });\n }\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/menu.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAiE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGtH,eAAO,MAAM,oBAAoB;;;EAAuC,CAAC;AACzE,eAAO,MAAM,yBAAyB;;;EAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/menu.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAiE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGtH,eAAO,MAAM,oBAAoB;;;EAAuC,CAAC;AACzE,eAAO,MAAM,yBAAyB;;;EAAwC,CAAC;AAqF/E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAmT/F"}
|
|
@@ -11,6 +11,29 @@ import { SITE_SYSTEM_DESKTOP_VIEWPORT, siteSystemJourneyTitle as title } from '.
|
|
|
11
11
|
export const HOVER_OPEN_WINDOW_MS = Object.freeze({ min: 50, max: 400 });
|
|
12
12
|
export const MOUSE_OUT_CLOSE_WINDOW_MS = Object.freeze({ min: 120, max: 600 });
|
|
13
13
|
const within = (value, window) => value >= window.min && value <= window.max;
|
|
14
|
+
/**
|
|
15
|
+
* Move the pointer onto the middle of `target` without Playwright's actionability wait. Its "stable" check counts
|
|
16
|
+
* animation frames, and with scripts off Chromium can stop producing them (or a CSS animation keeps the box moving), so
|
|
17
|
+
* `hover()` waited for the test timeout. The box is read until two reads agree (at most 1 s), then the pointer moves
|
|
18
|
+
* from a resting corner, so the item's own :hover is what shows the panel.
|
|
19
|
+
*/
|
|
20
|
+
async function pointAt(page, target) {
|
|
21
|
+
await target.scrollIntoViewIfNeeded({ timeout: 2_000 }).catch(() => undefined);
|
|
22
|
+
let box = await target.boundingBox();
|
|
23
|
+
for (let reads = 0; reads < 10; reads += 1) {
|
|
24
|
+
await page.waitForTimeout(100);
|
|
25
|
+
const next = await target.boundingBox();
|
|
26
|
+
const same = box && next && Math.abs(box.x - next.x) < 0.5 && Math.abs(box.y - next.y) < 0.5 && Math.abs(box.width - next.width) < 0.5 && Math.abs(box.height - next.height) < 0.5;
|
|
27
|
+
box = next;
|
|
28
|
+
if (same)
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (!box)
|
|
32
|
+
throw new Error('the menu item has no box to hover');
|
|
33
|
+
const viewport = page.viewportSize();
|
|
34
|
+
await page.mouse.move((viewport?.width ?? 1440) - 2, (viewport?.height ?? 900) - 2);
|
|
35
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 4 });
|
|
36
|
+
}
|
|
14
37
|
/** The visible main menu and its disclosures; fails when the menu is missing or hides links without disclosures. */
|
|
15
38
|
async function prepareMenu(page, plan, testInfo, path = plan.home) {
|
|
16
39
|
await openSitePage(page, path, plan);
|
|
@@ -187,7 +210,7 @@ export function defineMenuJourneys(plan, runs) {
|
|
|
187
210
|
// Hover the item's own visible link (panel links are hidden at rest), else the button itself.
|
|
188
211
|
const item = trigger.locator('xpath=ancestor::li[1]');
|
|
189
212
|
const link = item.locator('a[href]').filter({ visible: true }).first();
|
|
190
|
-
await ((await item.count()) && (await link.count()) ? link : trigger)
|
|
213
|
+
await pointAt(page, (await item.count()) && (await link.count()) ? link : trigger);
|
|
191
214
|
await page.waitForTimeout(250);
|
|
192
215
|
const visible = await panel.evaluate((element) => getComputedStyle(element).visibility === 'visible' && getComputedStyle(element).display !== 'none' && element.getBoundingClientRect().height > 0);
|
|
193
216
|
if (!visible)
|