Compare commits
6
Commits
v1.3.0
...
8013d05d68
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8013d05d68 | ||
|
|
6b29c0e78e
|
||
|
|
7ea6616ba0 | ||
|
|
7fdf97b073
|
||
|
|
d3843186c0 | ||
|
|
e44972abe9 |
@@ -5,7 +5,8 @@
|
||||
"Bash(composer lint *)",
|
||||
"Bash(tea actions:*)",
|
||||
"Bash(tea issue *)",
|
||||
"Bash(tea label *)"
|
||||
"Bash(tea label *)",
|
||||
"Bash(composer cs *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ When a `v*` tag is pushed, `.gitea/workflows/release.yml` publishes the matching
|
||||
the plugin to the next patch version and adds a fresh section here for it. Record
|
||||
each change under the current top section as you work.
|
||||
|
||||
## [1.3.1]
|
||||
|
||||
### Fixed
|
||||
- Upcoming lesson rows no longer render on top of each other. The row's text sits in inline elements that a theme can pull out of normal flow, which dropped the date and time onto the lesson title and the status pill onto the Cancel button; those elements are now pinned into flow alongside the rest of the panel's theme-proofing. The rows held behind **Show all** also stayed visible under the `div { display: block }` reset that many themes still carry, since `[hidden]` is only a browser default — they are now hidden for real.
|
||||
|
||||
## [1.3.0]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -4,100 +4,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
composer install # Install all dependencies
|
||||
|
||||
composer test # Run the full test suite (required after every change)
|
||||
composer lint # PHPStan static analysis
|
||||
composer cs # PHPCS coding standards check
|
||||
composer cs:fix # Auto-fix coding standards
|
||||
|
||||
# Run a single test file
|
||||
./vendor/bin/phpunit tests/Unit/Availability/AvailabilityRepositoryTest.php
|
||||
|
||||
# Run a single test by name
|
||||
./vendor/bin/phpunit --filter testInsertCallsWpdbInsertAndReturnsId
|
||||
```
|
||||
|
||||
**Run `composer test` after every code change before considering a task complete.**
|
||||
|
||||
## Architecture
|
||||
|
||||
### Plugin Bootstrap
|
||||
`unsupervised-schedular.php` defines constants (`USC_VERSION`, `USC_PLUGIN_DIR`, `USC_PLUGIN_URL`), registers activation/deactivation hooks, then calls `Plugin::boot()` on `plugins_loaded`. No logic lives in the root file.
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
src/ — All plugin PHP (PSR-4 namespace: Unsupervised\Schedular\)
|
||||
Availability/ — Availability slots: value object, repository, controller, REST endpoint
|
||||
Booking/ — Lessons/bookings: value object, repository, controller, REST endpoint, shortcode page
|
||||
Auth/ — Roles, capabilities, login page
|
||||
Plugin.php — Wires all components together on plugins_loaded
|
||||
Installer.php — Creates DB tables and roles on activation
|
||||
Schema.php — CREATE TABLE SQL for dbDelta
|
||||
AdminMenu.php — Registers wp-admin menu pages
|
||||
RestRegistrar.php — Registers all REST routes under us-scheduler/v1
|
||||
ShortcodeRegistrar.php — Registers [us_booking] and [us_student_login] shortcodes
|
||||
BlockRegistrar.php — Registers Gutenberg dynamic-block wrappers for the shortcodes
|
||||
BlockPreview.php — Static editor-preview markup for the blocks
|
||||
templates/ — PHP view files included by controllers/shortcodes
|
||||
assets/ — CSS and JS (vanilla JS, no build step)
|
||||
tests/Unit/ — PHPUnit unit tests (PSR-4: Unsupervised\Schedular\Tests\)
|
||||
Availability/ — Tests for src/Availability/
|
||||
Booking/ — Tests for src/Booking/
|
||||
Auth/ — Tests for src/Auth/
|
||||
docs/features/ — One markdown file per feature describing data model, API, and test locations
|
||||
```
|
||||
|
||||
**Code is organised package-by-domain** (Availability, Booking, Auth). Each domain package contains everything related to that domain: value objects, repositories, controllers, REST endpoints, and shortcode pages. Cross-cutting wiring classes (Plugin, AdminMenu, RestRegistrar, ShortcodeRegistrar, Schema) live directly under `src/`.
|
||||
### Code organisation
|
||||
**Code is organised package-by-domain.** Each domain package under `src/<Domain>/` contains everything related to that domain: value objects, repositories, controllers, REST endpoints, and shortcode pages. Cross-cutting wiring classes (Plugin, AdminMenu, RestRegistrar, ShortcodeRegistrar, Schema) live directly under `src/`.
|
||||
|
||||
### Data Storage
|
||||
Two custom database tables (created via `dbDelta` on activation):
|
||||
- `{prefix}us_availability` — instructor availability windows
|
||||
- `{prefix}us_lessons` — booked lessons
|
||||
Custom database tables are created via `dbDelta` on activation; `Schema.php` holds the SQL.
|
||||
|
||||
All database access goes through repository classes within their domain package. No direct `$wpdb` calls outside repositories.
|
||||
|
||||
### Key Classes
|
||||
|
||||
| Class | Responsibility |
|
||||
|---|---|
|
||||
| `Plugin` | Wires all components together on `plugins_loaded` |
|
||||
| `Installer` | Creates DB tables and roles on activation |
|
||||
| `Schema` | CREATE TABLE SQL strings for dbDelta |
|
||||
| `AdminMenu` | Registers wp-admin menu pages |
|
||||
| `RestRegistrar` | Registers all REST routes under `us-scheduler/v1` |
|
||||
| `ShortcodeRegistrar` | Registers `[us_booking]` and `[us_student_login]` shortcodes |
|
||||
| `BlockRegistrar` | Registers Gutenberg dynamic-block wrappers for the shortcodes |
|
||||
| `BlockPreview` | Static editor-preview markup for the blocks |
|
||||
| `Val` | Runtime coercion of untyped WP boundary values (wpdb rows, REST params, superglobals) |
|
||||
| `Auth\RoleManager` | Registers `us_instructor` and `us_student` roles with custom caps |
|
||||
| `Auth\LoginPage` | Renders front-end student login form |
|
||||
| `Availability\AvailabilitySlot` | Immutable value object for a slot row |
|
||||
| `Availability\AvailabilityRepository` | CRUD for availability slots |
|
||||
| `Availability\AvailabilityController` | Instructor availability management page |
|
||||
| `Availability\AvailabilityEndpoint` | REST handlers for availability CRUD |
|
||||
| `Booking\Lesson` | Immutable value object for a lesson row |
|
||||
| `Booking\BookingRepository` | CRUD for lesson bookings |
|
||||
| `Booking\BookingEndpoint` | REST handlers for booking and status updates |
|
||||
| `Booking\BookingPage` | Renders student booking UI shell (JS takes over) |
|
||||
| `Booking\LessonController` | Admin and instructor lesson list pages |
|
||||
|
||||
### REST API Namespace
|
||||
All endpoints live under `/wp-json/us-scheduler/v1/`. Permissions are enforced via `permission_callback` using capability checks (`manage_availability`, `book_lesson`), never role name checks.
|
||||
|
||||
### Testing Approach
|
||||
Tests use [Brain\Monkey](https://brain-wp.github.io/BrainMonkey/) to stub WordPress functions without a full WP installation, and Mockery to mock `$wpdb` and other dependencies.
|
||||
|
||||
All test classes extend `tests/Unit/TestCase.php`, which handles `Monkey\setUp()` / `Monkey\tearDown()` and stubs all WP translation/escape functions automatically.
|
||||
|
||||
**Brain\Monkey API notes:**
|
||||
- `Functions\when('fn')->alias(fn() => ...)` — stub with a closure (NOT `returnUsing()`)
|
||||
- `Functions\when('fn')->justReturn($val)` — stub returning a fixed value
|
||||
- `Functions\expect('fn')->once()->with(...)` — assert call count and arguments
|
||||
- Use `Functions\when()` (not `Functions\expect()`) when you need argument-routing (e.g. `get_role` returning different values per argument) to avoid chaining ambiguity
|
||||
- Mockery matchers (e.g. `\Mockery::type()`) inside plain PHP arrays do not work with `with()` — use `\Mockery::on(fn($arr) => ...)` or `\Mockery::any()` instead
|
||||
- When mocking `$wpdb`, set `$mock->prefix = 'wp_'` explicitly — it is a public property, not a method
|
||||
Tests stub WordPress with Brain\Monkey rather than booting a real WP install. The setup and the Brain\Monkey/Mockery API gotchas are in `tests/CLAUDE.md`.
|
||||
|
||||
### Adding a Feature
|
||||
0. **If the feature touches `Schema.php`, bump both the `Version:` header and `USC_VERSION` in `unsupervised-schedular.php`.** `Plugin::boot()` only re-runs `Installer`/`dbDelta` when the stored `us_schedular_version` differs, so a schema change without a version bump never reaches existing sites and inserts into new columns fail silently.
|
||||
@@ -106,10 +29,3 @@ All test classes extend `tests/Unit/TestCase.php`, which handles `Monkey\setUp()
|
||||
3. Add template(s) under `templates/` if needed.
|
||||
4. Write unit tests under `tests/Unit/<Domain>/` mirroring the `src/<Domain>/` structure.
|
||||
5. Run `composer test` — all tests must pass before the feature is complete.
|
||||
|
||||
### CI
|
||||
Gitea Actions (`.gitea/workflows/ci.yml`) runs on every push and pull request:
|
||||
- **lint** — PHPCS WordPress coding standards
|
||||
- **static-analysis** — PHPStan level 10
|
||||
- **test** — PHPUnit on PHP 8.1, 8.2, 8.3
|
||||
- **no-debug** — rejects commits with `var_dump`, `error_log`, etc. in `src/`
|
||||
|
||||
+37
-2
@@ -99,6 +99,36 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme-proofing for the leaf text. The row and its two columns are divs with
|
||||
* explicit flex rules above, but the text itself still sits in inline elements
|
||||
* a theme is free to take out of normal flow — an absolutely positioned,
|
||||
* floated or negatively offset span drops the date/time on top of the title and
|
||||
* the status pill on top of the Cancel button. Pinning the three properties
|
||||
* that would have to change keeps the leaves in flow, at the same id-level
|
||||
* specificity the rules above rely on.
|
||||
*/
|
||||
#us-booking-app .us-my-lesson-title,
|
||||
#us-booking-app .us-my-lesson-when,
|
||||
#us-booking-app .us-my-lesson-duration,
|
||||
#us-booking-app .us-my-lesson-who,
|
||||
#us-booking-app .us-lesson-status {
|
||||
position: static;
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The rows the "Show all" button reveals. `[hidden]` is only a UA-stylesheet
|
||||
* rule, so any author rule setting a display on div beats it — the html5-reset
|
||||
* `div { display: block }` is still widespread in themes — and the rows the
|
||||
* button is meant to gate render anyway. An author !important is the only way
|
||||
* to win that cascade.
|
||||
*/
|
||||
#us-booking-app [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#us-booking-app .us-show-all-lessons {
|
||||
background: transparent;
|
||||
border: 1px solid #ccc;
|
||||
@@ -397,8 +427,13 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Whose lesson a row in the upcoming panel is — only shown on a family account. */
|
||||
.us-my-lesson-who {
|
||||
/*
|
||||
* Whose lesson a row in the upcoming panel is — only shown on an account that
|
||||
* books for more than one person. Scoped under #us-booking-app like the rest of
|
||||
* the panel; as a bare class it was the one rule in the group a theme could
|
||||
* outrank on a plain span.
|
||||
*/
|
||||
#us-booking-app .us-my-lesson-who {
|
||||
font-weight: normal;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Writing tests
|
||||
|
||||
Tests use [Brain\Monkey](https://brain-wp.github.io/BrainMonkey/) to stub WordPress functions without a full WP installation, and Mockery to mock `$wpdb` and other dependencies.
|
||||
|
||||
All test classes extend `tests/Unit/TestCase.php`, which handles `Monkey\setUp()` / `Monkey\tearDown()` and stubs all WP translation/escape functions automatically.
|
||||
|
||||
**Brain\Monkey API notes:**
|
||||
|
||||
- `Functions\when('fn')->alias(fn() => ...)` — stub with a closure (NOT `returnUsing()`)
|
||||
- `Functions\when('fn')->justReturn($val)` — stub returning a fixed value
|
||||
- `Functions\expect('fn')->once()->with(...)` — assert call count and arguments
|
||||
- Use `Functions\when()` (not `Functions\expect()`) when you need argument-routing (e.g. `get_role` returning different values per argument) to avoid chaining ambiguity
|
||||
- Mockery matchers (e.g. `\Mockery::type()`) inside plain PHP arrays do not work with `with()` — use `\Mockery::on(fn($arr) => ...)` or `\Mockery::any()` instead
|
||||
- When mocking `$wpdb`, set `$mock->prefix = 'wp_'` explicitly — it is a public property, not a method
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Unsupervised Scheduler
|
||||
* Plugin URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
|
||||
* Description: Instructor/student lesson scheduling for WordPress.
|
||||
* Version: 1.3.0
|
||||
* Version: 1.3.1
|
||||
* Requires at least: 6.2
|
||||
* Requires PHP: 8.1
|
||||
* Author: Unsupervised
|
||||
@@ -21,7 +21,7 @@ if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define('USC_VERSION', '1.3.0');
|
||||
define('USC_VERSION', '1.3.1');
|
||||
define('USC_PLUGIN_FILE', __FILE__);
|
||||
define('USC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('USC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
Reference in New Issue
Block a user