ultimate-jekyll-manager 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/CHANGELOG.md +61 -1
  3. package/CLAUDE.md +36 -15
  4. package/README.md +4 -2
  5. package/TODO-AUTH-TESTING.md +1 -1
  6. package/dist/assets/themes/newsflash/README.md +58 -0
  7. package/dist/assets/themes/newsflash/_config.scss +138 -0
  8. package/dist/assets/themes/newsflash/_theme.js +27 -0
  9. package/dist/assets/themes/newsflash/_theme.scss +37 -0
  10. package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
  11. package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
  12. package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
  13. package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
  14. package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
  15. package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
  16. package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
  17. package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
  18. package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
  19. package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  20. package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
  21. package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
  22. package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
  23. package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
  24. package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
  25. package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
  26. package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
  27. package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
  28. package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
  29. package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
  30. package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
  31. package/dist/assets/themes/newsflash/pages/index.scss +159 -0
  32. package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
  33. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
  34. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
  35. package/dist/commands/blogify.js +6 -3
  36. package/dist/commands/test.js +34 -5
  37. package/dist/defaults/CLAUDE.md +17 -4
  38. package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
  39. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
  40. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
  41. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
  42. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
  43. package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
  44. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
  45. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
  46. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
  47. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
  48. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
  49. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
  50. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
  51. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
  52. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
  53. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
  54. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
  55. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
  56. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
  57. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
  58. package/dist/defaults/test/README.md +4 -0
  59. package/dist/gulp/tasks/jekyll.js +4 -2
  60. package/dist/test/runner.js +50 -3
  61. package/dist/test/suites/build/attach-log-file.test.js +102 -0
  62. package/dist/test/suites/build/theme-contract.test.js +173 -0
  63. package/dist/test/utils/extended-mode-warning.js +13 -0
  64. package/dist/utils/attach-log-file.js +70 -43
  65. package/docs/appearance.md +1 -0
  66. package/docs/assets.md +9 -0
  67. package/docs/audit.md +27 -7
  68. package/docs/build-system.md +57 -0
  69. package/docs/common-mistakes.md +15 -0
  70. package/docs/{project-structure.md → directory-structure.md} +1 -1
  71. package/docs/environment-detection.md +1 -1
  72. package/docs/javascript-libraries.md +38 -1
  73. package/docs/layouts-and-pages.md +146 -0
  74. package/docs/local-development.md +1 -8
  75. package/docs/logging.md +30 -0
  76. package/docs/migration.md +131 -0
  77. package/docs/no-inline-scripts.md +304 -0
  78. package/docs/purgecss.md +164 -0
  79. package/docs/seo.md +131 -4
  80. package/docs/templating.md +23 -0
  81. package/docs/test-boot-layer.md +1 -1
  82. package/docs/test-framework.md +56 -8
  83. package/docs/themes.md +254 -13
  84. package/logs/test.log +111 -0
  85. package/package.json +1 -1
