diff --git a/.github/workflows/behat-test.yml b/.github/workflows/behat-test.yml index 67110a79a..3a0bdc9a3 100644 --- a/.github/workflows/behat-test.yml +++ b/.github/workflows/behat-test.yml @@ -153,6 +153,12 @@ jobs: echo "Coverage files: $FILES" echo "files=$FILES" >> $GITHUB_OUTPUT + - name: Import Codecov GPG key + if: ${{ matrix.coverage }} + run: | + gpg --keyserver keyserver.ubuntu.com --recv-keys 27034E7FDB850E0BBC2C62FF806BB28AED779869 2>/dev/null || \ + curl -fsSL https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --import --no-default-keyring --keyring trustedkeys.gpg 2>/dev/null || true + - name: Upload code coverage report if: ${{ matrix.coverage }} uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 diff --git a/.github/workflows/php-test.yml b/.github/workflows/php-test.yml index 3ff6b5bca..f74e5d77b 100644 --- a/.github/workflows/php-test.yml +++ b/.github/workflows/php-test.yml @@ -92,11 +92,20 @@ jobs: - name: Start WordPress run: | - if [[ ${{ matrix.coverage == true }} == true ]]; then - npm run wp-env:start:tests -- --xdebug=coverage - else - npm run wp-env:start:tests - fi + set +e + for i in 1 2 3 4 5; do + if [[ ${{ matrix.coverage == true }} == true ]]; then + npm run wp-env:start:tests -- --xdebug=coverage + else + npm run wp-env:start:tests + fi + if [ $? -eq 0 ]; then + exit 0 + fi + echo "wp-env start failed (attempt $i), retrying in 60s..." + sleep 60 + done + exit 1 - name: Run tests run: | @@ -108,6 +117,12 @@ jobs: npm run test-php-multisite fi + - name: Import Codecov GPG key + if: ${{ matrix.coverage }} + run: | + gpg --keyserver keyserver.ubuntu.com --recv-keys 27034E7FDB850E0BBC2C62FF806BB28AED779869 2>/dev/null || \ + curl -fsSL https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --import --no-default-keyring --keyring trustedkeys.gpg 2>/dev/null || true + - name: Upload code coverage report if: ${{ matrix.coverage }} uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 diff --git a/assets/css/plugin-check-admin.css b/assets/css/plugin-check-admin.css index 115a348f7..19d207cf6 100644 --- a/assets/css/plugin-check-admin.css +++ b/assets/css/plugin-check-admin.css @@ -126,6 +126,74 @@ cursor: pointer; } +.plugin-check__collapse-expand-controls { + margin: 16px 0; + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.plugin-check__collapse-expand-controls.is-hidden { + display: none; +} + +.plugin-check__file-section { + margin: 1em 0; + border: 1px solid #dcdcde; + background: #fff; +} + +.plugin-check__file-section-header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 8px 12px; + font-weight: 600; + cursor: pointer; + user-select: none; + background: transparent; + border: 0; + text-align: left; + color: inherit; + font-size: inherit; +} + +.plugin-check__file-section-header:focus { + outline: 2px solid #2271b1; + outline-offset: -2px; +} + +.plugin-check__file-section-chevron { + transition: transform 0.15s ease-in-out; + font-size: 18px; + line-height: 1; +} + +.plugin-check__file-section-title { + color: var(--wp-admin-theme-color); + display: inline-flex; + align-items: center; + gap: 6px; +} + +.plugin-check__file-section-icon { + font-size: 18px; + line-height: 1; +} + +.plugin-check__file-section-header[aria-expanded="true"] .plugin-check__file-section-chevron { + transform: rotate(180deg); +} + +.plugin-check__file-section-content { + padding: 0 14px 14px; +} + +.plugin-check__file-section--collapsed .plugin-check__file-section-content { + display: none; +} + .plugin-check__false-positive-results { padding: 0 14px 14px; } diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 22227cc4e..95b840213 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -4,6 +4,9 @@ const exportContainer = document.getElementById( 'plugin-check__export-controls' ); + const collapseExpandContainer = document.getElementById( + 'plugin-check__collapse-expand-controls' + ); const spinner = document.getElementById( 'plugin-check__spinner' ); const pluginsList = document.getElementById( 'plugin-check__plugins-dropdown' @@ -20,6 +23,7 @@ ! pluginsList || ! resultsContainer || ! exportContainer || + ! collapseExpandContainer || ! spinner || ! categoriesList.length || ! typesList.length @@ -33,6 +37,7 @@ let checksCompleted = false; exportContainer.classList.add( 'is-hidden' ); exportContainer.addEventListener( 'click', onExportContainerClick ); + collapseExpandContainer.classList.add( 'is-hidden' ); const includeExperimental = document.getElementById( 'plugin-check__include-experimental' @@ -122,6 +127,7 @@ resultsContainer.innerText = ''; exportContainer.innerHTML = ''; exportContainer.classList.add( 'is-hidden' ); + collapseExpandContainer.classList.add( 'is-hidden' ); resetAggregatedResults(); checksCompleted = false; } @@ -424,10 +430,12 @@ exportContainer.innerHTML = ''; if ( ! checksCompleted ) { exportContainer.classList.add( 'is-hidden' ); + collapseExpandContainer.classList.add( 'is-hidden' ); return; } exportContainer.classList.remove( 'is-hidden' ); + collapseExpandContainer.classList.remove( 'is-hidden' ); const exportButtonConfigs = [ { @@ -1389,4 +1397,100 @@ const template = templates[ templateSlug ]; return template( data ); } + + /** + * Toggles the open/collapsed state of a single file section. + * + * @since 2.1.0 + * + * @param {HTMLElement} section The file section element. + */ + function toggleFileSection( section ) { + if ( ! section ) { + return; + } + const header = section.querySelector( + '.plugin-check__file-section-header' + ); + const isCollapsed = section.classList.contains( + 'plugin-check__file-section--collapsed' + ); + if ( isCollapsed ) { + section.classList.remove( 'plugin-check__file-section--collapsed' ); + if ( header ) { + header.setAttribute( 'aria-expanded', 'true' ); + } + } else { + section.classList.add( 'plugin-check__file-section--collapsed' ); + if ( header ) { + header.setAttribute( 'aria-expanded', 'false' ); + } + } + } + + /** + * Sets the open state of every file section in the results container. + * + * @since 2.1.0 + * + * @param {boolean} open Open state to apply. + */ + function setAllFileSectionsOpen( open ) { + const sections = resultsContainer.querySelectorAll( + '.plugin-check__file-section' + ); + sections.forEach( function ( section ) { + const header = section.querySelector( + '.plugin-check__file-section-header' + ); + if ( open ) { + section.classList.remove( + 'plugin-check__file-section--collapsed' + ); + if ( header ) { + header.setAttribute( 'aria-expanded', 'true' ); + } + } else { + section.classList.add( + 'plugin-check__file-section--collapsed' + ); + if ( header ) { + header.setAttribute( 'aria-expanded', 'false' ); + } + } + } ); + } + + // Delegated click handler for individual file section headers. + resultsContainer.addEventListener( 'click', function ( event ) { + const header = event.target.closest( + '.plugin-check__file-section-header' + ); + if ( ! header ) { + return; + } + event.preventDefault(); + const section = header.closest( '.plugin-check__file-section' ); + toggleFileSection( section ); + } ); + + // Wire up the Collapse All / Expand All buttons. + const expandAllButton = document.getElementById( + 'plugin-check__expand-all' + ); + const collapseAllButton = document.getElementById( + 'plugin-check__collapse-all' + ); + + if ( expandAllButton ) { + expandAllButton.addEventListener( 'click', function () { + setAllFileSectionsOpen( true ); + } ); + } + + if ( collapseAllButton ) { + collapseAllButton.addEventListener( 'click', function () { + setAllFileSectionsOpen( false ); + } ); + } } )( PLUGIN_CHECK ); diff --git a/templates/admin-page.php b/templates/admin-page.php index d8bfe935e..567ce1ead 100644 --- a/templates/admin-page.php +++ b/templates/admin-page.php @@ -104,6 +104,16 @@
+
diff --git a/templates/results-table.php b/templates/results-table.php index 2c10949b2..208463e46 100644 --- a/templates/results-table.php +++ b/templates/results-table.php @@ -1,29 +1,39 @@ -

{{ data.file }}

- - - - - - - - - <# if ( data.hasLinks ) { #> - - <# } #> - - - -
- - - - - - - - - - - -
-
+
+ +
+ + + + + + + + + <# if ( data.hasLinks ) { #> + + <# } #> + + + +
+ + + + + + + + + + + +
+
+
+