diff --git a/src/Admin/Schema/SchemaUsers.php b/src/Admin/Schema/SchemaUsers.php index d4597db3..7bf52a74 100644 --- a/src/Admin/Schema/SchemaUsers.php +++ b/src/Admin/Schema/SchemaUsers.php @@ -611,8 +611,8 @@ class SchemaUsers { ]], ], ], + 'ticket' => Users\SchemaUsersTicket::build(), ], - 'ticket' => Users\SchemaUsersTicket::build(), ]; } } diff --git a/tests/widget-system/ticket-lifecycle-contract.test.cjs b/tests/widget-system/ticket-lifecycle-contract.test.cjs index aef8d19f..a727054c 100644 --- a/tests/widget-system/ticket-lifecycle-contract.test.cjs +++ b/tests/widget-system/ticket-lifecycle-contract.test.cjs @@ -28,14 +28,14 @@ test('transition rules live in one place', () => { assert.match(body, /if \(\$from === self::KEY_PENDING && \$to === self::KEY_CLOSED\) \{\s*\n\s*return false;/); // 上层只调用 can_transition,不复制合法性判断。 - for (const file of ['src/Ticket/TicketRepository-traits/TicketStatus.php', 'src/Ticket/TicketRestAdmin.php', 'src/Admin/Native/UsersMenusTicketsCenter.php']) { + for (const file of ['src/Ticket/TicketStatus.php', 'src/Ticket/TicketRestAdmin.php', 'src/Admin/Native/UsersMenusTicketsCenter.php']) { const src = read(file); assert.ok(src.includes('can_transition'), `${file} must delegate to can_transition`); } }); test('reply and receive drive the documented state changes', () => { - const status = read('src/Ticket/TicketRepository-traits/TicketStatus.php'); + const status = read('src/Ticket/TicketStatus.php'); // 管理员回复 → 已回复;用户回复 → 待回复。 assert.match(status, /\$target = \$author_type === 'admin'\s*\n\s*\? TicketStatusService::KEY_ADMIN_REPLIED\s*\n\s*: TicketStatusService::KEY_USER_REPLIED;/); @@ -52,7 +52,7 @@ test('reply and receive drive the documented state changes', () => { }); test('closing notifies the counterparty exactly once', () => { - const status = read('src/Ticket/TicketRepository-traits/TicketStatus.php'); + const status = read('src/Ticket/TicketStatus.php'); assert.match(status, /if \(\$to === TicketStatusService::KEY_CLOSED\) \{\s*\n\s*TicketNotifier::closed\(\$ticket, \$actor\);/); const notifier = read('src/Ticket/TicketNotifier.php'); diff --git a/tests/widget-system/ticket-permission-contract.test.cjs b/tests/widget-system/ticket-permission-contract.test.cjs index 0d60ee4a..ee24819e 100644 --- a/tests/widget-system/ticket-permission-contract.test.cjs +++ b/tests/widget-system/ticket-permission-contract.test.cjs @@ -116,7 +116,7 @@ test('closed tickets refuse replies and cannot be re-closed', () => { assert.match(admin, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_closed'/); // close_ticket 自身也挡已完结(双保险,避免前端绕过 UI 直接 POST)。 - const status = read('src/Ticket/TicketRepository-traits/TicketStatus.php'); + const status = read('src/Ticket/TicketStatus.php'); assert.match(status, /if \(TicketStatusService::is_closed\(\(string\) \(\$ticket\['status'\] \?\? ''\)\)\) \{\s*\n\s*return false;/); }); diff --git a/tests/widget-system/ticket-schema-contract.test.cjs b/tests/widget-system/ticket-schema-contract.test.cjs index 41e24579..8d9b3971 100644 --- a/tests/widget-system/ticket-schema-contract.test.cjs +++ b/tests/widget-system/ticket-schema-contract.test.cjs @@ -84,6 +84,28 @@ test('ticket schema is registered as a schema page under users', () => { // PHP 侧:分区由 SchemaUsers 组装,SchemaUsersTicket 提供内容。 const users = read('src/Admin/Schema/SchemaUsers.php'); assert.ok(users.includes("'ticket' => Users\\SchemaUsersTicket::build()"), 'PHP section not composed'); + // 组装位置必须在 'sections' => [ ... ] 内部。SchemaProvider::get_sections() 只读 + // $schema['sections'],分区挂在同级会让 /settings/schema/users/ticket 404 + // (SPA 报「未知的设置分组」)。定位 sections 开括号后用配平扫描找闭括号, + // 确认 compose 调用落在区间内——单纯 includes() 查不出层级错误。 + const sectionsAt = users.indexOf("'sections' => ["); + assert.ok(sectionsAt !== -1, 'sections array not found'); + const composeAt = users.indexOf("'ticket' => Users\\SchemaUsersTicket::build()"); + assert.ok(composeAt > sectionsAt, 'ticket must be composed after sections opens'); + let depth = 0; + let sectionsEnd = -1; + for (let i = users.indexOf('[', sectionsAt); i < users.length; i++) { + if (users[i] === '[') depth++; + else if (users[i] === ']') { + depth--; + if (depth === 0) { + sectionsEnd = i; + break; + } + } + } + assert.ok(sectionsEnd !== -1, 'unbalanced sections array'); + assert.ok(composeAt < sectionsEnd, 'ticket section must be INSIDE the sections array'); const ticket = read('src/Admin/Schema/Users/SchemaUsersTicket.php'); assert.ok(ticket.includes("'key' => 'ticket'"), 'PHP section key missing'); assert.ok(ticket.includes("'option_key' => 'aksrt_settings'"), 'PHP option key missing');