@@ -0,0 +1,164 @@
1
+ # PurgeCSS Safelist
2
+
3
+ PurgeCSS removes unused CSS in production builds. Classes added dynamically via JavaScript (e.g., `classList.add()`, `innerHTML` templates) won't be found in static HTML, so they get purged.
4
+
5
+ ## IMPORTANT: Update Safelist When Writing Dynamic JS
6
+
7
+ **When writing JavaScript that dynamically adds CSS classes** (via `classList.add()`, `classList.toggle()`, `className =`, or `innerHTML` with class attributes), you MUST check if the class is already safelisted. If not, add it to the appropriate safelist:
8
+
9
+ - **UJM-internal classes** → `src/gulp/tasks/sass.js` safelist
10
+ - **Consuming project classes** → `config/ultimate-jekyll-manager.json` → `sass.purgecss.safelist`
11
+
12
+ **NEVER edit `dist/` or `node_modules/`.** Those are build outputs. Edit `src/` only.
13
+
14
+ ## Two Safelist Locations
15
+
16
+ ### 1. UJM Internal Safelist (sass.js)
17
+
18
+ **File:** `src/gulp/tasks/sass.js`
19
+
20
+ This is where UJM's own safelist lives — patterns for Bootstrap utilities, UJM core classes, and third-party injected elements. Uses native RegExp objects.
21
+
22
+ **Changes to sass.js require a dev server restart** — gulp tasks are loaded once at startup.
23
+
24
+ ### 2. Consuming Project Safelist (JSON config)
25
+
26
+ **File:** `config/ultimate-jekyll-manager.json` → `sass.purgecss.safelist`
27
+
28
+ For consuming project-specific classes. Uses regex strings (converted to `new RegExp(s)` by UJM).
29
+
30
+ ```json5
31
+ {
32
+ sass: {
33
+ purgecss: {
34
+ safelist: {
35
+ standard: [], // Class names (converted to RegExp)
36
+ deep: [], // Pseudo-selector patterns
37
+ greedy: [], // Broader patterns (matches substrings)
38
+ keyframes: [], // @keyframes animation names
39
+ },
40
+ },
41
+ },
42
+ }
43
+ ```
44
+
45
+ ## PurgeCSS Gotchas
46
+
47
+ ### `!` Negation patterns DON'T work in the content array
48
+
49
+ PurgeCSS passes each content pattern to `fast-glob` **individually** (not as a batch), so `!path/**` returns 0 files instead of excluding anything. Use `skippedContentGlobs` for exclusions instead.
50
+
51
+ ### Option naming is counterintuitive
52
+
53
+ `true` = "yes, **purge** this category", `false` = "leave it alone":
54
+
55
+ | Option | `true` | `false` |
56
+ |--------|--------|---------|
57
+ | `variables` | Remove unused CSS variables | Don't touch variables |
58
+ | `keyframes` | Remove unused @keyframes | Don't touch keyframes |
59
+ | `fontFace` | Remove unused @font-face | Don't touch font-face |
60
+
61
+ ### `variables` must stay `false`
62
+
63
+ PurgeCSS processes each CSS file independently. Page-specific CSS sets CSS custom properties (e.g., `--bs-btn-bg` in `.btn-paypal`) that are consumed by the base `.btn` class in the main bundle. With `variables: true`, these cross-file variable references are falsely flagged as unused and removed.
64
+
65
+ ### PurgeCSS processes per-file, not per-bundle
66
+
67
+ Each CSS file is processed against the content files independently. A class defined in a page CSS file must be found in the content files on its own — it can't rely on being "seen" in the context of the main bundle.
68
+
69
+ ## All Entries Are RegExp
70
+
71
+ Every entry in the consuming project safelist arrays is wrapped in `new RegExp(s)`. This means:
72
+
73
+ - `'dot'` becomes `/dot/` — matches ANY class containing "dot" (e.g., `dotted`, `polkadot`)
74
+ - **Always anchor exact class names** with `^...$`
75
+
76
+ ### Correct Usage
77
+
78
+ ```json5
79
+ {
80
+ standard: ['^fw-semibold$'], // Top-level exact matches
81
+ deep: ['^dot$'], // Nested selectors (.chat-typing .dot)
82
+ greedy: ['^chat-'], // Prefix match (all chat-* classes)
83
+ keyframes: ['chat-typing-bounce'], // Specific enough, no anchor needed
84
+ }
85
+ ```
86
+
87
+ ## Current UJM Safelist (sass.js)
88
+
89
+ Before adding classes, check what UJM already safelists in `src/gulp/tasks/sass.js`:
90
+
91
+ ### Bootstrap Utilities
92
+ - `/^btn-/` — button variants
93
+ - `/^d-/` — display utilities
94
+ - `/^text-/` — text utilities
95
+ - `/^bg-/` — background utilities
96
+ - `/^flex-/` — flex utilities
97
+ - `/^justify-/`, `/^align-/` — alignment
98
+ - `/^position-/` — positioning
99
+ - `/^[mp][trblxy]?-[0-9]+$/` — margin/padding
100
+ - `/^w-/`, `/^h-/`, `/^mw-/`, `/^mh-/`, `/^min-/`, `/^max-/` — sizing
101
+ - `/^border-/`, `/^rounded-/`, `/^shadow-/` — borders/shadows
102
+ - `/^overflow-/`, `/^order-/` — overflow/order
103
+ - `/^fw-/` — font weight
104
+ - `/^ratio-/`, `/^object-/` — aspect ratio/object-fit
105
+ - `/^filter-/` — filter utilities
106
+
107
+ ### Bootstrap Components
108
+ - `/^modal-/`, `/^carousel-/`, `/^dropdown-/`, `/^offcanvas-/`
109
+ - `/^tooltip-/`, `/^popover-/`, `/^toast-/`
110
+ - `/^accordion/`, `/^collapse/`, `/^collapsed$/`, `/^collapsing$/`
111
+ - `/^show$/`, `/^showing$/`, `/^hide$/`, `/^fade$/`, `/^active$/`, `/^disabled$/`
112
+ - `/^bs-/`, `/^data-bs-/`
113
+
114
+ ### State & Dynamic Classes
115
+ - `/^is-/`, `/^has-/`, `/^was-/` — state classes
116
+ - `/^animation-/` — all animation-* classes
117
+
118
+ ### Form & Validation
119
+ - `/^invalid-feedback$/` — form validation messages
120
+ - `/^spinner-/` — loading spinners
121
+ - `/^file-drop-/` — file drop zone states
122
+
123
+ ### Libraries & Third-Party
124
+ - `/^fa-/` — Font Awesome
125
+ - `/^lazy-/` — lazy loading
126
+ - `/^cookie-consent-/` — cookie consent
127
+ - `/^social-share-/` — social sharing
128
+ - `/^grecaptcha/` — Google reCAPTCHA
129
+ - `/^adsbygoogle$/` — Google AdSense
130
+ - `/^uj-vert-unit$/` — UJM ad units
131
+ - `/^uptime-tooltip$/` — status page tooltip
132
+
133
+ ### Also Safe: Classes in Static HTML
134
+
135
+ Classes that appear in any HTML file (includes, layouts, pages) are automatically found by PurgeCSS and won't be purged. Only classes created exclusively in JavaScript need safelisting.
136
+
137
+ ## When to Use Each Category
138
+
139
+ | Category | Use For | Example |
140
+ |----------|---------|---------|
141
+ | `standard` | Top-level class selectors only in JS | `'^fw-semibold$'` or `/^fw-/` |
142
+ | `deep` | Classes used as **nested/descendant** selectors | `'^dot$'` (for `.chat-typing .dot`) |
143
+ | `greedy` | Prefix-based families of classes | `'^chat-'` (matches chat-row, chat-bubble, etc.) |
144
+ | `keyframes` | Custom @keyframes animation names | `'chat-typing-bounce'` |
145
+
146
+ ### standard vs deep
147
+
148
+ - **standard**: Only preserves top-level selectors. If a class is ONLY used as a descendant (e.g., `.chat-typing .dot`), `standard` won't preserve the rule.
149
+ - **deep**: Preserves the entire rule tree when any part of the selector matches.
150
+
151
+ ## Workflow
152
+
153
+ 1. **Find dynamic classes**: Search JS files for `classList.add()`, `classList.toggle()`, `className =`, `innerHTML` with `class=`, template literals with classes
154
+ 2. **Check safelist**: Cross-reference against the patterns above — don't add what's already covered
155
+ 3. **Check static HTML**: If a class appears in any HTML file, it doesn't need safelisting
156
+ 4. **Add to correct location**: UJM core classes → `sass.js`, consuming project classes → `config/ultimate-jekyll-manager.json`
157
+ 5. **Group by prefix**: If multiple classes share a prefix, use a prefix pattern like `/^chat-/`
158
+ 6. **Restart required**: Changes to `sass.js` require a dev server restart (gulp tasks load once at startup)
159
+
160
+ ## See also
161
+
162
+ - [css.md](css.md) — CSS guidelines, theme-adaptive classes
163
+ - [build-system.md](build-system.md) — where the sass/purge task runs in the pipeline
164
+ - [themes.md](themes.md) — theme SCSS structure (theme classes ship in static CSS, not JS)
package/docs/seo.md CHANGED
@@ -1,9 +1,125 @@
1
- # SEO — Alternatives Pages & Structured Data
1
+ # SEO & Content
2
2
 
