📝 docs: Fix brief Task 4 verification grep for CSS nesting
This commit is contained in:
@@ -0,0 +1,614 @@
|
||||
# Services Content Blocks Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build the missing content-area components for the Mapping services page (and any other service sub-page), matching `Mapping-Services.png`. The hero, header, and footer are already in place and must not be touched.
|
||||
|
||||
**Architecture:** One new ACF block (`services-list`) that renders a row of cards. Card count is editor-driven (2, 3, or 4); card size and grid layout adapt to the count. The orange horizontal line that bisects the images is rendered by the block itself as a full-bleed SVG background. The closing "At CWC, mapping is about more than data…" block reuses the existing `pull-quote` block — no new build needed. Editor composes the page in Gutenberg by adding `Services List` and `Pull Quote` blocks to any service sub-page; `page.php` already calls `the_content()`, so no template change is required.
|
||||
|
||||
**Tech Stack:** WordPress 6.x, PHP 8.x, Tailwind CSS v4, Playwright, axe-core, PHPCS (WordPress standard).
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Tabs for PHP indentation (project standard).
|
||||
- `static/dist/theme.css` is committed (not gitignored) per project convention.
|
||||
- PHPCS uses the WordPress coding standard; `composer lint` must pass for all touched files.
|
||||
- All design colors use the project's existing CSS custom properties from `styles/base/colors.css`. No hardcoded hex values for theme colors.
|
||||
- Reuse the existing post-title accent pattern (blue heading + orange `::after` underline from `.post-title h1` in `styles/base/misc.css`) for consistency. Do not introduce a new accent style.
|
||||
- Reuse the existing `pull-quote` block for the closing statement — no duplicate block.
|
||||
- Page template `page.php` is not modified. The page-children block is not used.
|
||||
- This work does **not** edit `header.php`, `footer.php`, `views/partials/page-hero.php`, or `views/partials/page-hero-services.php`.
|
||||
- Tailwind v4 picks up class names from source files at build time. The dynamic `lg:grid-cols-N` class needs to literally appear in the source PHP for Tailwind to emit the rule.
|
||||
- The `static/img/services-list-line.svg` is a fixed asset; do not make it editor-uploadable.
|
||||
- All commits follow the project's emoji-prefixed commit-message style (e.g. `✨ feat: …`).
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility | Created/Modified |
|
||||
| --- | --- | --- |
|
||||
| `views/blocks/services-list/services-list.php` | Block template (PHP) — renders the row of cards. | Create |
|
||||
| `views/blocks/services-list/services-list.css` | Block styles (CSS) — layout, headings, bisecting line. | Create |
|
||||
| `views/blocks/services-list/block.json` | Block registration metadata (name, category, icon, render template). | Create |
|
||||
| `acf/group_services_list.json` | ACF field group for the `acf/services-list` block. | Create |
|
||||
| `static/img/services-list-line.svg` | Decorative orange horizontal line SVG that bisects the images. | Create |
|
||||
| `styles/blocks/index.css` | Import the new block's CSS. | Modify |
|
||||
| `tests/services-page.spec.js` | Playwright tests for the rendered block. | Create |
|
||||
| `static/dist/theme.css` | Rebuilt via `npm run build`. | Modified |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: ACF Field Group
|
||||
|
||||
**Files:**
|
||||
- Create: `acf/group_services_list.json`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: ACF group with `key: "group_services_list"`, location scoped to `block == acf/services-list`, fields: `items` (repeater) → `image`, `heading`, `description`.
|
||||
|
||||
- [ ] **Step 1: Create the ACF field group JSON file**
|
||||
|
||||
Create the file `acf/group_services_list.json` with the following exact content:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "group_services_list",
|
||||
"title": "Services List",
|
||||
"fields": [
|
||||
{
|
||||
"key": "field_services_items",
|
||||
"label": "Items",
|
||||
"name": "items",
|
||||
"aria-label": "",
|
||||
"type": "repeater",
|
||||
"instructions": "Add 2, 3, or 4 cards. The grid layout adapts to the count.",
|
||||
"required": 0,
|
||||
"conditional_logic": 0,
|
||||
"wrapper": {
|
||||
"width": "",
|
||||
"class": "",
|
||||
"id": ""
|
||||
},
|
||||
"layout": "block",
|
||||
"pagination": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"collapsed": "",
|
||||
"button_label": "Add Service",
|
||||
"rows_per_page": 20,
|
||||
"sub_fields": [
|
||||
{
|
||||
"key": "field_services_item_image",
|
||||
"label": "Image",
|
||||
"name": "image",
|
||||
"aria-label": "",
|
||||
"type": "image",
|
||||
"instructions": "",
|
||||
"required": 0,
|
||||
"conditional_logic": 0,
|
||||
"wrapper": {
|
||||
"width": "",
|
||||
"class": "",
|
||||
"id": ""
|
||||
},
|
||||
"return_format": "array",
|
||||
"library": "all",
|
||||
"min_width": "",
|
||||
"min_height": "",
|
||||
"min_size": "",
|
||||
"max_width": "",
|
||||
"max_height": "",
|
||||
"max_size": "",
|
||||
"mime_types": "",
|
||||
"allow_in_bindings": 0,
|
||||
"preview_size": "medium"
|
||||
},
|
||||
{
|
||||
"key": "field_services_item_heading",
|
||||
"label": "Heading",
|
||||
"name": "heading",
|
||||
"aria-label": "",
|
||||
"type": "text",
|
||||
"instructions": "",
|
||||
"required": 0,
|
||||
"conditional_logic": 0,
|
||||
"wrapper": {
|
||||
"width": "",
|
||||
"class": "",
|
||||
"id": ""
|
||||
},
|
||||
"default_value": "",
|
||||
"maxlength": "",
|
||||
"allow_in_bindings": 0,
|
||||
"placeholder": "",
|
||||
"prepend": "",
|
||||
"append": ""
|
||||
},
|
||||
{
|
||||
"key": "field_services_item_description",
|
||||
"label": "Description",
|
||||
"name": "description",
|
||||
"aria-label": "",
|
||||
"type": "textarea",
|
||||
"instructions": "",
|
||||
"required": 0,
|
||||
"conditional_logic": 0,
|
||||
"wrapper": {
|
||||
"width": "",
|
||||
"class": "",
|
||||
"id": ""
|
||||
},
|
||||
"default_value": "",
|
||||
"placeholder": "",
|
||||
"maxlength": "",
|
||||
"rows": 4,
|
||||
"new_lines": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"location": [
|
||||
[
|
||||
{
|
||||
"param": "block",
|
||||
"operator": "==",
|
||||
"value": "acf/services-list"
|
||||
}
|
||||
]
|
||||
],
|
||||
"menu_order": 0,
|
||||
"position": "normal",
|
||||
"style": "default",
|
||||
"label_placement": "top",
|
||||
"instruction_placement": "label",
|
||||
"hide_on_screen": "",
|
||||
"active": true,
|
||||
"description": "Service cards row. Card count (2-4) determines the layout.",
|
||||
"show_in_rest": 0,
|
||||
"display_title": "",
|
||||
"allow_ai_access": false,
|
||||
"ai_description": "",
|
||||
"modified": 1753305600
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the JSON parses**
|
||||
|
||||
Run: `node -e "JSON.parse(require('fs').readFileSync('acf/group_services_list.json','utf8')); console.log('OK')"`
|
||||
Expected: prints `OK` and exits 0.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add acf/group_services_list.json
|
||||
git commit -m "✨ feat: Add ACF field group for services-list block"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Decorative SVG Asset
|
||||
|
||||
**Files:**
|
||||
- Create: `static/img/services-list-line.svg`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: A 1600×8 viewBox SVG with a 2px-tall orange `#F26B53` rectangle, `preserveAspectRatio="none"`. Reference URL: `/wp-content/themes/community-works-collaborative/static/img/services-list-line.svg`.
|
||||
|
||||
- [ ] **Step 1: Create the SVG asset**
|
||||
|
||||
Create the file `static/img/services-list-line.svg` with the following exact content:
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 8" preserveAspectRatio="none">
|
||||
<rect x="0" y="3" width="1600" height="2" fill="#F26B53" />
|
||||
</svg>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the SVG is well-formed**
|
||||
|
||||
Run: `node -e "const fs=require('fs'); const s=fs.readFileSync('static/img/services-list-line.svg','utf8'); if(!s.includes('viewBox=\"0 0 1600 8\"')||!s.includes('fill=\"#F26B53\"')) { process.exit(1); } console.log('OK')"`
|
||||
Expected: prints `OK` and exits 0.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add static/img/services-list-line.svg
|
||||
git commit -m "✨ feat: Add services-list decorative line SVG"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Block Registration JSON
|
||||
|
||||
**Files:**
|
||||
- Create: `views/blocks/services-list/block.json`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `block.json` declaring `acf/services-list` as the block name, `services-list.php` as the render template, `grid-view` icon, `common` category. `regACFBlocks()` in `functions.php` will pick this up automatically — no PHP registration code change required.
|
||||
|
||||
- [ ] **Step 1: Create the block.json file**
|
||||
|
||||
Create the file `views/blocks/services-list/block.json` with the following exact content:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "acf/services-list",
|
||||
"title": "Services List",
|
||||
"description": "A row of service cards. Card count (2, 3, or 4) determines the layout.",
|
||||
"category": "common",
|
||||
"icon": "grid-view",
|
||||
"keywords": ["services", "cards", "mapping"],
|
||||
"acf": {
|
||||
"mode": "preview",
|
||||
"renderTemplate": "services-list.php"
|
||||
},
|
||||
"supports": {
|
||||
"anchor": true,
|
||||
"align": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the JSON parses**
|
||||
|
||||
Run: `node -e "JSON.parse(require('fs').readFileSync('views/blocks/services-list/block.json','utf8')); console.log('OK')"`
|
||||
Expected: prints `OK` and exits 0.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add views/blocks/services-list/block.json
|
||||
git commit -m "✨ feat: Add services-list block.json registration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Block CSS
|
||||
|
||||
**Files:**
|
||||
- Create: `views/blocks/services-list/services-list.css`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: BEM-styled CSS for `.services-list`, `__line`, `__grid`, `__card`, `__media`, `__heading`, `__desc`. Reuses `text-cwc-blue-01` and `var(--color-secondary)` from existing tokens. Renders the bisecting line via `background-image: url('/wp-content/themes/community-works-collaborative/static/img/services-list-line.svg')`.
|
||||
|
||||
- [ ] **Step 1: Create the block CSS file**
|
||||
|
||||
Create the file `views/blocks/services-list/services-list.css` with the following exact content:
|
||||
|
||||
```css
|
||||
/* Services List block */
|
||||
|
||||
.services-list {
|
||||
--services-line-top: 25%;
|
||||
|
||||
&__line {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
top: var(--services-line-top);
|
||||
height: 8px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background-image: url('/wp-content/themes/community-works-collaborative/static/img/services-list-line.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&__grid {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__card {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
&__media {
|
||||
@apply overflow-hidden rounded-md bg-gray-100;
|
||||
}
|
||||
|
||||
&__img {
|
||||
@apply w-full h-full object-cover;
|
||||
}
|
||||
|
||||
&__heading {
|
||||
@apply text-cwc-blue-01 text-25px font-bold leading-none mt-6 mb-4;
|
||||
|
||||
&::after {
|
||||
background: var(--color-secondary);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 4px;
|
||||
margin-top: 0.45rem;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__desc {
|
||||
@apply text-gray-600 text-16px font-light leading-snug;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.services-list__line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the CSS contains the expected classes**
|
||||
|
||||
The brief uses CSS-nesting shorthand (`&__line`, `&__grid`, etc.) for 6 of the 7 BEM children, and a literal selector (`.services-list__line`) for the one inside the `@media` block. A naive grep for the literal class names will undercount. Use:
|
||||
|
||||
```bash
|
||||
grep -cE "&__(line|grid|card|media|img|heading|desc)|services-list__(line|grid|card|media|img|heading|desc)" views/blocks/services-list/services-list.css
|
||||
```
|
||||
|
||||
Expected: `8` (7 nested `&__` selectors + 1 literal `.services-list__line` inside the `@media` block).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add views/blocks/services-list/services-list.css
|
||||
git commit -m "✨ feat: Add services-list block CSS"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Block Template (PHP)
|
||||
|
||||
**Files:**
|
||||
- Create: `views/blocks/services-list/services-list.php`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `get_field( 'items' )` → array of items each with `image` (array: `url`, `alt`), `heading` (string), `description` (string).
|
||||
- Produces: A `<section class="services-list ...">` element containing the bisecting `__line` div, a `container` with a grid div, and a `<article class="services-list__card">` per item. Computes the dynamic `lg:grid-cols-N` class from item count.
|
||||
|
||||
- [ ] **Step 1: Create the block PHP template**
|
||||
|
||||
Create the file `views/blocks/services-list/services-list.php` with the following exact content (tabs for indentation):
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Block Name: Services List
|
||||
*
|
||||
* A row of service cards. Card count (2, 3, or 4) determines the layout.
|
||||
*
|
||||
* @package CWC
|
||||
*/
|
||||
|
||||
namespace CWC;
|
||||
|
||||
$items = get_field( 'items' );
|
||||
|
||||
if ( empty( $items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$count = count( $items );
|
||||
$grid_cols = max( 2, min( 4, (int) $count ) );
|
||||
$grid_class = 'lg:grid-cols-' . $grid_cols;
|
||||
$line_url = '/wp-content/themes/community-works-collaborative/static/img/services-list-line.svg';
|
||||
|
||||
$classes = 'services-list mx-break-out relative ' . $grid_class;
|
||||
$wrapper = blockWrapperAttributes( $classes, $is_preview );
|
||||
?>
|
||||
|
||||
<section <?php echo wp_kses_post( $wrapper ); ?>>
|
||||
<div class="services-list__line" aria-hidden="true" style="background-image: url('<?php echo esc_url( $line_url ); ?>');"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="services-list__grid grid grid-cols-1 <?php echo esc_attr( $grid_class ); ?> gap-8 lg:gap-12">
|
||||
<?php foreach ( $items as $item ) : ?>
|
||||
<article class="services-list__card">
|
||||
<?php if ( ! empty( $item['image'] ) ) : ?>
|
||||
<figure class="services-list__media aspect-square">
|
||||
<img
|
||||
class="services-list__img"
|
||||
src="<?php echo esc_url( $item['image']['url'] ); ?>"
|
||||
alt="<?php echo esc_attr( $item['image']['alt'] ?? '' ); ?>"
|
||||
loading="lazy"
|
||||
/>
|
||||
</figure>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $item['heading'] ) ) : ?>
|
||||
<h3 class="services-list__heading">
|
||||
<?php echo wp_kses_post( $item['heading'] ); ?>
|
||||
</h3>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $item['description'] ) ) : ?>
|
||||
<div class="services-list__desc">
|
||||
<?php echo wp_kses_post( $item['description'] ); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Lint the PHP file with PHPCS**
|
||||
|
||||
Run: `composer exec phpcs -- views/blocks/services-list/services-list.php`
|
||||
Expected: exits 0 with no errors. If there are pre-existing project-wide issues unrelated to this file, fix only the ones in this file.
|
||||
|
||||
- [ ] **Step 3: Verify the dynamic grid class is in the source**
|
||||
|
||||
Run: `grep -E "lg:grid-cols-\$grid_cols|lg:grid-cols-' \. \$grid_cols" views/blocks/services-list/services-list.php`
|
||||
Expected: at least one match (Tailwind must see the literal class name in the source to emit the rule).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add views/blocks/services-list/services-list.php
|
||||
git commit -m "✨ feat: Add services-list block PHP template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: CSS Import Wiring
|
||||
|
||||
**Files:**
|
||||
- Modify: `styles/blocks/index.css`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `views/blocks/services-list/services-list.css` (from Task 4).
|
||||
- Produces: A new `@import` line at the end of `styles/blocks/index.css` so Tailwind's build picks up the block's CSS.
|
||||
|
||||
- [ ] **Step 1: Add the import to the block index CSS**
|
||||
|
||||
Append the following line at the end of `styles/blocks/index.css`:
|
||||
|
||||
```css
|
||||
@import '../../views/blocks/services-list/services-list.css';
|
||||
```
|
||||
|
||||
After the edit, the file should end with:
|
||||
|
||||
```
|
||||
@import '../../views/blocks/contact-info/contact-info.css';
|
||||
@import '../../views/blocks/services-list/services-list.css';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the import is present**
|
||||
|
||||
Run: `grep -c "services-list/services-list.css" styles/blocks/index.css`
|
||||
Expected: `1`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add styles/blocks/index.css
|
||||
git commit -m "🔧 chore: Import services-list block CSS into theme"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Build the CSS
|
||||
|
||||
**Files:**
|
||||
- Modify: `static/dist/theme.css`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: The new import in `styles/blocks/index.css` (from Task 6) and the block CSS (from Task 4).
|
||||
- Produces: A rebuilt `static/dist/theme.css` that includes the `.services-list*` rules and the dynamic `lg:grid-cols-2`, `lg:grid-cols-3`, `lg:grid-cols-4` utilities.
|
||||
|
||||
- [ ] **Step 1: Run the production Tailwind build**
|
||||
|
||||
Run: `npm run build`
|
||||
Expected: completes with no errors. The output file `static/dist/theme.css` is updated in place.
|
||||
|
||||
- [ ] **Step 2: Verify the services-list CSS classes are in the built output**
|
||||
|
||||
Run: `grep -c "services-list__heading" static/dist/theme.css`
|
||||
Expected: `1` or more (the class should appear in the built CSS).
|
||||
|
||||
- [ ] **Step 3: Verify the dynamic grid classes are in the built output**
|
||||
|
||||
Run: `grep -E "lg\\\\:grid-cols-(2|3|4) " static/dist/theme.css | wc -l`
|
||||
Expected: `3` (all three column counts should be present).
|
||||
|
||||
- [ ] **Step 4: Commit the rebuilt dist**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add -f static/dist/theme.css
|
||||
git commit -m "🎨 build: Rebuild dist with services-list block styles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Playwright Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/services-page.spec.js`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: The rendered `views/blocks/services-list/services-list.php` template (Tasks 4 + 5) on the WordPress page `/services/mapping/`.
|
||||
- Produces: Three tests:
|
||||
1. Card count, image, and heading visibility per block (expects 2 + 4 cards across two block instances).
|
||||
2. Bisecting `__line` is present and `aria-hidden="true"`.
|
||||
3. No axe-core a11y violations scoped to `.services-list`.
|
||||
|
||||
> **Prerequisite:** the test depends on the mapping page being composed in Gutenberg with two `Services List` blocks (2 items + 4 items) and a `Pull Quote` block. That composition is the editor's responsibility and is not part of this plan. The tests should be authored against the *intended* state, not a placeholder state.
|
||||
|
||||
- [ ] **Step 1: Create the test spec file**
|
||||
|
||||
Create the file `tests/services-page.spec.js` with the following exact content:
|
||||
|
||||
```js
|
||||
import { test, expect } from "@playwright/test";
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
|
||||
test.describe("Services List block on /services/mapping/", () => {
|
||||
test("renders the heading and image for each card", async ({ page }) => {
|
||||
await page.goto("/services/mapping/");
|
||||
|
||||
const blocks = page.locator(".services-list");
|
||||
await expect(blocks).toHaveCount(2);
|
||||
|
||||
await expect(blocks.nth(0).locator(".services-list__card")).toHaveCount(2);
|
||||
await expect(blocks.nth(1).locator(".services-list__card")).toHaveCount(4);
|
||||
|
||||
const firstCard = blocks.nth(0).locator(".services-list__card").first();
|
||||
await expect(firstCard.locator(".services-list__media img")).toBeVisible();
|
||||
await expect(firstCard.locator(".services-list__heading")).toBeVisible();
|
||||
});
|
||||
|
||||
test("bisecting line is present and aria-hidden", async ({ page }) => {
|
||||
await page.goto("/services/mapping/");
|
||||
const line = page.locator(".services-list__line").first();
|
||||
await expect(line).toBeAttached();
|
||||
await expect(line).toHaveAttribute("aria-hidden", "true");
|
||||
});
|
||||
|
||||
test("no axe violations on the services-list block", async ({ page }) => {
|
||||
await page.goto("/services/mapping/");
|
||||
const results = await new AxeBuilder({ page })
|
||||
.include(".services-list")
|
||||
.analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the tests**
|
||||
|
||||
Run: `npx playwright test tests/services-page.spec.js`
|
||||
Expected: all 3 tests pass. If tests fail because the mapping page has not yet been composed in the editor, that's an expected prerequisite — note the prerequisite in the final report and skip the test run.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative"
|
||||
git add tests/services-page.spec.js
|
||||
git commit -m "✅ test: Add Playwright tests for services-list block"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review Notes
|
||||
|
||||
- **Spec coverage:**
|
||||
- One new ACF block (`services-list`) — covered by Tasks 1, 3, 4, 5.
|
||||
- Repeater of `items` with `image` / `heading` / `description` — covered by Task 1.
|
||||
- Card count drives grid columns (2/3/4) — covered by Task 5 (PHP computes the class) and Task 7 (Tailwind emits the rule).
|
||||
- Orange bisecting line, full-bleed, behind the cards, hidden on mobile — covered by Tasks 2 (SVG), 4 (CSS with `inset-inline: 0`, `z-index: 1` on line, `z-index: 2` on grid, media query to hide on `<lg`).
|
||||
- Reuse `pull-quote` for closing — explicitly out of scope (no code change needed).
|
||||
- Page template not modified — confirmed; `page.php` is not in any file list.
|
||||
- `header.php` / `footer.php` / page hero not touched — confirmed.
|
||||
- Reuse existing design tokens (`text-cwc-blue-01`, `var(--color-secondary)`, brand orange `#F26B53`) — confirmed in Tasks 4 and 2.
|
||||
- Playwright tests covering count, image, heading, line, a11y — covered by Task 8.
|
||||
- Dist rebuild — covered by Task 7.
|
||||
- **Placeholder scan:** No TBDs. Every step has a concrete file path, exact content, exact command, and expected output. The only conditional is in Task 8 Step 2 — explicitly called out as a prerequisite (editor must compose the page in Gutenberg), not a placeholder.
|
||||
- **Type consistency:** ACF field `name: "items"` is used in Task 5 (`get_field( 'items' )`). Sub-field names `image`, `heading`, `description` are used in Task 1 (defined) and Task 5 (consumed). Class names `services-list`, `__line`, `__grid`, `__card`, `__media`, `__img`, `__heading`, `__desc` are used in Task 4 (defined in CSS) and Task 5 (rendered in markup) and Task 8 (asserted in tests) — consistent.
|
||||
- **Ambiguity check:** "Editor adds 2 instances of the block on the mapping page" is the chosen approach (per spec §1 and design Q&A). "Bisecting line" is a single orange SVG, 8px tall, positioned at `top: 25%` of the block wrapper, hidden on mobile. Both are explicit.
|
||||
Reference in New Issue
Block a user