Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/review/170/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,13 @@ Historical PNGs, result JSONs, captions and conflict observer assertions remain
byte-for-byte unchanged; refreshed hashes alone are not browser acceptance
evidence. Hosted Admin must rerun #170 in both browsers against the exact PR
head before acceptance.

### #281 merchant outcome copy source refresh

The Admin fingerprint now binds presentation-only merchant outcome lines in
`class-free-admin.php` (item headline, persisted-count summary, one existing
next step). No planner math, storage, nonce, route, or handler change.
Historical PNGs, result JSONs, captions and the conflict observer sentence
remain byte-for-byte unchanged; this refresh does not claim new screenshots.
Hosted Admin must rerun #170 in both browsers against the exact PR head before
acceptance.
2 changes: 1 addition & 1 deletion docs/review/170/proof.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schema": 1,
"base_main": "bd66a79",
"source_hashes": {
"class-free-admin.php": "85ec78ba49b79727d113ffc6f7ee720163691e16ca7b12f605ab4d498cb78b8e",
"class-free-admin.php": "806698fb7788b1e89904a52d95912713b05d2561b4e7ff31075bf59200de396a",
"free-selection.js": "19f60a7914b3b845e5adb9585f4b347e93e61d1ccd44d1e04b7601501e32e28e",
"free-selection.css": "77673d2620b0fea759ef4ffa168f175a8ff1ead605cf6cac96a76ac4f155b199"
},
Expand Down
2 changes: 1 addition & 1 deletion wordpress/tests/admin/integration.php
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ function wl122_current_rows( string $html ): array {
finally { remove_filter( 'query', $trace_queries ); remove_filter( 'woocommerce_product_get_regular_price', $shopper_price ); }
$current_rows = wl122_current_rows( $current_html );
eq( array_slice( $current_rows[$current_id], 1, 3 ), array( '$18.00 USD', '$21.00 USD', '$14.40 USD' ), 'same conflict row preserves saved and current decimal strings' );
ok( str_contains( $current_rows[$current_id][4], 'Not changed' ) && str_contains( $current_rows[$current_id][4], 'left the newer value unchanged' ), 'merchant conflict explanation in the affected row' );
ok( str_contains( $current_rows[$current_id][4], 'Newer price kept' ) && str_contains( $current_rows[$current_id][4], 'left the newer value unchanged' ), 'merchant conflict explanation in the affected row' );
eq( $current_rows[$current_other][2], '$19.20 USD', 'ordinary applied row also uses fresh Current' );
foreach ( $render_queries as $query ) {
ok( ! preg_match( '/^\s*(?:INSERT|UPDATE|DELETE|REPLACE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|START\s+TRANSACTION|BEGIN|LOCK)\b|\bFOR\s+UPDATE\b/i', $query ), 'Current render is SELECT-only, with no mutation/worker lock' );
Expand Down
67 changes: 67 additions & 0 deletions wordpress/tests/admin/outcome-copy-unit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
// #281 merchant outcome copy. Presentation only: public item_label / result_summary stay put.
require __DIR__ . '/progress-unit.php';

$outcome = new ReflectionClass( WriteLeash\Free_Admin::class );
$line = $outcome->getMethod( 'merchant_item_line' );
$line->setAccessible( true );
$summary = $outcome->getMethod( 'merchant_outcome_summary' );
$summary->setAccessible( true );
$next = $outcome->getMethod( 'merchant_next_step' );
$next->setAccessible( true );
$cell = $outcome->getMethod( 'render_item_outcome' );
$cell->setAccessible( true );

$expect = array(
'APPLIED' => 'Changed',
'UNCHANGED' => 'Not changed',
'UNSUPPORTED' => 'Skipped',
'CONFLICT' => 'Newer price kept',
'FAILED' => 'Not changed; needs attention',
'NEEDS_REVIEW' => 'Needs review; outcome uncertain',
'UNDONE' => 'Restored',
'UNDO_CONFLICT' => 'Not restored; newer price kept',
'UNDO_FAILED' => 'Not restored; needs attention',
'UNDO_NEEDS_REVIEW' => 'Needs review; restoration uncertain',
);
foreach ( $expect as $state => $copy ) {
check204( $line->invoke( null, $state, 'COMPLETED_WITH_ISSUES' ), $copy, 'merchant line ' . $state );
check204( preg_match( '/rolled back|full rollback|accessibility certified/i', $copy ), 0, 'merchant line stays bounded: ' . $state );
}
check204( WriteLeash\Free_Admin::item_label( 'FAILED', 'COMPLETED_WITH_ISSUES' ), 'Needs attention; not changed', 'item_label contract unchanged' );
check204( WriteLeash\Free_Admin::item_label( 'UNSUPPORTED', 'PLANNED' ), 'Skipped at preview', 'item_label skip contract unchanged' );
check204( WriteLeash\Free_Admin::item_label( 'CONFLICT', 'COMPLETED_WITH_ISSUES' ), 'Not changed', 'item_label conflict contract unchanged' );
check204( str_contains( WriteLeash\Free_Admin::result_summary( array( 'planned' => 4, 'pending' => 0, 'applying' => 0, 'applied' => 1, 'unchanged' => 1, 'conflict' => 2, 'failed' => 0, 'needs_review' => 0, 'unsupported' => 1 ), 'COMPLETED_WITH_ISSUES', true ), '2 conflicts' ), true, 'result_summary contract unchanged' );

$counts = array( 'planned' => 6, 'pending' => 1, 'applying' => 1, 'applied' => 1, 'unchanged' => 1, 'conflict' => 1, 'failed' => 1, 'needs_review' => 1, 'unsupported' => 1 );
check204( $summary->invoke( null, $counts, 'RUNNING' ), 'Outcomes: 1 changed · 1 not changed · 1 skipped · 1 newer price kept · 1 not changed; needs attention · 1 needs review; outcome uncertain · 1 in progress; outcome not yet confirmed · 1 remaining.', 'partial summary uses persisted counts' );
check204( $summary->invoke( null, $counts, 'COMPLETED_WITH_ISSUES' ), 'Outcomes: 1 changed · 1 not changed · 1 skipped · 1 newer price kept · 1 not changed; needs attention · 1 needs review; outcome uncertain · 1 in progress; outcome not yet confirmed · 1 not processed.', 'terminal pending is not processed' );
check204( $summary->invoke( null, array( 'planned' => 2, 'pending' => 2, 'applied' => 0, 'unchanged' => 0, 'unsupported' => 0, 'conflict' => 0, 'failed' => 0, 'needs_review' => 0 ), 'PLANNED' ), '', 'planned pending is not invented as an outcome' );
check204( str_contains( $summary->invoke( null, $counts, 'RUNNING' ), '%' ), false, 'merchant summary does not infer a percent' );

check204( $next->invoke( null, true, true, true ), 'Next: use Resume remaining products. Completed changes are kept.', 'one next step prefers the existing Resume control' );
check204( $next->invoke( null, false, true, true ), 'Next: use Re-preview conflicted products to build a fresh preview. Prices do not change until you approve that preview.', 'conflict next step is a fresh preview' );
check204( $next->invoke( null, false, false, true ), 'Next: Undo restores eligible prices only. It does not reverse orders, sales, emails, webhooks, or other plugins.', 'Undo next step is not a full rollback' );
check204( $next->invoke( null, false, false, false ), 'Next: review the product.', 'default next step is inspect the product' );

ob_start();
$cell->invoke( null, 'FAILED', null, 'COMPLETED_WITH_ISSUES', array( 'context' => '' ) );
$failed_html = (string) ob_get_clean();
check204( str_contains( $failed_html, '<strong>Not changed; needs attention</strong>' ), true, 'failed cell headline' );
check204( str_contains( $failed_html, 'Review the product. This outcome is not confirmed as unchanged.' ), true, 'failed cell one next step' );
check204( str_contains( $failed_html, 'FAILED ·' ), true, 'typed failed state stays in Details' );
check204( str_contains( $failed_html, 'rolled back' ), false, 'failed cell does not claim a rollback' );
check204( str_contains( $failed_html, 'name="action"' ), false, 'outcome cell adds no form' );

ob_start();
$cell->invoke( null, 'CONFLICT', null, 'COMPLETED_WITH_ISSUES', array( 'context' => '' ) );
$conflict_html = (string) ob_get_clean();
check204( str_contains( $conflict_html, '<strong>Newer price kept</strong>' ), true, 'conflict cell headline' );
check204( str_contains( $conflict_html, 'Create a new preview' ), true, 'conflict cell keeps the existing preview link' );
check204( substr_count( $conflict_html, '<a ' ), 1, 'conflict cell has one next-step link' );
check204( str_contains( $conflict_html, 'CONFLICT ·' ), true, 'typed conflict state stays in Details' );

$admin_source = (string) file_get_contents( __DIR__ . '/../../writeleash/includes/free/class-free-admin.php' );
check204( str_contains( $admin_source, 'writeleash_free_resume' ), true, 'no new resume action; existing hook remains' );
check204( substr_count( $admin_source, 'function render_item_outcome' ), 1, 'item outcome renderer stays singular' );
echo "#281 merchant outcome copy: item lines, persisted counts, one existing next step, Details retained PASS\n";
2 changes: 1 addition & 1 deletion wordpress/tests/admin/presentation-browser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async function row(page, id) { return page.locator('tr[data-product-id="' + id +
const conflict = await row(page, fixture.mixed_ids[0]);
const cells = await conflict.locator('td').allTextContents();
ok(cells[1] === '$18.00 USD' && cells[2] === '$21.00 USD' && cells[3] === '$14.40 USD', 'exact stale expected/current/target in same row');
ok((await conflict.locator('td strong').allTextContents()).includes('Not changed'), 'merchant conflict label primary');
ok((await conflict.locator('td strong').allTextContents()).includes('Newer price kept'), 'merchant conflict label primary');
ok((await conflict.innerText()).includes('left the newer value unchanged'), 'preserved newer edit explanation');
ok(await conflict.getByRole('link', { name: 'Review product', exact: true }).count() === 1 && await conflict.getByRole('link', { name: 'Create a new preview', exact: true }).count() === 1, 'safe conflict next actions');
ok(!/Force apply|Retry conflict/.test(await text(page)), 'no blind overwrite or terminal retry');
Expand Down
7 changes: 5 additions & 2 deletions wordpress/tests/admin/presentation-integration.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
eq( array_slice( $rows168[$identity168], 1, 3 ), array( '$18.00 USD', '$21.00 USD', '$14.40 USD' ), 'truthful expected/current/target conflict values' );
ok( str_contains( $rows168[$sale168][4], 'sale price or schedule' ), 'equal-regular-price conflict explains live eligibility drift' );
eq( $rows168[$sale168][2], '$18.00 USD', 'sale drift regular price still matches expected' );
ok( str_contains( $html168, '2 conflicts' ) && str_contains( $html168, '1 already at the target price' ) && str_contains( $html168, '1 skipped' ), 'disjoint results categories visible' );
ok( str_contains( $html168, '2 conflicts' ) && str_contains( $html168, '1 already at the target price' ) && str_contains( $html168, '1 skipped' ), 'disjoint results categories visible' );
ok( str_contains( $html168, 'Outcomes: 1 not changed · 1 skipped · 2 newer prices kept.' ) && str_contains( $rows168[$identity168][4], 'Newer price kept' ) && str_contains( $rows168[$identity168][4], 'CONFLICT ·' ), 'merchant outcome line and typed Details stay together' );
ok( ! str_contains( $rows168[$identity168][4], 'rolled back' ) && ! str_contains( $html168, 'full rollback' ), 'conflict copy does not claim a rollback' );
ok( ! str_contains( $html168, 'name="action" value="' . Admin::ACTION_RESUME . '"' ), 'terminal conflict never offers Resume' );
ok( str_contains( $html168, 'Changes needing attention' ), 'needs-attention results filter is exposed' );
wp_delete_post( $identity168, true );
Expand Down Expand Up @@ -75,7 +77,8 @@
require_once ABSPATH . 'wp-admin/includes/user.php';
wp_delete_user( $actor168 );
$html168 = render_view( 'history', '', 0 );
ok( str_contains( $html168, 'Decrease regular prices by 20% · 1 product' ) && str_contains( $html168, 'Deleted user (User #' . $actor168 . ')' ) && str_contains( $html168, 'Awaiting review' ), 'history descriptive task and honest deleted creator' );
ok( str_contains( $html168, 'Decrease regular prices by 20% · 1 product' ) && str_contains( $html168, 'Deleted user (User #' . $actor168 . ')' ) && str_contains( $html168, 'Awaiting review' ), 'history descriptive task and honest deleted creator' );
ok( str_contains( $html168, 'Outcomes: 1 not changed · 1 skipped · 2 newer prices kept.' ) && str_contains( $html168, 'Finished with products needing attention' ), 'history outcome column uses merchant counts and keeps the job label' );
ok( str_contains( $html168, wp_timezone_string() ), 'history identifies site timezone' );

function csv168( array $job ): array {
Expand Down
2 changes: 1 addition & 1 deletion wordpress/tests/admin/progress-unit.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function refused204() { return array( 'status' => 'FORBIDDEN', 'reason' => 'prog
// text, dropping the outcome emphasis and the conflict next-action links that
// the server-rendered cells carry. The payload must ship the same markup.
$html_rows = Admin::progress_snapshot( $post, 'POST' )['rows'][0];
check204( str_contains( $html_rows['apply_html'], '<strong>Not changed</strong>' ), true, 'polled Apply cell keeps the outcome emphasis' );
check204( str_contains( $html_rows['apply_html'], '<strong>Newer price kept</strong>' ), true, 'polled Apply cell keeps the merchant outcome emphasis' );
check204( str_contains( $html_rows['apply_html'], $long_apply ), true, 'polled Apply cell keeps the long guidance' );
check204( str_contains( $html_rows['apply_html'], 'Create a new preview' ), true, 'polled Apply cell keeps the conflict next action' );
check204( str_contains( $html_rows['apply_html'], '<script' ), false, 'polled Apply markup carries no executable bytes' );
Expand Down
1 change: 1 addition & 0 deletions wordpress/tests/admin/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ here="$(cd "$(dirname "$0")" && pwd)"
ls "$here" >/dev/null
node "$here/progress-client.cjs"
php "$here/progress-unit.php"
php "$here/outcome-copy-unit.php"
php "$here/recovery-unit.php"
php "$here/repeat-unit.php"
php "$here/presets-unit.php"
Expand Down
2 changes: 1 addition & 1 deletion wordpress/tests/admin/selection-browser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ async function search(page, term) {
await page.getByRole('link', { name: 'Continue review', exact: true }).click();
await action(page, 'Approve and apply');
await action(page, 'Resume remaining products');
ok((await page.locator('#wpbody-content').innerText()).includes('Not changed'), 'existing execution checks detect stale product');
ok((await page.locator('#wpbody-content').innerText()).includes('Newer price kept'), 'existing execution checks detect stale product');
ok(Number(observe().prices[fixture.products[0]].stored) === 120, 'independent observer proves no blind overwrite');
await page.goto(previewURL);
ok(await page.getByRole('button', { name: 'Approve and apply', exact: true }).count() === 0, 'approved preview routes to actual results, cannot reapprove');
Expand Down
Loading
Loading