3
- This doc covers two SEO-focused subsystems:
3
+ This doc covers UJM's SEO and content subsystems:
4
4
 
5
- 1. **Alternatives Collection** — competitor comparison landing pages.
6
- 2. **Schema / Structured Data (JSON-LD)** `SoftwareApplication` and `FAQPage` JSON-LD blocks.
5
+ 1. **Content writing rules** — headlines, sentence case, keywords (applies to ALL pages).
6
+ 2. **Content page types** Services (main offerings), Solutions (SEO landing pages), Alternatives (competitor comparisons).
7
+ 3. **Schema / Structured Data (JSON-LD)** — `SoftwareApplication` and `FAQPage` JSON-LD blocks.
8
+
9
+ ## Content Writing Rules (applies to ALL pages)
10
+
11
+ ### H1 Headlines
12
+
13
+ - **Start with an action verb** — tell the user what they're getting immediately
14
+ - "Grow your business with social media ads" NOT "Social Media Advertising Services"
15
+ - "Automate your workflow in minutes" NOT "Workflow Automation Platform"
16
+
17
+ ### Sentence Case (ALL headings)
18
+
19
+ - First word capitalized, rest lowercase
20
+ - "Grow your business with social media ads" NOT "Grow Your Business With Social Media Ads"
21
+
22
+ ### Keywords
23
+
24
+ - Use primary keywords naturally throughout the page — in headings, subheadings, and body text
25
+ - Keep content readable and natural — don't keyword stuff but still use primary and secondary keywords where relevant
26
+
27
+ ### Headline Structure (headline / headline_accent)
28
+
29
+ **Section headlines (non-hero):**
30
+
31
+ - Maximum 5-6 words total
32
+ - ONLY the LAST word should be accented (placed in `headline_accent`)
33
+
34
+ ```yaml
35
+ # CORRECT — Last word only
36
+ headline: "Compare all"
37
+ headline_accent: "plans"
38
+
39
+ headline: "Frequently asked"
40
+ headline_accent: "questions"
41
+
42
+ # WRONG — Multiple words accented or wrong position
43
+ headline: "Compare"
44
+ headline_accent: "all plans" # Too many words accented
45
+ ```
46
+
47
+ **Hero section headlines (exception):**
48
+
49
+ - Hero sections may accent MORE than just the last word — up to half of the phrase for emphasis
50
+
51
+ ```yaml
52
+ hero:
53
+ headline: "The right plans for"
54
+ headline_accent: "your business" # Multiple words OK in hero
55
+ ```
56
+
57
+ **Homepage hero (special case):** more flexible — longer phrases with creative accent placement; focus on impact and brand messaging.
58
+
59
+ ### Frontmatter SEO Rules
60
+
61
+ - **Default pages** (blueprint layouts): Do NOT include `meta.title` or `meta.description` — these are already set in the layout
62
+ - **Custom pages**: MUST include `meta.title` and `meta.description` in frontmatter
63
+
64
+ ### Superheadline Rules
65
+
66
+ - Homepage: Do NOT change (keep default icon AND text)
67
+ - Other pages: Can customize `text` but keep default `icon`
68
+
69
+ ### General Content Guidelines
70
+
71
+ - Keep copy concise and scannable; use active voice
72
+ - Focus on benefits, not features
73
+ - Match the brand tone from `_config.yml`
74
+ - Maintain consistency across all pages
75
+
76
+ ## Services Pages (Main Offerings)
77
+
78
+ Services (or Features) pages represent the MAIN OFFERINGS of the business — the core products, services, or capabilities the business publicly promotes (e.g. an agency's "Social Media Advertising", "SEO Services", "Web Design").
79
+
80
+ **Structure:**
81
+
82
+ ```
83
+ src/pages/services/
84
+ ├── index.md # Lists all services
85
+ ├── [service-1].md # layout: blueprint/index
86
+ ├── [service-2].md
87
+ └── [service-3].md
88
+ ```
89
+
90
+ **Page strategy — exactly 3 service pages** based on the business's core offerings:
91
+
92
+ - Featured prominently in nav dropdown AND the services index page
93
+ - Linked throughout the site (footer, CTAs, internal links)
94
+ - Fully customized with tailored features, testimonials, case studies, etc.
95
+
96
+ Each service page uses `layout: blueprint/index` with a hero like:
97
+
98
+ ```yaml
99
+ hero:
100
+ tagline: "{{ site.brand.name }}"
101
+ headline: "Professional"
102
+ headline_accent: "[Service Name]"
103
+ subheadline: "[Value proposition for this service]"
104
+ ```
105
+
106
+ **Navigation:** add a `Services` dropdown to `src/_includes/frontend/sections/nav.json` (3 services + divider + "All Services" link) and a `Services` column to `footer.json` — see [assets.md](assets.md) for the JSON section system.
107
+
108
+ ## Solutions Pages (SEO Landing Pages)
109
+
110
+ Solutions pages are SEO-focused landing pages that target SPECIFIC keywords, niches, or audience segments. They are NOT publicly linked on the site — they LINK BACK to the main Services pages (e.g. for a "Social Media Advertising" service: "TikTok Ads Agency", "Facebook Ads for E-commerce").
111
+
112
+ **Purpose:** drive organic traffic via long-tail keywords; target specific industries/platforms/use cases; funnel visitors to main service pages via CTAs and internal links.
113
+
114
+ **Structure:** individual pages only (`src/pages/solutions/*.md`, `layout: blueprint/index`) — **NO index page**; solutions are discovered via search engines, not site navigation.
115
+
116
+ **Guidelines:**
117
+
118
+ - **NOT linked in nav, footer, or services index** — search-engine discovery only
119
+ - **Always link back** to the related main service page (CTAs, body content, related-services section)
120
+ - Can be lighter customizations (hero + key sections) since they're keyword-focused
121
+ - Focus on specific search intent (e.g., "[Platform] + [Service] + [Location/Industry]")
122
+ - Any number of pages — use keyword research to identify opportunities
7
123
 
8
124
  ## Alternatives Collection (SEO Competitor Comparison Pages)
9
125
 
@@ -96,6 +212,17 @@ Which renders as "Can I import my data from ExampleApp?" for an ExampleApp compe
96
212
  | CSS | `src/assets/css/pages/alternatives/alternative/index.scss` |
97
213
  | JS | `src/assets/js/pages/alternatives/alternative/index.js` |
98
214
 
215
+ ### Content Guidelines (alternatives)
216
+
217
+ - **Comparison features:** include 8-12 features for a thorough comparison
218
+ - **Values:** use `true`/`false` for binary features, strings for nuanced values ("Unlimited", "Limited", "Basic")
219
+ - **Tone:** confident but not aggressive — focus on our strengths, not bashing competitors
220
+ - **Shared sections:** do NOT repeat testimonials, stats, FAQs, CTA, or why_switch per page — they're inherited from the layout; override only when a specific competitor needs unique content
221
+ - **Video:** optional — set `video.youtube_id` only if a comparison video exists for that competitor
222
+ - **Up to 20 pages**
223
+ - The `/alternatives` index page is auto-generated — do NOT create it manually
224
+ - Do NOT add alternatives to the main nav or footer — discovered via search engines, like Solutions pages
225
+
99
226
  ## Schema / Structured Data (JSON-LD)
100
227
 
101
228
  UJ automatically generates JSON-LD structured data in `foot.html`. The SoftwareApplication schema with AggregateRating is opt-in via frontmatter.
@@ -0,0 +1,23 @@
1
+ # Templating
2
+
3
+ UJM uses node-powertools' `template()` for build-time token replacement, alongside (and deliberately distinct from) Jekyll's own Liquid templating.
4
+
5
+ ## How it works
6
+
7
+ Two bracket conventions, chosen per call site so node-powertools tokens never collide with Liquid:
8
+
9
+ | Brackets | Used by | Files |
10
+ |---|---|---|
11
+ | `{ x }` (node-powertools default) | Any `template()` call without a `brackets:` option — e.g. `defaults.js` Gemfile templating | Gemfile, scaffolded defaults |
12
+ | `[ x ]` | `distribute.js` theme fallback + [template-transform.js](../src/gulp/tasks/utils/template-transform.js) | `.html` / `.md` / `.liquid` / `.json` |
13
+
14
+ ## Liquid coexistence
15
+
16
+ Jekyll's Liquid `{{ }}` / `{% %}` is processed by **Jekyll itself**, NOT by node-powertools — those placeholders pass through node-powertools untouched. The square-bracket convention exists precisely so the two engines can template the same file without fighting.
17
+
18
+ Corollary for consumer JS: Jekyll does NOT process `src/assets/js/**/*.js` — never leave Liquid tokens inside JS modules; bridge values via `data-*` attributes or `<template>` elements instead. See [common-mistakes.md](common-mistakes.md).
19
+
20
+ ## See also
21
+
22
+ - [build-system.md](build-system.md) — where the templating passes run in the pipeline
23
+ - [layouts-and-pages.md](layouts-and-pages.md) — Liquid-side layout/frontmatter reference
@@ -1,4 +1,4 @@
1
- # Boot Layer
1
+ # Test Framework — Boot Layer
2
2
 
3
3
  The boot layer runs Puppeteer against a real built `_site/` served by a tiny embedded HTTP server. It's the integration smoke that catches "did my actually-shipped site boot?" regressions — things plain-Node build tests can't see.
4
4
 
@@ -22,7 +22,19 @@ Mock **nothing** by default. There are exactly two cases where the real dependen
22
22
 
23
23
  If you can run it for real, you must. These exceptions are not a license to unit-test in isolation when a real-harness layer would work.
24
24
 
25
- **External APIs are skipped in-source, NOT mocked.** UJM build/gulp code that would hit the network (e.g. fetching Firebase auth files) short-circuits in its own source when `Manager.isTesting()` is true — it returns early, it does not return canned/mocked data. See [environment-detection.md](environment-detection.md). If a suite has slower live-integration tests, gate them behind the `--integration` flag (`UJ_TEST_INTEGRATION=1`) and run the real path; anything such a test creates externally MUST be cleaned up by the test (`cleanup`/`inspect` teardown) — the runner does not clean external systems.
25
+ **External APIs are skipped in-source, NOT mocked.** UJM build/gulp code that would hit the network (e.g. fetching Firebase auth files) short-circuits in its own source when `Manager.isTesting()` is true — it returns early, it does not return canned/mocked data. See [environment-detection.md](environment-detection.md). If a suite has slower live-integration tests, gate them behind [extended mode](#extended-mode-test_extended_mode) (`--extended` / `TEST_EXTENDED_MODE=true`) and run the real path; anything such a test creates externally MUST be cleaned up by the test (`cleanup`/`inspect` teardown) — the runner does not clean external systems.
26
+
27
+ ## Test coverage — every surface gets a test (HARD RULE)
28
+
29
+ A feature is not done when it works — it's done when every surface it exposes is covered in the layer that owns that surface:
30
+
31
+ | Coverage | Layer | Proves |
32
+ |---|---|---|
33
+ | **Logic** | `build` / `page` | The feature's functions do the right thing when called directly (real build Manager, real frontend Manager surface) |
34
+ | **UI** | `page` | The feature's interface is WIRED — a real event on the real DOM triggers the behavior and the visible result appears |
35
+ | **End-to-end** | `boot` | The feature survives in the consumer's actual built `_site/` (extend the boot suite's `inspect` assertions) |
36
+
37
+ **Skipping a layer is the exception, not the default.** A layer may be skipped ONLY when the feature genuinely has no surface there — a pure build-time utility has no UI; a CSS-only tweak has no logic to call. Convenience is never a reason: "the logic test already covers it" does NOT excuse the UI test — logic tests prove the logic, UI tests prove the wiring (a button can come unhooked while every logic test stays green), boot tests prove the built site. When in doubt, write the test.
26
38
 
27
39
  ## Quick start
28
40
 
@@ -37,25 +49,46 @@ npx mgr test --reporter json # machine-readable __UJM_TEST__ events
37
49
 
38
50
  `npm test` works too — added to consumer `package.json#scripts.test` on `npx mgr setup`.
39
51
 
52
+ All test output is also teed (ANSI-stripped) to `<projectRoot>/logs/test.log`, truncated fresh on each run — same pattern as `dev.log`/`build.log` and EM's/BEM's `test.log`. Skipped on CI (`isServer()`). Grep it after a run instead of scrolling terminal output.
53
+
40
54
  ### Filtering tests
41
55
 
42
- Pass a path (relative to `test/`) as a positional argument to run specific tests:
56
+ Pass a path (relative to `test/`) as a positional **target** to select which test FILES run:
43
57
 
44
58
  ```bash
45
- # Run a single test file
59
+ # Run a single test file (matches both framework + project)
46
60
  npx mgr test pages/home
47
61
 
48
- # Run only UJM framework tests
49
- npx mgr test ujm:pages/home
62
+ # Run ONLY consumer project tests (no framework suites at all)
63
+ npx mgr test project:
50
64
 
51
- # Run only consumer project tests
65
+ # Run a single project test file
52
66
  npx mgr test project:custom-test
53
67
 
68
+ # Run ONLY framework tests (universal cross-framework alias)
69
+ npx mgr test mgr:
70
+
71
+ # Run ONLY UJM framework tests (UJM-specific aliases, equivalent to mgr:)
72
+ npx mgr test ujm:
73
+ npx mgr test framework:
74
+
75
+ # Run framework tests matching a path
76
+ npx mgr test mgr:pages/home
77
+ npx mgr test ujm:pages/home
78
+
54
79
  # Combine with extended mode
55
80
  TEST_EXTENDED_MODE=true npx mgr test pages/boot-test
56
81
  ```
57
82
 
58
- The filter matches against the test file path. `ujm:` and `project:` prefixes scope the filter to framework-only or project-only tests respectively. Without a prefix, both are searched.
83
+ The target matches against the test file path. The source prefix scopes selection to framework-only or project-only tests a prefixed target excludes the other source entirely:
84
+
85
+ - `mgr:` — the **universal cross-framework alias** for "the manager's own tests" (framework-only). Works identically in UJM, EM, BXM, and BEM.
86
+ - `ujm:` / `framework:` — UJM-specific aliases for framework-only tests, equivalent to `mgr:`.
87
+ - `project:` — consumer project tests only.
88
+
89
+ A bare prefix (`mgr:` / `ujm:` / `project:` with no path) runs every test in that source. A bare path (no prefix) searches both sources by path.
90
+
91
+ > **Target vs `--filter`.** The positional target selects test FILES (by path + source). The `--filter=<substring>` flag is orthogonal: it matches test NAMES/descriptions within the selected files. They compose, e.g. `npx mgr test project: --filter=foo`. The `--layer=build|page|boot` flag further narrows to a single layer.
59
92
 
60
93
  ## Layers
61
94
 
@@ -227,12 +260,27 @@ module.exports = ({ projectRoot }) => ({
227
260
  });
228
261
  ```
229
262
 
263
+ ## Extended mode (`TEST_EXTENDED_MODE`)
264
+
265
+ By default `npx mgr test` is fast and offline-safe: tests that would hit real external services are **skipped** (the code short-circuits in-source — it does NOT mock). Extended mode opts those tests in to run against the real path.
266
+
267
+ - **Turn it on:** `npx mgr test --extended` or `TEST_EXTENDED_MODE=true npx mgr test`.
268
+ - **Shared, unprefixed name.** `TEST_EXTENDED_MODE` is the SAME env var across BEM, BXM, UJM, and EM — cross-framework parity. Setting it in CI (or your shell) flips every framework's extended suites on.
269
+ - **Propagates to spawned environments.** Once the test command sets `process.env.TEST_EXTENDED_MODE`, it reaches every child it spawns — the Jekyll build (via inherited `process.env`) and the boot HTTP server / Puppeteer browsers — automatically.
270
+ - **A warning prints** when extended mode is on (also teed to `logs/test.log`), since it makes real network calls against live backends.
271
+ - **Tests gate on `process.env.TEST_EXTENDED_MODE`.** Skip the live path unless it's set, e.g. `if (process.env.TEST_EXTENDED_MODE !== 'true') return ctx.skip('extended mode only');`. Anything an extended test creates in a real external system MUST be cleaned up by the test (`cleanup`/`inspect` teardown) — the runner never resets external systems.
272
+
273
+ ```bash
274
+ TEST_EXTENDED_MODE=true npx mgr test # all extended suites, all layers
275
+ npx mgr test --extended mgr:build # extended + a specific target
276
+ ```
277
+
230
278
  ## Env vars
231
279
 
232
280
  | Env | Set by | Purpose |
233
281
  |---|---|---|
234
282
  | `UJ_TEST_MODE=true` | `npx mgr test` always | Canonical test signal. `Manager.isTesting()` reads this. Use it to short-circuit network calls / prompts / long timers in code that runs during tests. |
235
- | `UJ_TEST_INTEGRATION=1` | `--integration` flag | Opt-in flag for slower live-integration tests if your suite has them. These run the **real** external path (NOT mocked); anything they create externally MUST be cleaned up by the test. |
283
+ | `TEST_EXTENDED_MODE=true` | `--extended` flag, or set in the env | Opt into tests that hit **real** external services (network fetches, Firebase via web-manager, live APIs). Off by default. Unprefixed + shared across BEM/BXM/UJM/EM. Propagates to every spawned child (Jekyll build, boot HTTP server / Puppeteer). Gate such tests on `process.env.TEST_EXTENDED_MODE`; anything they create externally MUST be cleaned up by the test. See [Extended mode](#extended-mode-test_extended_mode). |
236
284
  | `UJ_TEST_BOOT_PROJECT` | Auto-set when UJM tests itself; else manual | Project root the boot runner uses (its `_site/` is the boot target) |
237
285
  | `UJ_TEST_BOOT_DIR` | Manual | Absolute override for the `_site/` directory. Wins over `UJ_TEST_BOOT_PROJECT/_site` and `<cwd>/_site` |
238
286
  | `UJ_TEST_DEBUG=1` | Manual | Verbose Puppeteer console output piped to the parent stdout |