diff --git a/CLAUDE.md b/CLAUDE.md index 781da08..df6def9 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -668,6 +668,111 @@ Test database uses suffix `_test` (configured in `config/packages/doctrine.yaml` make prepare-test-db ``` +### Testing Patterns Quick Reference + +**User Creation (via Factory):** +```php +use Tests\Support\Factory\Account\UserFactory; + +$user = UserFactory::createPendingUser(); // PENDING status +$user = UserFactory::createApprovedUser(); // APPROVED status +$user = UserFactory::createSuperAdmin(); // SUPER_ADMIN role +``` + +**LARP Creation (via Factory):** +```php +use Tests\Support\Factory\Core\LarpFactory; + +$larp = LarpFactory::new()->create(); // Default LARP +$larp = LarpFactory::createDraftLarp($organizer); // DRAFT status +$larp = LarpFactory::createPublishedLarp($organizer); // PUBLISHED status +``` + +**Participant Roles (via Factory):** +```php +use Tests\Support\Factory\Core\LarpParticipantFactory; + +LarpParticipantFactory::new() + ->forLarp($larp) + ->forUser($user) + ->player() // PLAYER role + ->create(); + +// Available role methods: +// ->player(), ->organizer(), ->staff(), ->gameMaster(), +// ->trustPerson(), ->photographer(), ->medic() +``` + +**Access Control Testing Pattern:** +```php +public function roleCannotAccessFeature(FunctionalTester $I): void +{ + $user = UserFactory::createApprovedUser(); + $larp = LarpFactory::new()->create(); + LarpParticipantFactory::new() + ->forLarp($larp) + ->forUser($user) + ->player() + ->create(); + + $I->amLoggedInAs($user); + $I->amOnRoute('route_name', ['larp' => $larp->getId()]); + $I->seeResponseCodeIs(403); +} +``` + +**Voter Testing Pattern:** +```php +public function voterGrantsPermission(FunctionalTester $I): void +{ + $user = UserFactory::createApprovedUser(); + $larp = LarpFactory::new()->create(); + LarpParticipantFactory::new() + ->forLarp($larp) + ->forUser($user) + ->organizer() + ->create(); + + $I->amLoggedInAs($user); + + $authChecker = $I->grabService('security.authorization_checker'); + $canAccess = $authChecker->isGranted('VOTER_ATTRIBUTE', $larp); + + $I->assertTrue($canAccess, 'Organizer should have access'); +} +``` + +**Service Testing Pattern:** +```php +public function serviceMethodWorks(FunctionalTester $I): void +{ + // Setup test data with factories + $larp = LarpFactory::new()->create(); + $participant = LarpParticipantFactory::new() + ->forLarp($larp) + ->organizer() + ->create(); + + /** @var MyService $service */ + $service = $I->grabService(MyService::class); + + // Call service method (use ->_real() to get actual entity from proxy) + $result = $service->doSomething($participant->_real()); + + // Assert results + $I->assertNotNull($result); +} +``` + +**Available Assertions:** +- `$I->seeResponseCodeIsSuccessful()` - 2xx status codes +- `$I->seeResponseCodeIs(403)` - Specific status code +- `$I->seeResponseCodeIsRedirection()` - 3xx status codes +- `$I->followRedirect()` - Follow redirect +- `$I->assertTrue($condition, $message)` / `$I->assertFalse(...)` +- `$I->assertEquals($expected, $actual)` / `$I->assertNotNull(...)` +- `$I->assertCount($expected, $array)` + ## Code Quality Standards - **PHP Version**: 8.2+ diff --git a/assets/controllers/leaflet-map_controller.js b/assets/controllers/leaflet-map_controller.js index aa06e80..f243e95 100755 --- a/assets/controllers/leaflet-map_controller.js +++ b/assets/controllers/leaflet-map_controller.js @@ -9,10 +9,12 @@ export default class extends Controller { gridColumns: Number, gridOpacity: Number, gridVisible: Boolean, - locations: Array + locations: Array, + staffPositions: Array }; connect() { + this.staffPositionsLayer = null; this.initMap(); } @@ -27,6 +29,8 @@ export default class extends Controller { img.src = this.imageUrlValue; img.onload = () => { + this.imageWidth = img.width; + this.imageHeight = img.height; const bounds = [[0, 0], [img.height, img.width]]; this.map = L.map('map', { @@ -48,9 +52,23 @@ export default class extends Controller { // Add location markers this.addLocationMarkers(img.width, img.height); + + // Add staff position markers if present + if (this.hasStaffPositionsValue && this.staffPositionsValue.length > 0) { + this.addStaffPositionMarkers(); + } }; } + toggleStaffPositions(event) { + if (event.target.checked) { + this.addStaffPositionMarkers(); + } else if (this.staffPositionsLayer) { + this.staffPositionsLayer.remove(); + this.staffPositionsLayer = null; + } + } + drawGrid(width, height) { const rows = this.gridRowsValue; const cols = this.gridColumnsValue; @@ -102,66 +120,75 @@ export default class extends Controller { return; } - const cellWidth = width / this.gridColumnsValue; - const cellHeight = height / this.gridRowsValue; - this.locationsValue.forEach(location => { - if (!location.gridCoordinates || location.gridCoordinates.length === 0) { - return; - } - const markerColor = location.color || '#3388ff'; + const shape = location.shape || 'dot'; - // Highlight each grid cell for this location - let totalX = 0, totalY = 0; - location.gridCoordinates.forEach(coord => { - const { row, col } = this.parseCellCoordinate(coord); - - // Calculate cell bounds - const x1 = col * cellWidth; - const y1 = row * cellHeight; - const x2 = x1 + cellWidth; - const y2 = y1 + cellHeight; - - // Draw highlighted rectangle for this cell - L.rectangle([[y1, x1], [y2, x2]], { - color: markerColor, - fillColor: markerColor, - fillOpacity: 0.3, - weight: 2 - }).addTo(this.map); - - // Accumulate center coordinates - totalX += col * cellWidth + cellWidth / 2; - totalY += row * cellHeight + cellHeight / 2; - }); - - const centerX = totalX / location.gridCoordinates.length; - const centerY = totalY / location.gridCoordinates.length; + // Convert percentage position to pixel coordinates + const x = (location.positionX / 100) * width; + const y = (location.positionY / 100) * height; - // Create marker with custom icon at the center - const marker = L.marker([centerY, centerX], { - icon: L.divIcon({ - className: 'location-marker', - html: `
`, - iconSize: [20, 20] - }) + // Create marker with shaped icon + const marker = L.marker([y, x], { + icon: this.createShapedIcon(shape, markerColor) }).addTo(this.map); // Add popup with location info - let popupContent = `${location.name}{{ 'larp.map.on_map'|trans }}: {{ map.name }}
+ + {{ 'staff_position.tap_to_select'|trans }} +
+ ++ + {{ 'staff_position.tap_to_select'|trans }} +
+ + {% if map.imageFile %} +| {{ 'staff_position.staff_member'|trans }} | +{{ 'staff_position.role'|trans }} | +{{ 'staff_position.cell'|trans }} | +{{ 'staff_position.status'|trans }} | +{{ 'staff_position.updated'|trans }} | +
|---|---|---|---|---|
| {{ position.participant.user.username }} | ++ {% for role in position.participant.roles %} + {{ role.value|replace({'ROLE_': ''})|lower }} + {% endfor %} + | +{{ position.gridCell }} | +{{ position.statusNote ?? '-' }} | +{{ position.positionUpdatedAt|date('H:i') }} | +